Merge branch 'stable-2.13' * stable-2.13: Set version to 2.13 Update 2.13 release notes Upgrade JGit to version 4.5.0.201609210915-r Update 2.13 release notes Change-Id: I67abdbc477c4355a3e040a3ec37debdce109dc7b
diff --git a/.buckconfig b/.buckconfig index b347a96..60fd02a 100644 --- a/.buckconfig +++ b/.buckconfig
@@ -20,8 +20,11 @@ [java] jar_spool_mode = direct_to_jar src_roots = java, resources, src + source_level = 8 + target_level = 8 [project] + allow_symlinks = allow ignore = .git, eclipse-out, bazel-gerrit, bin parallel_parsing = true
diff --git a/.buckversion b/.buckversion index f5fe016..efb68ecf 100644 --- a/.buckversion +++ b/.buckversion
@@ -1 +1 @@ -e64a2e2ada022f81e42be750b774024469551398 +fd3105a0b62899f74662f4cdc156de6990bdc24c
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 828234b..fd57ff7 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs
@@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -113,7 +113,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=enabled -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..28dd0f5 --- /dev/null +++ b/BUILD
@@ -0,0 +1,14 @@ +load('//tools/bzl:genrule2.bzl', 'genrule2') +load('//tools/bzl:pkg_war.bzl', 'pkg_war') + +genrule2( + name = 'version', + srcs = ['VERSION'], + cmd = "grep GERRIT_VERSION $< | cut -d \"'\" -f 2 >$@", + out = 'version.txt', + visibility = ['//visibility:public'], +) + +pkg_war(name = 'gerrit') +pkg_war(name = 'headless', ui = None) +
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt index 2cc8c05..3af3bca 100644 --- a/Documentation/access-control.txt +++ b/Documentation/access-control.txt
@@ -466,7 +466,10 @@ To push lightweight (non-annotated) tags, grant `Create Reference` for reference name `+refs/tags/*+`, as lightweight -tags are implemented just like branches in Git. +tags are implemented just like branches in Git. To push a lightweight +tag on a new commit (commit not reachable from any branch/tag) grant +`Push` permission on `+refs/tags/*+` too. The `Push` permission on +`+refs/tags/*+` also allows fast-forwarding of lightweight tags. For example, to grant the possibility to create new branches under the namespace `foo`, you have to grant this permission on @@ -480,6 +483,19 @@ you grant the users the push force permission to be able to clean up stale branches. +[[category_delete]] +=== Delete Reference + +The delete reference category controls whether it is possible to delete +references, branches or tags. It doesn't allow any other update of +references. + +Deletion of references is also possible if `Push` with the force option +is granted, however that includes the permission to fast-forward and +force-update references to exiting and new commits. Being able to push +references for new commits is bad if bypassing of code review must be +prevented. + [[category_forge_author]] === Forge Author @@ -644,7 +660,8 @@ [[category_push_annotated]] -=== Push Annotated Tag +[[category_create_annotated]] +=== Create Annotated Tag This category permits users to push an annotated tag object into the project's repository. Typically this would be done with a command line @@ -671,7 +688,7 @@ To push tags created by users other than the current user (such as tags mirrored from an upstream project), `Forge Committer Identity` -must be also granted in addition to `Push Annotated Tag`. +must be also granted in addition to `Create Annotated Tag`. To push lightweight (non annotated) tags, grant <<category_create,`Create Reference`>> for reference name @@ -682,9 +699,16 @@ option enabled for reference name `+refs/tags/*+`, as deleting a tag requires the same permission as deleting a branch. +To push an annotated tag on a new commit (commit not reachable from any +branch/tag) grant `Push` permission on `+refs/tags/*+` too. +The `Push` permission on `+refs/tags/*+` does *not* allow updating of annotated +tags, not even fast-forwarding of annotated tags. Update of annotated tags +is only allowed by granting `Push` with `force` option on `+refs/tags/*+`. + [[category_push_signed]] -=== Push Signed Tag +[[category_create_signed]] +=== Create Signed Tag This category permits users to push a PGP signed tag object into the project's repository. Typically this would be done with a command @@ -863,6 +887,14 @@ can always edit or remove hashtags (even without having the `Edit Hashtags` access right assigned). +[[category_edit_assigned_to]] +=== Edit Assignee + +This category permits users to set who is assigned to a change that is +uploaded for review. + +The change owner, ref owners, and the user currently assigned to a change +can always change the assignee. [[example_roles]] == Examples of typical roles in a project @@ -997,7 +1029,7 @@ * <<category_push_merge,`Push merge commit`>> to 'refs/heads/*' * <<category_forge_committer,`Forge Committer Identity`>> to 'refs/for/refs/heads/*' * <<category_create,`Create Reference`>> to 'refs/heads/*' -* <<category_push_annotated,`Push Annotated Tag`>> to 'refs/tags/*' +* <<category_create_annotated,`Create Annotated Tag`>> to 'refs/tags/*' [[examples_project-owner]] @@ -1067,12 +1099,15 @@ [[block]] === 'BLOCK' access rule -The 'BLOCK' rule blocks a permission globally. An inherited 'BLOCK' rule cannot -be overridden in the inheriting project. Any 'ALLOW' rule, from a different -access section or from an inheriting project, which conflicts with an -inherited 'BLOCK' rule will not be honored. Searching for 'BLOCK' rules, in -the chain of parent projects, ignores the Exclusive flag that is normally -applied to access sections. +The 'BLOCK' rule blocks a permission globally. An inherited 'BLOCK' +rule cannot be overridden in the inheriting project. Any 'ALLOW' rule +from an inheriting project, which conflicts with an inherited 'BLOCK' +rule will not be honored. Searching for 'BLOCK' rules, in the chain +of parent projects, ignores the Exclusive flag, unless the rule with +the Exclusive flag is defined on the same project as the 'BLOCK' +rule. This means within the same project a 'BLOCK' rule can be +overruled by 'ALLOW' rules on the same access section and 'ALLOW' +rules with Exclusive flag on access section for more specific refs. A 'BLOCK' rule that blocks the 'push' permission blocks any type of push, force or not. A blocking force push rule blocks only force pushes, but
diff --git a/Documentation/config-cla.txt b/Documentation/config-cla.txt index c07a24f..2234808 100644 --- a/Documentation/config-cla.txt +++ b/Documentation/config-cla.txt
@@ -37,8 +37,13 @@ Each `contributor-agreement` section within the `project.config` file must have a unique name. The section name will appear in the web UI. -If not already present, add the UUID of the groups used in the -`autoVerify` and `accepted` variables in the groups file. +If not already present, add the group(s) used in the `autoVerify` and +`accepted` variables in the `groups` file: +---- + # UUID Group Name + # + 3dedb32915ecdbef5fced9f0a2587d164cd614d4 CLA Accepted - Individual +---- Commit the configuration change, and push it back: ----
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index a956d52..8661d40 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt
@@ -3327,6 +3327,14 @@ + By default, true, allowing notifications to be sent. +[[sendemail.html]]sendemail.html:: ++ +If false, Gerrit will only send plain-text emails. +If true, Gerrit will send multi-part emails with an HTML and +plain text part. ++ +By default, true, allowing HTML in the emails Gerrit sends. + [[sendemail.connectTimeout]]sendemail.connectTimeout:: + The connection timeout of opening a socket connected to a @@ -3358,7 +3366,9 @@ Full Name and Preferred Email. This may cause messages to be classified as spam if the user's domain has SPF or DKIM enabled and <<sendemail.smtpServer,sendemail.smtpServer>> is not a trusted -relay for that domain. +relay for that domain. You can specify +<<sendemail.allowedDomain,sendemail.allowedDomain>> to instruct Gerrit to only +send as USER if USER is from those domains. + * `MIXED` + @@ -3384,6 +3394,16 @@ + By default, MIXED. +[[sendemail.allowedDomain]]sendemail.allowedDomain:: ++ +Only used when `sendemail.from` is set to `USER`. +List of allowed domains. If user's email matches one of the domains, emails will +be sent as USER, otherwise as MIXED mode. Wildcards may be specified by +including `*` to match any number of characters, for example `*.example.com` +matches any subdomain of `example.com`. ++ +By default, `*`. + [[sendemail.smtpServer]]sendemail.smtpServer:: + Hostname (or IP address) of a SMTP server that will relay @@ -3469,6 +3489,15 @@ [[site]] === Section site +[[site.allowOriginRegex]]site.allowOriginRegex:: ++ +List of regular expressions matching origins that should be permitted +to use the Gerrit REST API to read content. These should be trusted +applications as the sites may be able to use the user's credentials. +Only applies to GET and HEAD requests. ++ +By default, unset, denying all cross-origin requests. + [[site.refreshHeaderFooter]]site.refreshHeaderFooter:: + If true the server checks the site header, footer and CSS files for
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt index 7121265..5483d85 100644 --- a/Documentation/config-project-config.txt +++ b/Documentation/config-project-config.txt
@@ -20,6 +20,11 @@ that you will have to configure push rights for the +refs/meta/config+ name space if you'd like to use the possibility to automate permission updates. +== Property inheritance + +If a property is set to INHERIT, then the value from the parent project is +used. If the property is not set in any parent project, the default value is +FALSE. [[file-project_config]] == The file +project.config+
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt index 315c0b0..219da7e 100644 --- a/Documentation/dev-buck.txt +++ b/Documentation/dev-buck.txt
@@ -3,7 +3,7 @@ == Installation -You need to use Java 7 and Node.js for building gerrit. +You need to use Java 8 and Node.js for building gerrit. There is currently no binary distribution of Buck, so it has to be manually built and installed. Apache Ant and gcc are required. Currently only Linux @@ -547,7 +547,7 @@ ---- cat > .buckjavaargs <<EOF - -XX:MaxPermSize=512m -Xms8000m -Xmx16000m + -Xms8000m -Xmx16000m EOF ----
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt index 37c8ebe..3260e23 100644 --- a/Documentation/dev-plugins.txt +++ b/Documentation/dev-plugins.txt
@@ -36,7 +36,7 @@ ---- mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \ -DarchetypeArtifactId=gerrit-plugin-archetype \ - -DarchetypeVersion=2.13 \ + -DarchetypeVersion=2.14-SNAPSHOT \ -DgroupId=com.googlesource.gerrit.plugins.testplugin \ -DartifactId=testplugin ---- @@ -1112,6 +1112,10 @@ + Panel will be shown below the related info block. +** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`: ++ +Panel will be shown in the history bar on the right side of the buttons. + ** The following parameters are provided: *** `GerritUiExtensionPoint.Key.CHANGE_INFO`: +
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt index 921244f..d43c863 100644 --- a/Documentation/dev-release-deploy-config.txt +++ b/Documentation/dev-release-deploy-config.txt
@@ -89,17 +89,15 @@ To upload artifacts to a bucket the user must authenticate with a username and password. The username and password need to be retrieved -from the link:https://console.developers.google.com/project/164060093628[ -Google Developers Console]: +from the link:https://console.cloud.google.com/storage/settings?project=api-project-164060093628[ +Storage Setting in the Google Cloud Platform Console]: -* In the menu on the left select `Storage` -> `Cloud Storage` > -> `Storage access` -* Select the `Interoperability` tab -* If no keys are listed under `Interoperable storage access keys`, select "Create a new key" -* Use the `Access Key` as username, and `Secret` as the password +Select the `Interoperability` tab, and if no keys are listed under +`Interoperable storage access keys`, select 'Create a new key'. -To make the username and password known to Maven, they must be -configured in the `~/.m2/settings.xml` file. +Using `Access Key` as username and `Secret` as the password, add the +configuration in the `~/.m2/settings.xml` file to make the credentials +known to Maven: ---- <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" @@ -143,10 +141,9 @@ ---- [NOTE] -In case of JGit the `pom.xml` already contains a distributionManagement -section. Replace the existing distributionManagement section with this snippet -in order to deploy the artifacts only in the gerrit-maven repository. - +In case of JGit the `pom.xml` already contains a `distributionManagement` +section. To deploy the artifacts to the `gerrit-maven` repository, replace +the existing `distributionManagement` section with this snippet. * Add these two snippets to the `pom.xml` to enable the wagon provider:
diff --git a/Documentation/dev-release-jgit.txt b/Documentation/dev-release-jgit.txt index f6d4d68..1a8b501 100644 --- a/Documentation/dev-release-jgit.txt +++ b/Documentation/dev-release-jgit.txt
@@ -1,33 +1,44 @@ -= Making a Release of JGit += Making a Snapshot Release of JGit This step is only necessary if we need to create an unofficial JGit snapshot release and publish it to the link:https://developers.google.com/storage/[Google Cloud Storage]. +[[prepare-environment]] +== Prepare the Maven Environment + +First, make sure you have done the necessary +link:dev-release-deploy-config.html#deploy-configuration-settings-xml[ +configuration in Maven `settings.xml`]. + +To apply the necessary settings in JGit's `pom.xml`, follow the instructions +in link:dev-release-deploy-config.html#deploy-configuration-subprojects[ +Configuration for Subprojects in `pom.xml`], or apply the provided diff by +executing the following command in the JGit workspace: + +---- + git apply /path/to/gerrit/tools/jgit-snapshot-deploy-pom.diff +---- [[prepare-release]] == Prepare the Release -Since JGit has its own release process we do not push any release tags -for JGit. Instead we will use the output of the `git describe` as the -version of the current JGit snapshot. +Since JGit has its own release process we do not push any release tags. Instead +we will use the output of `git describe` as the version of the current JGit +snapshot. + +In the JGit workspace, execute the following command: ---- ./tools/version.sh --release $(git describe) ---- - [[publish-release]] == Publish the Release -* Make sure you have done the configuration needed for deployment: -** link:dev-release-deploy-config.html#deploy-configuration-settings-xml[ -Configuration in Maven `settings.xml`] -** link:dev-release-deploy-config.html#deploy-configuration-subprojects[ -Configuration for Subprojects in `pom.xml`] +To deploy the new snapshot, execute the following command in the JGit +workspace: -* Deploy the new snapshot. From JGit workspace execute: -+ ---- mvn deploy ----
diff --git a/Documentation/error-prohibited-by-gerrit.txt b/Documentation/error-prohibited-by-gerrit.txt index 3d9bbad..3e5f23b 100644 --- a/Documentation/error-prohibited-by-gerrit.txt +++ b/Documentation/error-prohibited-by-gerrit.txt
@@ -17,10 +17,10 @@ link:access-control.html#category_create['Create Reference'] access right on `+refs/heads/*+` 4. if you push an annotated tag without - link:access-control.html#category_push_annotated['Push Annotated Tag'] + link:access-control.html#category_create_annotated['Create Annotated Tag'] access right on `+refs/tags/*+` 5. if you push a signed tag without - link:access-control.html#category_push_signed['Push Signed Tag'] + link:access-control.html#category_create_signed['Create Signed Tag'] access right on `+refs/tags/*+` 6. if you push a lightweight tag without the access right link:access-control.html#category_create['Create Reference'] for the reference name `+refs/tags/*+`
diff --git a/Documentation/install-quick.txt b/Documentation/install-quick.txt index 2623256..a8115db 100644 --- a/Documentation/install-quick.txt +++ b/Documentation/install-quick.txt
@@ -26,14 +26,14 @@ ---- $ java -version - java version "1.7.0_21" - Java(TM) SE Runtime Environment (build 1.7.0_21-b11) - Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode) + openjdk version "1.8.0_72" + OpenJDK Runtime Environment (build 1.8.0_72-b15) + OpenJDK 64-Bit Server VM (build 25.72-b15, mixed mode) ---- If Java isn't installed, get it: -* JDK, minimum version 1.7 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download] +* JDK, minimum version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download] [[user]]
diff --git a/Documentation/install.txt b/Documentation/install.txt index e3fb28d..44f6189 100644 --- a/Documentation/install.txt +++ b/Documentation/install.txt
@@ -5,7 +5,7 @@ To run the Gerrit service, the following requirements must be met on the host: -* JDK, minimum version 1.7 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download] +* JDK, minimum version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download] You'll also need an SQL database to house the review metadata. You have the choice of either using the embedded H2 or to host your own MySQL or PostgreSQL.
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt index 9bf6842..c0af651 100644 --- a/Documentation/intro-user.txt +++ b/Documentation/intro-user.txt
@@ -470,11 +470,16 @@ link:user-review-ui.html#project-branch-topic[change screen]. It is also possible to link:user-upload.html#topic[set a topic on -push]. +push], either by appending `%topic=...` to the ref name or through +the use of the command line flag `--push-option`, aliased to `-o`, +followed by `topic=...`. .Set Topic on Push ---- $ git push origin HEAD:refs/for/master%topic=multi-master + + // this is the same as: + $ git push origin HEAD:refs/heads/master -o topic=multi-master ---- [[drafts]] @@ -639,6 +644,23 @@ + Email notifications are disabled. +- [[default-base-for-merges]]`Default Base For Merges`: ++ +This setting controls which base should be pre-selected in the +`Diff Against` drop-down list when the change screen is opened for a +merge commit. ++ +** `Auto Merge`: ++ +Pre-selects `Auto Merge` in the `Diff Against` drop-down list when the +change screen is opened for a merge commit. ++ +** `First Parent`: ++ +Pre-selects `Parent 1` in the `Diff Against` drop-down list when the +change screen is opened for a merge commit. ++ + - [[diff-view]]`Diff View`: + Whether the Side-by-Side diff view or the Unified diff view should be
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt index ee3e8ce..4531446 100644 --- a/Documentation/rest-api-access.txt +++ b/Documentation/rest-api-access.txt
@@ -132,7 +132,7 @@ }, "refs/tags/*": { "permissions": { - "pushSignedTag": { + "createSignedTag": { "rules": { "53a4f647a89ea57992571187d8025f830625192a": { "action": "ALLOW" @@ -142,7 +142,7 @@ } } }, - "pushTag": { + "createTag": { "rules": { "53a4f647a89ea57992571187d8025f830625192a": { "action": "ALLOW"
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt index 77ca75a..ababc16 100644 --- a/Documentation/rest-api-accounts.txt +++ b/Documentation/rest-api-accounts.txt
@@ -396,7 +396,7 @@ HTTP/1.1 204 No Content ---- -If the account was already inactive the response is "`404 Not Found`". +If the account was already inactive the response is "`409 Conflict`". [[get-http-password]] === Get HTTP Password @@ -1213,6 +1213,7 @@ "size_bar_in_change_table": true, "review_category_strategy": "ABBREV", "mute_common_path_prefixes": true, + "default_base_for_merges": "FIRST_PARENT", "my": [ { "url": "#/dashboard/self", @@ -2440,8 +2441,6 @@ link:rest-api-config.html#download-info[DownloadInfo]. |`download_command` || The type of download command the user prefers to use. -|`copy_self_on_email` |not set if `false`| -Whether to CC me on comments I write. |`date_format` || The format to display the date in. Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`. @@ -2450,21 +2449,21 @@ Allowed values are `HHMM_12`, `HHMM_24`. |`relative_date_in_change_table`|not set if `false`| Whether to show relative dates in the changes table. +|`diff_view` || +The type of diff view to show. +Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`. |`size_bar_in_change_table` |not set if `false`| Whether to show the change sizes as colored bars in the change table. |`legacycid_in_change_table` |not set if `false`| Whether to show change number in the change table. +|`review_category_strategy` || +The strategy used to displayed info in the review category column. +Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`. |`mute_common_path_prefixes` |not set if `false`| Whether to mute common path prefixes in file names in the file table. |`signed_off_by` |not set if `false`| Whether to insert Signed-off-by footer in changes created with the inline edit feature. -|`review_category_strategy` || -The strategy used to displayed info in the review category column. -Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`. -|`diff_view` || -The type of diff view to show. -Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`. |`my` || The menu items of the `MY` top menu as a list of link:rest-api-config.html#top-menu-item-info[TopMenuItemInfo] entities. @@ -2477,6 +2476,10 @@ their own comments. On `DISABLED` the user will not receive any email notifications from Gerrit. Allowed values are `ENABLED`, `CC_ON_OWN_COMMENTS`, `DISABLED`. +|`default_base_for_merges` || +The base which should be pre-selected in the 'Diff Against' drop-down +list when the change screen is opened for a merge commit. +Allowed values are `AUTO_MERGE` and `FIRST_PARENT`. |============================================ [[preferences-input]] @@ -2498,8 +2501,6 @@ The type of download URL the user prefers to use. |`download_command` |optional| The type of download command the user prefers to use. -|`copy_self_on_email` |optional| -Whether to CC me on comments I write. |`date_format` |optional| The format to display the date in. Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`. @@ -2508,21 +2509,21 @@ Allowed values are `HHMM_12`, `HHMM_24`. |`relative_date_in_change_table`|optional| Whether to show relative dates in the changes table. +|`diff_view` |optional| +The type of diff view to show. +Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`. |`size_bar_in_change_table` |optional| Whether to show the change sizes as colored bars in the change table. |`legacycid_in_change_table` |optional| Whether to show change number in the change table. +|`review_category_strategy` |optional| +The strategy used to displayed info in the review category column. +Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`. |`mute_common_path_prefixes` |optional| Whether to mute common path prefixes in file names in the file table. |`signed_off_by` |optional| Whether to insert Signed-off-by footer in changes created with the inline edit feature. -|`review_category_strategy` |optional| -The strategy used to displayed info in the review category column. -Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`. -|`diff_view` |optional| -The type of diff view to show. -Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`. |`my` |optional| The menu items of the `MY` top menu as a list of link:rest-api-config.html#top-menu-item-info[TopMenuItemInfo] entities. @@ -2535,6 +2536,10 @@ their own comments. On `DISABLED` the user will not receive any email notifications from Gerrit. Allowed values are `ENABLED`, `CC_ON_OWN_COMMENTS`, `DISABLED`. +|`default_base_for_merges` |optional| +The base which should be pre-selected in the 'Diff Against' drop-down +list when the change screen is opened for a merge commit. +Allowed values are `AUTO_MERGE` and `FIRST_PARENT`. |============================================ [[query-limit-info]]
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index d0e504c..e28332c 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt
@@ -2135,9 +2135,17 @@ Promotes change edit to a regular patch set. +Options can be provided in the request body as a +link:#publish-change-edit-input[PublishChangeEditInput] entity. + .Request ---- POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit:publish HTTP/1.0 + Content-Type: application/json; charset=UTF-8 + + { + "notify": "NONE" + } ---- As response "`204 No Content`" is returned. @@ -2408,14 +2416,33 @@ [[delete-reviewer]] === Delete Reviewer -- -'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]' +'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]' + +'POST /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/delete' -- Deletes a reviewer from a change. +Options can be provided in the request body as a +link:#delete-reviewer-input[DeleteReviewerInput] entity. + .Request ---- DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers/John%20Doe HTTP/1.0 + POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers/John%20Doe/delete HTTP/1.0 +---- + +Please note that some proxies prohibit request bodies for DELETE +requests. In this case, if you want to specify options, use a POST +request: + +.Request +---- + POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers/John%20Doe/delete HTTP/1.0 + Content-Type: application/json; charset=UTF-8 + + { + "notify": "NONE" + } ---- .Response @@ -2456,7 +2483,7 @@ [[delete-vote]] === Delete Vote -- -'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]' +'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]' + 'POST /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]/delete' -- @@ -2545,6 +2572,60 @@ Adding query parameter `links` (for example `/changes/.../commit?links`) returns a link:#commit-info[CommitInfo] with the additional field `web_links`. +[[get-merge-list]] +=== Get Merge List +-- +'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/mergelist' +-- + +Returns the list of commits that are being integrated into a target +branch by a merge commit. By default the first parent is assumed to be +uninteresting. By using the `parent` option another parent can be set +as uninteresting (parents are 1-based). + +The list of commits is returned as a list of +link:#commit-info[CommitInfo] entities. Web links are only included if +the `links` option was set. + +.Request +---- + GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/7e30d802b890ec8d0be45b1cc2a8ef092bcfc858/mergelist HTTP/1.0 +---- + +.Response +---- +HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + [ + { + "commit": "674ac754f91e64a0efb8087e59a176484bd534d1", + "parents": [ + { + "commit": "1eee2c9d8f352483781e772f35dc586a69ff5646", + "subject": "Migrate contributor agreements to All-Projects." + } + ], + "author": { + "name": "Shawn O. Pearce", + "email": "sop@google.com", + "date": "2012-04-24 18:08:08.000000000", + "tz": -420 + }, + "committer": { + "name": "Shawn O. Pearce", + "email": "sop@google.com", + "date": "2012-04-24 18:08:08.000000000", + "tz": -420 + }, + "subject": "Use an EventBus to manage star icons", + "message": "Use an EventBus to manage star icons\n\nImage widgets that need to ..." + } + ] +---- + [[get-revision-actions]] === Get Revision Actions -- @@ -3176,6 +3257,59 @@ will suggest the browser save the patch as `commitsha1.diff.base64`, for later processing by command line tools. +[[submit-preview]] +===Submit Preview +-- +'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/preview_submit' +-- +Gets a file containing thin bundles of all modified projects if this +change was submitted. The bundles are named `${ProjectName}.git`. +Each thin bundle contains enough to construct the state in which a project would +be in if this change were submitted. The base of the thin bundles are the +current target branches, so to make use of this call in a non-racy way, first +get the bundles and then fetch all projects contained in the bundle. +(This assumes no non-fastforward pushes). + +You need to give a parameter '?format=zip' or '?format=tar' to specify the +format for the outer container. + +To make good use of this call, you would roughly need code as found at: +---- + $ curl -Lo preview_submit_test.sh http://review.example.com:8080/tools/scripts/preview_submit_test.sh +---- +.Request +---- + GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/preview_submit?zip HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Date: Tue, 13 Sep 2016 19:13:46 GMT + Content-Disposition: attachment; filename="submit-preview-147.zip" + X-Content-Type-Options: nosniff + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Pragma: no-cache + Expires: Mon, 01 Jan 1990 00:00:00 GMT + Content-Type: application/x-zip + Transfer-Encoding: chunked + + [binary stuff] +---- + +In case of an error, the response is not a zip file but a regular json response, +containing only the error message: + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + "Anonymous users cannot submit" +---- + [[get-mergeable]] === Get Mergeable -- @@ -4340,6 +4474,9 @@ |`mergeable` |optional| Whether the change is mergeable. + Not set for merged changes, or if the change has not yet been tested. +|`submittable` |optional| +Whether the change has been approved by the project submit rules. + +Provided only by link:#submitted-together[submitted_together]. |`insertions` || Number of inserted lines. |`deletions` || @@ -4589,6 +4726,21 @@ link:#web-link-info[WebLinkInfo] entities. |=========================== +[[delete-reviewer-input]] +=== DeleteReviewerInput +The `DeleteReviewerInput` entity contains options for the deletion of a +reviewer. + +[options="header",cols="1,^1,5"] +|======================= +|Field Name||Description +|`notify` |optional| +Notify handling that defines to whom email notifications should be sent +after the reviewer is deleted. + +Allowed values are `NONE`, `OWNER`, `OWNER_REVIEWERS` and `ALL`. + +If not set, the default is `ALL`. +|======================= + [[delete-vote-input]] === DeleteVoteInput The `DeleteVoteInput` entity contains options for the deletion of a @@ -4974,6 +5126,21 @@ outcome of the fix. |=========================== +[[publish-change-edit-input]] +=== PublishChangeEditInput +The `PublishChangeEditInput` entity contains options for the publishing of +change edit. + +[options="header",cols="1,^1,5"] +|======================= +|Field Name||Description +|`notify` |optional| +Notify handling that defines to whom email notifications should be sent +after the change edit is published. + +Allowed values are `NONE` and `ALL`. + +If not set, the default is `ALL`. +|======================= + [[push-certificate-info]] === PushCertificateInfo The `PushCertificateInfo` entity contains information about a push
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index c7c0878..de21034 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt
@@ -54,6 +54,14 @@ { "auth": { "auth_type": "LDAP", + "use_contributor_agreements": true, + "contributor_agreements": [ + { + "name": "Individual", + "description": "If you are going to be contributing code on your own, this is the one you want. You can sign this one online.", + "url": "static/cla_individual.html" + } + ], "editable_account_fields": [ "FULL_NAME", "REGISTER_NEW_EMAIL" @@ -115,7 +123,10 @@ "gerrit": { "all_projects": "All-Projects", "all_users": "All-Users" - "doc_search": true + "doc_search": true, + "web_uis": [ + "gwt" + ] }, "sshd": {}, "suggest": { @@ -1226,6 +1237,9 @@ |`use_contributor_agreements` |not set if `false`| Whether link:config-gerrit.html#auth.contributorAgreements[contributor agreements] are required. +|`contributor_agreements` |not set if `use_contributor_agreements` is `false`| +List of contributor agreements as link:rest-api-accounts.html#contributor-agreement-info[ +ContributorAgreementInfo] entities. |`editable_account_fields` || List of account fields that are editable. Possible values are `FULL_NAME`, `USER_NAME` and `REGISTER_NEW_EMAIL`. @@ -1458,6 +1472,9 @@ |`report_bug_text` |optional, not set if default| link:config-gerrit.html#gerrit.reportBugText[Display text for report bugs link]. +|`web_uis` || +List of web UIs supported by the HTTP server. Possible values are `GWT` +and `POLYGERRIT`. |================================= [[hit-ration-info]]
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt index ca79b93..546f13f 100644 --- a/Documentation/user-upload.txt +++ b/Documentation/user-upload.txt
@@ -176,12 +176,16 @@ To include a short tag associated with all of the changes in the same group, such as the local topic branch name, append it after -the destination branch name. In this example the short topic tag -'driver/i42' will be saved on each change this push creates or +the destination branch name or add it with the command line flag +`--push-option`, aliased to `-o`. In this example the short topic +tag 'driver/i42' will be saved on each change this push creates or updates: ---- git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/experimental%topic=driver/i42 + + // this is the same as: + git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/experimental -o topic=driver/i42 ---- [[message]] @@ -399,11 +403,11 @@ link:access-control.html#category_push_direct['Push'] with the 'Force' option ticked. -To push annotated tags, the `Push Annotated Tag` project right must +To push annotated tags, the `Create Annotated Tag` project right must be granted to one (or more) of the user's groups. There is only one level of access in this category. -Project owners may wish to grant themselves `Push Annotated Tag` +Project owners may wish to grant themselves `Create Annotated Tag` only at times when a new release is being prepared, and otherwise grant nothing at all. This ensures that accidental pushes don't make undesired changes to the public repository. @@ -452,6 +456,23 @@ git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/master%base=commit-id1,base=commit-id2 ---- +[[merged]] +=== Creating Changes for Merged Commits + +Normally, changes are only created for commits that have not yet +been merged into the branch. In some cases, you may want to review a +change that has already been merged. A new change for a merged commit +can be created by using the '%merged' argument: + +---- + git push ssh://john.doe@git.example.com:29418/kernel/common my-merged-commit:refs/for/master%merged +---- + +This only creates one merged change at a time, corresponding to +exactly `my-merged-commit`. It doesn't walk all of history up to that +point, which could be slow and create lots of unintended new changes. +To create multiple new changes, run push multiple times. + == repo upload
diff --git a/VERSION b/VERSION index 253cc19..3035c93 100644 --- a/VERSION +++ b/VERSION
@@ -2,4 +2,4 @@ # Used by :api_install and :api_deploy targets # when talking to the destination repository. # -GERRIT_VERSION = '2.13' +GERRIT_VERSION = '2.14-SNAPSHOT'
diff --git a/WORKSPACE b/WORKSPACE index d465b37..77d6dbc 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -24,24 +24,30 @@ sha1 = '83cd2cd674a217ade95a4bb83a8a14f351f48bd0', ) -GUICE_VERS = '4.0' +GUICE_VERS = '4.1.0' maven_jar( name = 'guice_library', artifact = 'com.google.inject:guice:' + GUICE_VERS, - sha1 = '0f990a43d3725781b6db7cd0acf0a8b62dfd1649', + sha1 = 'eeb69005da379a10071aa4948c48d89250febb07', ) maven_jar( name = 'guice_assistedinject', artifact = 'com.google.inject.extensions:guice-assistedinject:' + GUICE_VERS, - sha1 = '8fa6431da1a2187817e3e52e967535899e2e46ca', + sha1 = 'af799dd7e23e6fe8c988da12314582072b07edcb', ) maven_jar( name = 'guice_servlet', artifact = 'com.google.inject.extensions:guice-servlet:' + GUICE_VERS, - sha1 = '4503da866f4c402b5090579b40c1c4aaefabb164', + sha1 = '90ac2db772d9b85e2b05417b74f7464bcc061dcb', +) + +maven_jar( + name = 'multibindings', + artifact = 'com.google.inject.extensions:guice-multibindings:' + GUICE_VERS, + sha1 = '3b27257997ac51b0f8d19676f1ea170427e86d51', ) maven_jar( @@ -62,18 +68,18 @@ sha1 = '5d9e2e895e3111622720157d0aa540066d5fce3a', ) -GWT_VERS = '2.7.0' +GWT_VERS = '2.8.0-rc2' maven_jar( name = 'user', artifact = 'com.google.gwt:gwt-user:' + GWT_VERS, - sha1 = 'bdc7af42581745d3d79c2efe0b514f432b998a5b', + sha1 = 'ad99b09a626c20cce2bdacf3726a51b2cd16b99e', ) maven_jar( name = 'dev', artifact = 'com.google.gwt:gwt-dev:' + GWT_VERS, - sha1 = 'c2c3dd5baf648a0bb199047a818be5e560f48982', + sha1 = 'd70a6feb4661c07488090cb81303415e9110b15a', ) maven_jar( @@ -82,27 +88,63 @@ sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e', ) -JGIT_VERS = '4.4.1.201607150455-r.105-g81ba2be' +maven_jar( + name = 'jsinterop_annotations', + artifact = 'com.google.jsinterop:jsinterop-annotations:1.0.0', + sha1 = '23c3a3c060ffe4817e67673cc8294e154b0a4a95', +) + +maven_jar( + name = 'ant', + artifact = 'ant:ant:1.6.5', + sha1 = '7d18faf23df1a5c3a43613952e0e8a182664564b', +) + +maven_jar( + name = 'colt', + artifact = 'colt:colt:1.2.0', + sha1 = '0abc984f3adc760684d49e0f11ddf167ba516d4f', +) + +maven_jar( + name = 'tapestry', + artifact = 'tapestry:tapestry:4.0.2', + sha1 = 'e855a807425d522e958cbce8697f21e9d679b1f7', +) + +maven_jar( + name = 'w3c_css_sac', + artifact = 'org.w3c.css:sac:1.3', + sha1 = 'cdb2dcb4e22b83d6b32b93095f644c3462739e82', +) + +http_jar( + name = "javax_validation_src", + url = "http://repo1.maven.org/maven2/javax/validation/validation-api/1.0.0.GA/validation-api-1.0.0.GA-sources.jar", + sha256 = 'a394d52a9b7fe2bb14f0718d2b3c8308ffe8f37e911956012398d55c9f9f9b54', +) + +JGIT_VERS = '4.4.1.201607150455-r.118-g1096652' maven_jar( name = 'jgit', repository = 'http://gerrit-maven.storage.googleapis.com/', artifact = 'org.eclipse.jgit:org.eclipse.jgit:' + JGIT_VERS, - sha1 = 'c07c9c66da7983095a40945c0bfab211a473c4c5', + sha1 = 'cd142b9030910babd119702f1c4eeae13ee90018', ) maven_jar( name = 'jgit_servlet', repository = 'http://gerrit-maven.storage.googleapis.com/', artifact = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + JGIT_VERS, - sha1 = 'bb01841b74a48abe506c2e44f238e107188e6c8f', + sha1 = 'fa67bf925001cfc663bf98772f37d5c5c1abd756', ) # TODO(davido): Remove this hack when maven_jar supports pulling sources # https://github.com/bazelbuild/bazel/issues/308 http_file( name = 'jgit_src', - sha256 = '881906cb1e6743cb78df6dd3788cab7e974308fbb98cab4915e6591a62aa9374', + sha256 = '1a0b2d637359b1b51eba4d094491ef39877a6fc192e2fc1da0422a9adf04f0b8', url = 'http://gerrit-maven.storage.googleapis.com/org/eclipse/jgit/org.eclipse.jgit/' + '%s/org.eclipse.jgit-%s-sources.jar' % (JGIT_VERS, JGIT_VERS), ) @@ -117,44 +159,44 @@ name = 'jgit_archive', repository = 'http://gerrit-maven.storage.googleapis.com/', artifact = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + JGIT_VERS, - sha1 = 'fc3bc40e070c54198a046fcd3a1f7cac47163961', + sha1 = '3f45cd199e40a7c68ee07a1743c06d1c3d07308a', ) maven_jar( name = 'jgit_junit', repository = 'http://gerrit-maven.storage.googleapis.com/', artifact = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + JGIT_VERS, - sha1 = 'b4565ee84a6e1d0952010282b9fcf705ac6171a7', + sha1 = 'dc7edb9c3060655c7fb93ab9b9349e815bab266f', ) maven_jar( name = 'gwtjsonrpc', - artifact = 'com.google.gerrit:gwtjsonrpc:1.8', - sha1 = 'c264bf2f543cffddceada5cdf031eea06dbd44a0', + artifact = 'com.google.gerrit:gwtjsonrpc:1.11', + sha1 = '0990e7eec9eec3a15661edcf9232acbac4aeacec', ) http_jar( name = 'gwtjsonrpc_src', - sha256 = '2ef86396861a7c555c404b5a20a72dc6599b541ce2d1370a62f6470eefe7142d', - url = 'http://repo.maven.apache.org/maven2/com/google/gerrit/gwtjsonrpc/1.8/gwtjsonrpc-1.8-sources.jar', + sha256 = 'fc503488872c022073e244015fcb6806a64b65afe546bdac2db167a3875fb418', + url = 'http://repo.maven.apache.org/maven2/com/google/gerrit/gwtjsonrpc/1.11/gwtjsonrpc-1.11-sources.jar', ) maven_jar( name = 'gson', - artifact = 'com.google.code.gson:gson:2.6.2', - sha1 = 'f1bc476cc167b18e66c297df599b2377131a8947', + artifact = 'com.google.code.gson:gson:2.7', + sha1 = '751f548c85fa49f330cecbb1875893f971b33c4e', ) maven_jar( name = 'gwtorm_client', - artifact = 'com.google.gerrit:gwtorm:1.15', - sha1 = '26a2459f543ed78977535f92e379dc0d6cdde8bb', + artifact = 'com.google.gerrit:gwtorm:1.16', + sha1 = '3e41b6d7bb352fa0539ce23b9bce97cf8c26c3bf', ) http_jar( name = 'gwtorm_client_src', - sha256 = 'e0cf9382ed8c3cd1f0884ab77dabe634a04546676c4960d8b4c4b64a20132ef6', - url = 'http://repo.maven.apache.org/maven2/com/google/gerrit/gwtorm/1.15/gwtorm-1.15-sources.jar', + sha256 = 'd3e482c9ac1f828aa853debe6545c16503fbbde3bda94b18f652d9830b7f84b1', + url = 'http://repo.maven.apache.org/maven2/com/google/gerrit/gwtorm/1.16/gwtorm-1.16-sources.jar', ) maven_jar( @@ -165,14 +207,14 @@ maven_jar( name = 'joda_time', - artifact = 'joda-time:joda-time:2.8', - sha1 = '9f2785d7184b97d005a44241ccaf980f43b9ccdb', + artifact = 'joda-time:joda-time:2.9.4', + sha1 = '1c295b462f16702ebe720bbb08f62e1ba80da41b', ) maven_jar( name = 'joda_convert', - artifact = 'org.joda:joda-convert:1.2', - sha1 = '35ec554f0cd00c956cc69051514d9488b1374dec', + artifact = 'org.joda:joda-convert:1.8.1', + sha1 = '675642ac208e0b741bc9118dcbcae44c271b992a', ) maven_jar( @@ -287,8 +329,8 @@ maven_jar( name = 'commons_net', - artifact = 'commons-net:commons-net:2.2', - sha1 = '07993c12f63c78378f8c90de4bc2ee62daa7ca3a', + artifact = 'commons-net:commons-net:3.5', + sha1 = '342fc284019f590e1308056990fdb24a08f06318', ) maven_jar( @@ -327,42 +369,42 @@ sha1 = '2e35862b0435c1b027a21f3d6eecbe50e6e08d54', ) -OW2_VERS = '5.0.3' +OW2_VERS = '5.1' maven_jar( name = 'ow2_asm', artifact = 'org.ow2.asm:asm:' + OW2_VERS, - sha1 = 'dcc2193db20e19e1feca8b1240dbbc4e190824fa', + sha1 = '5ef31c4fe953b1fd00b8a88fa1d6820e8785bb45', ) maven_jar( name = 'ow2_asm_analysis', artifact = 'org.ow2.asm:asm-analysis:' + OW2_VERS, - sha1 = 'c7126aded0e8e13fed5f913559a0dd7b770a10f3', + sha1 = '6d1bf8989fc7901f868bee3863c44f21aa63d110', ) maven_jar( name = 'ow2_asm_commons', artifact = 'org.ow2.asm:asm-commons:' + OW2_VERS, - sha1 = 'a7111830132c7f87d08fe48cb0ca07630f8cb91c', + sha1 = '25d8a575034dd9cfcb375a39b5334f0ba9c8474e', ) maven_jar( name = 'ow2_asm_tree', artifact = 'org.ow2.asm:asm-tree:' + OW2_VERS, - sha1 = '287749b48ba7162fb67c93a026d690b29f410bed', + sha1 = '87b38c12a0ea645791ead9d3e74ae5268d1d6c34', ) maven_jar( name = 'ow2_asm_util', artifact = 'org.ow2.asm:asm-util:' + OW2_VERS, - sha1 = '1512e5571325854b05fb1efce1db75fcced54389', + sha1 = 'b60e33a6bd0d71831e0c249816d01e6c1dd90a47', ) maven_jar( name = 'auto_value', - artifact = 'com.google.auto.value:auto-value:1.2', - sha1 = '6873fed014fe1de1051aae2af68ba266d2934471', + artifact = 'com.google.auto.value:auto-value:1.3-rc1', + sha1 = 'b764e0fb7e11353fbff493b22fd6e83bf091a179', ) maven_jar( @@ -371,36 +413,36 @@ sha1 = '18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3', ) -LUCENE_VERS = '5.4.1' +LUCENE_VERS = '5.5.3' maven_jar( name = 'lucene_core', artifact = 'org.apache.lucene:lucene-core:' + LUCENE_VERS, - sha1 = 'c52b2088e2c30dfd95fd296ab6fb9cf8de9855ab', + sha1 = '20540c6347259f35a0d264605b22ce2a13917066', ) maven_jar( name = 'lucene_analyzers_common', artifact = 'org.apache.lucene:lucene-analyzers-common:' + LUCENE_VERS, - sha1 = 'c2aa2c4e00eb9cdeb5ac00dc0495e70c441f681e', + sha1 = 'cf734ab72813af33dc1544ce61abc5c17b9d35e9', ) maven_jar( name = 'backward_codecs', artifact = 'org.apache.lucene:lucene-backward-codecs:' + LUCENE_VERS, - sha1 = '5273da96380dfab302ad06c27fe58100db4c4e2f', + sha1 = 'a167789e52a9dc6d93bf3b588f79fdc9d7559c15', ) maven_jar( name = 'lucene_misc', artifact = 'org.apache.lucene:lucene-misc:' + LUCENE_VERS, - sha1 = '95f433b9d7dd470cc0aa5076e0f233907745674b', + sha1 = 'e356975c46447f06c71842632d0af9ec1baecfce', ) maven_jar( name = 'lucene_queryparser', artifact = 'org.apache.lucene:lucene-queryparser:' + LUCENE_VERS, - sha1 = 'dccd5279bfa656dec21af444a7a66820eb1cd618', + sha1 = 'e2452203d2c44cac5ac42b34e5dcc0a44bf29a53', ) maven_jar( @@ -447,8 +489,8 @@ maven_jar( name = 'jsr305', - artifact = 'com.google.code.findbugs:jsr305:2.0.2', - sha1 = '516c03b21d50a644d538de0f0369c620989cd8f0', + artifact = 'com.google.code.findbugs:jsr305:3.0.1', + sha1 = 'f7be08ec23c21485b9b5a1cf1654c2ec8c58168d', ) maven_jar( @@ -458,6 +500,19 @@ sha1 = '51d35e6f8bbc2412265066cea9653dd758c95826', ) +# Keep this version of Soy synchronized with the version used in Gitiles. +maven_jar( + name = 'soy', + artifact = 'com.google.template:soy:2016-08-09', + sha1 = '43d33651e95480d515fe26c10a662faafe3ad1e4', +) + +maven_jar( + name = 'icu4j', + artifact = 'com.ibm.icu:icu4j:57.1', + sha1 = '198ea005f41219f038f4291f0b0e9f3259730e92', +) + maven_jar( name = 'dropwizard_core', artifact = 'io.dropwizard.metrics:metrics-core:3.1.2', @@ -534,8 +589,8 @@ maven_jar( name = 'jimfs', - artifact = 'com.google.jimfs:jimfs:1.0', - sha1 = 'edd65a2b792755f58f11134e76485a928aab4c97', + artifact = 'com.google.jimfs:jimfs:1.1', + sha1 = '8fbd0579dc68aba6186935cc1bee21d2f3e7ec1c', ) maven_jar( @@ -624,60 +679,60 @@ sha1 = 'df4b50061e8e4c348ce243b921f53ee63ba9bbe1', ) -JETTY_VERS = '9.2.14.v20151106' +JETTY_VERS = '9.3.11.v20160721' maven_jar( name = 'jetty_servlet', artifact = 'org.eclipse.jetty:jetty-servlet:' + JETTY_VERS, - sha1 = '3a2cd4d8351a38c5d60e0eee010fee11d87483ef', + sha1 = 'd550147b85c73ea81084a4ac7915ba7f609021c5', ) maven_jar( name = 'jetty_security', artifact = 'org.eclipse.jetty:jetty-security:' + JETTY_VERS, - sha1 = '2d36974323fcb31e54745c1527b996990835db67', + sha1 = '1cbefc5d1196b9e1ca6f4cc36738998a6ebde8bf', ) maven_jar( name = 'jetty_servlets', artifact = 'org.eclipse.jetty:jetty-servlets:' + JETTY_VERS, - sha1 = 'a75c78a0ee544073457ca5ee9db20fdc6ed55225', + sha1 = 'a9f7a43977151a463aa21a9b0e882aa3d25452ef', ) maven_jar( name = 'jetty_server', artifact = 'org.eclipse.jetty:jetty-server:' + JETTY_VERS, - sha1 = '70b22c1353e884accf6300093362b25993dac0f5', + sha1 = 'd932e0dc1e9bd4839ae446754615163d60271a66', ) maven_jar( name = 'jetty_jmx', artifact = 'org.eclipse.jetty:jetty-jmx:' + JETTY_VERS, - sha1 = '617edc5e966b4149737811ef8b289cd94b831bab', + sha1 = '21a658d2f5eb87c23eef4911966625ea95f66d32', ) maven_jar( name = 'jetty_continuation', artifact = 'org.eclipse.jetty:jetty-continuation:' + JETTY_VERS, - sha1 = '8909d62fd7e28351e2da30de6fb4105539b949c0', + sha1 = '92a91c0dcc5f5d779a1c9f94038332be3f46c9df', ) maven_jar( name = 'jetty_http', artifact = 'org.eclipse.jetty:jetty-http:' + JETTY_VERS, - sha1 = '699ad1f2fa6fb0717e1b308a8c9e1b8c69d81ef6', + sha1 = 'dcfb95e5b886a981bb76467b911c5b706117f9cf', ) maven_jar( name = 'jetty_io', artifact = 'org.eclipse.jetty:jetty-io:' + JETTY_VERS, - sha1 = 'dfa4137371a3f08769820138ca1a2184dacda267', + sha1 = 'db5f4f481159894a4b670072a34917b5414d0c98', ) maven_jar( name = 'jetty_util', artifact = 'org.eclipse.jetty:jetty-util:' + JETTY_VERS, - sha1 = '0057e00b912ae0c35859ac81594a996007706a0b', + sha1 = '1812ffd5a04698051180d582c146ca807760c808', ) maven_jar( @@ -697,3 +752,29 @@ artifact = 'xerces:xercesImpl:2.8.1', sha1 = '25101e37ec0c907db6f0612cbf106ee519c1aef1', ) + +maven_jar( + name = 'postgresql', + artifact = 'postgresql:postgresql:9.1-901-1.jdbc4', + sha1 = '9bfabe48876ec38f6cbaa6931bad05c64a9ea942', +) + +CM_VERSION = '5.18.2' + +maven_jar( + name = 'codemirror_minified', + artifact = 'org.webjars.npm:codemirror-minified:' + CM_VERSION, + sha1 = '6755af157a7eaf2401468906bef67bbacc3c97f6', +) + +maven_jar( + name = 'codemirror_original', + artifact = 'org.webjars.npm:codemirror:' + CM_VERSION, + sha1 = '18c721ae88eed27cddb458c42f5d221fa3d9713e', +) + +maven_jar( + name = 'diff_match_patch', + artifact = 'org.webjars:google-diff-match-patch:20121119-1', + sha1 = '0cf1782dbcb8359d95070da9176059a5a9d37709', +)
diff --git a/contrib/build-consistency.go b/contrib/build-consistency.go new file mode 100644 index 0000000..db63a27 --- /dev/null +++ b/contrib/build-consistency.go
@@ -0,0 +1,108 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" +) + +var ( + // Define regex to find a comment in the build files + commentRE = regexp.MustCompile("#.*") + // Define regexes to extract the lib name and sha1 + mvnRE = regexp.MustCompile("maven_jar([^)]*)") + sha1RE = regexp.MustCompile("sha1=[\"'](?P<SHA1>[^,]*)[\"']") + bSha1RE = regexp.MustCompile("bin_sha1=[\"'](?P<SHA1>[^,]*)[\"']") + libNameRE = regexp.MustCompile("name=[\"'](?P<NAME>[^,]*)[\"']") +) + +func sanitize(s string) string { + // Strip out comments + s = commentRE.ReplaceAllString(s, "") + // Remove newlines and blanks + s = strings.Replace(s, "\n", "", -1) + s = strings.Replace(s, " ", "", -1) + // WORKSPACE syntax disallows the dash char in artifact name and we use an underscore + // So we make this a consistent underscore in all files + s = strings.Replace(s, "-", "_", -1) + return s +} + +func main() { + // Load bazel WORKSPACE file + bzlDat, err := ioutil.ReadFile("WORKSPACE") + if err != nil { + log.Fatal(err) + } + bzlStr := sanitize(string(bzlDat)) + + // Walk all files nested under lib. Find, load and sanitize BUCK files + bckStrs := []string{} + err = filepath.Walk("lib/", func(path string, f os.FileInfo, err error) error { + bckFile := filepath.Join(path, "BUCK") + if _, err := os.Stat(bckFile); err == nil { + bckDat, err := ioutil.ReadFile(bckFile) + if err != nil { + return err + } + bckStrs = append(bckStrs, sanitize(string(bckDat))) + } + return nil + }) + if err != nil { + log.Fatal(err) + } + bckStr := strings.Join(bckStrs, "") + + // Find all bazel dependencies + // bzlVersions maps from a lib name to the referenced sha1 + bzlVersions := make(map[string]string) + for _, mvn := range mvnRE.FindAllString(bzlStr, -1) { + sha1s := sha1RE.FindStringSubmatch(mvn) + names := libNameRE.FindStringSubmatch(mvn) + if len(sha1s) > 1 && len(names) > 1 { + bzlVersions[names[1]] = sha1RE.FindStringSubmatch(mvn)[1] + } else { + fmt.Printf("Can't parse lib sha1/name of target %s\n", mvn) + } + } + + // Find all buck dependencies and check if we have the correct bazel dependency on file + for _, mvn := range mvnRE.FindAllString(bckStr, -1) { + sha1s := bSha1RE.FindStringSubmatch(mvn) + if len(sha1s) < 2 { + // Buck knows two dep version representations: just a SHA1 or a bin_sha1 and src_sha1 + // We try to extract the bin_sha1 first. If that fails, we use the sha1 + sha1s = sha1RE.FindStringSubmatch(mvn) + } + names := libNameRE.FindStringSubmatch(mvn) + if len(sha1s) > 1 && len(names) > 1 { + if _, ok := bzlVersions[names[1]]; !ok { + // TODO(hiesel) This produces too many false positives. + //fmt.Printf("Don't have lib %s in bazel\n", names[1]) + } else if bzlVersions[names[1]] != sha1s[1] { + fmt.Printf("SHA1 of lib %s does not match: buck has %s while bazel has %s\n", names[1], sha1s[1], bzlVersions[names[1]]) + } + } else { + fmt.Printf("Can't parse lib sha1/name on target %s\n", mvn) + } + } +}
diff --git a/gerrit-acceptance-framework/BUCK b/gerrit-acceptance-framework/BUCK index ba68fa3..4bce902 100644 --- a/gerrit-acceptance-framework/BUCK +++ b/gerrit-acceptance-framework/BUCK
@@ -1,6 +1,7 @@ SRCS = glob(['src/test/java/com/google/gerrit/acceptance/*.java']) DEPS = [ + '//gerrit-antlr:query_exception', '//gerrit-gpg:gpg', '//gerrit-launcher:launcher', '//gerrit-openid:openid', @@ -37,10 +38,20 @@ java_binary( name = 'acceptance-framework', + merge_manifests = False, + manifest_file = ':manifest', deps = [':lib'], visibility = ['PUBLIC'], ) +genrule( + name = 'manifest', + cmd = 'echo "Manifest-Version: 1.0" >$OUT;' + + 'echo "Implementation-Title: Gerrit Acceptance Test Framework" >>$OUT;' + + 'echo "Implementation-Vendor: Gerrit Code Review Project" >>$OUT', + out = 'manifest.txt', +) + java_library( name = 'lib', srcs = SRCS, @@ -57,18 +68,8 @@ ) java_sources( - name = 'src', - srcs = SRCS, - visibility = ['PUBLIC'], -) - -# The above java_sources produces a .jar somewhere in the depths of -# buck-out, but it does not bring it to -# buck-out/gen/gerrit-acceptance-framework/gerrit-acceptance-framework-src.jar. -# We fix that by the following java_binary. -java_binary( name = 'acceptance-framework-src', - deps = [ ':src' ], + srcs = SRCS, visibility = ['PUBLIC'], ) @@ -76,7 +77,7 @@ name = 'acceptance-framework-javadoc', title = 'Gerrit Acceptance Test Framework Documentation', pkgs = [' com.google.gerrit.acceptance'], - paths = ['src/test/java'], + source_jar = ':acceptance-framework-src', srcs = SRCS, deps = DEPS + PROVIDED + [ '//lib:guava',
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD index 1439ba9..58c13f5 100644 --- a/gerrit-acceptance-framework/BUILD +++ b/gerrit-acceptance-framework/BUILD
@@ -3,6 +3,7 @@ SRCS = glob(['src/test/java/com/google/gerrit/acceptance/*.java']) DEPS = [ + '//gerrit-antlr:query_exception', '//gerrit-gpg:gpg', '//gerrit-launcher:launcher', '//gerrit-openid:openid', @@ -58,3 +59,12 @@ ], visibility = ['//visibility:public'], ) + +load('//tools/bzl:javadoc.bzl', 'java_doc') + +java_doc( + name = 'acceptance-framework-javadoc', + title = 'Gerrit Acceptance Test Framework Documentation', + libs = [':lib'], + pkgs = ['com.google.gerrit.acceptance'], +)
diff --git a/gerrit-acceptance-framework/pom.xml b/gerrit-acceptance-framework/pom.xml index 5278ede..d9d701c 100644 --- a/gerrit-acceptance-framework/pom.xml +++ b/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.13</version> + <version>2.14-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-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 ae480c7..82de9f8 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
@@ -17,25 +17,30 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.GitUtil.initSsh; import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES; +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 org.eclipse.jgit.lib.Constants.HEAD; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; import com.google.common.collect.Sets; import com.google.common.primitives.Chars; import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context; 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.Permission; import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.extensions.api.GerritApi; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.RevisionApi; import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo; +import com.google.gerrit.extensions.api.groups.GroupApi; import com.google.gerrit.extensions.api.groups.GroupInput; import com.google.gerrit.extensions.api.projects.BranchApi; import com.google.gerrit.extensions.api.projects.BranchInput; @@ -45,7 +50,10 @@ import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.extensions.common.ActionInfo; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.extensions.common.ChangeType; +import com.google.gerrit.extensions.common.DiffInfo; import com.google.gerrit.extensions.common.EditInfo; +import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.reviewdb.client.AccountGroup; @@ -61,7 +69,9 @@ import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.GroupCache; +import com.google.gerrit.server.change.Abandon; import com.google.gerrit.server.change.ChangeResource; +import com.google.gerrit.server.change.FileContentUtil; import com.google.gerrit.server.change.RevisionResource; import com.google.gerrit.server.change.Revisions; import com.google.gerrit.server.config.AllProjectsName; @@ -70,6 +80,8 @@ import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.server.index.change.ChangeIndex; +import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.mail.EmailHeader; import com.google.gerrit.server.notedb.ChangeNoteUtil; @@ -97,12 +109,19 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.TransportBundleStream; +import org.eclipse.jgit.transport.URIish; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -114,14 +133,20 @@ import org.junit.runner.RunWith; import org.junit.runners.model.Statement; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; @RunWith(ConfigSuite.class) public abstract class AbstractDaemonTest { @@ -217,6 +242,9 @@ @Inject private EventRecorder.Factory eventRecorderFactory; + @Inject + private ChangeIndexCollection changeIndexes; + protected TestRepository<InMemoryRepository> testRepo; protected GerritServer server; protected TestAccount admin; @@ -235,6 +263,9 @@ @Inject protected ChangeNotes.Factory notesFactory; + @Inject + protected Abandon changeAbandoner; + @Rule public ExpectedException exception = ExpectedException.none(); @@ -520,21 +551,26 @@ protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception { + return createMergeCommitChange(ref, "foo"); + } + + protected PushOneCommit.Result createMergeCommitChange(String ref, String file) + throws Exception { ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId(); PushOneCommit.Result p1 = pushFactory.create(db, admin.getIdent(), - testRepo, "parent 1", ImmutableMap.of("foo", "foo-1", "bar", "bar-1")) + testRepo, "parent 1", ImmutableMap.of(file, "foo-1", "bar", "bar-1")) .to(ref); // reset HEAD in order to create a sibling of the first change testRepo.reset(initial); PushOneCommit.Result p2 = pushFactory.create(db, admin.getIdent(), - testRepo, "parent 2", ImmutableMap.of("foo", "foo-2", "bar", "bar-2")) + testRepo, "parent 2", ImmutableMap.of(file, "foo-2", "bar", "bar-2")) .to(ref); PushOneCommit m = pushFactory.create(db, admin.getIdent(), testRepo, "merge", - ImmutableMap.of("foo", "foo-1", "bar", "bar-2")); + ImmutableMap.of(file, "foo-1", "bar", "bar-2")); m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit())); PushOneCommit.Result result = m.to(ref); result.assertOkStatus(); @@ -666,6 +702,22 @@ atrScope.set(preDisableContext); } + protected void disableChangeIndexWrites() { + for (ChangeIndex i : changeIndexes.getWriteIndexes()) { + if (!(i instanceof ReadOnlyChangeIndex)) { + changeIndexes.addWriteIndex(new ReadOnlyChangeIndex(i)); + } + } + } + + protected void enableChangeIndexWrites() { + for (ChangeIndex i : changeIndexes.getWriteIndexes()) { + if (i instanceof ReadOnlyChangeIndex) { + changeIndexes.addWriteIndex(((ReadOnlyChangeIndex)i).unwrap()); + } + } + } + protected static Gson newGson() { return OutputFormat.JSON_COMPACT.newGson(); } @@ -800,6 +852,19 @@ } } + protected void removePermission(String permission, Project.NameKey project, + String ref) throws IOException, ConfigInvalidException { + try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { + md.setMessage(String.format("Remove %s on %s", permission, ref)); + ProjectConfig config = ProjectConfig.read(md); + AccessSection s = config.getAccessSection(ref, true); + Permission p = s.getPermission(permission, true); + p.getRules().clear(); + config.commit(md); + projectCache.evict(config.getProject()); + } + } + protected void blockRead(String ref) throws Exception { block(Permission.READ, REGISTERED_USERS, ref); } @@ -831,13 +896,7 @@ } private static Iterable<String> changeIds(Iterable<ChangeInfo> changes) { - return Iterables.transform(changes, - new Function<ChangeInfo, String>() { - @Override - public String apply(ChangeInfo input) { - return input.changeId; - } - }); + return Iterables.transform(changes, i -> i.changeId); } protected void assertSubmittedTogether(String chId, String... expected) @@ -908,7 +967,8 @@ protected RevCommit getHead(Repository repo, String name) throws Exception { try (RevWalk rw = new RevWalk(repo)) { - return rw.parseCommit(repo.exactRef(name).getObjectId()); + Ref r = repo.exactRef(name); + return r != null ? rw.parseCommit(r.getObjectId()) : null; } } @@ -940,4 +1000,141 @@ (EmailHeader.String)message.headers().get("Reply-To"); assertThat(replyTo.getString()).isEqualTo(email); } + + protected ContributorAgreement configureContributorAgreement( + boolean autoVerify) throws Exception { + ContributorAgreement ca; + if (autoVerify) { + String g = createGroup("cla-test-group"); + GroupApi groupApi = gApi.groups().id(g); + groupApi.description("CLA test group"); + AccountGroup caGroup = groupCache.get( + new AccountGroup.UUID(groupApi.detail().id)); + GroupReference groupRef = GroupReference.forGroup(caGroup); + PermissionRule rule = new PermissionRule(groupRef); + rule.setAction(PermissionRule.Action.ALLOW); + ca = new ContributorAgreement("cla-test"); + ca.setAutoVerify(groupRef); + ca.setAccepted(ImmutableList.of(rule)); + } else { + ca = new ContributorAgreement("cla-test-no-auto-verify"); + } + ca.setDescription("description"); + ca.setAgreementUrl("agreement-url"); + + ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig(); + cfg.replace(ca); + saveProjectConfig(allProjects, cfg); + return ca; + } + + /** + * Fetches each bundle into a newly cloned repository, then it applies + * the bundle, and returns the resulting tree id. + */ + protected Map<Branch.NameKey, RevTree> + fetchFromBundles(BinaryResult bundles) throws Exception { + + assertThat(bundles.getContentType()).isEqualTo("application/x-zip"); + + File tempfile = File.createTempFile("test", null); + bundles.writeTo(new FileOutputStream(tempfile)); + + Map<Branch.NameKey, RevTree> ret = new HashMap<>(); + try (ZipFile readback = new ZipFile(tempfile);) { + for (ZipEntry entry : ImmutableList.copyOf( + Iterators.forEnumeration(readback.entries()))) { + String bundleName = entry.getName(); + InputStream bundleStream = readback.getInputStream(entry); + + int len = bundleName.length(); + assertThat(bundleName).endsWith(".git"); + String repoName = bundleName.substring(0, len - 4); + Project.NameKey proj = new Project.NameKey(repoName); + TestRepository<?> localRepo = cloneProject(proj); + + try (TransportBundleStream tbs = new TransportBundleStream( + localRepo.getRepository(), new URIish(bundleName), bundleStream);) { + + FetchResult fr = tbs.fetch(NullProgressMonitor.INSTANCE, + Arrays.asList(new RefSpec("refs/*:refs/preview/*"))); + for (Ref r : fr.getAdvertisedRefs()) { + String branchName = r.getName(); + Branch.NameKey n = new Branch.NameKey(proj, branchName); + + RevCommit c = localRepo.getRevWalk().parseCommit(r.getObjectId()); + ret.put(n, c.getTree()); + } + } + } + } + return ret; + } + + /** + * Assert that the given branches have the given tree ids. + */ + protected void assertRevTrees(Project.NameKey proj, + Map<Branch.NameKey, RevTree> trees) throws Exception { + TestRepository<?> localRepo = cloneProject(proj); + GitUtil.fetch(localRepo, "refs/*:refs/*"); + Map<String, Ref> refs = localRepo.getRepository().getAllRefs(); + Map<Branch.NameKey, RevTree> refValues = new HashMap<>(); + + for (Branch.NameKey b : trees.keySet()) { + if (!b.getParentKey().equals(proj)) { + continue; + } + + Ref r = refs.get(b.get()); + assertThat(r).isNotNull(); + RevWalk rw = localRepo.getRevWalk(); + RevCommit c = rw.parseCommit(r.getObjectId()); + refValues.put(b, c.getTree()); + + assertThat(trees.get(b)).isEqualTo(refValues.get(b)); + } + assertThat(refValues.keySet()).containsAnyIn(trees.keySet()); + } + + protected void assertDiffForNewFile(DiffInfo diff, RevCommit commit, + String path, String expectedContentSideB) throws Exception { + List<String> expectedLines = new ArrayList<>(); + for (String line : expectedContentSideB.split("\n")) { + expectedLines.add(line); + } + + assertThat(diff.binary).isNull(); + assertThat(diff.changeType).isEqualTo(ChangeType.ADDED); + assertThat(diff.diffHeader).isNotNull(); + assertThat(diff.intralineStatus).isNull(); + assertThat(diff.webLinks).isNull(); + + assertThat(diff.metaA).isNull(); + assertThat(diff.metaB).isNotNull(); + assertThat(diff.metaB.commitId).isEqualTo(commit.name()); + + String expectedContentType = "text/plain"; + if (COMMIT_MSG.equals(path)) { + expectedContentType = FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE; + } else if (MERGE_LIST.equals(path)) { + expectedContentType = FileContentUtil.TEXT_X_GERRIT_MERGE_LIST; + } + assertThat(diff.metaB.contentType).isEqualTo(expectedContentType); + + assertThat(diff.metaB.lines).isEqualTo(expectedLines.size()); + assertThat(diff.metaB.name).isEqualTo(path); + assertThat(diff.metaB.webLinks).isNull(); + + assertThat(diff.content).hasSize(1); + DiffInfo.ContentEntry contentEntry = diff.content.get(0); + assertThat(contentEntry.b).containsExactlyElementsIn(expectedLines) + .inOrder(); + assertThat(contentEntry.a).isNull(); + assertThat(contentEntry.ab).isNull(); + assertThat(contentEntry.common).isNull(); + assertThat(contentEntry.editA).isNull(); + assertThat(contentEntry.editB).isNull(); + assertThat(contentEntry.skip).isNull(); + } }
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 0196d1f..ceab04fe 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
@@ -28,12 +28,15 @@ import org.eclipse.jgit.api.FetchCommand; import org.eclipse.jgit.api.PushCommand; +import org.eclipse.jgit.api.TagCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.FetchResult; @@ -137,6 +140,26 @@ return cloneProject(project, sshSession.getUrl() + "/" + project.get()); } + public static Ref createAnnotatedTag(TestRepository<?> testRepo, String name, + PersonIdent tagger) throws GitAPIException { + TagCommand cmd = testRepo.git().tag() + .setName(name) + .setAnnotated(true) + .setMessage(name) + .setTagger(tagger); + return cmd.call(); + } + + public static Ref updateAnnotatedTag(TestRepository<?> testRepo, String name, + PersonIdent tagger) throws GitAPIException { + TagCommand tc = testRepo.git().tag().setName(name); + return tc.setAnnotated(true) + .setMessage(name) + .setTagger(tagger) + .setForceUpdate(true) + .call(); + } + public static void fetch(TestRepository<?> testRepo, String spec) throws GitAPIException { FetchCommand fetch = testRepo.git().fetch(); @@ -144,6 +167,11 @@ fetch.call(); } + public static PushResult pushHead(TestRepository<?> testRepo, String ref) + throws GitAPIException { + return pushHead(testRepo, ref, false); + } + public static PushResult pushHead(TestRepository<?> testRepo, String ref, boolean pushTags) throws GitAPIException { return pushHead(testRepo, ref, pushTags, false); @@ -151,9 +179,27 @@ public static PushResult pushHead(TestRepository<?> testRepo, String ref, boolean pushTags, boolean force) throws GitAPIException { + return pushOne(testRepo, "HEAD", ref, pushTags, force, null); + } + + public static PushResult pushHead(TestRepository<?> testRepo, String ref, + boolean pushTags, boolean force, List<String> pushOptions) + throws GitAPIException { + return pushOne(testRepo, "HEAD", ref, pushTags, force, pushOptions); + } + + public static PushResult deleteRef(TestRepository<?> testRepo, String ref) + throws GitAPIException { + return pushOne(testRepo, "", ref, false, true, null); + } + + public static PushResult pushOne(TestRepository<?> testRepo, String source, + String target, boolean pushTags, boolean force, List<String> pushOptions) + throws GitAPIException { PushCommand pushCmd = testRepo.git().push(); pushCmd.setForce(force); - pushCmd.setRefSpecs(new RefSpec("HEAD:" + ref)); + pushCmd.setPushOptions(pushOptions); + pushCmd.setRefSpecs(new RefSpec(source + ":" + target)); if (pushTags) { pushCmd.setPushTags(); } @@ -175,6 +221,20 @@ assertThat(rru.getMessage()).isEqualTo(expectedMessage); } + public static PushResult pushTag(TestRepository<?> testRepo, String tag) + throws GitAPIException { + return pushTag(testRepo, tag, false); + } + + public static PushResult pushTag(TestRepository<?> testRepo, String tag, + boolean force) throws GitAPIException { + PushCommand pushCmd = testRepo.git().push(); + pushCmd.setForce(force); + pushCmd.setRefSpecs(new RefSpec("refs/tags/" + tag + ":refs/tags/" + tag)); + Iterable<PushResult> r = pushCmd.call(); + return Iterables.getOnlyElement(r); + } + public static Optional<String> getChangeId(TestRepository<?> tr, ObjectId id) throws IOException { RevCommit c = tr.getRevWalk().parseCommit(id);
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpResponse.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpResponse.java index 390cae3..e9c6e96 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpResponse.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
@@ -16,6 +16,7 @@ import com.google.common.base.Preconditions; +import org.apache.http.Header; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; @@ -52,7 +53,12 @@ } public String getContentType() { - return response.getFirstHeader("X-FYI-Content-Type").getValue(); + return getHeader("X-FYI-Content-Type"); + } + + public String getHeader(String name) { + Header hdr = response.getFirstHeader(name); + return hdr != null ? hdr.getValue() : null; } public boolean hasContent() {
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java index 1e0920e..669b991 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java
@@ -37,7 +37,11 @@ account.username, account.httpPassword); } - protected RestResponse execute(Request request) throws IOException { + public String url() { + return url; + } + + public RestResponse execute(Request request) throws IOException { return new RestResponse(executor.execute(request).returnResponse()); } }
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java index f53202f..0783688 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -27,6 +27,8 @@ import com.google.gerrit.server.config.TrackingFooters; import com.google.gerrit.server.config.TrackingFootersProvider; import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.notedb.ChangeBundleReader; +import com.google.gerrit.server.notedb.GwtormChangeBundleReader; import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.schema.DataSourceType; import com.google.gerrit.server.schema.NotesMigrationSchemaFactory; @@ -89,6 +91,7 @@ bind(Key.get(schemaFactory, ReviewDbFactory.class)) .to(InMemoryDatabase.class); bind(InMemoryDatabase.class).in(SINGLETON); + bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class); listener().to(CreateDatabase.class);
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java index d79e573..5505263 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.GitUtil.pushHead; +import static org.junit.Assert.assertEquals; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; @@ -136,6 +137,7 @@ private String changeId; private Tag tag; private boolean force; + private List<String> pushOptions; private final TestRepository<?>.CommitBuilder commitBuilder; @@ -275,8 +277,8 @@ } tagCommand.call(); } - return new Result(ref, pushHead(testRepo, ref, tag != null, force), c, - subject); + return new Result(ref, + pushHead(testRepo, ref, tag != null, force, pushOptions), c, subject); } public void setTag(final Tag tag) { @@ -287,6 +289,14 @@ this.force = force; } + public List<String> getPushOptions() { + return pushOptions; + } + + public void setPushOptions(List<String> pushOptions) { + this.pushOptions = pushOptions; + } + public void noParents() { commitBuilder.noParents(); } @@ -326,6 +336,10 @@ return commit; } + public void assertPushOptions(List<String> pushOptions) { + assertEquals(pushOptions, getPushOptions()); + } + public void assertChange(Change.Status expectedStatus, String expectedTopic, TestAccount... expectedReviewers) throws OrmException, NoSuchChangeException {
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java new file mode 100644 index 0000000..cdecf05 --- /dev/null +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
@@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.acceptance; + +import com.google.gerrit.reviewdb.client.Change.Id; +import com.google.gerrit.server.index.QueryOptions; +import com.google.gerrit.server.index.Schema; +import com.google.gerrit.server.index.change.ChangeIndex; +import com.google.gerrit.server.query.DataSource; +import com.google.gerrit.server.query.Predicate; +import com.google.gerrit.server.query.QueryParseException; +import com.google.gerrit.server.query.change.ChangeData; + +import java.io.IOException; + +public class ReadOnlyChangeIndex implements ChangeIndex { + private final ChangeIndex index; + + public ReadOnlyChangeIndex(ChangeIndex index) { + this.index = index; + } + + public ChangeIndex unwrap() { + return index; + } + + @Override + public Schema<ChangeData> getSchema() { + return index.getSchema(); + } + + @Override + public void close() { + index.close(); + } + + @Override + public void replace(ChangeData obj) throws IOException { + // do nothing + } + + @Override + public void delete(Id key) throws IOException { + // do nothing + } + + @Override + public void deleteAll() throws IOException { + // do nothing + } + + @Override + public DataSource<ChangeData> getSource(Predicate<ChangeData> p, + QueryOptions opts) throws QueryParseException { + return index.getSource(p, opts); + } + + @Override + public void markReady(boolean ready) throws IOException { + // do nothing + } +}
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java index 9c59e10..90ece46 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -45,7 +45,7 @@ new BasicHeader(HttpHeaders.ACCEPT, "application/json")); } - private RestResponse getWithHeader(String endPoint, Header header) + public RestResponse getWithHeader(String endPoint, Header header) throws IOException { Request get = Request.Get(url + "/a" + endPoint); if (header != null) {
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java index 7f08b6f..c1a815a 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -14,7 +14,6 @@ package com.google.gerrit.acceptance; -import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.mail.Address; @@ -29,13 +28,7 @@ public class TestAccount { public static FluentIterable<Account.Id> ids( Iterable<TestAccount> accounts) { - return FluentIterable.from(accounts) - .transform(new Function<TestAccount, Account.Id>() { - @Override - public Account.Id apply(TestAccount in) { - return in.id; - } - }); + return FluentIterable.from(accounts).transform(a -> a.id); } public static FluentIterable<Account.Id> ids(TestAccount... accounts) { @@ -43,13 +36,7 @@ } public static FluentIterable<String> names(Iterable<TestAccount> accounts) { - return FluentIterable.from(accounts) - .transform(new Function<TestAccount, String>() { - @Override - public String apply(TestAccount in) { - return in.fullName; - } - }); + return FluentIterable.from(accounts).transform(a -> a.fullName); } public static FluentIterable<String> names(TestAccount... accounts) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java index 93525a4..ec77484 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -29,6 +29,7 @@ import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; @@ -199,6 +200,36 @@ } @Test + public void active() throws Exception { + assertThat(gApi.accounts().id("user").getActive()).isTrue(); + gApi.accounts().id("user").setActive(false); + assertThat(gApi.accounts().id("user").getActive()).isFalse(); + gApi.accounts().id("user").setActive(true); + assertThat(gApi.accounts().id("user").getActive()).isTrue(); + } + + @Test + public void deactivateSelf() throws Exception { + exception.expect(ResourceConflictException.class); + exception.expectMessage("cannot deactivate own account"); + gApi.accounts().self().setActive(false); + } + + @Test + public void deactivateNotActive() throws Exception { + assertThat(gApi.accounts().id("user").getActive()).isTrue(); + gApi.accounts().id("user").setActive(false); + assertThat(gApi.accounts().id("user").getActive()).isFalse(); + try { + gApi.accounts().id("user").setActive(false); + fail("Expected exception"); + } catch (ResourceConflictException e) { + assertThat(e.getMessage()).isEqualTo("account not active"); + } + gApi.accounts().id("user").setActive(true); + } + + @Test public void starUnstarChange() throws Exception { PushOneCommit.Result r = createChange(); String triplet = project.get() + "~master~" + r.getChangeId();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java index 8cd696c..00b48b4 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
@@ -19,27 +19,22 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.fail; -import com.google.common.collect.ImmutableList; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.common.data.ContributorAgreement; -import com.google.gerrit.common.data.GroupReference; -import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.extensions.api.changes.CherryPickInput; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.SubmitInput; -import com.google.gerrit.extensions.api.groups.GroupApi; import com.google.gerrit.extensions.api.projects.BranchInfo; import com.google.gerrit.extensions.api.projects.BranchInput; import com.google.gerrit.extensions.client.InheritableBoolean; import com.google.gerrit.extensions.common.AgreementInfo; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInput; +import com.google.gerrit.extensions.common.ServerInfo; 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.UnprocessableEntityException; -import com.google.gerrit.reviewdb.client.AccountGroup; -import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.TestTimeUtil; @@ -52,8 +47,8 @@ import java.util.List; public class AgreementsIT extends AbstractDaemonTest { - private ContributorAgreement ca; - private ContributorAgreement ca2; + private ContributorAgreement caAutoVerify; + private ContributorAgreement caNoAutoVerify; @ConfigSuite.Config public static Config enableAgreementsConfig() { @@ -74,32 +69,26 @@ @Before public void setUp() throws Exception { - String g = createGroup("cla-test-group"); - GroupApi groupApi = gApi.groups().id(g); - groupApi.description("CLA test group"); - AccountGroup caGroup = groupCache.get( - new AccountGroup.UUID(groupApi.detail().id)); - GroupReference groupRef = GroupReference.forGroup(caGroup); - PermissionRule rule = new PermissionRule(groupRef); - rule.setAction(PermissionRule.Action.ALLOW); - ca = new ContributorAgreement("cla-test"); - ca.setDescription("description"); - ca.setAgreementUrl("agreement-url"); - ca.setAutoVerify(groupRef); - ca.setAccepted(ImmutableList.of(rule)); - - ca2 = new ContributorAgreement("cla-test-no-auto-verify"); - ca2.setDescription("description"); - ca2.setAgreementUrl("agreement-url"); - - ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig(); - cfg.replace(ca); - cfg.replace(ca2); - saveProjectConfig(allProjects, cfg); + caAutoVerify = configureContributorAgreement(true); + caNoAutoVerify = configureContributorAgreement(false); setApiUser(user); } @Test + public void getAvailableAgreements() throws Exception { + ServerInfo info = gApi.config().server().getInfo(); + if (isContributorAgreementsEnabled()) { + assertThat(info.auth.useContributorAgreements).isTrue(); + assertThat(info.auth.contributorAgreements).hasSize(2); + assertAgreement(info.auth.contributorAgreements.get(0), caAutoVerify); + assertAgreement(info.auth.contributorAgreements.get(1), caNoAutoVerify); + } else { + assertThat(info.auth.useContributorAgreements).isNull(); + assertThat(info.auth.contributorAgreements).isNull(); + } + } + + @Test public void signNonExistingAgreement() throws Exception { assume().that(isContributorAgreementsEnabled()).isTrue(); exception.expect(UnprocessableEntityException.class); @@ -112,7 +101,7 @@ assume().that(isContributorAgreementsEnabled()).isTrue(); exception.expect(BadRequestException.class); exception.expectMessage("cannot enter a non-autoVerify agreement"); - gApi.accounts().self().signAgreement(ca2.getName()); + gApi.accounts().self().signAgreement(caNoAutoVerify.getName()); } @Test @@ -124,7 +113,7 @@ assertThat(result).isEmpty(); // Sign the agreement - gApi.accounts().self().signAgreement(ca.getName()); + gApi.accounts().self().signAgreement(caAutoVerify.getName()); // Explicitly reset the user to force a new request context setApiUser(user); @@ -133,12 +122,10 @@ result = gApi.accounts().self().listAgreements(); assertThat(result).hasSize(1); AgreementInfo info = result.get(0); - assertThat(info.name).isEqualTo(ca.getName()); - assertThat(info.description).isEqualTo(ca.getDescription()); - assertThat(info.url).isEqualTo(ca.getAgreementUrl()); + assertAgreement(info, caAutoVerify); // Signing the same agreement again has no effect - gApi.accounts().self().signAgreement(ca.getName()); + gApi.accounts().self().signAgreement(caAutoVerify.getName()); result = gApi.accounts().self().listAgreements(); assertThat(result).hasSize(1); } @@ -148,7 +135,7 @@ assume().that(isContributorAgreementsEnabled()).isFalse(); exception.expect(MethodNotAllowedException.class); exception.expectMessage("contributor agreements disabled"); - gApi.accounts().self().signAgreement(ca.getName()); + gApi.accounts().self().signAgreement(caAutoVerify.getName()); } @Test @@ -227,7 +214,7 @@ } // Sign the agreement - gApi.accounts().self().signAgreement(ca.getName()); + gApi.accounts().self().signAgreement(caAutoVerify.getName()); // Explicitly reset the user to force a new request context setApiUser(user); @@ -236,6 +223,18 @@ gApi.changes().create(newChangeInput()); } + private void assertAgreement(AgreementInfo info, ContributorAgreement ca) { + assertThat(info.name).isEqualTo(ca.getName()); + assertThat(info.description).isEqualTo(ca.getDescription()); + assertThat(info.url).isEqualTo(ca.getAgreementUrl()); + if (ca.getAutoVerify() != null) { + assertThat(info.autoVerifyGroup.name) + .isEqualTo(ca.getAutoVerify().getName()); + } else { + assertThat(info.autoVerifyGroup).isNull(); + } + } + private ChangeInput newChangeInput() { ChangeInput in = new ChangeInput(); in.branch = "master";
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java index f45bfbbe..f8cfbc9 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -22,6 +22,7 @@ import com.google.gerrit.acceptance.TestAccount; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat; +import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy; @@ -87,6 +88,7 @@ i.dateFormat = DateFormat.US; i.timeFormat = TimeFormat.HHMM_24; i.emailStrategy = EmailStrategy.DISABLED; + i.defaultBaseForMerges = DefaultBase.AUTO_MERGE; i.relativeDateInChangeTable ^= true; i.sizeBarInChangeTable ^= true; i.legacycidInChangeTable ^= true;
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 006d8a4..520c7db 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
@@ -31,6 +31,7 @@ import static org.junit.Assert.fail; import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.gerrit.acceptance.AbstractDaemonTest; @@ -41,9 +42,11 @@ import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.TestProjectInput; +import com.google.gerrit.common.FooterConstants; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.Permission; import com.google.gerrit.extensions.api.changes.AddReviewerInput; +import com.google.gerrit.extensions.api.changes.DeleteReviewerInput; import com.google.gerrit.extensions.api.changes.DeleteVoteInput; import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.api.changes.RebaseInput; @@ -58,6 +61,7 @@ import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInput; import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.common.CommitInfo; import com.google.gerrit.extensions.common.GitPerson; import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.common.RevisionInfo; @@ -71,6 +75,7 @@ import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; +import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.change.ChangeResource; import com.google.gerrit.server.config.AnonymousCowardNameProvider; import com.google.gerrit.server.git.ProjectConfig; @@ -78,7 +83,6 @@ import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.Util; import com.google.gerrit.testutil.FakeEmailSender.Message; -import com.google.gerrit.testutil.NoteDbMode; import com.google.gerrit.testutil.TestTimeUtil; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; @@ -184,6 +188,64 @@ } @Test + public void batchAbandon() throws Exception { + CurrentUser user = atrScope.get().getUser(); + PushOneCommit.Result a = createChange(); + List<ChangeControl> controlA = changeFinder.find(a.getChangeId(), user); + assertThat(controlA).hasSize(1); + PushOneCommit.Result b = createChange(); + List<ChangeControl> controlB = changeFinder.find(b.getChangeId(), user); + assertThat(controlB).hasSize(1); + List<ChangeControl> list = + ImmutableList.of(controlA.get(0), controlB.get(0)); + changeAbandoner.batchAbandon( + controlA.get(0).getProject().getNameKey(), user, list, "deadbeef"); + + ChangeInfo info = get(a.getChangeId()); + 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()); + assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED); + assertThat(Iterables.getLast(info.messages).message.toLowerCase()) + .contains("abandoned"); + assertThat(Iterables.getLast(info.messages).message.toLowerCase()) + .contains("deadbeef"); + } + + @Test + public void batchAbandonChangeProject() throws Exception { + String project1Name = name("Project1"); + String project2Name = name("Project2"); + gApi.projects().create(project1Name); + gApi.projects().create(project2Name); + TestRepository<InMemoryRepository> project1 = + cloneProject(new Project.NameKey(project1Name)); + TestRepository<InMemoryRepository> project2 = + cloneProject(new Project.NameKey(project2Name)); + + CurrentUser user = atrScope.get().getUser(); + PushOneCommit.Result a = + createChange(project1, "master", "x", "x", "x", ""); + List<ChangeControl> controlA = changeFinder.find(a.getChangeId(), user); + assertThat(controlA).hasSize(1); + PushOneCommit.Result b = + createChange(project2, "master", "x", "x", "x", ""); + List<ChangeControl> controlB = changeFinder.find(b.getChangeId(), user); + assertThat(controlB).hasSize(1); + List<ChangeControl> list = + ImmutableList.of(controlA.get(0), controlB.get(0)); + exception.expect(ResourceConflictException.class); + exception.expectMessage(String.format( + "Project name \"%s\" doesn't match \"%s\"", + project2Name, project1Name)); + changeAbandoner.batchAbandon(new Project.NameKey(project1Name), user, list); + } + + @Test public void abandonDraft() throws Exception { PushOneCommit.Result r = createDraftChange(); String changeId = r.getChangeId(); @@ -538,6 +600,149 @@ } @Test + public void pushCommitOfOtherUser() throws Exception { + // admin pushes commit of user + PushOneCommit push = pushFactory.create(db, user.getIdent(), testRepo); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + ChangeInfo change = gApi.changes().id(result.getChangeId()).get(); + assertThat(change.owner._accountId).isEqualTo(admin.id.get()); + CommitInfo commit = change.revisions.get(change.currentRevision).commit; + assertThat(commit.author.email).isEqualTo(user.email); + assertThat(commit.committer.email).isEqualTo(user.email); + + // check that the author/committer was added as reviewer + Collection<AccountInfo> reviewers = change.reviewers.get(REVIEWER); + assertThat(reviewers).isNotNull(); + assertThat(reviewers).hasSize(1); + assertThat(reviewers.iterator().next()._accountId) + .isEqualTo(user.getId().get()); + assertThat(change.reviewers.get(CC)).isNull(); + + List<Message> messages = sender.getMessages(); + assertThat(messages).hasSize(1); + Message m = messages.get(0); + assertThat(m.rcpt()).containsExactly(user.emailAddress); + assertThat(m.body()) + .contains(admin.fullName + " has uploaded a new change for review"); + assertThat(m.body()) + .contains("Change subject: " + PushOneCommit.SUBJECT + "\n"); + assertMailFrom(m, admin.email); + } + + @Test + public void pushCommitOfOtherUserThatCannotSeeChange() throws Exception { + // create hidden project that is only visible to administrators + Project.NameKey p = createProject("p"); + ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); + Util.allow(cfg, + Permission.READ, + groupCache.get(new AccountGroup.NameKey("Administrators")) + .getGroupUUID(), + "refs/*"); + Util.block(cfg, Permission.READ, REGISTERED_USERS, "refs/*"); + saveProjectConfig(p, cfg); + + // admin pushes commit of user + TestRepository<InMemoryRepository> repo = cloneProject(p, admin); + PushOneCommit push = pushFactory.create(db, user.getIdent(), repo); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + ChangeInfo change = gApi.changes().id(result.getChangeId()).get(); + assertThat(change.owner._accountId).isEqualTo(admin.id.get()); + CommitInfo commit = change.revisions.get(change.currentRevision).commit; + assertThat(commit.author.email).isEqualTo(user.email); + assertThat(commit.committer.email).isEqualTo(user.email); + + // check the user cannot see the change + setApiUser(user); + try { + gApi.changes().id(result.getChangeId()).get(); + fail("Expected ResourceNotFoundException"); + } catch (ResourceNotFoundException e) { + // Expected. + } + + // check that the author/committer was NOT added as reviewer (he can't see + // the change) + assertThat(change.reviewers.get(REVIEWER)).isNull(); + assertThat(change.reviewers.get(CC)).isNull(); + assertThat(sender.getMessages()).isEmpty(); + } + + @Test + public void pushCommitWithFooterOfOtherUser() throws Exception { + // admin pushes commit that references 'user' in a footer + PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo, + PushOneCommit.SUBJECT + "\n\n" + + FooterConstants.REVIEWED_BY.getName() + ": " + + user.getIdent().toExternalString(), + PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + // check that 'user' was added as reviewer + ChangeInfo change = gApi.changes().id(result.getChangeId()).get(); + Collection<AccountInfo> reviewers = change.reviewers.get(REVIEWER); + assertThat(reviewers).isNotNull(); + assertThat(reviewers).hasSize(1); + assertThat(reviewers.iterator().next()._accountId) + .isEqualTo(user.getId().get()); + assertThat(change.reviewers.get(CC)).isNull(); + + List<Message> messages = sender.getMessages(); + assertThat(messages).hasSize(1); + Message m = messages.get(0); + assertThat(m.rcpt()).containsExactly(user.emailAddress); + assertThat(m.body()).contains("Hello " + user.fullName + ",\n"); + assertThat(m.body()).contains("I'd like you to do a code review."); + assertThat(m.body()) + .contains("Change subject: " + PushOneCommit.SUBJECT + "\n"); + assertMailFrom(m, admin.email); + } + + @Test + public void pushCommitWithFooterOfOtherUserThatCannotSeeChange() + throws Exception { + // create hidden project that is only visible to administrators + Project.NameKey p = createProject("p"); + ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); + Util.allow(cfg, + Permission.READ, groupCache + .get(new AccountGroup.NameKey("Administrators")).getGroupUUID(), + "refs/*"); + Util.block(cfg, Permission.READ, REGISTERED_USERS, "refs/*"); + saveProjectConfig(p, cfg); + + // admin pushes commit that references 'user' in a footer + TestRepository<InMemoryRepository> repo = cloneProject(p, admin); + PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo, + PushOneCommit.SUBJECT + "\n\n" + FooterConstants.REVIEWED_BY.getName() + + ": " + user.getIdent().toExternalString(), + PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + // check that 'user' cannot see the change + setApiUser(user); + try { + gApi.changes().id(result.getChangeId()).get(); + fail("Expected ResourceNotFoundException"); + } catch (ResourceNotFoundException e) { + // Expected. + } + + // check that 'user' was NOT added as cc ('user' can't see the change) + setApiUser(admin); + ChangeInfo change = gApi.changes().id(result.getChangeId()).get(); + assertThat(change.reviewers.get(REVIEWER)).isNull(); + assertThat(change.reviewers.get(CC)).isNull(); + assertThat(sender.getMessages()).isEmpty(); + } + + @Test public void addReviewerThatCannotSeeChange() throws Exception { // create hidden project that is only visible to administrators Project.NameKey p = createProject("p"); @@ -577,6 +782,22 @@ } @Test + public void addReviewerThatIsInactive() throws Exception { + PushOneCommit.Result r = createChange(); + + String username = name("new-user"); + gApi.accounts().create(username).setActive(false); + + AddReviewerInput in = new AddReviewerInput(); + in.reviewer = username; + exception.expect(UnprocessableEntityException.class); + exception.expectMessage("Account of " + username + " is inactive."); + gApi.changes() + .id(r.getChangeId()) + .addReviewer(in); + } + + @Test public void addReviewer() throws Exception { TestTimeUtil.resetWithClockStep(1, SECONDS); PushOneCommit.Result r = createChange(); @@ -755,11 +976,18 @@ assertThat(reviewers.iterator().next()._accountId) .isEqualTo(user.getId().get()); + sender.clear(); gApi.changes() .id(changeId) .reviewer(user.getId().toString()) .remove(); - assertThat(gApi.changes().id(changeId).get().reviewers.isEmpty()); + assertThat(gApi.changes().id(changeId).get().reviewers).isEmpty(); + + assertThat(sender.getMessages()).hasSize(1); + Message message = sender.getMessages().get(0); + assertThat(message.body()).contains( + "Removed reviewer " + user.fullName + "."); + assertThat(message.body()).doesNotContain("with the following votes"); // Make sure the reviewer can still be added again. gApi.changes() @@ -785,6 +1013,15 @@ @Test public void removeReviewer() throws Exception { + testRemoveReviewer(true); + } + + @Test + public void removeNoNotify() throws Exception { + testRemoveReviewer(false); + } + + private void testRemoveReviewer(boolean notify) throws Exception { PushOneCommit.Result r = createChange(); String changeId = r.getChangeId(); gApi.changes() @@ -810,11 +1047,26 @@ assertThat(reviewerIt.next()._accountId) .isEqualTo(user.getId().get()); + sender.clear(); setApiUser(admin); + DeleteReviewerInput input = new DeleteReviewerInput(); + if (!notify) { + input.notify = NotifyHandling.NONE; + } gApi.changes() .id(changeId) .reviewer(user.getId().toString()) - .remove(); + .remove(input); + + if (notify) { + assertThat(sender.getMessages()).hasSize(1); + Message message = sender.getMessages().get(0); + assertThat(message.body()).contains( + "Removed reviewer " + user.fullName + " with the following votes"); + assertThat(message.body()).contains("* Code-Review+1 by " + user.fullName); + } else { + assertThat(sender.getMessages()).hasSize(0); + } reviewers = gApi.changes() .id(changeId) @@ -882,18 +1134,8 @@ .reviewer(user.getId().toString()) .votes(); - if (NoteDbMode.readWrite()) { - // When NoteDb is enabled each reviewer is explicitly recorded in the - // NoteDb and this record stays even when all votes of that user have been - // deleted, hence there is no dummy 0 approval left when a vote is - // deleted. - assertThat(m).isEmpty(); - } else { - // When NoteDb is disabled there is a dummy 0 approval on the change so - // that the user is still returned as CC when all votes of that user have - // been deleted. - assertThat(m).containsEntry("Code-Review", Short.valueOf((short)0)); - } + // Dummy 0 approval on the change to block vote copying to this patch set. + assertThat(m).containsExactly("Code-Review", Short.valueOf((short)0)); ChangeInfo c = gApi.changes() .id(r.getChangeId()) @@ -1205,6 +1447,31 @@ } @Test + public void submitStaleChange() throws Exception { + PushOneCommit.Result r = createChange(); + + disableChangeIndexWrites(); + try { + r = amendChange(r.getChangeId()); + } finally { + enableChangeIndexWrites(); + } + + gApi.changes() + .id(r.getChangeId()) + .current() + .review(ReviewInput.approve()); + + gApi.changes() + .id(r.getChangeId()) + .current() + .submit(); + assertThat(gApi.changes() + .id(r.getChangeId()) + .info().status).isEqualTo(ChangeStatus.MERGED); + } + + @Test public void check() throws Exception { // TODO(dborowitz): Re-enable when ConsistencyChecker supports NoteDb. assume().that(notesMigration.enabled()).isFalse();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/MergeListIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/MergeListIT.java new file mode 100644 index 0000000..481df31 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/MergeListIT.java
@@ -0,0 +1,209 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.acceptance.api.change; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.HEAD; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.NoHttpd; +import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.common.RawInputUtil; +import com.google.gerrit.extensions.api.changes.RevisionApi; +import com.google.gerrit.extensions.common.CommitInfo; +import com.google.gerrit.extensions.common.DiffInfo; +import com.google.gerrit.extensions.restapi.BinaryResult; +import com.google.gerrit.server.edit.ChangeEditModifier; +import com.google.gerrit.server.edit.ChangeEditUtil; +import com.google.gerrit.server.project.InvalidChangeOperationException; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.inject.Inject; + +import org.eclipse.jgit.dircache.InvalidPathException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.Set; + +@NoHttpd +public class MergeListIT extends AbstractDaemonTest { + + private String changeId; + private RevCommit merge; + private RevCommit parent1; + private RevCommit grandParent1; + private RevCommit parent2; + private RevCommit grandParent2; + + @Inject + private ChangeEditModifier modifier; + + @Inject + private ChangeEditUtil editUtil; + + @Before + public void setup() throws Exception { + ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId(); + + PushOneCommit.Result gp1 = pushFactory + .create(db, admin.getIdent(), testRepo, "grand parent 1", + ImmutableMap.of("foo", "foo-1.1", "bar", "bar-1.1")) + .to("refs/for/master"); + grandParent1 = gp1.getCommit(); + + PushOneCommit.Result p1 = pushFactory + .create(db, admin.getIdent(), testRepo, "parent 1", + ImmutableMap.of("foo", "foo-1.2", "bar", "bar-1.2")) + .to("refs/for/master"); + parent1 = p1.getCommit(); + + // reset HEAD in order to create a sibling of the first change + testRepo.reset(initial); + + PushOneCommit.Result gp2 = pushFactory + .create(db, admin.getIdent(), testRepo, "grand parent 2", + ImmutableMap.of("foo", "foo-2.1", "bar", "bar-2.1")) + .to("refs/for/master"); + grandParent2 = gp2.getCommit(); + + PushOneCommit.Result p2 = pushFactory + .create(db, admin.getIdent(), testRepo, "parent 2", + ImmutableMap.of("foo", "foo-2.2", "bar", "bar-2.2")) + .to("refs/for/master"); + parent2 = p2.getCommit(); + + PushOneCommit m = pushFactory.create(db, admin.getIdent(), testRepo, + "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2")); + m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit())); + PushOneCommit.Result result = m.to("refs/for/master"); + result.assertOkStatus(); + merge = result.getCommit(); + changeId = result.getChangeId(); + } + + @Test + public void getMergeList() throws Exception { + List<CommitInfo> mergeList = current(changeId).getMergeList().get(); + assertThat(mergeList).hasSize(2); + assertThat(mergeList.get(0).commit).isEqualTo(parent2.name()); + assertThat(mergeList.get(1).commit).isEqualTo(grandParent2.name()); + + mergeList = current(changeId).getMergeList() + .withUninterestingParent(2).get(); + assertThat(mergeList).hasSize(2); + assertThat(mergeList.get(0).commit).isEqualTo(parent1.name()); + assertThat(mergeList.get(1).commit).isEqualTo(grandParent1.name()); + } + + @Test + public void getMergeListContent() throws Exception { + BinaryResult bin = current(changeId).file(MERGE_LIST).content(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bin.writeTo(os); + String content = new String(os.toByteArray(), UTF_8); + assertThat(content).isEqualTo( + getMergeListContent(parent2, grandParent2)); + } + + @Test + public void getFileList() throws Exception { + assertThat(getFiles(changeId)).contains(MERGE_LIST); + assertThat(getFiles(changeId, 1)).contains(MERGE_LIST); + assertThat(getFiles(changeId, 2)).contains(MERGE_LIST); + + assertThat(getFiles(createChange().getChangeId())) + .doesNotContain(MERGE_LIST); + } + + @Test + public void getDiffForMergeList() throws Exception { + DiffInfo diff = getMergeListDiff(changeId); + assertDiffForNewFile(diff, merge, MERGE_LIST, + getMergeListContent(parent2, grandParent2)); + + diff = getMergeListDiff(changeId, 1); + assertDiffForNewFile(diff, merge, MERGE_LIST, + getMergeListContent(parent2, grandParent2)); + + diff = getMergeListDiff(changeId, 2); + assertDiffForNewFile(diff, merge, MERGE_LIST, + getMergeListContent(parent1, grandParent1)); + } + + @Test + public void editMergeList() throws Exception { + ChangeData cd = getOnlyElement(queryProvider.get().byKeyPrefix(changeId)); + modifier.createEdit(cd.change(), cd.currentPatchSet()); + + exception.expect(InvalidPathException.class); + exception.expectMessage("Invalid path: " + MERGE_LIST); + modifier.modifyFile(editUtil.byChange(cd.change()).get(), MERGE_LIST, + RawInputUtil.create("new content")); + } + + @Test + public void deleteMergeList() throws Exception { + ChangeData cd = getOnlyElement(queryProvider.get().byKeyPrefix(changeId)); + modifier.createEdit(cd.change(), cd.currentPatchSet()); + + exception.expect(InvalidChangeOperationException.class); + exception.expectMessage("no changes were made"); + modifier.deleteFile(editUtil.byChange(cd.change()).get(), MERGE_LIST); + } + + private String getMergeListContent(RevCommit... commits) { + StringBuilder mergeList = new StringBuilder("Merge List:\n\n"); + for (RevCommit c : commits) { + mergeList.append("* ") + .append(c.abbreviate(8).name()) + .append(" ") + .append(c.getShortMessage()) + .append("\n"); + } + return mergeList.toString(); + } + + private Set<String> getFiles(String changeId) throws Exception { + return current(changeId).files().keySet(); + } + + private Set<String> getFiles(String changeId, int parent) throws Exception { + return current(changeId).files(parent).keySet(); + } + + private DiffInfo getMergeListDiff(String changeId) throws Exception { + return current(changeId).file(MERGE_LIST).diff(); + } + + private DiffInfo getMergeListDiff(String changeId, int parent) + throws Exception { + return current(changeId).file(MERGE_LIST).diff(parent); + } + + private RevisionApi current(String changeId) throws Exception { + return gApi.changes() + .id(changeId) + .current(); + } +}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java index 54fe28f..4da22d3 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -311,6 +311,27 @@ assertVotes(c, user, 0, 0, REWORK); } + @Test + public void deleteStickyVote() throws Exception { + String label = "Code-Review"; + ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); + cfg.getLabelSections().get(label) + .setCopyMaxScore(true); + saveProjectConfig(project, cfg); + + + // Vote max score on PS1 + String changeId = createChange(REWORK); + vote(admin, changeId, label, 2); + assertVotes(detailedChange(changeId), admin, label, 2, null); + updateChange(changeId, REWORK); + assertVotes(detailedChange(changeId), admin, label, 2, REWORK); + + // Delete vote that was copied via sticky approval + deleteVote(admin, changeId, "Code-Review"); + assertVotes(detailedChange(changeId), admin, label, 0, REWORK); + } + private ChangeInfo detailedChange(String changeId) throws Exception { return gApi.changes().id(changeId) .get(EnumSet.of(ListChangesOption.DETAILED_LABELS, @@ -495,6 +516,15 @@ return c.revisions.get(c.currentRevision).kind; } + private void vote(TestAccount user, String changeId, String label, int vote) + throws Exception { + setApiUser(user); + gApi.changes() + .id(changeId) + .current() + .review(new ReviewInput().label(label, vote)); + } + private void vote(TestAccount user, String changeId, int codeReviewVote, int verifiedVote) throws Exception { setApiUser(user); @@ -504,6 +534,15 @@ gApi.changes().id(changeId).current().review(in); } + private void deleteVote(TestAccount user, String changeId, String label) + throws Exception { + setApiUser(user); + gApi.changes() + .id(changeId) + .reviewer(user.getId().toString()) + .deleteVote(label); + } + private void assertVotes(ChangeInfo c, TestAccount user, int codeReviewVote, int verifiedVote) { assertVotes(c, user, codeReviewVote, verifiedVote, null);
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 ee2dbfe..de0c3c1 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
@@ -19,11 +19,14 @@ import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME; import static com.google.gerrit.acceptance.PushOneCommit.PATCH; import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT; +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 java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.junit.Assert.fail; +import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.gerrit.acceptance.AbstractDaemonTest; @@ -46,6 +49,7 @@ import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.extensions.common.DiffInfo; +import com.google.gerrit.extensions.common.FileInfo; import com.google.gerrit.extensions.common.MergeableInfo; import com.google.gerrit.extensions.common.RevisionInfo; import com.google.gerrit.extensions.restapi.AuthException; @@ -53,7 +57,6 @@ import com.google.gerrit.extensions.restapi.ETagView; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.UnprocessableEntityException; -import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.server.change.GetRevisionActions; import com.google.gerrit.server.change.RevisionResource; import com.google.gerrit.server.git.ProjectConfig; @@ -62,13 +65,16 @@ import com.google.inject.Inject; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -511,17 +517,17 @@ @Test public void files() throws Exception { PushOneCommit.Result r = createChange(); - assertThat(Iterables.all(gApi.changes() + Map<String, FileInfo> files = gApi.changes() .id(r.getChangeId()) .revision(r.getCommit().name()) - .files() - .keySet(), new Predicate<String>() { - @Override - public boolean apply(String file) { - return file.matches(FILE_NAME + '|' + Patch.COMMIT_MSG); - } - })) - .isTrue(); + .files(); + assertThat(files).hasSize(2); + assertThat(Iterables.all(files.keySet(), new Predicate<String>() { + @Override + public boolean apply(String file) { + return file.matches(FILE_NAME + '|' + COMMIT_MSG); + } + })).isTrue(); } @Test @@ -534,7 +540,7 @@ .revision(r.getCommit().name()) .files() .keySet() - ).containsExactly(Patch.COMMIT_MSG, "foo", "bar"); + ).containsExactly(COMMIT_MSG, MERGE_LIST, "foo", "bar"); // list files against parent 1 assertThat(gApi.changes() @@ -542,7 +548,7 @@ .revision(r.getCommit().name()) .files(1) .keySet() - ).containsExactly(Patch.COMMIT_MSG, "bar"); + ).containsExactly(COMMIT_MSG, MERGE_LIST, "bar"); // list files against parent 2 assertThat(gApi.changes() @@ -550,19 +556,30 @@ .revision(r.getCommit().name()) .files(2) .keySet() - ).containsExactly(Patch.COMMIT_MSG, "foo"); + ).containsExactly(COMMIT_MSG, MERGE_LIST, "foo"); } @Test public void diff() throws Exception { PushOneCommit.Result r = createChange(); + assertDiffForNewFile(r, FILE_NAME, FILE_CONTENT); + assertDiffForNewFile(r, COMMIT_MSG, r.getCommit().getFullMessage()); + } + + @Test + public void diffDeletedFile() throws Exception { + pushFactory.create(db, admin.getIdent(), testRepo) + .to("refs/heads/master"); + PushOneCommit.Result r = + pushFactory.create(db, admin.getIdent(), testRepo) + .rm("refs/for/master"); DiffInfo diff = gApi.changes() .id(r.getChangeId()) .revision(r.getCommit().name()) .file(FILE_NAME) .diff(); - assertThat(diff.metaA).isNull(); - assertThat(diff.metaB.lines).isEqualTo(1); + assertThat(diff.metaA.lines).isEqualTo(1); + assertThat(diff.metaB).isNull(); } @Test @@ -610,15 +627,8 @@ @Test public void content() throws Exception { PushOneCommit.Result r = createChange(); - BinaryResult bin = gApi.changes() - .id(r.getChangeId()) - .revision(r.getCommit().name()) - .file(FILE_NAME) - .content(); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - bin.writeTo(os); - String res = new String(os.toByteArray(), UTF_8); - assertThat(res).isEqualTo(FILE_CONTENT); + assertContent(r, FILE_NAME, FILE_CONTENT); + assertContent(r, COMMIT_MSG, r.getCommit().getFullMessage()); } @Test @@ -822,4 +832,62 @@ assertThat(eTag).isNotEqualTo(oldETag); return eTag; } + + private void assertContent(PushOneCommit.Result pushResult, String path, + String expectedContent) throws Exception { + BinaryResult bin = gApi.changes() + .id(pushResult.getChangeId()) + .revision(pushResult.getCommit().name()) + .file(path) + .content(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bin.writeTo(os); + String res = new String(os.toByteArray(), UTF_8); + assertThat(res).isEqualTo(expectedContent); + } + + private void assertDiffForNewFile(PushOneCommit.Result pushResult, String path, + String expectedContentSideB) throws Exception { + DiffInfo diff = gApi.changes() + .id(pushResult.getChangeId()) + .revision(pushResult.getCommit().name()) + .file(path) + .diff(); + + List<String> headers = new ArrayList<>(); + if (path.equals(COMMIT_MSG)) { + RevCommit c = pushResult.getCommit(); + + RevCommit parentCommit = c.getParents()[0]; + String parentCommitId = testRepo.getRevWalk().getObjectReader() + .abbreviate(parentCommit.getId(), 8).name(); + headers.add("Parent: " + parentCommitId + " (" + + parentCommit.getShortMessage() + ")"); + + SimpleDateFormat dtfmt = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US); + PersonIdent author = c.getAuthorIdent(); + dtfmt.setTimeZone(author.getTimeZone()); + headers.add("Author: " + author.getName() + " <" + + author.getEmailAddress() + ">"); + headers.add("AuthorDate: " + + dtfmt.format(Long.valueOf(author.getWhen().getTime()))); + + PersonIdent committer = c.getCommitterIdent(); + dtfmt.setTimeZone(committer.getTimeZone()); + headers.add("Commit: " + committer.getName() + " <" + + committer.getEmailAddress() + ">"); + headers.add("CommitDate: " + + dtfmt.format(Long.valueOf(committer.getWhen().getTime()))); + headers.add(""); + } + + if (!headers.isEmpty()) { + String header = Joiner.on("\n").join(headers); + expectedContentSideB = header + "\n" + expectedContentSideB; + } + + assertDiffForNewFile(diff, pushResult.getCommit(), path, + expectedContentSideB); + } }
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 e47d570..c611f2c 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
@@ -31,6 +31,9 @@ import com.google.gerrit.common.RawInputUtil; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.Permission; +import com.google.gerrit.extensions.api.changes.AddReviewerInput; +import com.google.gerrit.extensions.api.changes.NotifyHandling; +import com.google.gerrit.extensions.api.changes.PublishChangeEditInput; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.client.InheritableBoolean; import com.google.gerrit.extensions.client.ListChangesOption; @@ -172,7 +175,7 @@ assertThat( modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME, RawInputUtil.create(CONTENT_NEW2))).isEqualTo(RefUpdate.Result.FORCED); - editUtil.publish(editUtil.byChange(change).get()); + editUtil.publish(editUtil.byChange(change).get(), NotifyHandling.NONE); Optional<ChangeEdit> edit = editUtil.byChange(change); assertThat(edit.isPresent()).isFalse(); assertChangeMessages(change, @@ -202,6 +205,24 @@ } @Test + public void publishEditNotifyRest() throws Exception { + AddReviewerInput in = new AddReviewerInput(); + in.reviewer = user.email; + gApi.changes().id(change.getChangeId()).addReviewer(in); + + modifier.createEdit(change, getCurrentPatchSet(changeId)); + assertThat( + modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME, + RawInputUtil.create(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED); + + sender.clear(); + PublishChangeEditInput input = new PublishChangeEditInput(); + input.notify = NotifyHandling.NONE; + adminRestSession.post(urlPublish(), input).assertNoContent(); + assertThat(sender.getMessages()).hasSize(0); + } + + @Test public void deleteEditRest() throws Exception { assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW); assertThat( @@ -365,7 +386,7 @@ edit = editUtil.byChange(change); assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(msg); - editUtil.publish(edit.get()); + editUtil.publish(edit.get(), NotifyHandling.NONE); assertThat(editUtil.byChange(change).isPresent()).isFalse(); ChangeInfo info = get(changeId, ListChangesOption.CURRENT_COMMIT, @@ -408,7 +429,7 @@ assertThat(readContentFromJson(r)).isEqualTo(commit.getFullMessage()); } - editUtil.publish(edit.get()); + editUtil.publish(edit.get(), NotifyHandling.NONE); assertChangeMessages(change, ImmutableList.of("Uploaded patch set 1.", "Uploaded patch set 2.", @@ -711,7 +732,7 @@ assertThat(modifier.modifyMessage(edit.get(), newMsg)) .isEqualTo(RefUpdate.Result.FORCED); edit = editUtil.byChange(change); - editUtil.publish(edit.get()); + editUtil.publish(edit.get(), NotifyHandling.NONE); ChangeInfo info = get(changeId); assertThat(info.subject).isEqualTo(newSubj); @@ -738,7 +759,7 @@ editUtil.delete(editUtil.byChange(change).get()); assertThat(queryEdits()).hasSize(1); - editUtil.publish(editUtil.byChange(change2).get()); + editUtil.publish(editUtil.byChange(change2).get(), NotifyHandling.NONE); assertThat(queryEdits()).hasSize(0); setApiUser(user);
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 ca669b4..6e2e8b1 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
@@ -19,6 +19,7 @@ import static com.google.gerrit.acceptance.GitUtil.assertPushOk; import static com.google.gerrit.acceptance.GitUtil.assertPushRejected; import static com.google.gerrit.acceptance.GitUtil.pushHead; +import static com.google.gerrit.common.FooterConstants.CHANGE_ID; 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; @@ -33,6 +34,7 @@ import com.google.gerrit.acceptance.GitUtil; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.TestAccount; +import com.google.gerrit.acceptance.TestProjectInput; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.Permission; import com.google.gerrit.extensions.api.changes.NotifyHandling; @@ -40,6 +42,7 @@ import com.google.gerrit.extensions.api.projects.BranchInput; import com.google.gerrit.extensions.client.ChangeStatus; import com.google.gerrit.extensions.client.InheritableBoolean; +import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.EditInfo; @@ -61,6 +64,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteRefUpdate; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -68,6 +72,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -129,6 +134,27 @@ } @Test + @TestProjectInput(createEmptyCommit = false) + public void pushInitialCommitForMasterBranch() throws Exception { + RevCommit c = + testRepo.commit().message("Initial commit").insertChangeId().create(); + String id = GitUtil.getChangeId(testRepo, c).get(); + testRepo.reset(c); + + String r = "refs/for/master"; + PushResult pr = pushHead(testRepo, r, false); + assertPushOk(pr, r); + + ChangeInfo change = gApi.changes().id(id).info(); + assertThat(change.branch).isEqualTo("master"); + assertThat(change.status).isEqualTo(ChangeStatus.NEW); + + try (Repository repo = repoManager.openRepository(project)) { + assertThat(repo.resolve("master")).isNull(); + } + } + + @Test public void output() throws Exception { String url = canonicalWebUrl.get(); ObjectId initialHead = testRepo.getRepository().resolve("HEAD"); @@ -176,6 +202,21 @@ } @Test + public void pushForMasterWithTopicOption() throws Exception { + String topicOption = "topic=myTopic"; + List<String> pushOptions = new ArrayList<>(); + pushOptions.add(topicOption); + + PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); + push.setPushOptions(pushOptions); + PushOneCommit.Result r = push.to("refs/for/master"); + + r.assertOkStatus(); + r.assertChange(Change.Status.NEW, "myTopic"); + r.assertPushOptions(pushOptions); + } + + @Test public void pushForMasterWithNotify() throws Exception { TestAccount user2 = accounts.user2(); String pushSpec = "refs/for/master" @@ -783,6 +824,179 @@ pushWithReviewerInFooter("Notauser", null); } + @Test + public void pushNewPatchsetOverridingStickyLabel() throws Exception { + ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); + LabelType codeReview = Util.codeReview(); + codeReview.setCopyMaxScore(true); + cfg.getLabelSections().put(codeReview.getName(), codeReview); + saveProjectConfig(cfg); + + PushOneCommit.Result r = pushTo("refs/for/master%l=Code-Review+2"); + r.assertOkStatus(); + PushOneCommit push = + pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, + "b.txt", "anotherContent", r.getChangeId()); + r = push.to("refs/for/master%l=Code-Review+1"); + r.assertOkStatus(); + } + + @Test + public void createChangeForMergedCommit() throws Exception { + String master = "refs/heads/master"; + grant(Permission.PUSH, project, master, true); + + // Update master with a direct push. + RevCommit c1 = testRepo.commit() + .message("Non-change 1") + .create(); + RevCommit c2 = testRepo.parseBody( + testRepo.commit() + .parent(c1) + .message("Non-change 2") + .insertChangeId() + .create()); + String changeId = Iterables.getOnlyElement(c2.getFooterLines(CHANGE_ID)); + + testRepo.reset(c2); + assertPushOk(pushHead(testRepo, master, false, true), master); + + String q = "commit:" + c1.name() + + " OR commit:" + c2.name() + + " OR change:" + changeId; + assertThat(gApi.changes().query(q).get()).isEmpty(); + + // Push c2 as a merged change. + String r = "refs/for/master%merged"; + assertPushOk(pushHead(testRepo, r, false), r); + + EnumSet<ListChangesOption> opts = + EnumSet.of(ListChangesOption.CURRENT_REVISION); + ChangeInfo info = gApi.changes().id(changeId).get(opts); + assertThat(info.currentRevision).isEqualTo(c2.name()); + assertThat(info.status).isEqualTo(ChangeStatus.MERGED); + + // Only c2 was created as a change. + String q1 = "commit: " + c1.name(); + assertThat(gApi.changes().query(q1).get()).isEmpty(); + + // Push c1 as a merged change. + testRepo.reset(c1); + assertPushOk(pushHead(testRepo, r, false), r); + List<ChangeInfo> infos = + gApi.changes().query(q1).withOptions(opts).get(); + assertThat(infos).hasSize(1); + info = infos.get(0); + assertThat(info.currentRevision).isEqualTo(c1.name()); + assertThat(info.status).isEqualTo(ChangeStatus.MERGED); + } + + @Test + public void mergedOptionFailsWhenCommitIsNotMerged() throws Exception { + PushOneCommit.Result r = pushTo("refs/for/master%merged"); + r.assertErrorStatus("not merged into branch"); + } + + @Test + public void mergedOptionFailsWhenCommitIsMergedOnOtherBranch() + throws Exception { + PushOneCommit.Result r = pushTo("refs/for/master"); + r.assertOkStatus(); + gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve()); + gApi.changes().id(r.getChangeId()).current().submit(); + + try (Repository repo = repoManager.openRepository(project)) { + TestRepository<?> tr = new TestRepository<>(repo); + tr.branch("refs/heads/branch") + .commit() + .message("Initial commit on branch") + .create(); + } + + pushTo("refs/for/master%merged") + .assertErrorStatus("not merged into branch"); + } + + @Test + public void mergedOptionFailsWhenChangeExists() throws Exception { + PushOneCommit.Result r = pushTo("refs/for/master"); + r.assertOkStatus(); + gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve()); + gApi.changes().id(r.getChangeId()).current().submit(); + + testRepo.reset(r.getCommit()); + String ref = "refs/for/master%merged"; + PushResult pr = pushHead(testRepo, ref, false); + RemoteRefUpdate rru = pr.getRemoteUpdate(ref); + assertThat(rru.getStatus()) + .isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); + assertThat(rru.getMessage()).contains("no new changes"); + } + + @Test + public void mergedOptionWithNewCommitWithSameChangeIdFails() + throws Exception { + PushOneCommit.Result r = pushTo("refs/for/master"); + r.assertOkStatus(); + gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve()); + gApi.changes().id(r.getChangeId()).current().submit(); + + RevCommit c2 = testRepo.amend(r.getCommit()) + .message("New subject") + .insertChangeId(r.getChangeId().substring(1)) + .create(); + testRepo.reset(c2); + + String ref = "refs/for/master%merged"; + PushResult pr = pushHead(testRepo, ref, false); + RemoteRefUpdate rru = pr.getRemoteUpdate(ref); + assertThat(rru.getStatus()) + .isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); + assertThat(rru.getMessage()).contains("not merged into branch"); + } + + @Test + public void mergedOptionWithExistingChangeInsertsPatchSet() + throws Exception { + String master = "refs/heads/master"; + grant(Permission.PUSH, project, master, true); + + PushOneCommit.Result r = pushTo("refs/for/master"); + r.assertOkStatus(); + ObjectId c1 = r.getCommit().copy(); + + // Create a PS2 commit directly on master in the server's repo. This + // simulates the client amending locally and pushing directly to the branch, + // expecting the change to be auto-closed, but the change metadata update + // fails. + ObjectId c2; + try (Repository repo = repoManager.openRepository(project)) { + TestRepository<?> tr = new TestRepository<>(repo); + RevCommit commit2 = tr.amend(c1) + .message("New subject") + .insertChangeId(r.getChangeId().substring(1)) + .create(); + c2 = commit2.copy(); + tr.update(master, c2); + } + + testRepo.git().fetch() + .setRefSpecs(new RefSpec("refs/heads/master")).call(); + testRepo.reset(c2); + + String ref = "refs/for/master%merged"; + assertPushOk(pushHead(testRepo, ref, false), ref); + + EnumSet<ListChangesOption> opts = + EnumSet.of(ListChangesOption.ALL_REVISIONS); + ChangeInfo info = gApi.changes().id(r.getChangeId()).get(opts); + assertThat(info.currentRevision).isEqualTo(c2.name()); + assertThat(info.revisions.keySet()) + .containsExactly(c1.name(), c2.name()); + // TODO(dborowitz): Fix ReceiveCommits to also auto-close the change. + assertThat(info.status).isEqualTo(ChangeStatus.NEW); + } + private void pushWithReviewerInFooter(String nameEmail, TestAccount expectedReviewer) throws Exception { int n = 5;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java index 28f7ff8..dfeac35 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -29,6 +29,7 @@ import org.eclipse.jgit.junit.TestRepository; 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.RevObject; import org.eclipse.jgit.revwalk.RevTree; @@ -75,8 +76,9 @@ } protected TestRepository<?> createProjectWithPush(String name, - @Nullable Project.NameKey parent, SubmitType submitType) throws Exception { - Project.NameKey project = createProject(name, parent, submitType); + @Nullable Project.NameKey parent, boolean createEmptyCommit, + SubmitType submitType) throws Exception { + Project.NameKey project = createProject(name, parent, createEmptyCommit, submitType); grant(Permission.PUSH, project, "refs/heads/*"); grant(Permission.SUBMIT, project, "refs/for/refs/heads/*"); return cloneProject(project); @@ -84,12 +86,17 @@ protected TestRepository<?> createProjectWithPush(String name, @Nullable Project.NameKey parent) throws Exception { - return createProjectWithPush(name, parent, getSubmitType()); + return createProjectWithPush(name, parent, true, getSubmitType()); + } + + protected TestRepository<?> createProjectWithPush(String name, + boolean createEmptyCommit) throws Exception { + return createProjectWithPush(name, null, createEmptyCommit, getSubmitType()); } protected TestRepository<?> createProjectWithPush(String name) throws Exception { - return createProjectWithPush(name, null); + return createProjectWithPush(name, null, true, getSubmitType()); } private static AtomicInteger contentCounter = new AtomicInteger(0); @@ -305,8 +312,13 @@ String submodule) throws Exception { submodule = name(submodule); - ObjectId commitId = repo.git().fetch().setRemote("origin").call() - .getAdvertisedRef("refs/heads/" + branch).getObjectId(); + Ref branchTip = repo.git().fetch().setRemote("origin").call() + .getAdvertisedRef("refs/heads/" + branch); + if (branchTip == null) { + return false; + } + + ObjectId commitId = branchTip.getObjectId(); RevWalk rw = repo.getRevWalk(); RevCommit c = rw.parseCommit(commitId);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK index f6796a5..42ece25 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
@@ -6,6 +6,7 @@ deps = [ ':submodule_util', ':push_for_review', + '//gerrit-extension-api:api', ], labels = ['git'], )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java index 848b428..ae2e8aa 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -77,6 +77,7 @@ @Test public void submitOnPushWithAnnotatedTag() throws Exception { grant(Permission.SUBMIT, project, "refs/for/refs/heads/master"); + grant(Permission.PUSH, project, "refs/tags/*"); PushOneCommit.AnnotatedTag tag = new PushOneCommit.AnnotatedTag("v1.0", "annotation", admin.getIdent()); PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java index 0ff3af5..d62a27b 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -21,15 +21,22 @@ import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.client.ChangeStatus; +import com.google.gerrit.extensions.client.SubmitType; +import com.google.gerrit.extensions.restapi.BinaryResult; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.testutil.ConfigSuite; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.transport.RefSpec; import org.junit.Test; +import java.util.Map; + @NoHttpd public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSubscription { @@ -101,22 +108,50 @@ gApi.changes().id(id2).current().review(ReviewInput.approve()); gApi.changes().id(id3).current().review(ReviewInput.approve()); + BinaryResult request = gApi.changes().id(id1).current().submitPreview(); + Map<Branch.NameKey, RevTree> preview = + fetchFromBundles(request); + gApi.changes().id(id1).current().submit(); ObjectId subRepoId = subRepo.git().fetch().setRemote("origin").call() .getAdvertisedRef("refs/heads/master").getObjectId(); expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project", subRepoId); + + // As the submodules have changed commits, the superproject tree will be + // different, so we cannot directly compare the trees here, so make + // assumptions only about the changed branches: + Project.NameKey p1 = new Project.NameKey(name("super-project")); + Project.NameKey p2 = new Project.NameKey(name("subscribed-to-project")); + assertThat(preview).containsKey( + new Branch.NameKey(p1, "refs/heads/master")); + assertThat(preview).containsKey( + new Branch.NameKey(p2, "refs/heads/master")); + + if (getSubmitType() == SubmitType.CHERRY_PICK) { + // each change is updated and the respective target branch is updated: + assertThat(preview).hasSize(5); + } else if (getSubmitType() == SubmitType.REBASE_IF_NECESSARY) { + // Either the first is used first as is, then the second and third need + // rebasing, or those two stay as is and the first is rebased. + // add in 2 master branches, expect 3 or 4: + assertThat(preview.size()).isAnyOf(3, 4); + } else { + assertThat(preview).hasSize(2); + } } @Test - public void testSubscriptionUpdateIncludingChangeInSuperproject() throws Exception { + public void testSubscriptionUpdateIncludingChangeInSuperproject() + throws Exception { TestRepository<?> superRepo = createProjectWithPush("super-project"); TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project"); - allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", - "super-project", "refs/heads/master"); + allowMatchingSubmoduleSubscription("subscribed-to-project", + "refs/heads/master", "super-project", "refs/heads/master"); - createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master"); + createSubmoduleSubscription(superRepo, "master", + "subscribed-to-project", "master"); ObjectId subHEAD = subRepo.branch("HEAD").commit().insertChangeId() .message("some change") @@ -223,6 +258,56 @@ } @Test + public void testDoNotUseFastForward() throws Exception { + TestRepository<?> superRepo = createProjectWithPush("super-project", false); + TestRepository<?> sub = createProjectWithPush("sub", false); + + allowMatchingSubmoduleSubscription("sub", "refs/heads/master", + "super-project", "refs/heads/master"); + + createSubmoduleSubscription(superRepo, "master", "sub", "master"); + + ObjectId subId = + pushChangeTo(sub, "refs/for/master", "some message", "same-topic"); + + ObjectId superId = + pushChangeTo(superRepo, "refs/for/master", "some message", "same-topic"); + + String subChangeId = getChangeId(sub, subId).get(); + approve(subChangeId); + approve(getChangeId(superRepo, superId).get()); + + gApi.changes().id(subChangeId).current().submit(); + + expectToHaveSubmoduleState(superRepo, "master", "sub", sub, "master"); + RevCommit superHead = getRemoteHead(name("super-project"), "master"); + assertThat(superHead.getShortMessage()).contains("some message"); + assertThat(superHead.getId()).isNotEqualTo(superId); + } + + @Test + public void testUseFastForwardWhenNoSubmodule() throws Exception { + TestRepository<?> superRepo = createProjectWithPush("super-project", false); + TestRepository<?> sub = createProjectWithPush("sub", false); + + ObjectId subId = + pushChangeTo(sub, "refs/for/master", "some message", "same-topic"); + + ObjectId superId = + pushChangeTo(superRepo, "refs/for/master", "some message", "same-topic"); + + String subChangeId = getChangeId(sub, subId).get(); + approve(subChangeId); + approve(getChangeId(superRepo, superId).get()); + + gApi.changes().id(subChangeId).current().submit(); + + RevCommit superHead = getRemoteHead(name("super-project"), "master"); + assertThat(superHead.getShortMessage()).isEqualTo("some message"); + assertThat(superHead.getId()).isEqualTo(superId); + } + + @Test public void testDifferentPaths() throws Exception { TestRepository<?> superRepo = createProjectWithPush("super-project"); TestRepository<?> sub = createProjectWithPush("sub"); @@ -310,7 +395,8 @@ createSubmoduleSubscription(midRepo, "master", "bottom-project", "master"); ObjectId bottomHead = - pushChangeTo(bottomRepo, "refs/for/master", "some message", "same-topic"); + pushChangeTo(bottomRepo, "refs/for/master", + "some message", "same-topic"); ObjectId topHead = pushChangeTo(topRepo, "refs/for/master", "some message", "same-topic"); @@ -322,8 +408,10 @@ gApi.changes().id(id1).current().submit(); - expectToHaveSubmoduleState(midRepo, "master", "bottom-project", bottomRepo, "master"); - expectToHaveSubmoduleState(topRepo, "master", "mid-project", midRepo, "master"); + expectToHaveSubmoduleState(midRepo, "master", "bottom-project", + bottomRepo, "master"); + expectToHaveSubmoduleState(topRepo, "master", "mid-project", + midRepo, "master"); } @Test @@ -346,7 +434,8 @@ pushSubmoduleConfig(topRepo, "master", config); ObjectId bottomHead = - pushChangeTo(bottomRepo, "refs/for/master", "some message", "same-topic"); + pushChangeTo(bottomRepo, "refs/for/master", + "some message", "same-topic"); ObjectId topHead = pushChangeTo(topRepo, "refs/for/master", "some message", "same-topic"); @@ -358,13 +447,16 @@ gApi.changes().id(id1).current().submit(); - expectToHaveSubmoduleState(midRepo, "master", "bottom-project", bottomRepo, "master"); - expectToHaveSubmoduleState(topRepo, "master", "mid-project", midRepo, "master"); - expectToHaveSubmoduleState(topRepo, "master", "bottom-project", bottomRepo, "master"); + expectToHaveSubmoduleState(midRepo, "master", + "bottom-project", bottomRepo, "master"); + expectToHaveSubmoduleState(topRepo, "master", + "mid-project", midRepo, "master"); + expectToHaveSubmoduleState(topRepo, "master", + "bottom-project", bottomRepo, "master"); } - @Test - public void testBranchCircularSubscription() throws Exception { + + private String prepareBranchCircularSubscription() throws Exception { TestRepository<?> topRepo = createProjectWithPush("top-project"); TestRepository<?> midRepo = createProjectWithPush("mid-project"); TestRepository<?> bottomRepo = createProjectWithPush("bottom-project"); @@ -385,15 +477,23 @@ String changeId = getChangeId(bottomRepo, bottomMasterHead).get(); approve(changeId); - exception.expectMessage("Branch level circular subscriptions detected"); exception.expectMessage("top-project,refs/heads/master"); exception.expectMessage("mid-project,refs/heads/master"); exception.expectMessage("bottom-project,refs/heads/master"); - gApi.changes().id(changeId).current().submit(); + return changeId; + } - assertThat(hasSubmodule(midRepo, "master", "bottom-project")).isFalse(); - assertThat(hasSubmodule(topRepo, "master", "mid-project")).isFalse(); + @Test + public void testBranchCircularSubscription() throws Exception { + String changeId = prepareBranchCircularSubscription(); + gApi.changes().id(changeId).current().submit(); + } + + @Test + public void testBranchCircularSubscriptionPreview() throws Exception { + String changeId = prepareBranchCircularSubscription(); + gApi.changes().id(changeId).current().submitPreview(); } @Test @@ -401,8 +501,8 @@ TestRepository<?> superRepo = createProjectWithPush("super-project"); TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project"); - allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", - "super-project", "refs/heads/master"); + allowMatchingSubmoduleSubscription("subscribed-to-project", + "refs/heads/master", "super-project", "refs/heads/master"); allowMatchingSubmoduleSubscription("super-project", "refs/heads/dev", "subscribed-to-project", "refs/heads/dev");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK index ff167ac..3522991 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
@@ -3,6 +3,5 @@ acceptance_tests( group = 'pgm', srcs = glob(['*IT.java']), - source_under_test = ['//gerrit-pgm:pgm'], labels = ['pgm'], )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD index 806acd2..56cecb8 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD
@@ -3,6 +3,5 @@ acceptance_tests( group = 'pgm', srcs = glob(['*IT.java']), - source_under_test = ['//gerrit-pgm:pgm'], labels = ['pgm'], )
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 c9c81df..a8e33ba 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
@@ -20,6 +20,8 @@ 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.DETAILED_LABELS; +import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER; +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.fail; @@ -30,6 +32,7 @@ import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.TestProjectInput; +import com.google.gerrit.common.data.Permission; import com.google.gerrit.extensions.api.changes.SubmitInput; import com.google.gerrit.extensions.api.projects.ProjectInput; import com.google.gerrit.extensions.client.ChangeStatus; @@ -40,6 +43,7 @@ import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.webui.UiAction; @@ -52,12 +56,15 @@ import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.change.RevisionResource; import com.google.gerrit.server.change.Submit; +import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gerrit.server.project.Util; import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.TestTimeUtil; import com.google.inject.Inject; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; @@ -65,13 +72,16 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayOutputStream; +import java.util.HashMap; import java.util.List; +import java.util.Map; @NoHttpd public abstract class AbstractSubmit extends AbstractDaemonTest { @@ -110,9 +120,223 @@ @Test @TestProjectInput(createEmptyCommit = false) public void submitToEmptyRepo() throws Exception { + RevCommit initialHead = getRemoteHead(); PushOneCommit.Result change = createChange(); + BinaryResult request = submitPreview(change.getChangeId()); + RevCommit headAfterSubmitPreview = getRemoteHead(); + assertThat(headAfterSubmitPreview).isEqualTo(initialHead); + Map<Branch.NameKey, RevTree> actual = + fetchFromBundles(request); + assertThat(actual).hasSize(1); + submit(change.getChangeId()); assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit()); + assertRevTrees(project, actual); + } + + @Test + public void submitSingleChange() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result change = createChange(); + BinaryResult request = submitPreview(change.getChangeId()); + RevCommit headAfterSubmit = getRemoteHead(); + assertThat(headAfterSubmit).isEqualTo(initialHead); + assertRefUpdatedEvents(); + assertChangeMergedEvents(); + + Map<Branch.NameKey, RevTree> actual = + fetchFromBundles(request); + + if (getSubmitType() == SubmitType.CHERRY_PICK) { + // The change is updated as well: + assertThat(actual).hasSize(2); + } else { + assertThat(actual).hasSize(1); + } + + submit(change.getChangeId()); + assertRevTrees(project, actual); + } + + @Test + public void submitMultipleChangesOtherMergeConflictPreview() + throws Exception { + RevCommit initialHead = getRemoteHead(); + + PushOneCommit.Result change = + createChange("Change 1", "a.txt", "content"); + submit(change.getChangeId()); + + RevCommit headAfterFirstSubmit = getRemoteHead(); + testRepo.reset(initialHead); + PushOneCommit.Result change2 = createChange("Change 2", + "a.txt", "other content"); + PushOneCommit.Result change3 = createChange("Change 3", "d", "d"); + PushOneCommit.Result change4 = createChange("Change 4", "e", "e"); + // change 2 is not approved, but we ignore labels + approve(change3.getChangeId()); + BinaryResult request = null; + String msg = null; + try { + request = submitPreview(change4.getChangeId()); + } catch (Exception e) { + msg = e.getMessage(); + } + + if (getSubmitType() == SubmitType.CHERRY_PICK) { + Map<Branch.NameKey, RevTree> s = + fetchFromBundles(request); + submit(change4.getChangeId()); + assertRevTrees(project, s); + } else if (getSubmitType() == SubmitType.FAST_FORWARD_ONLY) { + assertThat(msg).isEqualTo( + "Failed to submit 3 changes due to the following problems:\n" + + "Change " + change2.getChange().getId() + ": internal error: " + + "change not processed by merge strategy\n" + + "Change " + change3.getChange().getId() + ": internal error: " + + "change not processed by merge strategy\n" + + "Change " + change4.getChange().getId() + ": Project policy " + + "requires all submissions to be a fast-forward. Please " + + "rebase the change locally and upload again for review."); + RevCommit headAfterSubmit = getRemoteHead(); + assertThat(headAfterSubmit).isEqualTo(headAfterFirstSubmit); + assertRefUpdatedEvents(initialHead, headAfterFirstSubmit); + assertChangeMergedEvents(change.getChangeId(), + headAfterFirstSubmit.name()); + } else if(getSubmitType() == SubmitType.REBASE_IF_NECESSARY) { + String change2hash = change2.getChange().currentPatchSet() + .getRevision().get(); + assertThat(msg).isEqualTo( + "Cannot rebase " + change2hash + ": The change could " + + "not be rebased due to a conflict during merge."); + RevCommit headAfterSubmit = getRemoteHead(); + assertThat(headAfterSubmit).isEqualTo(headAfterFirstSubmit); + assertRefUpdatedEvents(initialHead, headAfterFirstSubmit); + assertChangeMergedEvents(change.getChangeId(), + headAfterFirstSubmit.name()); + } else { + assertThat(msg).isEqualTo( + "Failed to submit 3 changes due to the following problems:\n" + + "Change " + change2.getChange().getId() + ": Change could not be " + + "merged due to a path conflict. Please rebase the change " + + "locally and upload the rebased commit for review.\n" + + "Change " + change3.getChange().getId() + ": Change could not be " + + "merged due to a path conflict. Please rebase the change " + + "locally and upload the rebased commit for review.\n" + + "Change " + change4.getChange().getId() + ": Change could not be " + + "merged due to a path conflict. Please rebase the change " + + "locally and upload the rebased commit for review."); + RevCommit headAfterSubmit = getRemoteHead(); + assertThat(headAfterSubmit).isEqualTo(headAfterFirstSubmit); + assertRefUpdatedEvents(initialHead, headAfterFirstSubmit); + assertChangeMergedEvents(change.getChangeId(), + headAfterFirstSubmit.name()); + } + } + + @Test + public void submitMultipleChangesPreview() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result change2 = createChange("Change 2", + "a.txt", "other content"); + PushOneCommit.Result change3 = createChange("Change 3", "d", "d"); + PushOneCommit.Result change4 = createChange("Change 4", "e", "e"); + // change 2 is not approved, but we ignore labels + approve(change3.getChangeId()); + BinaryResult request = submitPreview(change4.getChangeId()); + + Map<String, Map<String, Integer>> expected = new HashMap<>(); + expected.put(project.get(), new HashMap<String, Integer>()); + expected.get(project.get()).put("refs/heads/master", 3); + Map<Branch.NameKey, RevTree> actual = + fetchFromBundles(request); + + assertThat(actual).containsKey( + new Branch.NameKey(project, "refs/heads/master")); + if (getSubmitType() == SubmitType.CHERRY_PICK) { + assertThat(actual).hasSize(2); + } else { + assertThat(actual).hasSize(1); + } + + // check that the submit preview did not actually submit + RevCommit headAfterSubmit = getRemoteHead(); + assertThat(headAfterSubmit).isEqualTo(initialHead); + assertRefUpdatedEvents(); + assertChangeMergedEvents(); + + // now check we actually have the same content: + approve(change2.getChangeId()); + submit(change4.getChangeId()); + assertRevTrees(project, actual); + } + + @Test + public void submitNoPermission() throws Exception { + // create project where submit is blocked + Project.NameKey p = createProject("p"); + block(Permission.SUBMIT, REGISTERED_USERS, "refs/*", p); + + TestRepository<InMemoryRepository> repo = cloneProject(p, admin); + PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + submit(result.getChangeId(), new SubmitInput(), AuthException.class, + "submit not permitted"); + } + + @Test + public void noSelfSubmit() throws Exception { + // create project where submit is blocked for the change owner + Project.NameKey p = createProject("p"); + ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); + Util.block(cfg, Permission.SUBMIT, CHANGE_OWNER, "refs/*"); + Util.allow(cfg, Permission.SUBMIT, REGISTERED_USERS, "refs/heads/*"); + Util.allow(cfg, Permission.forLabel("Code-Review"), -2, +2, + REGISTERED_USERS, "refs/*"); + saveProjectConfig(p, cfg); + + TestRepository<InMemoryRepository> repo = cloneProject(p, admin); + PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + ChangeInfo change = gApi.changes().id(result.getChangeId()).get(); + assertThat(change.owner._accountId).isEqualTo(admin.id.get()); + + submit(result.getChangeId(), new SubmitInput(), AuthException.class, + "submit not permitted"); + + setApiUser(user); + submit(result.getChangeId()); + } + + @Test + public void onlySelfSubmit() throws Exception { + // create project where only the change owner can submit + Project.NameKey p = createProject("p"); + ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); + Util.block(cfg, Permission.SUBMIT, REGISTERED_USERS, "refs/*"); + Util.allow(cfg, Permission.SUBMIT, CHANGE_OWNER, "refs/*"); + Util.allow(cfg, Permission.forLabel("Code-Review"), -2, +2, + REGISTERED_USERS, "refs/*"); + saveProjectConfig(p, cfg); + + TestRepository<InMemoryRepository> repo = cloneProject(p, admin); + PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo); + PushOneCommit.Result result = push.to("refs/for/master"); + result.assertOkStatus(); + + ChangeInfo change = gApi.changes().id(result.getChangeId()).get(); + assertThat(change.owner._accountId).isEqualTo(admin.id.get()); + + setApiUser(user); + submit(result.getChangeId(), new SubmitInput(), AuthException.class, + "submit not permitted"); + + setApiUser(admin); + submit(result.getChangeId()); } @Test @@ -252,10 +476,11 @@ assertMerged(change.changeId); } + protected BinaryResult submitPreview(String changeId) throws Exception { + return gApi.changes().id(changeId).current().submitPreview(); + } + protected void assertSubmittable(String changeId) throws Exception { - assertThat(gApi.changes().id(changeId).info().submittable) - .named("submit bit on ChangeInfo") - .isEqualTo(true); RevisionResource rsrc = parseCurrentRevisionResource(changeId); UiAction.Description desc = submitHandler.getDescription(rsrc); assertThat(desc.isVisible()).named("visible bit on submit action").isTrue();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java index aa7e864..b1b3f2a 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -32,7 +32,9 @@ import com.google.gerrit.extensions.api.changes.ReviewResult; import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.common.ApprovalInfo; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.common.ReviewerUpdateInfo; import com.google.gerrit.server.change.PostReviewers; import com.google.gerrit.server.mail.Address; @@ -43,8 +45,10 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; public class ChangeReviewersIT extends AbstractDaemonTest { @Test @@ -260,6 +264,165 @@ } @Test + public void driveByComment() throws Exception { + // Create change owned by admin. + PushOneCommit.Result r = createChange(); + + // Post drive-by message as user. + ReviewInput input = new ReviewInput().message("hello"); + RestResponse resp = userRestSession.post( + "/changes/" + r.getChangeId() + "/revisions/" + + r.getCommit().getName() + "/review", input); + ReviewResult result = readContentFromJson(resp, 200, ReviewResult.class); + assertThat(result.labels).isNull(); + assertThat(result.reviewers).isNull(); + + // Verify user is not added as reviewer. + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + assertReviewers(c, REVIEWER); + assertReviewers(c, CC); + } + + @Test + public void addSelfAsReviewer() throws Exception { + // Create change owned by admin. + PushOneCommit.Result r = createChange(); + + // user adds self as REVIEWER. + ReviewInput input = new ReviewInput().reviewer(user.username); + RestResponse resp = userRestSession.post( + "/changes/" + r.getChangeId() + "/revisions/" + + r.getCommit().getName() + "/review", input); + ReviewResult result = readContentFromJson(resp, 200, ReviewResult.class); + assertThat(result.labels).isNull(); + assertThat(result.reviewers).isNotNull(); + assertThat(result.reviewers).hasSize(1); + + // Verify reviewer state. + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + assertReviewers(c, REVIEWER, user); + assertReviewers(c, CC); + LabelInfo label = c.labels.get("Code-Review"); + assertThat(label).isNotNull(); + assertThat(label.all).isNotNull(); + assertThat(label.all).hasSize(1); + ApprovalInfo approval = label.all.get(0); + assertThat(approval._accountId).isEqualTo(user.getId().get()); + } + + @Test + public void addSelfAsCc() throws Exception { + // Create change owned by admin. + PushOneCommit.Result r = createChange(); + + // user adds self as CC. + ReviewInput input = new ReviewInput().reviewer(user.username, CC, false); + RestResponse resp = userRestSession.post( + "/changes/" + r.getChangeId() + "/revisions/" + + r.getCommit().getName() + "/review", input); + ReviewResult result = readContentFromJson(resp, 200, ReviewResult.class); + assertThat(result.labels).isNull(); + assertThat(result.reviewers).isNotNull(); + assertThat(result.reviewers).hasSize(1); + + // Verify reviewer state. + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + if (notesMigration.readChanges()) { + assertReviewers(c, REVIEWER); + assertReviewers(c, CC, user); + // Verify no approvals were added. + assertThat(c.labels).isNotNull(); + LabelInfo label = c.labels.get("Code-Review"); + assertThat(label).isNotNull(); + assertThat(label.all).isNull(); + } else { + // When approvals are stored in ReviewDb, we still create a label for + // the reviewing user, and force them into the REVIEWER state. + assertReviewers(c, REVIEWER, user); + assertReviewers(c, CC); + LabelInfo label = c.labels.get("Code-Review"); + assertThat(label).isNotNull(); + assertThat(label.all).isNotNull(); + assertThat(label.all).hasSize(1); + ApprovalInfo approval = label.all.get(0); + assertThat(approval._accountId).isEqualTo(user.getId().get()); + } + } + + @Test + public void reviewerReplyWithoutVote() throws Exception { + // Create change owned by admin. + PushOneCommit.Result r = createChange(); + + // Verify reviewer state. + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + assertReviewers(c, REVIEWER); + assertReviewers(c, CC); + LabelInfo label = c.labels.get("Code-Review"); + assertThat(label).isNotNull(); + assertThat(label.all).isNull(); + + // Add user as REVIEWER. + ReviewInput input = new ReviewInput().reviewer(user.username); + ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input); + assertThat(result.labels).isNull(); + assertThat(result.reviewers).isNotNull(); + assertThat(result.reviewers).hasSize(1); + + // Verify reviewer state. Both admin and user should be REVIEWERs now, + // because admin gets forced into REVIEWER state by virtue of being owner. + c = gApi.changes() + .id(r.getChangeId()) + .get(); + assertReviewers(c, REVIEWER, admin, user); + assertReviewers(c, CC); + label = c.labels.get("Code-Review"); + assertThat(label).isNotNull(); + assertThat(label.all).isNotNull(); + assertThat(label.all).hasSize(2); + Map<Integer, Integer> approvals = new HashMap<>(); + for (ApprovalInfo approval : label.all) { + approvals.put(approval._accountId, approval.value); + } + assertThat(approvals).containsEntry(admin.getId().get(), 0); + assertThat(approvals).containsEntry(user.getId().get(), 0); + + // Comment as user without voting. This should delete the approval and + // then replace it with the default value. + input = new ReviewInput().message("hello"); + RestResponse resp = userRestSession.post( + "/changes/" + r.getChangeId() + "/revisions/" + + r.getCommit().getName() + "/review", input); + result = readContentFromJson(resp, 200, ReviewResult.class); + assertThat(result.labels).isNull(); + + // Verify reviewer state. + c = gApi.changes() + .id(r.getChangeId()) + .get(); + assertReviewers(c, REVIEWER, admin, user); + assertReviewers(c, CC); + label = c.labels.get("Code-Review"); + assertThat(label).isNotNull(); + assertThat(label.all).isNotNull(); + assertThat(label.all).hasSize(2); + approvals.clear(); + for (ApprovalInfo approval : label.all) { + approvals.put(approval._accountId, approval.value); + } + assertThat(approvals).containsEntry(admin.getId().get(), 0); + assertThat(approvals).containsEntry(user.getId().get(), 0); + } + + @Test public void reviewAndAddReviewers() throws Exception { TestAccount observer = accounts.user2(); PushOneCommit.Result r = createChange(); @@ -316,7 +479,7 @@ assertThat(m.rcpt()).containsExactly(user.emailAddress, observer.emailAddress); assertThat(m.body()).contains(admin.fullName + " has posted comments on this change."); assertThat(m.body()).contains("Change subject: " + PushOneCommit.SUBJECT + "\n"); - assertThat(m.body()).contains("Patch Set 1: Code-Review+2\n"); + assertThat(m.body()).contains("Patch Set 1: Code-Review+2"); } @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java new file mode 100644 index 0000000..f5ae072 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CorsIT.java
@@ -0,0 +1,160 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.acceptance.rest.change; + +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD; +import static com.google.common.net.HttpHeaders.ORIGIN; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.PushOneCommit.Result; +import com.google.gerrit.acceptance.RestResponse; +import com.google.gerrit.testutil.ConfigSuite; + +import org.apache.http.Header; +import org.apache.http.client.fluent.Request; +import org.apache.http.message.BasicHeader; +import org.eclipse.jgit.lib.Config; +import org.junit.Test; + +public class CorsIT extends AbstractDaemonTest { + @ConfigSuite.Default + public static Config allowExampleDotCom() { + Config cfg = new Config(); + cfg.setStringList( + "site", null, "allowOriginRegex", + ImmutableList.of( + "https?://(.+[.])?example[.]com", + "http://friend[.]ly")); + return cfg; + } + + @Test + public void origin() throws Exception { + Result change = createChange(); + + String url = "/changes/" + change.getChangeId() + "/detail"; + RestResponse r = adminRestSession.get(url); + r.assertOK(); + assertThat(r.getHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isNull(); + assertThat(r.getHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS)).isNull(); + + check(url, true, "http://example.com"); + check(url, true, "https://sub.example.com"); + check(url, true, "http://friend.ly"); + + check(url, false, "http://evil.attacker"); + check(url, false, "http://friendsly"); + } + + @Test + public void putWithOriginRefused() throws Exception { + Result change = createChange(); + String origin = "http://example.com"; + RestResponse r = adminRestSession.putWithHeader( + "/changes/" + change.getChangeId() + "/topic", + new BasicHeader(ORIGIN, origin), + "A"); + r.assertOK(); + checkCors(r, false, origin); + } + + @Test + public void preflightOk() throws Exception { + Result change = createChange(); + + String origin = "http://example.com"; + Request req = Request.Options(adminRestSession.url() + + "/a/changes/" + change.getChangeId() + "/detail"); + req.addHeader(ORIGIN, origin); + req.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + req.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "X-Requested-With"); + + RestResponse res = adminRestSession.execute(req); + res.assertOK(); + checkCors(res, true, origin); + } + + @Test + public void preflightBadOrigin() throws Exception { + Result change = createChange(); + + Request req = Request.Options(adminRestSession.url() + + "/a/changes/" + change.getChangeId() + "/detail"); + req.addHeader(ORIGIN, "http://evil.attacker"); + req.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + + adminRestSession.execute(req).assertBadRequest(); + } + + @Test + public void preflightBadMethod() throws Exception { + Result change = createChange(); + + for (String method : new String[] {"POST", "PUT", "DELETE", "PATCH"}) { + Request req = Request.Options(adminRestSession.url() + + "/a/changes/" + change.getChangeId() + "/detail"); + req.addHeader(ORIGIN, "http://example.com"); + req.addHeader(ACCESS_CONTROL_REQUEST_METHOD, method); + adminRestSession.execute(req).assertBadRequest(); + } + } + + @Test + public void preflightBadHeader() throws Exception { + Result change = createChange(); + + Request req = Request.Options(adminRestSession.url() + + "/a/changes/" + change.getChangeId() + "/detail"); + req.addHeader(ORIGIN, "http://example.com"); + req.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + req.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "X-Gerrit-Auth"); + + adminRestSession.execute(req).assertBadRequest(); + } + + private RestResponse check(String url, boolean accept, String origin) + throws Exception { + Header hdr = new BasicHeader(ORIGIN, origin); + RestResponse r = adminRestSession.getWithHeader(url, hdr); + r.assertOK(); + checkCors(r, accept, origin); + return r; + } + + private void checkCors(RestResponse r, boolean accept, String origin) { + String allowOrigin = r.getHeader(ACCESS_CONTROL_ALLOW_ORIGIN); + String allowCred = r.getHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS); + String allowMethods = r.getHeader(ACCESS_CONTROL_ALLOW_METHODS); + String allowHeaders = r.getHeader(ACCESS_CONTROL_ALLOW_HEADERS); + if (accept) { + assertThat(allowOrigin).isEqualTo(origin); + assertThat(allowCred).isEqualTo("true"); + assertThat(allowMethods).isEqualTo("GET, OPTIONS"); + assertThat(allowHeaders).isEqualTo("X-Requested-With"); + } else { + assertThat(allowOrigin).isNull(); + assertThat(allowCred).isNull(); + assertThat(allowMethods).isNull(); + assertThat(allowHeaders).isNull(); + } + } +}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java index 29fda2d..e160374 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -15,6 +15,7 @@ package com.google.gerrit.acceptance.rest.change; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import com.google.gerrit.acceptance.GitUtil; import com.google.gerrit.acceptance.PushOneCommit; @@ -24,14 +25,19 @@ import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.projects.BranchInput; import com.google.gerrit.extensions.client.SubmitType; +import com.google.gerrit.extensions.restapi.BinaryResult; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Project; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.transport.RefSpec; import org.junit.Test; import java.util.List; +import java.util.Map; public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge { @@ -144,6 +150,12 @@ approve(change2a.getChangeId()); approve(change2b.getChangeId()); approve(change3.getChangeId()); + + // get a preview before submitting: + BinaryResult request = submitPreview(change1b.getChangeId()); + Map<Branch.NameKey, RevTree> preview = + fetchFromBundles(request); + submit(change1b.getChangeId()); RevCommit tip1 = getRemoteLog(p1, "master").get(0); @@ -158,11 +170,28 @@ change2b.getCommit().getShortMessage()); assertThat(tip3.getShortMessage()).isEqualTo( change3.getCommit().getShortMessage()); + + // check that the preview matched what happened: + assertThat(preview).hasSize(3); + + assertThat(preview).containsKey( + new Branch.NameKey(p1, "refs/heads/master")); + assertRevTrees(p1, preview); + + assertThat(preview).containsKey( + new Branch.NameKey(p2, "refs/heads/master")); + assertRevTrees(p2, preview); + + assertThat(preview).containsKey( + new Branch.NameKey(p3, "refs/heads/master")); + assertRevTrees(p3, preview); } else { assertThat(tip2.getShortMessage()).isEqualTo( initialHead2.getShortMessage()); assertThat(tip3.getShortMessage()).isEqualTo( initialHead3.getShortMessage()); + assertThat(preview).hasSize(1); + assertThat(preview.get(new Branch.NameKey(p1, "refs/heads/master"))).isNotNull(); } } @@ -215,11 +244,23 @@ approve(change3.getChangeId()); if (isSubmitWholeTopicEnabled()) { - submitWithConflict(change1b.getChangeId(), + String msg = "Failed to submit 5 changes due to the following problems:\n" + "Change " + change3.getChange().getId() + ": Change could not be " + "merged due to a path conflict. Please rebase the change locally " + - "and upload the rebased commit for review."); + "and upload the rebased commit for review."; + + // Get a preview before submitting: + try { + // We cannot just use the ExpectedException infrastructure as provided + // by AbstractDaemonTest, as then we'd stop early and not test the + // actual submit. + submitPreview(change1b.getChangeId()); + fail("expected failure"); + } catch (RestApiException e) { + assertThat(e.getMessage()).isEqualTo(msg); + } + submitWithConflict(change1b.getChangeId(), msg); } else { submit(change1b.getChangeId()); }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java index d5b6f14..ce7e76d 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
@@ -30,6 +30,7 @@ import com.google.gerrit.testutil.ConfigSuite; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; +import com.google.inject.Provider; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -47,7 +48,7 @@ @NoHttpd public class SubmitResolvingMergeCommitIT extends AbstractDaemonTest { @Inject - private MergeSuperSet mergeSuperSet; + private Provider<MergeSuperSet> mergeSuperSet; @Inject private Submit submit; @@ -293,7 +294,7 @@ throws MissingObjectException, IncorrectObjectTypeException, IOException, OrmException { ChangeSet cs = - mergeSuperSet.completeChangeSet(db, change.change(), user(admin)); + mergeSuperSet.get().completeChangeSet(db, change.change(), user(admin)); assertThat(submit.unmergeableChanges(cs).isEmpty()).isEqualTo(expected); }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java index 54fa74c..ce7e8c9 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -20,19 +20,20 @@ import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.GerritConfig; import com.google.gerrit.acceptance.GerritConfigs; -import com.google.gerrit.acceptance.RestResponse; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.AuthType; +import com.google.gerrit.acceptance.NoHttpd; +import com.google.gerrit.extensions.client.AccountFieldName; +import com.google.gerrit.extensions.client.AuthType; +import com.google.gerrit.extensions.common.ServerInfo; import com.google.gerrit.server.config.AllProjectsNameProvider; import com.google.gerrit.server.config.AllUsersNameProvider; import com.google.gerrit.server.config.AnonymousCowardNameProvider; -import com.google.gerrit.server.config.GetServerInfo.ServerInfo; import org.junit.Test; import java.nio.file.Files; import java.nio.file.Path; +@NoHttpd public class ServerInfoIT extends AbstractDaemonTest { @Test @@ -64,8 +65,10 @@ // gerrit @GerritConfig(name = "gerrit.allProjects", value = "Root"), @GerritConfig(name = "gerrit.allUsers", value = "Users"), - @GerritConfig(name = "gerrit.reportBugUrl", value = "https://example.com/report"), + @GerritConfig(name = "gerrit.enableGwtUi", value = "true"), + @GerritConfig(name = "gerrit.enablePolyGerrit", value = "true"), @GerritConfig(name = "gerrit.reportBugText", value = "REPORT BUG"), + @GerritConfig(name = "gerrit.reportBugUrl", value = "https://example.com/report"), // suggest @GerritConfig(name = "suggest.from", value = "3"), @@ -74,12 +77,12 @@ @GerritConfig(name = "user.anonymousCoward", value = "Unnamed User"), }) public void serverConfig() throws Exception { - ServerInfo i = getServerConfig(); + ServerInfo i = gApi.config().server().getInfo(); // auth assertThat(i.auth.authType).isEqualTo(AuthType.HTTP); assertThat(i.auth.editableAccountFields).containsExactly( - Account.FieldName.REGISTER_NEW_EMAIL, Account.FieldName.FULL_NAME); + AccountFieldName.REGISTER_NEW_EMAIL, AccountFieldName.FULL_NAME); assertThat(i.auth.useContributorAgreements).isTrue(); assertThat(i.auth.loginUrl).isEqualTo("https://example.com/login"); assertThat(i.auth.loginText).isEqualTo("LOGIN"); @@ -107,6 +110,9 @@ assertThat(i.gerrit.reportBugUrl).isEqualTo("https://example.com/report"); assertThat(i.gerrit.reportBugText).isEqualTo("REPORT BUG"); + // Acceptance tests force --headless even when UIs are specified in config. + assertThat(i.gerrit.webUis).isEmpty(); + // plugin assertThat(i.plugin.jsResourcePaths).isEmpty(); @@ -121,9 +127,9 @@ // notedb notesMigration.setReadChanges(true); - assertThat(getServerConfig().noteDbEnabled).isTrue(); + assertThat(gApi.config().server().getInfo().noteDbEnabled).isTrue(); notesMigration.setReadChanges(false); - assertThat(getServerConfig().noteDbEnabled).isNull(); + assertThat(gApi.config().server().getInfo().noteDbEnabled).isNull(); } @Test @@ -134,7 +140,7 @@ Files.write(jsplugin, "Gerrit.install(function(self){});\n".getBytes(UTF_8)); adminSshSession.exec("gerrit plugin reload"); - ServerInfo i = getServerConfig(); + ServerInfo i = gApi.config().server().getInfo(); // plugin assertThat(i.plugin.jsResourcePaths).hasSize(1); @@ -142,13 +148,13 @@ @Test public void serverConfigWithDefaults() throws Exception { - ServerInfo i = getServerConfig(); + ServerInfo i = gApi.config().server().getInfo(); // auth assertThat(i.auth.authType).isEqualTo(AuthType.OPENID); assertThat(i.auth.editableAccountFields).containsExactly( - Account.FieldName.REGISTER_NEW_EMAIL, Account.FieldName.FULL_NAME, - Account.FieldName.USER_NAME); + AccountFieldName.REGISTER_NEW_EMAIL, AccountFieldName.FULL_NAME, + AccountFieldName.USER_NAME); assertThat(i.auth.useContributorAgreements).isNull(); assertThat(i.auth.loginUrl).isNull(); assertThat(i.auth.loginText).isNull(); @@ -189,9 +195,12 @@ assertThat(i.user.anonymousCowardName).isEqualTo(AnonymousCowardNameProvider.DEFAULT); } - private ServerInfo getServerConfig() throws Exception { - RestResponse r = adminRestSession.get("/config/server/info/"); - r.assertOK(); - return newGson().fromJson(r.getReader(), ServerInfo.class); + @Test + @GerritConfig(name = "auth.contributorAgreements", value = "true") + public void anonymousAccess() throws Exception { + configureContributorAgreement(true); + + setApiUserAnonymous(); + gApi.config().server().getInfo(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java index 46f93b6..6377710 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -67,9 +67,9 @@ } @Test - public void createBranchByAdminCreateReferenceBlocked() throws Exception { + public void createBranchByAdminCreateReferenceBlocked_Forbidden() throws Exception { blockCreateReference(); - assertCreateSucceeds(); + assertCreateFails(AuthException.class); } @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java index 955e580..1c9711f 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -36,6 +36,7 @@ @Before public void setUp() throws Exception { + project = createProject(name("p")); branch = new Branch.NameKey(project, "test"); branch().create(new BranchInput()); } @@ -73,10 +74,32 @@ assertDeleteForbidden(); } + @Test + public void deleteBranchByUserWithForcePushPermission() throws Exception { + grantForcePush(); + setApiUser(user); + assertDeleteSucceeds(); + } + + @Test + public void deleteBranchByUserWithDeletePermission() throws Exception { + grantDelete(); + setApiUser(user); + assertDeleteSucceeds(); + } + private void blockForcePush() throws Exception { block(Permission.PUSH, ANONYMOUS_USERS, "refs/heads/*").setForce(true); } + private void grantForcePush() throws Exception { + grant(Permission.PUSH, project, "refs/heads/*", true, ANONYMOUS_USERS); + } + + private void grantDelete() throws Exception { + allow(Permission.DELETE, ANONYMOUS_USERS, "refs/*"); + } + private void grantOwner() throws Exception { allow(Permission.OWNER, REGISTERED_USERS, "refs/*"); } @@ -99,6 +122,7 @@ private void assertDeleteForbidden() throws Exception { exception.expect(AuthException.class); + exception.expectMessage("Cannot delete branch"); branch().delete(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushTagIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushTagIT.java new file mode 100644 index 0000000..01a2443 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushTagIT.java
@@ -0,0 +1,290 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.acceptance.rest.project; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.acceptance.GitUtil.createAnnotatedTag; +import static com.google.gerrit.acceptance.GitUtil.deleteRef; +import static com.google.gerrit.acceptance.GitUtil.pushHead; +import static com.google.gerrit.acceptance.GitUtil.updateAnnotatedTag; +import static com.google.gerrit.acceptance.rest.project.PushTagIT.TagType.ANNOTATED; +import static com.google.gerrit.acceptance.rest.project.PushTagIT.TagType.LIGHTWEIGHT; +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; + +import com.google.common.base.MoreObjects; +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.GitUtil; +import com.google.gerrit.acceptance.NoHttpd; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.reviewdb.client.RefNames; + +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.RemoteRefUpdate.Status; +import org.junit.Before; +import org.junit.Test; + +@NoHttpd +public class PushTagIT extends AbstractDaemonTest { + enum TagType { + LIGHTWEIGHT(Permission.CREATE), + ANNOTATED(Permission.CREATE_TAG); + + final String createPermission; + + TagType(String createPermission) { + this.createPermission = createPermission; + } + } + + private RevCommit initialHead; + + @Before + public void setup() throws Exception { + // clone with user to avoid inherited tag permissions of admin user + testRepo = cloneProject(project, user); + + initialHead = getRemoteHead(); + } + + @Test + public void createTagForExistingCommit() throws Exception { + for (TagType tagType : TagType.values()) { + pushTagForExistingCommit(tagType, Status.REJECTED_OTHER_REASON); + + allowTagCreation(tagType); + pushTagForExistingCommit(tagType, Status.OK); + + allowPushOnRefsTags(); + pushTagForExistingCommit(tagType, Status.OK); + + removePushFromRefsTags(); + } + } + + @Test + public void createTagForNewCommit() throws Exception { + for (TagType tagType : TagType.values()) { + pushTagForNewCommit(tagType, Status.REJECTED_OTHER_REASON); + + allowTagCreation(tagType); + pushTagForNewCommit(tagType, Status.REJECTED_OTHER_REASON); + + allowPushOnRefsTags(); + pushTagForNewCommit(tagType, Status.OK); + + removePushFromRefsTags(); + } + } + + @Test + public void fastForward() throws Exception { + for (TagType tagType : TagType.values()) { + allowTagCreation(tagType); + String tagName = pushTagForExistingCommit(tagType, Status.OK); + + fastForwardTagToExistingCommit(tagType, tagName, + Status.REJECTED_OTHER_REASON); + fastForwardTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON); + + allowTagDeletion(); + fastForwardTagToExistingCommit(tagType, tagName, + Status.REJECTED_OTHER_REASON); + fastForwardTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON); + + allowPushOnRefsTags(); + Status expectedStatus = + tagType == ANNOTATED ? Status.REJECTED_OTHER_REASON : Status.OK; + fastForwardTagToExistingCommit(tagType, tagName, expectedStatus); + fastForwardTagToNewCommit(tagType, tagName, expectedStatus); + + allowForcePushOnRefsTags(); + fastForwardTagToExistingCommit(tagType, tagName, Status.OK); + fastForwardTagToNewCommit(tagType, tagName, Status.OK); + + removePushFromRefsTags(); + } + } + + @Test + public void forceUpdate() throws Exception { + for (TagType tagType : TagType.values()) { + allowTagCreation(tagType); + String tagName = pushTagForExistingCommit(tagType, Status.OK); + + forceUpdateTagToExistingCommit(tagType, tagName, + Status.REJECTED_OTHER_REASON); + forceUpdateTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON); + + allowPushOnRefsTags(); + forceUpdateTagToExistingCommit(tagType, tagName, + Status.REJECTED_OTHER_REASON); + forceUpdateTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON); + + allowTagDeletion(); + forceUpdateTagToExistingCommit(tagType, tagName, + Status.REJECTED_OTHER_REASON); + forceUpdateTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON); + + allowForcePushOnRefsTags(); + forceUpdateTagToExistingCommit(tagType, tagName, Status.OK); + forceUpdateTagToNewCommit(tagType, tagName, Status.OK); + + removePushFromRefsTags(); + } + } + + @Test + public void delete() throws Exception { + for (TagType tagType : TagType.values()) { + allowTagCreation(tagType); + String tagName = pushTagForExistingCommit(tagType, Status.OK); + + pushTagDeletion(tagType, tagName, Status.REJECTED_OTHER_REASON); + + allowPushOnRefsTags(); + pushTagDeletion(tagType, tagName, Status.REJECTED_OTHER_REASON); + } + + allowForcePushOnRefsTags(); + for (TagType tagType : TagType.values()) { + String tagName = pushTagForExistingCommit(tagType, Status.OK); + pushTagDeletion(tagType, tagName, Status.OK); + } + + removePushFromRefsTags(); + allowTagDeletion(); + for (TagType tagType : TagType.values()) { + String tagName = pushTagForExistingCommit(tagType, Status.OK); + pushTagDeletion(tagType, tagName, Status.OK); + } + } + + private String pushTagForExistingCommit(TagType tagType, + Status expectedStatus) throws Exception { + return pushTag(tagType, null, false, false, expectedStatus); + } + + private String pushTagForNewCommit(TagType tagType, + Status expectedStatus) throws Exception { + return pushTag(tagType, null, true, false, expectedStatus); + } + + private void fastForwardTagToExistingCommit(TagType tagType, String tagName, + Status expectedStatus) throws Exception { + pushTag(tagType, tagName, false, false, expectedStatus); + } + + private void fastForwardTagToNewCommit(TagType tagType, String tagName, + Status expectedStatus) throws Exception { + pushTag(tagType, tagName, true, false, expectedStatus); + } + + private void forceUpdateTagToExistingCommit(TagType tagType, String tagName, + Status expectedStatus) throws Exception { + pushTag(tagType, tagName, false, true, expectedStatus); + } + + private void forceUpdateTagToNewCommit(TagType tagType, String tagName, + Status expectedStatus) throws Exception { + pushTag(tagType, tagName, true, true, expectedStatus); + } + + private String pushTag(TagType tagType, String tagName, boolean newCommit, + boolean force, Status expectedStatus) throws Exception { + if (force) { + testRepo.reset(initialHead); + } + commit(user.getIdent(), "subject"); + + boolean createTag = tagName == null; + tagName = MoreObjects.firstNonNull(tagName, "v1" + "_" + System.nanoTime()); + switch (tagType) { + case LIGHTWEIGHT: + break; + case ANNOTATED: + if (createTag) { + createAnnotatedTag(testRepo, tagName, user.getIdent()); + } else { + updateAnnotatedTag(testRepo, tagName, user.getIdent()); + } + break; + default: + throw new IllegalStateException("unexpected tag type: " + tagType); + } + + if (!newCommit) { + grant(Permission.SUBMIT, project, "refs/for/refs/heads/master", false, + REGISTERED_USERS); + pushHead(testRepo, "refs/for/master%submit"); + } + + String tagRef = tagRef(tagName); + PushResult r = tagType == LIGHTWEIGHT + ? pushHead(testRepo, tagRef, false, force) + : GitUtil.pushTag(testRepo, tagName, !createTag); + RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef); + assertThat(refUpdate.getStatus()) + .named(tagType.name()) + .isEqualTo(expectedStatus); + return tagName; + } + + private void pushTagDeletion(TagType tagType, String tagName, + Status expectedStatus) throws Exception { + String tagRef = tagRef(tagName); + PushResult r = deleteRef(testRepo, tagRef); + RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef); + assertThat(refUpdate.getStatus()).named(tagType.name()) + .isEqualTo(expectedStatus); + } + + private void allowTagCreation(TagType tagType) throws Exception { + grant(tagType.createPermission, project, "refs/tags/*", false, + REGISTERED_USERS); + } + + private void allowPushOnRefsTags() throws Exception { + removePushFromRefsTags(); + grant(Permission.PUSH, project, "refs/tags/*", false, REGISTERED_USERS); + } + + private void allowForcePushOnRefsTags() throws Exception { + removePushFromRefsTags(); + grant(Permission.PUSH, project, "refs/tags/*", true, REGISTERED_USERS); + } + + private void allowTagDeletion() throws Exception { + removePushFromRefsTags(); + grant(Permission.DELETE, project, "refs/tags/*", true, REGISTERED_USERS); + } + + private void removePushFromRefsTags() throws Exception { + removePermission(Permission.PUSH, project, "refs/tags/*"); + } + + private void commit(PersonIdent ident, String subject) throws Exception { + commitBuilder() + .ident(ident) + .message(subject + " (" + System.nanoTime() + ")") + .create(); + } + + private static String tagRef(String tagName) { + return RefNames.REFS_TAGS + tagName; + } +}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java index 33aa726..c4aee29 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -238,6 +238,7 @@ @Test public void createTagNotAllowed() throws Exception { + block(Permission.CREATE, REGISTERED_USERS, R_TAGS + "*"); TagInput input = new TagInput(); input.ref = "test"; exception.expect(AuthException.class); @@ -247,7 +248,7 @@ @Test public void createAnnotatedTagNotAllowed() throws Exception { - block(Permission.PUSH_TAG, REGISTERED_USERS, R_TAGS + "*"); + block(Permission.CREATE_TAG, REGISTERED_USERS, R_TAGS + "*"); TagInput input = new TagInput(); input.ref = "test"; input.message = "annotation"; @@ -338,8 +339,8 @@ private void grantTagPermissions() throws Exception { grant(Permission.CREATE, project, R_TAGS + "*"); - grant(Permission.PUSH_TAG, project, R_TAGS + "*"); - grant(Permission.PUSH_SIGNED_TAG, project, R_TAGS + "*"); + grant(Permission.CREATE_TAG, project, R_TAGS + "*"); + grant(Permission.CREATE_SIGNED_TAG, project, R_TAGS + "*"); } private ListRefsRequest<TagInfo> getTags() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java index d9f1a5c..5b6f3f9 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -32,9 +32,11 @@ import com.google.gerrit.extensions.client.Comment; import com.google.gerrit.extensions.client.Side; import com.google.gerrit.extensions.common.CommentInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.TopLevelResource; +import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.server.change.ChangeResource; import com.google.gerrit.server.change.ChangesCollection; import com.google.gerrit.server.change.PostReview; @@ -148,8 +150,8 @@ @Test public void postCommentOnMergeCommitChange() throws Exception { for (Integer line : lines) { - final String file = "/COMMIT_MSG"; - PushOneCommit.Result r = createMergeCommitChange("refs/for/master"); + String file = "foo"; + PushOneCommit.Result r = createMergeCommitChange("refs/for/master", file); String changeId = r.getChangeId(); String revId = r.getCommit().getName(); ReviewInput input = new ReviewInput(); @@ -165,6 +167,39 @@ assertThat(Lists.transform(result.get(file), infoToInput(file))) .containsExactly(c1, c2, c3, c4); } + + // for the commit message comments on the auto-merge are not possible + for (Integer line : lines) { + String file = Patch.COMMIT_MSG; + PushOneCommit.Result r = createMergeCommitChange("refs/for/master"); + String changeId = r.getChangeId(); + String revId = r.getCommit().getName(); + ReviewInput input = new ReviewInput(); + CommentInput c1 = newComment(file, Side.REVISION, line, "ps-1"); + CommentInput c2 = newCommentOnParent(file, 1, line, "parent-1 of ps-1"); + CommentInput c3 = newCommentOnParent(file, 2, line, "parent-2 of ps-1"); + input.comments = new HashMap<>(); + input.comments.put(file, ImmutableList.of(c1, c2, c3)); + revision(r).review(input); + Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); + assertThat(result).isNotEmpty(); + assertThat(Lists.transform(result.get(file), infoToInput(file))) + .containsExactly(c1, c2, c3); + } + } + + @Test + public void postCommentOnCommitMessageOnAutoMerge() throws Exception { + PushOneCommit.Result r = createMergeCommitChange("refs/for/master"); + ReviewInput input = new ReviewInput(); + CommentInput c = + newComment(Patch.COMMIT_MSG, Side.PARENT, 0, "comment on auto-merge"); + input.comments = new HashMap<>(); + input.comments.put(Patch.COMMIT_MSG, ImmutableList.of(c)); + exception.expect(BadRequestException.class); + exception.expectMessage( + "cannot comment on " + Patch.COMMIT_MSG + " on auto-merge"); + revision(r).review(input); } @Test @@ -504,8 +539,7 @@ assertThat(ps2List.get(2).message).isEqualTo("join lines"); assertThat(ps2List.get(3).message).isEqualTo("typo: content"); - ImmutableList<Message> messages = - email.getMessages(r2.getChangeId(), "comment"); + List<Message> messages = email.getMessages(r2.getChangeId(), "comment"); assertThat(messages).hasSize(1); String url = canonicalWebUrl.get(); int c = r1.getChange().getId().get(); @@ -544,6 +578,7 @@ + "\n" + "PS2, Line 2: nten\n" + "typo: content\n" + + "\n" + "\n"); }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java index 37e551f..e33d163 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -796,7 +796,7 @@ ins = patchSetInserterFactory.create(ctl, nextPatchSetId(ctl), commit) .setValidatePolicy(CommitValidators.Policy.NONE) .setFireRevisionCreated(false) - .setSendMail(false); + .setNotify(NotifyHandling.NONE); bu.addOp(ctl.getId(), ins).execute(); } return reload(ctl);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java index 40ea296..37ced5f 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -22,6 +22,7 @@ import com.google.common.collect.Iterables; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.common.RawInputUtil; import com.google.gerrit.common.TimeUtil; import com.google.gerrit.extensions.common.CommitInfo; @@ -646,6 +647,39 @@ changeAndCommit(psId1_1, c1_1, 1)); } + @Test + public void getRelatedForStaleChange() throws Exception { + RevCommit c1_1 = commitBuilder() + .add("a.txt", "1") + .message("subject: 1") + .create(); + + RevCommit c2_1 = commitBuilder() + .add("b.txt", "1") + .message("subject: 1") + .create(); + pushHead(testRepo, "refs/for/master", false); + + RevCommit c2_2 = testRepo.amend(c2_1) + .add("b.txt", "2") + .create(); + testRepo.reset(c2_2); + + disableChangeIndexWrites(); + try { + pushHead(testRepo, "refs/for/master", false); + } finally { + enableChangeIndexWrites(); + } + + PatchSet.Id psId1_1 = getPatchSetId(c1_1); + PatchSet.Id psId2_1 = getPatchSetId(c2_1); + PatchSet.Id psId2_2 = new PatchSet.Id(psId2_1.changeId, psId2_1.get() + 1); + + assertRelated(psId2_2, changeAndCommit(psId2_2, c2_2, 2), + changeAndCommit(psId1_1, c1_1, 1)); + } + private List<ChangeAndCommit> getRelated(PatchSet.Id ps) throws Exception { return getRelated(ps.getParentKey(), ps.get()); } @@ -654,8 +688,9 @@ throws Exception { String url = String.format("/changes/%d/revisions/%d/related", changeId.get(), ps); - return newGson().fromJson(adminRestSession.get(url).getReader(), - RelatedInfo.class).changes; + RestResponse r = adminRestSession.get(url); + r.assertOK(); + return newGson().fromJson(r.getReader(), RelatedInfo.class).changes; } private RevCommit parseBody(RevCommit c) throws Exception {
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 b443e66..8606ce7 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
@@ -58,11 +58,12 @@ import com.google.gerrit.server.git.RepoRefCache; import com.google.gerrit.server.git.UpdateException; import com.google.gerrit.server.notedb.ChangeBundle; +import com.google.gerrit.server.notedb.ChangeBundleReader; import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.notedb.ChangeRebuilder.NoPatchSetsException; import com.google.gerrit.server.notedb.NoteDbChangeState; import com.google.gerrit.server.notedb.NoteDbUpdateManager; import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException; import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.NoteDbChecker; import com.google.gerrit.testutil.NoteDbMode; @@ -123,6 +124,9 @@ @Inject private Sequences seq; + @Inject + private ChangeBundleReader bundleReader; + @Before public void setUp() throws Exception { assume().that(NoteDbMode.readWrite()).isFalse(); @@ -388,7 +392,7 @@ // Check that the bundles are equal. ChangeBundle actual = ChangeBundle.fromNotes( plcUtil, notesFactory.create(dbProvider.get(), project, id)); - ChangeBundle expected = ChangeBundle.fromReviewDb(getUnwrappedDb(), id); + ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id); assertThat(actual.differencesFrom(expected)).isEmpty(); } @@ -439,7 +443,7 @@ // Check that the bundles are equal. ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id); ChangeBundle actual = ChangeBundle.fromNotes(plcUtil, notes); - ChangeBundle expected = ChangeBundle.fromReviewDb(getUnwrappedDb(), id); + ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id); assertThat(actual.differencesFrom(expected)).isEmpty(); assertThat( Iterables.transform( @@ -478,7 +482,7 @@ // Check that the bundles are equal. ChangeBundle actual = ChangeBundle.fromNotes( plcUtil, notesFactory.create(dbProvider.get(), project, id)); - ChangeBundle expected = ChangeBundle.fromReviewDb(getUnwrappedDb(), id); + ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id); assertThat(actual.differencesFrom(expected)).isEmpty(); } @@ -508,7 +512,7 @@ assertChangeUpToDate(false, id); assertThat(getMetaRef(project, changeMetaRef(id))).isEqualTo(oldMetaId); ChangeBundle actual = ChangeBundle.fromNotes(plcUtil, notes); - ChangeBundle expected = ChangeBundle.fromReviewDb(getUnwrappedDb(), id); + ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id); assertThat(actual.differencesFrom(expected)).isEmpty(); assertChangeUpToDate(false, id); @@ -550,7 +554,7 @@ // Not up to date, but the actual returned state matches anyway. assertDraftsUpToDate(false, id, user); ChangeBundle actual = ChangeBundle.fromNotes(plcUtil, notes); - ChangeBundle expected = ChangeBundle.fromReviewDb(getUnwrappedDb(), id); + ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id); assertThat(actual.differencesFrom(expected)).isEmpty(); // Another rebuild attempt succeeds @@ -605,7 +609,7 @@ assertChangeUpToDate(true, id); assertDraftsUpToDate(false, id, user); ChangeBundle actual = ChangeBundle.fromNotes(plcUtil, notes); - ChangeBundle expected = ChangeBundle.fromReviewDb(getUnwrappedDb(), id); + ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id); assertThat(actual.differencesFrom(expected)).isEmpty(); // Another rebuild attempt succeeds @@ -711,6 +715,8 @@ rin.message = "comment"; Timestamp ts = new Timestamp(c.getCreatedOn().getTime() + 2000); + assertThat(ts).isGreaterThan(c.getCreatedOn()); + assertThat(ts).isLessThan(db.patchSets().get(psId).getCreatedOn()); RevisionResource revRsrc = parseCurrentRevisionResource(r.getChangeId()); postReview.get().apply(revRsrc, rin, ts);
diff --git a/gerrit-acceptance-tests/tests.bzl b/gerrit-acceptance-tests/tests.bzl index ff2562d..62a99e3 100644 --- a/gerrit-acceptance-tests/tests.bzl +++ b/gerrit-acceptance-tests/tests.bzl
@@ -11,7 +11,6 @@ flaky = 0, deps = [], labels = [], - source_under_test = [], #unused vm_args = ['-Xmx256m']): junit_tests( name = group,
diff --git a/gerrit-acceptance-tests/tests.defs b/gerrit-acceptance-tests/tests.defs index 85cc78b..648bd63 100644 --- a/gerrit-acceptance-tests/tests.defs +++ b/gerrit-acceptance-tests/tests.defs
@@ -8,7 +8,6 @@ srcs, deps = [], labels = [], - source_under_test = [], vm_args = ['-Xmx256m']): from os import path if path.exists('/dev/urandom'): @@ -20,11 +19,6 @@ deps = deps + BOUNCYCASTLE + [ '//gerrit-acceptance-tests:lib' ], - source_under_test = [ - '//gerrit-httpd:httpd', - '//gerrit-sshd:sshd', - '//gerrit-server:server', - ] + source_under_test, labels = labels + [ 'acceptance', 'slow',
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java index 5009771..8141dfb 100644 --- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java +++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -119,19 +119,8 @@ public void start() { if (executor != null) { for (final H2CacheImpl<?, ?> cache : caches) { - executor.execute(new Runnable() { - @Override - public void run() { - cache.start(); - } - }); - - cleanup.schedule(new Runnable() { - @Override - public void run() { - cache.prune(cleanup); - } - }, 30, TimeUnit.SECONDS); + executor.execute(cache::start); + cleanup.schedule(() -> cache.prune(cleanup), 30, TimeUnit.SECONDS); } } }
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java index 838f42c..7e05236 100644 --- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java +++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -144,24 +144,14 @@ final ValueHolder<V> h = new ValueHolder<>(val); h.created = TimeUtil.nowMs(); mem.put(key, h); - executor.execute(new Runnable() { - @Override - public void run() { - store.put(key, h); - } - }); + executor.execute(() -> store.put(key, h)); } @SuppressWarnings("unchecked") @Override public void invalidate(final Object key) { if (keyType.getRawType().isInstance(key) && store.mightContain((K) key)) { - executor.execute(new Runnable() { - @Override - public void run() { - store.invalidate((K) key); - } - }); + executor.execute(() -> store.invalidate((K) key)); } mem.invalidate(key); } @@ -212,12 +202,7 @@ cal.add(Calendar.DAY_OF_MONTH, 1); long delay = cal.getTimeInMillis() - TimeUtil.nowMs(); - service.schedule(new Runnable() { - @Override - public void run() { - prune(service); - } - }, delay, TimeUnit.MILLISECONDS); + service.schedule(() -> prune(service), delay, TimeUnit.MILLISECONDS); } static class ValueHolder<V> { @@ -252,12 +237,7 @@ final ValueHolder<V> h = new ValueHolder<>(loader.load(key)); h.created = TimeUtil.nowMs(); - executor.execute(new Runnable() { - @Override - public void run() { - store.put(key, h); - } - }); + executor.execute(() -> store.put(key, h)); return h; } } @@ -280,14 +260,9 @@ } } - final ValueHolder<V> h = new ValueHolder<V>(loader.call()); + final ValueHolder<V> h = new ValueHolder<>(loader.call()); h.created = TimeUtil.nowMs(); - executor.execute(new Runnable() { - @Override - public void run() { - store.put(key, h); - } - }); + executor.execute(() -> store.put(key, h)); return h; } }
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK index 847fd25..452b2fe 100644 --- a/gerrit-common/BUCK +++ b/gerrit-common/BUCK
@@ -62,7 +62,6 @@ '//lib:guava', '//lib:junit', ], - source_under_test = [':client'], ) java_test(
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java index 752f0d2..afd6734 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -16,13 +16,11 @@ import com.google.gerrit.common.audit.Audit; import com.google.gerrit.common.auth.SignInRequired; -import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gwtjsonrpc.common.AsyncCallback; import com.google.gwtjsonrpc.common.RemoteJsonService; import com.google.gwtjsonrpc.common.RpcImpl; import com.google.gwtjsonrpc.common.RpcImpl.Version; -import com.google.gwtjsonrpc.common.VoidResult; import java.util.List; import java.util.Set; @@ -36,14 +34,4 @@ @SignInRequired void deleteExternalIds(Set<AccountExternalId.Key> keys, AsyncCallback<Set<AccountExternalId.Key>> callback); - - @Audit - @SignInRequired - void updateContact(String fullName, String emailAddr, - AsyncCallback<Account> callback); - - @Audit - @SignInRequired - void enterAgreement(String agreementName, - AsyncCallback<VoidResult> callback); }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java deleted file mode 100644 index 22482c7..0000000 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java +++ /dev/null
@@ -1,27 +0,0 @@ -// Copyright (C) 2008 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.common.data; - -import com.google.gerrit.common.auth.SignInRequired; -import com.google.gwtjsonrpc.common.AsyncCallback; -import com.google.gwtjsonrpc.common.RemoteJsonService; -import com.google.gwtjsonrpc.common.RpcImpl; -import com.google.gwtjsonrpc.common.RpcImpl.Version; - -@RpcImpl(version = Version.V2_0) -public interface AccountService extends RemoteJsonService { - @SignInRequired - void myAgreements(AsyncCallback<AgreementInfo> callback); -}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java index 97f11b4..290b9f9 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -24,8 +24,12 @@ public static final String ABANDON = "abandon"; public static final String ADD_PATCH_SET = "addPatchSet"; public static final String CREATE = "create"; + public static final String DELETE = "delete"; + public static final String CREATE_TAG = "createTag"; + public static final String CREATE_SIGNED_TAG = "createSignedTag"; public static final String DELETE_DRAFTS = "deleteDrafts"; public static final String EDIT_HASHTAGS = "editHashtags"; + public static final String EDIT_ASSIGNEE = "editAssignee"; public static final String EDIT_TOPIC_NAME = "editTopicName"; public static final String FORGE_AUTHOR = "forgeAuthor"; public static final String FORGE_COMMITTER = "forgeCommitter"; @@ -36,8 +40,6 @@ public static final String PUBLISH_DRAFTS = "publishDrafts"; public static final String PUSH = "push"; public static final String PUSH_MERGE = "pushMerge"; - public static final String PUSH_TAG = "pushTag"; - public static final String PUSH_SIGNED_TAG = "pushSignedTag"; public static final String READ = "read"; public static final String REBASE = "rebase"; public static final String REMOVE_REVIEWER = "removeReviewer"; @@ -46,8 +48,8 @@ public static final String VIEW_DRAFTS = "viewDrafts"; private static final List<String> NAMES_LC; - private static final int labelIndex; - private static final int labelAsIndex; + private static final int LABEL_INDEX; + private static final int LABEL_AS_INDEX; static { NAMES_LC = new ArrayList<>(); @@ -56,13 +58,14 @@ NAMES_LC.add(ABANDON.toLowerCase()); NAMES_LC.add(ADD_PATCH_SET.toLowerCase()); NAMES_LC.add(CREATE.toLowerCase()); + NAMES_LC.add(CREATE_TAG.toLowerCase()); + NAMES_LC.add(CREATE_SIGNED_TAG.toLowerCase()); + NAMES_LC.add(DELETE.toLowerCase()); NAMES_LC.add(FORGE_AUTHOR.toLowerCase()); NAMES_LC.add(FORGE_COMMITTER.toLowerCase()); NAMES_LC.add(FORGE_SERVER.toLowerCase()); NAMES_LC.add(PUSH.toLowerCase()); NAMES_LC.add(PUSH_MERGE.toLowerCase()); - NAMES_LC.add(PUSH_TAG.toLowerCase()); - NAMES_LC.add(PUSH_SIGNED_TAG.toLowerCase()); NAMES_LC.add(LABEL.toLowerCase()); NAMES_LC.add(LABEL_AS.toLowerCase()); NAMES_LC.add(REBASE.toLowerCase()); @@ -72,11 +75,12 @@ NAMES_LC.add(VIEW_DRAFTS.toLowerCase()); NAMES_LC.add(EDIT_TOPIC_NAME.toLowerCase()); NAMES_LC.add(EDIT_HASHTAGS.toLowerCase()); + NAMES_LC.add(EDIT_ASSIGNEE.toLowerCase()); NAMES_LC.add(DELETE_DRAFTS.toLowerCase()); NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase()); - labelIndex = NAMES_LC.indexOf(Permission.LABEL); - labelAsIndex = NAMES_LC.indexOf(Permission.LABEL_AS.toLowerCase()); + LABEL_INDEX = NAMES_LC.indexOf(Permission.LABEL); + LABEL_AS_INDEX = NAMES_LC.indexOf(Permission.LABEL_AS.toLowerCase()); } /** @return true if the name is recognized as a permission name. */ @@ -247,9 +251,9 @@ private static int index(Permission a) { if (isLabel(a.getName())) { - return labelIndex; + return LABEL_INDEX; } else if (isLabelAs(a.getName())) { - return labelAsIndex; + return LABEL_AS_INDEX; } int index = NAMES_LC.indexOf(a.getName().toLowerCase());
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java index 272801f..fb54ef1 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SystemInfoService.java
@@ -14,7 +14,6 @@ package com.google.gerrit.common.data; -import com.google.gerrit.common.auth.SignInRequired; import com.google.gwtjsonrpc.common.AllowCrossSiteRequest; import com.google.gwtjsonrpc.common.AsyncCallback; import com.google.gwtjsonrpc.common.RemoteJsonService; @@ -29,8 +28,5 @@ @AllowCrossSiteRequest void daemonHostKeys(AsyncCallback<List<SshHostKey>> callback); - @SignInRequired - void contributorAgreements(AsyncCallback<List<ContributorAgreement>> callback); - void clientError(String message, AsyncCallback<VoidResult> callback); }
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK index 61cd406..a0e7495 100644 --- a/gerrit-extension-api/BUCK +++ b/gerrit-extension-api/BUCK
@@ -67,14 +67,13 @@ '//lib:truth', '//lib/guice:guice', ], - source_under_test = [':api'], ) java_doc( name = 'extension-api-javadoc', title = 'Gerrit Review Extension API Documentation', pkgs = ['com.google.gerrit.extensions'], - paths = ['src/main/java'], + source_jar = ':extension-api-src', srcs = SRCS, deps = [ '//lib:guava',
diff --git a/gerrit-extension-api/BUILD b/gerrit-extension-api/BUILD index 4a5cfe3..b66617a 100644 --- a/gerrit-extension-api/BUILD +++ b/gerrit-extension-api/BUILD
@@ -44,3 +44,12 @@ ], visibility = ['//visibility:public'], ) + +load('//tools/bzl:javadoc.bzl', 'java_doc') + +java_doc( + name = 'extension-api-javadoc', + title = 'Gerrit Review Extension API Documentation', + libs = [':api'], + pkgs = ['com.google.gerrit.extensions'], +)
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml index 1febace..7375893 100644 --- a/gerrit-extension-api/pom.xml +++ b/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.13</version> + <version>2.14-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Extension API</name> <description>API for Gerrit Extensions</description>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java index c1cb3ec..9765bbf 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -34,6 +34,9 @@ public interface AccountApi { AccountInfo get() throws RestApiException; + boolean getActive() throws RestApiException; + void setActive(boolean active) throws RestApiException; + String getAvatarUrl(int size) throws RestApiException; GeneralPreferencesInfo getPreferences() throws RestApiException; @@ -85,6 +88,16 @@ } @Override + public boolean getActive() throws RestApiException { + throw new NotImplementedException(); + } + + @Override + public void setActive(boolean active) throws RestApiException { + throw new NotImplementedException(); + } + + @Override public String getAvatarUrl(int size) throws RestApiException { throw new NotImplementedException(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DeleteReviewerInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DeleteReviewerInput.java new file mode 100644 index 0000000..6af0dbb --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/DeleteReviewerInput.java
@@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.api.changes; + +/** Input passed to {@code DELETE /changes/[id]/reviewers/[id]}. */ +public class DeleteReviewerInput { + /** Who to send email notifications to after the reviewer is deleted. */ + public NotifyHandling notify = NotifyHandling.ALL; +}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/PublishChangeEditInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/PublishChangeEditInput.java new file mode 100644 index 0000000..fa6f18f --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/PublishChangeEditInput.java
@@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.api.changes; + +/** Input passed to {@code POST /changes/[id]/edit:publish/}. */ +public class PublishChangeEditInput { + /** Who to send email notifications to after the change edit is published. */ + public NotifyHandling notify = NotifyHandling.ALL; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java index d1f09e8..79cc12e 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java
@@ -25,6 +25,7 @@ void deleteVote(String label) throws RestApiException; void deleteVote(DeleteVoteInput input) throws RestApiException; void remove() throws RestApiException; + void remove(DeleteReviewerInput input) throws RestApiException; /** * A default implementation which allows source compatibility @@ -50,5 +51,10 @@ public void remove() throws RestApiException { throw new NotImplementedException(); } + + @Override + public void remove(DeleteReviewerInput input) throws RestApiException { + throw new NotImplementedException(); + } } }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java index 2731476..d6897b1 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -17,6 +17,7 @@ import com.google.gerrit.extensions.client.SubmitType; 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.FileInfo; import com.google.gerrit.extensions.common.MergeableInfo; import com.google.gerrit.extensions.common.TestSubmitRuleInput; @@ -35,6 +36,7 @@ void submit() throws RestApiException; void submit(SubmitInput in) throws RestApiException; + BinaryResult submitPreview() throws RestApiException; void publish() throws RestApiException; ChangeApi cherryPick(CherryPickInput in) throws RestApiException; ChangeApi rebase() throws RestApiException; @@ -72,6 +74,33 @@ SubmitType submitType() throws RestApiException; SubmitType testSubmitType(TestSubmitRuleInput in) throws RestApiException; + MergeListRequest getMergeList() throws RestApiException; + + abstract class MergeListRequest { + private boolean addLinks; + private int uninterestingParent = 1; + + public abstract List<CommitInfo> get() throws RestApiException; + + public MergeListRequest withLinks() { + this.addLinks = true; + return this; + } + + public MergeListRequest withUninterestingParent(int uninterestingParent) { + this.uninterestingParent = uninterestingParent; + return this; + } + + public boolean getAddLinks() { + return addLinks; + } + + public int getUninterestingParent() { + return uninterestingParent; + } + } + /** * A default implementation which allows source compatibility * when adding new methods to the interface. @@ -213,9 +242,19 @@ } @Override + public BinaryResult submitPreview() throws RestApiException { + throw new NotImplementedException(); + } + + @Override public SubmitType testSubmitType(TestSubmitRuleInput in) throws RestApiException { throw new NotImplementedException(); } + + @Override + public MergeListRequest getMergeList() throws RestApiException { + throw new NotImplementedException(); + } } }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java index a43c29f..1e5c95e 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java
@@ -16,6 +16,7 @@ import com.google.gerrit.extensions.client.DiffPreferencesInfo; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; +import com.google.gerrit.extensions.common.ServerInfo; import com.google.gerrit.extensions.restapi.NotImplementedException; import com.google.gerrit.extensions.restapi.RestApiException; @@ -25,6 +26,8 @@ */ String getVersion() throws RestApiException; + ServerInfo getInfo() throws RestApiException; + GeneralPreferencesInfo getDefaultPreferences() throws RestApiException; GeneralPreferencesInfo setDefaultPreferences(GeneralPreferencesInfo in) throws RestApiException; @@ -43,6 +46,11 @@ } @Override + public ServerInfo getInfo() throws RestApiException { + throw new NotImplementedException(); + } + + @Override public GeneralPreferencesInfo getDefaultPreferences() throws RestApiException { throw new NotImplementedException();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/AccountFieldName.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/AccountFieldName.java new file mode 100644 index 0000000..07d9f37 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/AccountFieldName.java
@@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.client; + +public enum AccountFieldName { + FULL_NAME, USER_NAME, REGISTER_NEW_EMAIL +} \ No newline at end of file
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/AuthType.java similarity index 91% rename from gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/AuthType.java index 38a78ba..2056e25 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/AuthType.java
@@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.reviewdb.client; +package com.google.gerrit.extensions.client; public enum AuthType { - /** Login relies upon the OpenID standard: {@link "http://openid.net/"} */ + /** Login relies upon the <a href="http://openid.net/">OpenID standard</a> */ OPENID, - /** Login relies upon the OpenID standard: {@link "http://openid.net/"} in Single Sign On mode */ + /** Login relies upon the <a href="http://openid.net/">OpenID standard</a> in Single Sign On mode */ OPENID_SSO, /** @@ -49,7 +49,7 @@ * Jetty's SSL channel to request client's SSL certificate. For this * authentication to work a Gerrit administrator has to import the root * certificate of the trust chain used to issue the client's certificate - * into the <review-site>/etc/keystore. + * into the <review-site>/etc/keystore. * <p> * After the authentication is done Gerrit will obtain basic user * registration (name and email) from LDAP, and some group memberships.
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java index 7c8a3e8..78ffb82 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java
@@ -59,6 +59,13 @@ } } + public short side() { + if (side == Side.PARENT) { + return (short) (parent == null ? 0 : -parent.shortValue()); + } + return 1; + } + @Override public boolean equals(Object o) { if (this == o) {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java index 9754f12..ec9190a 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -83,6 +83,25 @@ DISABLED } + public enum DefaultBase { + AUTO_MERGE(null), + FIRST_PARENT(-1); + + private final String base; + + DefaultBase(String base) { + this.base = base; + } + + DefaultBase(int base) { + this(Integer.toString(base)); + } + + public String getBase() { + return base; + } + } + public enum TimeFormat { /** 12-hour clock: 1:15 am, 2:13 pm */ HHMM_12("h:mm a"), @@ -123,6 +142,7 @@ public List<MenuItem> my; public Map<String, String> urlAliases; public EmailStrategy emailStrategy; + public DefaultBase defaultBaseForMerges; public boolean isShowInfoInReviewCategory() { return getReviewCategoryStrategy() != ReviewCategoryStrategy.NONE; @@ -180,6 +200,7 @@ p.legacycidInChangeTable = false; p.muteCommonPathPrefixes = true; p.signedOffBy = false; + p.defaultBaseForMerges = DefaultBase.FIRST_PARENT; return p; } }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/UiType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/UiType.java new file mode 100644 index 0000000..0d9df39 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/UiType.java
@@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.client; + +public enum UiType { + NONE, + GWT, + POLYGERRIT; + + public static UiType parse(String str) { + if (str != null) { + for (UiType type : UiType.values()) { + if (type.name().equalsIgnoreCase(str)) { + return type; + } + } + } + return null; + } +}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AgreementInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AgreementInfo.java index 6ec5b1d..4242fcd 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AgreementInfo.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AgreementInfo.java
@@ -18,4 +18,5 @@ public String name; public String description; public String url; + public GroupInfo autoVerifyGroup; }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java new file mode 100644 index 0000000..1000e9c --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java
@@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +import com.google.gerrit.extensions.client.AccountFieldName; +import com.google.gerrit.extensions.client.AuthType; + +import java.util.List; + +public class AuthInfo { + public AuthType authType; + public Boolean useContributorAgreements; + public List<AgreementInfo> contributorAgreements; + public List<AccountFieldName> editableAccountFields; + public String loginUrl; + public String loginText; + public String switchAccountUrl; + public String registerUrl; + public String registerText; + public String editFullNameUrl; + public String httpPasswordUrl; + public Boolean isGitBasicAuth; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java new file mode 100644 index 0000000..206b2f0 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +public class ChangeConfigInfo { + public Boolean allowBlame; + public Boolean allowDrafts; + public int largeChange; + public String replyLabel; + public String replyTooltip; + public int updateDelay; + public Boolean submitWholeTopic; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DownloadInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DownloadInfo.java new file mode 100644 index 0000000..180e2d2 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DownloadInfo.java
@@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +import java.util.List; +import java.util.Map; + +public class DownloadInfo { + public Map<String, DownloadSchemeInfo> schemes; + public List<String> archives; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java new file mode 100644 index 0000000..0e8ad65 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java
@@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +import java.util.Map; + +public class DownloadSchemeInfo { + public String url; + public Boolean isAuthRequired; + public Boolean isAuthSupported; + public Map<String, String> commands; + public Map<String, String> cloneCommands; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java new file mode 100644 index 0000000..0c10ec7 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java
@@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +import com.google.gerrit.extensions.client.UiType; + +import java.util.Set; + +public class GerritInfo { + public String allProjects; + public String allUsers; + public Boolean docSearch; + public String docUrl; + public Boolean editGpgKeys; + public String reportBugUrl; + public String reportBugText; + public Set<UiType> webUis; +}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginConfigInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginConfigInfo.java new file mode 100644 index 0000000..845f7cb7 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginConfigInfo.java
@@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +import java.util.List; + +public class PluginConfigInfo { + public Boolean hasAvatars; + public List<String> jsResourcePaths; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ReceiveInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ReceiveInfo.java new file mode 100644 index 0000000..e66c242 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ReceiveInfo.java
@@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +public class ReceiveInfo { + public Boolean enableSignedPush; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ServerInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ServerInfo.java new file mode 100644 index 0000000..3dd8368 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ServerInfo.java
@@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +import java.util.Map; + +public class ServerInfo { + public AuthInfo auth; + public ChangeConfigInfo change; + public DownloadInfo download; + public GerritInfo gerrit; + public Boolean noteDbEnabled; + public PluginConfigInfo plugin; + public SshdInfo sshd; + public SuggestInfo suggest; + public Map<String, String> urlAliases; + public UserConfigInfo user; + public ReceiveInfo receive; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SshdInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SshdInfo.java new file mode 100644 index 0000000..98d650c --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SshdInfo.java
@@ -0,0 +1,18 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +public class SshdInfo { +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestInfo.java new file mode 100644 index 0000000..5b0dcbe --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SuggestInfo.java
@@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +public class SuggestInfo { + public int from; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/UserConfigInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/UserConfigInfo.java new file mode 100644 index 0000000..5010689 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/UserConfigInfo.java
@@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.common; + +public class UserConfigInfo { + public String anonymousCowardName; +} \ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java index d9a34bf..4dd8f02 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java
@@ -14,6 +14,8 @@ package com.google.gerrit.extensions.common; +import com.google.gerrit.extensions.webui.WebLink.Target; + public class WebLinkInfo { public String name; public String imageUrl; @@ -26,4 +28,8 @@ this.url = url; this.target = target; } + + public WebLinkInfo(String name, String imageUrl, String url) { + this(name, imageUrl, url, Target.SELF); + } }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeAbandonedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeAbandonedListener.java index 40b84a3..d18f3e5 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeAbandonedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeAbandonedListener.java
@@ -15,14 +15,11 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; /** Notified whenever a Change is abandoned. */ @ExtensionPoint public interface ChangeAbandonedListener { interface Event extends RevisionEvent { - @Deprecated - AccountInfo getAbandoner(); String getReason(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeMergedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeMergedListener.java index d0ca6d6..de74a86 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeMergedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeMergedListener.java
@@ -15,14 +15,11 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; /** Notified whenever a Change is merged. */ @ExtensionPoint public interface ChangeMergedListener { interface Event extends RevisionEvent { - @Deprecated - AccountInfo getMerger(); /** * Represents the merged Revision when the submit strategy is cherry-pick or * rebase-if-necessary.
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeRestoredListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeRestoredListener.java index e5f3330..f533339 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeRestoredListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeRestoredListener.java
@@ -15,14 +15,11 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; /** Notified whenever a Change is restored. */ @ExtensionPoint public interface ChangeRestoredListener { interface Event extends RevisionEvent { - @Deprecated - AccountInfo getRestorer(); String getReason(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/CommentAddedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/CommentAddedListener.java index 6c82034..e8388a9 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/CommentAddedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/CommentAddedListener.java
@@ -15,7 +15,6 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ApprovalInfo; import java.util.Map; @@ -24,8 +23,6 @@ @ExtensionPoint public interface CommentAddedListener { interface Event extends RevisionEvent { - @Deprecated - AccountInfo getAuthor(); String getComment(); Map<String, ApprovalInfo> getApprovals(); Map<String, ApprovalInfo> getOldApprovals();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/DraftPublishedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/DraftPublishedListener.java index 3857468..1fc574b 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/DraftPublishedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/DraftPublishedListener.java
@@ -15,14 +15,11 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; /** Notified whenever a Draft is published. */ @ExtensionPoint public interface DraftPublishedListener { interface Event extends RevisionEvent { - @Deprecated - AccountInfo getPublisher(); } void onDraftPublished(Event event);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HashtagsEditedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HashtagsEditedListener.java index c49b0f3..ad13267 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HashtagsEditedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HashtagsEditedListener.java
@@ -15,7 +15,6 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; import java.util.Collection; @@ -23,8 +22,6 @@ @ExtensionPoint public interface HashtagsEditedListener { interface Event extends ChangeEvent { - @Deprecated - AccountInfo getEditor(); Collection<String> getHashtags(); Collection<String> getAddedHashtags(); Collection<String> getRemovedHashtags();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java index 3cc3fdc..bb4ac9d 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ReviewerAddedListener.java
@@ -17,12 +17,14 @@ import com.google.gerrit.extensions.annotations.ExtensionPoint; import com.google.gerrit.extensions.common.AccountInfo; -/** Notified whenever a Reviewer is added to a change. */ +import java.util.List; + +/** Notified whenever one or more Reviewers are added to a change. */ @ExtensionPoint public interface ReviewerAddedListener { interface Event extends ChangeEvent { - AccountInfo getReviewer(); + List<AccountInfo> getReviewers(); } - void onReviewerAdded(Event event); + void onReviewersAdded(Event event); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/RevisionCreatedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/RevisionCreatedListener.java index 5e4e095..8d148b7 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/RevisionCreatedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/RevisionCreatedListener.java
@@ -15,14 +15,11 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; /** Notified whenever a Change Revision is created. */ @ExtensionPoint public interface RevisionCreatedListener { interface Event extends RevisionEvent { - @Deprecated - AccountInfo getUploader(); } void onRevisionCreated(Event event);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/TopicEditedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/TopicEditedListener.java index 68ba22c..0c36d9d 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/TopicEditedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/TopicEditedListener.java
@@ -15,14 +15,11 @@ package com.google.gerrit.extensions.events; import com.google.gerrit.extensions.annotations.ExtensionPoint; -import com.google.gerrit.extensions.common.AccountInfo; /** Notified whenever a Change Topic is changed. */ @ExtensionPoint public interface TopicEditedListener { interface Event extends ChangeEvent { - @Deprecated - AccountInfo getEditor(); String getOldTopic(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java index 068d9a0..4fc9ab6 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -86,12 +86,6 @@ } /** Set the character set used to encode text data and return {@code this}. */ - @Deprecated - public BinaryResult setCharacterEncoding(String encoding) { - return setCharacterEncoding(Charset.forName(encoding)); - } - - /** Set the character set used to encode text data and return {@code this}. */ public BinaryResult setCharacterEncoding(Charset encoding) { characterEncoding = encoding; return this; @@ -235,7 +229,7 @@ StringResult(String str) { super(str.getBytes(UTF_8)); setContentType("text/plain"); - setCharacterEncoding(UTF_8.name()); + setCharacterEncoding(UTF_8); this.str = str; }
diff --git a/gerrit-gpg/BUCK b/gerrit-gpg/BUCK index 73d9f04..fe93bf8 100644 --- a/gerrit-gpg/BUCK +++ b/gerrit-gpg/BUCK
@@ -52,6 +52,5 @@ '//lib/bouncycastle:bcprov', '//lib/jgit/org.eclipse.jgit.junit:junit', ], - source_under_test = [':gpg'], visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java index db6cb7a..3bbe6eb 100644 --- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java +++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -19,10 +19,8 @@ import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; -import com.google.common.collect.Ordering; import com.google.common.io.BaseEncoding; import com.google.gerrit.common.PageLinks; import com.google.gerrit.reviewdb.client.AccountExternalId; @@ -274,9 +272,7 @@ private static String missingUserIds(Set<String> allowedUserIds) { StringBuilder sb = new StringBuilder("Key must contain a valid" + " certification for one of the following identities:\n"); - Iterator<String> sorted = FluentIterable.from(allowedUserIds) - .toSortedList(Ordering.natural()) - .iterator(); + Iterator<String> sorted = allowedUserIds.stream().sorted().iterator(); while (sorted.hasNext()) { sb.append(" ").append(sorted.next()); if (sorted.hasNext()) {
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java index 49657c6..ddee18d 100644 --- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java +++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -19,7 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; -import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; @@ -211,14 +210,8 @@ @VisibleForTesting public static FluentIterable<AccountExternalId> getGpgExtIds(ReviewDb db, Account.Id accountId) throws OrmException { - return FluentIterable - .from(db.accountExternalIds().byAccount(accountId)) - .filter(new Predicate<AccountExternalId>() { - @Override - public boolean apply(AccountExternalId in) { - return in.isScheme(SCHEME_GPGKEY); - } - }); + return FluentIterable.from(db.accountExternalIds().byAccount(accountId)) + .filter(in -> in.isScheme(SCHEME_GPGKEY)); } private Iterable<AccountExternalId> getGpgExtIds(AccountResource rsrc)
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 2deae3f..7c5c6ea 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
@@ -18,7 +18,6 @@ import static com.google.gerrit.gpg.PublicKeyStore.keyToString; import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -161,13 +160,8 @@ if (!newExtIds.isEmpty()) { db.get().accountExternalIds().insert(newExtIds); } - db.get().accountExternalIds().deleteKeys(Iterables.transform(toRemove, - new Function<Fingerprint, AccountExternalId.Key>() { - @Override - public AccountExternalId.Key apply(Fingerprint fp) { - return toExtIdKey(fp.get()); - } - })); + db.get().accountExternalIds().deleteKeys( + Iterables.transform(toRemove, fp -> toExtIdKey(fp.get()))); accountCache.evict(rsrc.getUser().getAccountId()); return toJson(newKeys, toRemove, store, rsrc.getUser()); }
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java b/gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java deleted file mode 100644 index 728f276..0000000 --- a/gerrit-gwtdebug/src/main/java/com/google/gwt/dev/codeserver/WebServer.java +++ /dev/null
@@ -1,541 +0,0 @@ -/* - * Copyright 2011 Google Inc. - * - * 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.gwt.dev.codeserver; - -import com.google.gwt.core.ext.TreeLogger; -import com.google.gwt.core.ext.TreeLogger.Type; -import com.google.gwt.core.ext.UnableToCompleteException; -import com.google.gwt.dev.codeserver.CompileDir.PolicyFile; -import com.google.gwt.dev.codeserver.Pages.ErrorPage; -import com.google.gwt.dev.json.JsonObject; - -import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.server.HttpConnection; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.servlets.GzipFilter; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.DispatcherType; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * The web server for Super Dev Mode, also known as the code server. The URLs handled include: - * <ul> - * <li>HTML pages for the front page and module pages</li> - * <li>JavaScript that implementing the bookmarklets</li> - * <li>The web API for recompiling a GWT app</li> - * <li>The output files and log files from the GWT compiler</li> - * <li>Java source code (for source-level debugging)</li> - * </ul> - * - * <p>EXPERIMENTAL. There is no authentication, encryption, or XSS protection, so this server is - * only safe to run on localhost.</p> - */ -// This file was copied from GWT project and adjusted to run against -// Jetty 9.2.2. The original diff can be found here: -// https://gwt-review.googlesource.com/#/c/7857/13/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java -public class WebServer { - - private static final Pattern SAFE_DIRECTORY = - Pattern.compile("([a-zA-Z0-9_-]+\\.)*[a-zA-Z0-9_-]+"); // no extension needed - - private static final Pattern SAFE_FILENAME = - Pattern.compile("([a-zA-Z0-9_-]+\\.)+[a-zA-Z0-9_-]+"); // an extension is required - - private static final Pattern SAFE_MODULE_PATH = - Pattern.compile("/(" + SAFE_DIRECTORY + ")/$"); - - static final Pattern SAFE_DIRECTORY_PATH = - Pattern.compile("/(" + SAFE_DIRECTORY + "/)+$"); - - /* visible for testing */ - static final Pattern SAFE_FILE_PATH = - Pattern.compile("/(" + SAFE_DIRECTORY + "/)+" + SAFE_FILENAME + "$"); - - static final Pattern STRONG_NAME = Pattern.compile("[\\dA-F]{32}"); - - private static final Pattern CACHE_JS_FILE = Pattern.compile("/(" + STRONG_NAME + ").cache.js$"); - - private static final MimeTypes MIME_TYPES = new MimeTypes(); - - private static final String TIME_IN_THE_PAST = "Mon, 01 Jan 1990 00:00:00 GMT"; - - private final SourceHandler handler; - private final JsonExporter jsonExporter; - private final OutboxTable outboxes; - private final JobRunner runner; - private final JobEventTable eventTable; - - private final String bindAddress; - private final int port; - - private Server server; - - WebServer(SourceHandler handler, JsonExporter jsonExporter, OutboxTable outboxes, - JobRunner runner, JobEventTable eventTable, String bindAddress, int port) { - this.handler = handler; - this.jsonExporter = jsonExporter; - this.outboxes = outboxes; - this.runner = runner; - this.eventTable = eventTable; - this.bindAddress = bindAddress; - this.port = port; - } - - @SuppressWarnings("serial") - void start(final TreeLogger logger) throws UnableToCompleteException { - - Server newServer = new Server(); - ServerConnector connector = new ServerConnector(newServer); - connector.setHost(bindAddress); - connector.setPort(port); - connector.setReuseAddress(false); - connector.setSoLingerTime(0); - - newServer.addConnector(connector); - - ServletContextHandler newHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); - newHandler.setContextPath("/"); - newHandler.addServlet(new ServletHolder(new HttpServlet() { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - handleRequest(request.getPathInfo(), request, response, logger); - } - }), "/*"); - newHandler.addFilter(GzipFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); - newServer.setHandler(newHandler); - try { - newServer.start(); - } catch (Exception e) { - logger.log(TreeLogger.ERROR, "cannot start web server", e); - throw new UnableToCompleteException(); - } - this.server = newServer; - } - - public int getPort() { - return port; - } - - public void stop() throws Exception { - server.stop(); - server = null; - } - - /** - * Returns the location of the compiler output. (Changes after every recompile.) - * @param outputModuleName the module name that the GWT compiler used in its output. - */ - public File getCurrentWarDir(String outputModuleName) { - return outboxes.findByOutputModuleName(outputModuleName).getWarDir(); - } - - private void handleRequest(String target, HttpServletRequest request, - HttpServletResponse response, TreeLogger parentLogger) - throws IOException { - - if (request.getMethod().equalsIgnoreCase("get")) { - - TreeLogger logger = parentLogger.branch(Type.TRACE, "GET " + target); - - Response page = doGet(target, request, logger); - if (page == null) { - logger.log(Type.WARN, "not handled: " + target); - return; - } - - setHandled(request); - if (!target.endsWith(".cache.js")) { - // Make sure IE9 doesn't cache any pages. - // (Nearly all pages may change on server restart.) - response.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); - response.setHeader("Pragma", "no-cache"); - response.setHeader("Expires", TIME_IN_THE_PAST); - response.setDateHeader("Date", new Date().getTime()); - } - page.send(request, response, logger); - } - } - - /** - * Returns the page that should be sent in response to a GET request, or null for no response. - */ - private Response doGet(String target, HttpServletRequest request, TreeLogger logger) - throws IOException { - - if (target.equals("/")) { - JsonObject json = jsonExporter.exportFrontPageVars(); - return Pages.newHtmlPage("config", json, "frontpage.html"); - } - - if (target.equals("/dev_mode_on.js")) { - JsonObject json = jsonExporter.exportDevModeOnVars(); - return Responses.newJavascriptResponse("__gwt_codeserver_config", json, - "dev_mode_on.js"); - } - - // Recompile on request from the bookmarklet. - // This is a GET because a bookmarklet can call it from a different origin (JSONP). - if (target.startsWith("/recompile/")) { - String moduleName = target.substring("/recompile/".length()); - Outbox box = outboxes.findByOutputModuleName(moduleName); - if (box == null) { - return new ErrorPage("No such module: " + moduleName); - } - - // We are passing properties from an unauthenticated GET request directly to the compiler. - // This should be safe, but only because these are binding properties. For each binding - // property, you can only choose from a set of predefined values. So all an attacker can do is - // cause a spurious recompile, resulting in an unexpected permutation being loaded later. - // - // It would be unsafe to allow a configuration property to be changed. - Job job = box.makeJob(getBindingProperties(request), logger); - runner.submit(job); - Job.Result result = job.waitForResult(); - JsonObject json = jsonExporter.exportRecompileResponse(result); - return Responses.newJsonResponse(json); - } - - if (target.startsWith("/log/")) { - String moduleName = target.substring("/log/".length()); - Outbox box = outboxes.findByOutputModuleName(moduleName); - if (box == null) { - return new ErrorPage("No such module: " + moduleName); - } else if (box.containsStubCompile()) { - return new ErrorPage("This module hasn't been compiled yet."); - } else { - return makeLogPage(box); - } - } - - if (target.equals("/favicon.ico")) { - InputStream faviconStream = getClass().getResourceAsStream("favicon.ico"); - if (faviconStream == null) { - return new ErrorPage("icon not found"); - } - // IE8 will not load the favicon in an img tag with the default MIME type, - // so use "image/x-icon" instead. - return Responses.newBinaryStreamResponse("image/x-icon", faviconStream); - } - - if (target.equals("/policies/")) { - return makePolicyIndexPage(); - } - - if (target.equals("/progress")) { - // TODO: return a list of progress objects here, one for each job. - JobEvent event = eventTable.getCompilingJobEvent(); - - JsonObject json; - if (event == null) { - json = new JsonObject(); - json.put("status", "idle"); - } else { - json = jsonExporter.exportProgressResponse(event); - } - return Responses.newJsonResponse(json); - } - - Matcher matcher = SAFE_MODULE_PATH.matcher(target); - if (matcher.matches()) { - return makeModulePage(matcher.group(1)); - } - - matcher = SAFE_DIRECTORY_PATH.matcher(target); - if (matcher.matches() && SourceHandler.isSourceMapRequest(target)) { - return handler.handle(target, request, logger); - } - - matcher = SAFE_FILE_PATH.matcher(target); - if (matcher.matches()) { - if (SourceHandler.isSourceMapRequest(target)) { - return handler.handle(target, request, logger); - } - if (target.startsWith("/policies/")) { - return makePolicyFilePage(target); - } - return makeCompilerOutputPage(target); - } - - logger.log(TreeLogger.WARN, "ignored get request: " + target); - return null; // not handled - } - - /** - * Returns a file that the compiler wrote to its war directory. - */ - private Response makeCompilerOutputPage(String target) { - - int secondSlash = target.indexOf('/', 1); - String moduleName = target.substring(1, secondSlash); - Outbox box = outboxes.findByOutputModuleName(moduleName); - if (box == null) { - return new ErrorPage("No such module: " + moduleName); - } - - final String contentEncoding; - File file = box.getOutputFile(target); - if (!file.isFile()) { - // perhaps it's compressed - file = box.getOutputFile(target + ".gz"); - if (!file.isFile()) { - return new ErrorPage("not found: " + file.toString()); - } - contentEncoding = "gzip"; - } else { - contentEncoding = null; - } - - final String sourceMapUrl; - Matcher match = CACHE_JS_FILE.matcher(target); - if (match.matches()) { - String strongName = match.group(1); - String template = SourceHandler.sourceMapLocationTemplate(moduleName); - sourceMapUrl = template.replace("__HASH__", strongName); - } else { - sourceMapUrl = null; - } - - String mimeType = guessMimeType(target); - final Response barePage = Responses.newFileResponse(mimeType, file); - - // Wrap the response to send the extra headers. - return new Response() { - @Override - public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger) - throws IOException { - // TODO: why do we need this? Looks like Ray added it a long time ago. - response.setHeader("Access-Control-Allow-Origin", "*"); - - if (sourceMapUrl != null) { - response.setHeader("X-SourceMap", sourceMapUrl); - response.setHeader("SourceMap", sourceMapUrl); - } - - if (contentEncoding != null) { - if (!request.getHeader("Accept-Encoding").contains("gzip")) { - response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); - logger.log(TreeLogger.WARN, "client doesn't accept gzip; bailing"); - return; - } - response.setHeader("Content-Encoding", "gzip"); - } - - barePage.send(request, response, logger); - } - }; - } - - private Response makeModulePage(String moduleName) { - Outbox box = outboxes.findByOutputModuleName(moduleName); - if (box == null) { - return new ErrorPage("No such module: " + moduleName); - } - - JsonObject json = jsonExporter.exportModulePageVars(box); - return Pages.newHtmlPage("config", json, "modulepage.html"); - } - - private Response makePolicyIndexPage() { - - return new Response() { - - @Override - public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger) - throws IOException { - response.setContentType("text/html"); - - HtmlWriter out = new HtmlWriter(response.getWriter()); - - out.startTag("html").nl(); - out.startTag("head").nl(); - out.startTag("title").text("Policy Files").endTag("title").nl(); - out.endTag("head"); - out.startTag("body"); - - out.startTag("h1").text("Policy Files").endTag("h1").nl(); - - for (Outbox box : outboxes.getOutboxes()) { - List<PolicyFile> policies = box.readRpcPolicyManifest(); - if (!policies.isEmpty()) { - out.startTag("h2").text(box.getOutputModuleName()).endTag("h2").nl(); - - out.startTag("table").nl(); - for (PolicyFile policy : policies) { - - out.startTag("tr"); - - out.startTag("td"); - - out.startTag("a", "href=", policy.getServiceSourceUrl()); - out.text(policy.getServiceName()); - out.endTag("a"); - - out.endTag("td"); - - out.startTag("td"); - - out.startTag("a", "href=", policy.getUrl()); - out.text(policy.getName()); - out.endTag("a"); - - out.endTag("td"); - - out.endTag("tr").nl(); - } - out.endTag("table").nl(); - } - } - - out.endTag("body").nl(); - out.endTag("html").nl(); - } - }; - } - - private Response makePolicyFilePage(String target) { - - int secondSlash = target.indexOf('/', 1); - if (secondSlash < 1) { - return new ErrorPage("invalid URL for policy file: " + target); - } - - String rest = target.substring(secondSlash + 1); - if (rest.contains("/") || !rest.endsWith(".gwt.rpc")) { - return new ErrorPage("invalid name for policy file: " + rest); - } - - File fileToSend = outboxes.findPolicyFile(rest); - if (fileToSend == null) { - return new ErrorPage("Policy file not found: " + rest); - } - - return Responses.newFileResponse("text/plain", fileToSend); - } - - /** - * Sends the log file as html with errors highlighted in red. - */ - private Response makeLogPage(final Outbox box) { - final File file = box.getCompileLog(); - if (!file.isFile()) { - return new ErrorPage("log file not found"); - } - - return new Response() { - - @Override - public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger) - throws IOException { - BufferedReader reader = new BufferedReader(new FileReader(file)); - - response.setStatus(HttpServletResponse.SC_OK); - response.setContentType("text/html"); - response.setHeader("Content-Style-Type", "text/css"); - - HtmlWriter out = new HtmlWriter(response.getWriter()); - out.startTag("html").nl(); - out.startTag("head").nl(); - out.startTag("title").text(box.getOutputModuleName() + " compile log").endTag("title").nl(); - out.startTag("style").nl(); - out.text(".error { color: red; font-weight: bold; }").nl(); - out.endTag("style").nl(); - out.endTag("head").nl(); - out.startTag("body").nl(); - sendLogAsHtml(reader, out); - out.endTag("body").nl(); - out.endTag("html").nl(); - } - }; - } - - private static final Pattern ERROR_PATTERN = Pattern.compile("\\[ERROR\\]"); - - /** - * Copies in to out line by line, escaping each line for html characters and highlighting - * error lines. Closes <code>in</code> when done. - */ - private static void sendLogAsHtml(BufferedReader in, HtmlWriter out) throws IOException { - try { - out.startTag("pre").nl(); - String line = in.readLine(); - while (line != null) { - Matcher m = ERROR_PATTERN.matcher(line); - boolean error = m.find(); - if (error) { - out.startTag("span", "class=", "error"); - } - out.text(line); - if (error) { - out.endTag("span"); - } - out.nl(); // the readLine doesn't include the newline. - line = in.readLine(); - } - out.endTag("pre").nl(); - } finally { - in.close(); - } - } - - /* visible for testing */ - static String guessMimeType(String filename) { - String mimeType = MIME_TYPES.getMimeByExtension(filename); - return mimeType != null ? mimeType : ""; - } - - /** - * Returns the binding properties from the web page where dev mode is being used. (As passed in - * by dev_mode_on.js in a JSONP request to "/recompile".) - */ - private Map<String, String> getBindingProperties(HttpServletRequest request) { - Map<String, String> result = new HashMap<>(); - for (Object key : request.getParameterMap().keySet()) { - String propName = (String) key; - if (!propName.equals("_callback")) { - result.put(propName, request.getParameter(propName)); - } - } - return result; - } - - private static void setHandled(HttpServletRequest request) { - Request baseRequest = (request instanceof Request) ? (Request) request : - HttpConnection.getCurrentConnection().getHttpChannel().getRequest(); - baseRequest.setHandled(true); - } -}
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK index 79a97a9..23db13f 100644 --- a/gerrit-gwtexpui/BUCK +++ b/gerrit-gwtexpui/BUCK
@@ -80,7 +80,6 @@ '//lib/gwt:user', '//lib/gwt:dev', ], - source_under_test = [':SafeHtml'], ) gwt_module(
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK index ef78d98..d4d97a6 100644 --- a/gerrit-gwtui-common/BUCK +++ b/gerrit-gwtui-common/BUCK
@@ -66,7 +66,6 @@ '//lib/gwt:user', '//lib/jgit/org.eclipse.jgit:jgit', ], - source_under_test = [':client'], vm_args = ['-Xmx512m'], visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-gwtui-common/BUILD b/gerrit-gwtui-common/BUILD new file mode 100644 index 0000000..01a82af --- /dev/null +++ b/gerrit-gwtui-common/BUILD
@@ -0,0 +1,69 @@ +load('//tools/bzl:genrule2.bzl', 'genrule2') +load('//tools/bzl:java.bzl', 'java_library2') +load('//tools/bzl:junit.bzl', 'junit_tests') +load('//tools/bzl:gwt.bzl', 'gwt_module') + +EXPORTED_DEPS = [ + '//gerrit-common:client', + '//gerrit-gwtexpui:Clippy', + '//gerrit-gwtexpui:GlobalKey', + '//gerrit-gwtexpui:Progress', + '//gerrit-gwtexpui:SafeHtml', + '//gerrit-gwtexpui:UserAgent', +] +DEPS = ['//lib/gwt:user'] +SRC = 'src/main/java/com/google/gerrit/' +DIFFY = glob(['src/main/resources/com/google/gerrit/client/diffy*.png']) + +gwt_module( + name = 'client', + srcs = glob(['src/main/**/*.java']), + gwt_xml = SRC + 'GerritGwtUICommon.gwt.xml', + resources = glob( + ['src/main/**/*'], + exclude = [SRC + 'client/**/*.java'] + + [SRC + 'GerritGwtUICommon.gwt.xml'] + ), + exported_deps = EXPORTED_DEPS, + deps = DEPS, + visibility = ['//visibility:public'], +) + +java_library2( + name = 'client-lib', + srcs = glob(['src/main/**/*.java']), + resources = glob(['src/main/**/*']), + exported_deps = EXPORTED_DEPS, + deps = DEPS, + visibility = ['//visibility:public'], +) + +java_import( + name = 'diffy_logo', + jars = [':diffy_image_files_ln'], + visibility = ['//visibility:public'], +) + +genrule2( + name = 'diffy_image_files_ln', + srcs = [':diffy_image_files'], + cmd = 'ln -s $$ROOT/$(location :diffy_image_files) $@', + out = 'diffy_images.jar', +) + +java_library( + name = 'diffy_image_files', + resources = DIFFY, +) + +junit_tests( + name = 'client_tests', + srcs = glob(['src/test/java/**/*.java']), + deps = [ + ':client', + '//lib:junit', + '//lib/gwt:dev', + '//lib/jgit/org.eclipse.jgit:jgit', + ], + visibility = ['//visibility:public'], +)
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java index 0a339a1..eb10718 100644 --- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java
@@ -22,6 +22,7 @@ CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK, CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK, CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK, + CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS, /* MyPasswordScreen */ PASSWORD_SCREEN_BOTTOM,
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AgreementInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AgreementInfo.java new file mode 100644 index 0000000..5fb2f48 --- /dev/null +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AgreementInfo.java
@@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.client.info; + +import com.google.gwt.core.client.JavaScriptObject; + +public class AgreementInfo extends JavaScriptObject { + public final native String name() /*-{ return this.name; }-*/; + public final native String description() /*-{ return this.description; }-*/; + public final native String url() /*-{ return this.url; }-*/; + public final native GroupInfo autoVerifyGroup() /*-{ return this.auto_verify_group; }-*/; + + protected AgreementInfo() { + } +}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java index 0e3c32b..8669dd5 100644 --- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
@@ -15,10 +15,10 @@ package com.google.gerrit.client.info; import com.google.gerrit.client.rpc.Natives; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Account.FieldName; -import com.google.gerrit.reviewdb.client.AuthType; +import com.google.gerrit.extensions.client.AccountFieldName; +import com.google.gerrit.extensions.client.AuthType; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayString; import java.util.ArrayList; @@ -52,22 +52,30 @@ return authType() == AuthType.CUSTOM_EXTENSION; } - public final boolean canEdit(Account.FieldName f) { + public final boolean canEdit(AccountFieldName f) { return editableAccountFields().contains(f); } - public final List<Account.FieldName> editableAccountFields() { - List<Account.FieldName> fields = new ArrayList<>(); + public final List<AccountFieldName> editableAccountFields() { + List<AccountFieldName> fields = new ArrayList<>(); for (String f : Natives.asList(_editableAccountFields())) { - fields.add(Account.FieldName.valueOf(f)); + fields.add(AccountFieldName.valueOf(f)); } return fields; } + public final List<AgreementInfo> contributorAgreements() { + List<AgreementInfo> agreements = new ArrayList<>(); + for (AgreementInfo a : Natives.asList(_contributorAgreements())) { + agreements.add(a); + } + return agreements; + } + public final boolean siteHasUsernames() { if (isCustomExtension() && httpPasswordUrl() != null - && !canEdit(FieldName.USER_NAME)) { + && !canEdit(AccountFieldName.USER_NAME)) { return false; } return true; @@ -93,6 +101,8 @@ private native String authTypeRaw() /*-{ return this.auth_type; }-*/; private native JsArrayString _editableAccountFields() /*-{ return this.editable_account_fields; }-*/; + private native JsArray<AgreementInfo> _contributorAgreements() + /*-{ return this.contributor_agreements; }-*/; protected AuthInfo() { }
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java index 9b290a5..b21126d 100644 --- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
@@ -56,6 +56,12 @@ } else if (Patch.COMMIT_MSG.equals(b.path())) { return 1; } + if (Patch.MERGE_LIST.equals(a.path())) { + return -1; + } else if (Patch.MERGE_LIST.equals(b.path())) { + return 1; + } + // Look at file suffixes to check if it makes sense to use a different order int s1 = a.path().lastIndexOf('.'); int s2 = b.path().lastIndexOf('.'); @@ -76,9 +82,15 @@ } public static String getFileName(String path) { - String fileName = Patch.COMMIT_MSG.equals(path) - ? "Commit Message" - : path; + String fileName; + if (Patch.COMMIT_MSG.equals(path)) { + fileName = "Commit Message"; + } else if (Patch.MERGE_LIST.equals(path)) { + fileName = "Merge List"; + } else { + fileName = path; + } + int s = fileName.lastIndexOf('/'); return s >= 0 ? fileName.substring(s + 1) : fileName; }
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java index 45953cb..9e25f25 100644 --- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GeneralPreferences.java
@@ -19,6 +19,7 @@ import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat; +import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand; import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy; @@ -55,6 +56,7 @@ p.reviewCategoryStrategy(d.getReviewCategoryStrategy()); p.diffView(d.getDiffView()); p.emailStrategy(d.emailStrategy); + p.defaultBaseForMerges(d.defaultBaseForMerges); return p; } @@ -135,6 +137,14 @@ private native String emailStrategyRaw() /*-{ return this.email_strategy }-*/; + public final DefaultBase defaultBaseForMerges() { + String s = defaultBaseForMergesRaw(); + return s != null ? DefaultBase.valueOf(s) : null; + } + + private native String defaultBaseForMergesRaw() + /*-{ return this.default_base_for_merges }-*/; + public final native JsArray<TopMenuItem> my() /*-{ return this.my; }-*/; @@ -201,6 +211,12 @@ private native void emailStrategyRaw(String s) /*-{ this.email_strategy = s }-*/; + public final void defaultBaseForMerges(DefaultBase b) { + defaultBaseForMergesRaw(b != null ? b.toString() : null); + } + private native void defaultBaseForMergesRaw(String b) + /*-{ this.default_base_for_merges = b }-*/; + public final void setMyMenus(List<TopMenuItem> myMenus) { initMy(); for (TopMenuItem n : myMenus) {
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java index 750412d..a111896 100644 --- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
@@ -14,8 +14,13 @@ package com.google.gerrit.client.info; +import com.google.gerrit.extensions.client.UiType; import com.google.gerrit.reviewdb.client.Project; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; + +import java.util.ArrayList; +import java.util.List; public class GerritInfo extends JavaScriptObject { public final Project.NameKey allProjectsNameKey() { @@ -42,6 +47,19 @@ public final native String reportBugUrl() /*-{ return this.report_bug_url; }-*/; public final native String reportBugText() /*-{ return this.report_bug_text; }-*/; + private native JsArrayString _webUis() /*-{ return this.web_uis; }-*/; + public final List<UiType> webUis() { + JsArrayString webUis = _webUis(); + List<UiType> result = new ArrayList<>(webUis.length()); + for (int i = 0; i < webUis.length(); i++) { + UiType t = UiType.parse(webUis.get(i)); + if (t != null) { + result.add(t); + } + } + return result; + } + protected GerritInfo() { } }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupBaseInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GroupBaseInfo.java similarity index 95% rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupBaseInfo.java rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GroupBaseInfo.java index 4811e59..deed44d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupBaseInfo.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GroupBaseInfo.java
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.client.groups; +package com.google.gerrit.client.info; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gwt.core.client.JavaScriptObject;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GroupInfo.java similarity index 95% rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GroupInfo.java index c3fd4ed..fa051a1 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java +++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GroupInfo.java
@@ -12,9 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.client.groups; +package com.google.gerrit.client.info; -import com.google.gerrit.client.info.AccountInfo; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK index 1e39831..63d52b0 100644 --- a/gerrit-gwtui/BUCK +++ b/gerrit-gwtui/BUCK
@@ -61,7 +61,6 @@ '//lib/gwt:dev', '//lib/gwt:user', ], - source_under_test = [':ui_module'], vm_args = ['-Xmx512m'], visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-gwtui/BUILD b/gerrit-gwtui/BUILD new file mode 100644 index 0000000..dbf02e8 --- /dev/null +++ b/gerrit-gwtui/BUILD
@@ -0,0 +1,89 @@ +load('//tools/bzl:gwt.bzl', 'gwt_module') +load('//tools/bzl:genrule2.bzl', 'genrule2') +load(':gwt.bzl', 'gwt_binary') + +MODULE = 'com.google.gerrit.GerritGwtUI' + +GWT_JVM_ARGS = ['-Xmx512m'] + +GWT_COMPILER_ARGS = [ + '-XdisableClassMetadata', +] + +GWT_COMPILER_ARGS_RELEASE_MODE = GWT_COMPILER_ARGS + [ + '-XdisableCastChecking', +] + +GWT_TRANSITIVE_DEPS = [ + '//lib/gwt:javax-validation', + '//lib/gwt:javax-validation_src', + '//lib/ow2:ow2-asm', + '//lib/ow2:ow2-asm-analysis', + '//lib/ow2:ow2-asm-commons', + '//lib/ow2:ow2-asm-tree', + '//lib/ow2:ow2-asm-util', +] + +DEPS = GWT_TRANSITIVE_DEPS + [ + '//gerrit-gwtexpui:CSS', + '//lib:gwtjsonrpc', + '//lib/gwt:dev', + '@jgit_src//file', +] + +gwt_module( + name = 'ui_module', + srcs = glob(['src/main/java/**/*.java']), + gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'), + resources = glob( + ['src/main/java/**/*'], + exclude = ['src/main/java/**/*.java'] + + ['src/main/java/%s.gwt.xml' % MODULE.replace('.', '/')] + ), + deps = [ + '//gerrit-gwtui-common:diffy_logo', + '//gerrit-gwtui-common:client', + '//gerrit-gwtexpui:CSS', + '//lib/codemirror:codemirror', + '//lib/gwt:user', + ], + visibility = ['//visibility:public'], +) + +genrule2( + name = 'ui_optdbg', + srcs = [ + ':ui_dbg', + ':ui_opt', + ], + cmd = 'cd $$TMP;' + + 'unzip -q $$ROOT/$(location :ui_dbg);' + + 'mv' + + ' gerrit_ui/gerrit_ui.nocache.js' + + ' gerrit_ui/dbg_gerrit_ui.nocache.js;' + + 'unzip -qo $$ROOT/$(location :ui_opt);' + + 'mkdir -p $$(dirname $@);' + + 'zip -qr $$ROOT/$@ .', + out = 'ui_optdbg.zip', + visibility = ['//visibility:public'], +) + +gwt_binary( + name = 'ui_opt', + modules = [MODULE], + module_deps = [':ui_module'], + deps = DEPS, + compiler_args = GWT_COMPILER_ARGS, + jvm_args = GWT_JVM_ARGS, +) + +gwt_binary( + name = 'ui_dbg', + modules = [MODULE], + style = 'PRETTY', + optimize = "0", + module_deps = [':ui_module'], + deps = DEPS, + compiler_args = GWT_COMPILER_ARGS, + jvm_args = GWT_JVM_ARGS, +)
diff --git a/gerrit-gwtui/gwt.bzl b/gerrit-gwtui/gwt.bzl new file mode 100644 index 0000000..91936de --- /dev/null +++ b/gerrit-gwtui/gwt.bzl
@@ -0,0 +1,95 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Port of Buck native gwt_binary() rule. See discussion in context of +# https://github.com/facebook/buck/issues/109 + +jar_filetype = FileType(['.jar']) +GWT_COMPILER = "com.google.gwt.dev.Compiler" + +def _impl(ctx): + output_zip = ctx.outputs.output + output_dir = output_zip.path + '.gwt_output' + deploy_dir = output_zip.path + '.gwt_deploy' + + deps = _get_transitive_closure(ctx) + + paths = [] + for dep in deps: + paths.append(dep.path) + + cmd = "external/local_jdk/bin/java %s -Dgwt.normalizeTimestamps=true -cp %s %s -war %s -deploy %s " % ( + " ".join(ctx.attr.jvm_args), + ":".join(paths), + GWT_COMPILER, + output_dir, + deploy_dir, + ) + cmd += " ".join([ + "-style %s" % ctx.attr.style, + "-optimize %s" % ctx.attr.optimize, + "-strict", + " ".join(ctx.attr.compiler_args), + " ".join(ctx.attr.modules) + "\n", + "rm -rf %s/gwt-unitCache\n" % output_dir, + "root=`pwd`\n", + "cd %s; $root/%s Cc ../%s $(find .)\n" % ( + output_dir, + ctx.executable._zip.path, + output_zip.basename, + ) + ]) + + ctx.action( + inputs = list(deps) + ctx.files._jdk + ctx.files._zip, + outputs = [output_zip], + mnemonic = "GwtBinary", + progress_message = "GWT compiling " + output_zip.short_path, + command = "set -e\n" + cmd, + ) + +def _get_transitive_closure(ctx): + deps = set() + for dep in ctx.attr.module_deps: + deps += dep.java.transitive_runtime_deps + deps += dep.java.transitive_source_jars + for dep in ctx.attr.deps: + if hasattr(dep, 'java'): + deps += dep.java.transitive_runtime_deps + elif hasattr(dep, 'files'): + deps += dep.files + + return deps + +gwt_binary = rule( + implementation = _impl, + attrs = { + "style": attr.string(default = "OBF"), + "optimize": attr.string(default = "9"), + "deps": attr.label_list(allow_files=jar_filetype), + "modules": attr.string_list(mandatory=True), + "module_deps": attr.label_list(allow_files=jar_filetype), + "compiler_args": attr.string_list(), + "jvm_args": attr.string_list(), + "_jdk": attr.label( + default=Label("//tools/defaults:jdk")), + "_zip": attr.label( + default=Label("@bazel_tools//tools/zip:zipper"), + executable=True, + single_file=True), + }, + outputs = { + "output": "%{name}.zip", + }, +)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java index b7405c7..cb2ac07 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -89,7 +89,7 @@ import com.google.gerrit.client.documentation.DocScreen; import com.google.gerrit.client.editor.EditScreen; import com.google.gerrit.client.groups.GroupApi; -import com.google.gerrit.client.groups.GroupInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.ui.Screen; @@ -479,14 +479,18 @@ if (preferUnified()) { unified(token, baseId, id, side, line); } else { - codemirror(token, baseId, id, side, line, false); + codemirror(token, baseId, id, side, line); } } else if ("sidebyside".equals(panel)) { - codemirror(token, baseId, id, side, line, false); + codemirror(token, baseId, id, side, line); } else if ("unified".equals(panel)) { unified(token, baseId, id, side, line); } else if ("edit".equals(panel)) { - codemirror(token, null, id, side, line, true); + if (!Patch.isMagic(id.get()) || Patch.COMMIT_MSG.equals(id.get())) { + codemirrorForEdit(token, id, line); + } else { + Gerrit.display(token, new NotFoundScreen()); + } } else { Gerrit.display(token, new NotFoundScreen()); } @@ -509,14 +513,22 @@ } private static void codemirror(final String token, final PatchSet.Id baseId, - final Patch.Key id, final DisplaySide side, final int line, - final boolean edit) { + final Patch.Key id, final DisplaySide side, final int line) { GWT.runAsync(new AsyncSplit(token) { @Override public void onSuccess() { - Gerrit.display(token, edit - ? new EditScreen(baseId, id, line) - : new SideBySide(baseId, id.getParentKey(), id.get(), side, line)); + Gerrit.display(token, + new SideBySide(baseId, id.getParentKey(), id.get(), side, line)); + } + }); + } + + private static void codemirrorForEdit(final String token, final Patch.Key id, + final int line) { + GWT.runAsync(new AsyncSplit(token) { + @Override + public void onSuccess() { + Gerrit.display(token, new EditScreen(id, line)); } }); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java index dd1505c..3f0daa2 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -17,7 +17,6 @@ import com.google.gerrit.client.change.Resources; import com.google.gerrit.client.info.AccountInfo; import com.google.gerrit.client.info.GeneralPreferences; -import com.google.gerrit.reviewdb.client.Account; import com.google.gwt.i18n.client.NumberFormat; import java.util.Date; @@ -84,17 +83,6 @@ return createAccountFormatter().name(info); } - public static AccountInfo asInfo(Account acct) { - if (acct == null) { - return AccountInfo.create(0, null, null, null); - } - return AccountInfo.create( - acct.getId() != null ? acct.getId().get() : 0, - acct.getFullName(), - acct.getPreferredEmail(), - acct.getUserName()); - } - public static AccountInfo asInfo(com.google.gerrit.common.data.AccountInfo acct) { if (acct == null) { return AccountInfo.create(0, null, null, null);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java index d280e07..93246cb 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -50,6 +50,7 @@ import com.google.gerrit.extensions.client.DiffPreferencesInfo; import com.google.gerrit.extensions.client.EditPreferencesInfo; import com.google.gerrit.extensions.client.GerritTopMenu; +import com.google.gerrit.extensions.client.UiType; import com.google.gerrit.reviewdb.client.Project; import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.EntryPoint; @@ -537,6 +538,14 @@ btmmenu.add(new InlineHTML(M.poweredBy(vs))); + if (info().gerrit().webUis().contains(UiType.POLYGERRIT)) { + btmmenu.add(new InlineLabel(" | ")); + Anchor a = new Anchor( + C.polyGerrit(), GWT.getHostPageBaseURL() + "?polygerrit=1"); + a.setStyleName(""); + btmmenu.add(a); + } + String reportBugUrl = info().gerrit().reportBugUrl(); if (reportBugUrl != null) { String reportBugText = info().gerrit().reportBugText();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java index 4c8c58d..53d9260 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -130,4 +130,6 @@ String searchDropdownChanges(); String searchDropdownDoc(); + + String polyGerrit(); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties index 10d7e1d..d50ab34 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -113,3 +113,5 @@ searchDropdownChanges = Changes searchDropdownDoc = Docs + +polyGerrit = PolyGerrit
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java index acd2e78..9aca859 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -16,6 +16,7 @@ import com.google.gerrit.client.VoidResult; import com.google.gerrit.client.info.AccountInfo; +import com.google.gerrit.client.info.AgreementInfo; import com.google.gerrit.client.info.GpgKeyInfo; import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.NativeMap; @@ -83,6 +84,14 @@ new RestApi("/accounts/").id(account).view("name").get(cb); } + /** Set the account name */ + public static void setName(String account, String name, + AsyncCallback<NativeString> cb) { + AccountNameInput input = AccountNameInput.create(); + input.name(name); + new RestApi("/accounts/").id(account).view("name").put(input, cb); + } + /** Retrieve email addresses */ public static void getEmails(String account, AsyncCallback<JsArray<EmailInfo>> cb) { @@ -97,6 +106,13 @@ .ifNoneMatch().put(in, cb); } + /** Set preferred email address */ + public static void setPreferredEmail(String account, String email, + AsyncCallback<NativeString> cb) { + new RestApi("/accounts/").id(account).view("emails") + .id(email).view("preferred").put(cb); + } + /** Retrieve SSH keys */ public static void getSshKeys(String account, AsyncCallback<JsArray<SshKeyInfo>> cb) { @@ -196,6 +212,14 @@ new RestApi("/accounts/").id(account).view("password.http").delete(cb); } + /** Enter a contributor agreement */ + public static void enterAgreement(String account, String name, + AsyncCallback<NativeString> cb) { + AgreementInput in = AgreementInput.create(); + in.name(name); + new RestApi("/accounts/").id(account).view("agreements").put(in, cb); + } + private static JsArray<ProjectWatchInfo> projectWatchArrayFromSet( Set<ProjectWatchInfo> set) { JsArray<ProjectWatchInfo> jsArray = JsArray.createArray().cast(); @@ -205,6 +229,17 @@ return jsArray; } + private static class AgreementInput extends JavaScriptObject { + final native void name(String n) /*-{ if(n)this.name=n; }-*/; + + static AgreementInput create() { + return createObject().cast(); + } + + protected AgreementInput() { + } + } + private static class HttpPasswordInput extends JavaScriptObject { final native void generate(boolean g) /*-{ if(g)this.generate=g; }-*/; @@ -227,6 +262,17 @@ } } + private static class AccountNameInput extends JavaScriptObject { + final native void name(String n) /*-{ if(n)this.name=n; }-*/; + + static AccountNameInput create() { + return createObject().cast(); + } + + protected AccountNameInput() { + } + } + public static void addGpgKey(String account, String armored, AsyncCallback<NativeMap<GpgKeyInfo>> cb) { new RestApi("/accounts/") @@ -243,6 +289,12 @@ .post(GpgKeysInput.delete(fingerprints), cb); } + /** List contributor agreements */ + public static void getAgreements(String account, + AsyncCallback<JsArray<AgreementInfo>> cb) { + new RestApi("/accounts/").id(account).view("agreements").get(cb); + } + private static class GpgKeysInput extends JavaScriptObject { static GpgKeysInput add(String key) { return createWithAdd(Natives.arrayOf(key));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java index a084612..c58d6b5 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -141,11 +141,8 @@ String errorDialogTitleRegisterNewEmail(); String newAgreement(); - String agreementStatus(); String agreementName(); String agreementDescription(); - String agreementStatus_EXPIRED(); - String agreementStatus_VERIFIED(); String newAgreementSelectTypeHeading(); String newAgreementNoneAvailable(); @@ -171,4 +168,8 @@ String messageCCMeOnMyComments(); String messageDisabled(); String emailFieldLabel(); + + String defaultBaseForMerges(); + String autoMerge(); + String firstParent(); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties index ca2d316..cc15aae 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -19,6 +19,10 @@ messageCCMeOnMyComments = CC Me On Comments I Write messageDisabled = Disabled +defaultBaseForMerges = Default Base For Merges: +autoMerge = Auto Merge +firstParent = First Parent + maximumPageSizeFieldLabel = Maximum Page Size: diffViewLabel = Diff View: dateFormatLabel = Date/Time Format: @@ -151,10 +155,7 @@ newAgreement = New Contributor Agreement -agreementStatus = Status agreementName = Name -agreementStatus_EXPIRED = Expired -agreementStatus_VERIFIED = Verified agreementDescription = Description newAgreementSelectTypeHeading = Select an agreement type:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java index ae3599d..f5f38fb 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
@@ -15,7 +15,6 @@ package com.google.gerrit.client.account; import com.google.gerrit.client.ErrorDialog; -import com.google.gerrit.client.FormatUtil; import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.info.AccountInfo; import com.google.gerrit.client.rpc.CallbackGroup; @@ -25,8 +24,7 @@ import com.google.gerrit.client.ui.OnEditEnabler; import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.errors.EmailException; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Account.FieldName; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gwt.core.client.JsArray; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; @@ -46,7 +44,6 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwtexpui.globalkey.client.NpTextBox; import com.google.gwtexpui.user.client.AutoCenterDialogBox; -import com.google.gwtjsonrpc.common.AsyncCallback; class ContactPanelShort extends Composite { protected final FlowPanel body; @@ -104,7 +101,7 @@ } int row = 0; - if (!Gerrit.info().auth().canEdit(FieldName.USER_NAME) + if (!Gerrit.info().auth().canEdit(AccountFieldName.USER_NAME) && Gerrit.info().auth().siteHasUsernames()) { infoPlainText.resizeRows(infoPlainText.getRowCount() + 1); row(infoPlainText, row++, Util.C.userName(), new UsernameField()); @@ -146,7 +143,7 @@ save.addClickHandler(new ClickHandler() { @Override public void onClick(final ClickEvent event) { - doSave(null); + doSave(); } }); @@ -173,11 +170,11 @@ } private boolean canEditFullName() { - return Gerrit.info().auth().canEdit(Account.FieldName.FULL_NAME); + return Gerrit.info().auth().canEdit(AccountFieldName.FULL_NAME); } private boolean canRegisterNewEmail() { - return Gerrit.info().auth().canEdit(Account.FieldName.REGISTER_NEW_EMAIL); + return Gerrit.info().auth().canEdit(AccountFieldName.REGISTER_NEW_EMAIL); } void hideSaveButton() { @@ -347,10 +344,13 @@ inEmail.setFocus(true); } - void doSave(final AsyncCallback<Account> onSave) { - String newName = canEditFullName() ? nameTxt.getText() : null; - if (newName != null && newName.trim().isEmpty()) { + void doSave() { + final String newName; + String name = canEditFullName() ? nameTxt.getText() : null; + if (name != null && name.trim().isEmpty()) { newName = null; + } else { + newName = name; } final String newEmail; @@ -368,24 +368,40 @@ save.setEnabled(false); registerNewEmail.setEnabled(false); - Util.ACCOUNT_SEC.updateContact(newName, newEmail, - new GerritCallback<Account>() { - @Override - public void onSuccess(Account result) { - registerNewEmail.setEnabled(true); - onSaveSuccess(FormatUtil.asInfo(result)); - if (onSave != null) { - onSave.onSuccess(result); - } - } + CallbackGroup group = new CallbackGroup(); + if (!newEmail.equals(currentEmail)) { + AccountApi.setPreferredEmail("self", newEmail, + group.add(new GerritCallback<NativeString>() { + @Override + public void onSuccess(NativeString result) { + } + })); + } + AccountApi.setName("self", newName, + group.add(new GerritCallback<NativeString>() { + @Override + public void onSuccess(NativeString result) { + } - @Override - public void onFailure(final Throwable caught) { - save.setEnabled(true); - registerNewEmail.setEnabled(true); - super.onFailure(caught); - } - }); + @Override + public void onFailure(Throwable caught) { + save.setEnabled(true); + registerNewEmail.setEnabled(true); + super.onFailure(caught); + } + })); + group.done(); + group.addListener(new GerritCallback<Void>() { + @Override + public void onSuccess(Void result) { + currentEmail = newEmail; + AccountInfo me = Gerrit.getUserAccount(); + me.email(currentEmail); + me.name(newName); + onSaveSuccess(me); + registerNewEmail.setEnabled(true); + } + }); } void onSaveSuccess(AccountInfo result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java index 7c707b2..423d05f 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
@@ -129,24 +129,24 @@ public final native void lineLength(int c) /*-{ this.line_length = c }-*/; public final native void context(int c) /*-{ this.context = c }-*/; public final native void cursorBlinkRate(int r) /*-{ this.cursor_blink_rate = r }-*/; - public final native void intralineDifference(boolean i) /*-{ this.intraline_difference = i }-*/; - public final native void showLineEndings(boolean s) /*-{ this.show_line_endings = s }-*/; - public final native void showTabs(boolean s) /*-{ this.show_tabs = s }-*/; - public final native void showWhitespaceErrors(boolean s) /*-{ this.show_whitespace_errors = s }-*/; - public final native void syntaxHighlighting(boolean s) /*-{ this.syntax_highlighting = s }-*/; - public final native void hideTopMenu(boolean s) /*-{ this.hide_top_menu = s }-*/; - public final native void autoHideDiffTableHeader(boolean s) /*-{ this.auto_hide_diff_table_header = s }-*/; - public final native void hideLineNumbers(boolean s) /*-{ this.hide_line_numbers = s }-*/; - public final native void expandAllComments(boolean e) /*-{ this.expand_all_comments = e }-*/; - public final native void manualReview(boolean r) /*-{ this.manual_review = r }-*/; - public final native void renderEntireFile(boolean r) /*-{ this.render_entire_file = r }-*/; - public final native void retainHeader(boolean r) /*-{ this.retain_header = r }-*/; - public final native void hideEmptyPane(boolean s) /*-{ this.hide_empty_pane = s }-*/; - public final native void skipUnchanged(boolean s) /*-{ this.skip_unchanged = s }-*/; - public final native void skipUncommented(boolean s) /*-{ this.skip_uncommented = s }-*/; - public final native void skipDeleted(boolean s) /*-{ this.skip_deleted = s }-*/; - public final native void matchBrackets(boolean m) /*-{ this.match_brackets = m }-*/; - public final native void lineWrapping(boolean w) /*-{ this.line_wrapping = w }-*/; + public final native void intralineDifference(Boolean i) /*-{ this.intraline_difference = i }-*/; + public final native void showLineEndings(Boolean s) /*-{ this.show_line_endings = s }-*/; + public final native void showTabs(Boolean s) /*-{ this.show_tabs = s }-*/; + public final native void showWhitespaceErrors(Boolean s) /*-{ this.show_whitespace_errors = s }-*/; + public final native void syntaxHighlighting(Boolean s) /*-{ this.syntax_highlighting = s }-*/; + public final native void hideTopMenu(Boolean s) /*-{ this.hide_top_menu = s }-*/; + public final native void autoHideDiffTableHeader(Boolean s) /*-{ this.auto_hide_diff_table_header = s }-*/; + public final native void hideLineNumbers(Boolean s) /*-{ this.hide_line_numbers = s }-*/; + public final native void expandAllComments(Boolean e) /*-{ this.expand_all_comments = e }-*/; + public final native void manualReview(Boolean r) /*-{ this.manual_review = r }-*/; + public final native void renderEntireFile(Boolean r) /*-{ this.render_entire_file = r }-*/; + public final native void retainHeader(Boolean r) /*-{ this.retain_header = r }-*/; + public final native void hideEmptyPane(Boolean s) /*-{ this.hide_empty_pane = s }-*/; + public final native void skipUnchanged(Boolean s) /*-{ this.skip_unchanged = s }-*/; + public final native void skipUncommented(Boolean s) /*-{ this.skip_uncommented = s }-*/; + public final native void skipDeleted(Boolean s) /*-{ this.skip_deleted = s }-*/; + public final native void matchBrackets(Boolean m) /*-{ this.match_brackets = m }-*/; + public final native void lineWrapping(Boolean w) /*-{ this.line_wrapping = w }-*/; public final native boolean intralineDifference() /*-{ return this.intraline_difference || false }-*/; public final native boolean showLineEndings() /*-{ return this.show_line_endings || false }-*/; public final native boolean showTabs() /*-{ return this.show_tabs || false }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java index 308cf30..cd7c141 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
@@ -15,15 +15,19 @@ package com.google.gerrit.client.account; import com.google.gerrit.client.Gerrit; +import com.google.gerrit.client.info.AgreementInfo; +import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.rpc.ScreenLoadCallback; import com.google.gerrit.client.ui.FancyFlexTable; import com.google.gerrit.client.ui.Hyperlink; import com.google.gerrit.common.PageLinks; -import com.google.gerrit.common.data.AgreementInfo; import com.google.gerrit.common.data.ContributorAgreement; +import com.google.gwt.core.client.JsArray; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; +import java.util.List; + public class MyAgreementsScreen extends SettingsScreen { private AgreementTable agreements; @@ -39,10 +43,11 @@ @Override protected void onLoad() { super.onLoad(); - Util.ACCOUNT_SVC.myAgreements(new ScreenLoadCallback<AgreementInfo>(this) { + AccountApi.getAgreements( + "self", new ScreenLoadCallback<JsArray<AgreementInfo>>(this) { @Override - public void preDisplay(final AgreementInfo result) { - agreements.display(result); + public void preDisplay(JsArray<AgreementInfo> result) { + agreements.display(Natives.asList(result)); } }); } @@ -50,60 +55,43 @@ private static class AgreementTable extends FancyFlexTable<ContributorAgreement> { AgreementTable() { table.setWidth(""); - table.setText(0, 1, Util.C.agreementStatus()); - table.setText(0, 2, Util.C.agreementName()); - table.setText(0, 3, Util.C.agreementDescription()); + table.setText(0, 1, Util.C.agreementName()); + table.setText(0, 2, Util.C.agreementDescription()); - final FlexCellFormatter fmt = table.getFlexCellFormatter(); - for (int c = 1; c < 4; c++) { + FlexCellFormatter fmt = table.getFlexCellFormatter(); + for (int c = 1; c < 3; c++) { fmt.addStyleName(0, c, Gerrit.RESOURCES.css().dataHeader()); } } - void display(final AgreementInfo result) { + void display(List<AgreementInfo> result) { while (1 < table.getRowCount()) { table.removeRow(table.getRowCount() - 1); } - for (final String k : result.accepted) { - addOne(result, k); + for (AgreementInfo info : result) { + addOne(info); } } - void addOne(final AgreementInfo info, final String k) { - final int row = table.getRowCount(); + void addOne(AgreementInfo info) { + int row = table.getRowCount(); table.insertRow(row); applyDataRowStyle(row); - final ContributorAgreement cla = info.agreements.get(k); - final String statusName; - if (cla == null) { - statusName = Util.C.agreementStatus_EXPIRED(); + String url = info.url(); + if (url != null && url.length() > 0) { + Anchor a = new Anchor(info.name(), url); + a.setTarget("_blank"); + table.setWidget(row, 1, a); } else { - statusName = Util.C.agreementStatus_VERIFIED(); + table.setText(row, 1, info.name()); } - table.setText(row, 1, statusName); - - if (cla == null) { - table.setText(row, 2, ""); - table.setText(row, 3, ""); - } else { - final String url = cla.getAgreementUrl(); - if (url != null && url.length() > 0) { - final Anchor a = new Anchor(cla.getName(), url); - a.setTarget("_blank"); - table.setWidget(row, 2, a); - } else { - table.setText(row, 2, cla.getName()); - } - table.setText(row, 3, cla.getDescription()); - } - final FlexCellFormatter fmt = table.getFlexCellFormatter(); - for (int c = 1; c < 4; c++) { + table.setText(row, 2, info.description()); + FlexCellFormatter fmt = table.getFlexCellFormatter(); + for (int c = 1; c < 3; c++) { fmt.addStyleName(row, c, Gerrit.RESOURCES.css().dataCell()); } - - setRowItem(row, cla); } } }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java index 2b01b59..9433b04 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -61,6 +61,7 @@ private ListBox reviewCategoryStrategy; private ListBox diffView; private ListBox emailStrategy; + private ListBox defaultBaseForMerges; private StringListPanel myMenus; private Button save; @@ -106,6 +107,12 @@ GeneralPreferencesInfo.EmailStrategy.DISABLED .name()); + defaultBaseForMerges = new ListBox(); + defaultBaseForMerges.addItem(Util.C.autoMerge(), + GeneralPreferencesInfo.DefaultBase.AUTO_MERGE.name()); + defaultBaseForMerges.addItem(Util.C.firstParent(), + GeneralPreferencesInfo.DefaultBase.FIRST_PARENT.name()); + diffView = new ListBox(); diffView.addItem( com.google.gerrit.client.changes.Util.C.sideBySide(), @@ -156,7 +163,7 @@ signedOffBy = new CheckBox(Util.C.signedOffBy()); boolean flashClippy = !UserAgent.hasJavaScriptClipboard() && UserAgent.Flash.isInstalled(); - final Grid formGrid = new Grid(12 + (flashClippy ? 1 : 0), 2); + final Grid formGrid = new Grid(13 + (flashClippy ? 1 : 0), 2); int row = 0; @@ -176,6 +183,10 @@ formGrid.setWidget(row, fieldIdx, emailStrategy); row++; + formGrid.setText(row, labelIdx, Util.C.defaultBaseForMerges()); + formGrid.setWidget(row, fieldIdx, defaultBaseForMerges); + row++; + formGrid.setText(row, labelIdx, Util.C.diffViewLabel()); formGrid.setWidget(row, fieldIdx, diffView); row++; @@ -239,6 +250,7 @@ e.listenTo(diffView); e.listenTo(reviewCategoryStrategy); e.listenTo(emailStrategy); + e.listenTo(defaultBaseForMerges); } @Override @@ -272,6 +284,7 @@ reviewCategoryStrategy.setEnabled(on); diffView.setEnabled(on); emailStrategy.setEnabled(on); + defaultBaseForMerges.setEnabled(on); } private void display(GeneralPreferences p) { @@ -296,6 +309,9 @@ setListBox(emailStrategy, GeneralPreferencesInfo.EmailStrategy.ENABLED, p.emailStrategy()); + setListBox(defaultBaseForMerges, + GeneralPreferencesInfo.DefaultBase.FIRST_PARENT, + p.defaultBaseForMerges()); display(p.my()); } @@ -385,6 +401,10 @@ GeneralPreferencesInfo.EmailStrategy.ENABLED, GeneralPreferencesInfo.EmailStrategy.values())); + p.defaultBaseForMerges(getListBox(defaultBaseForMerges, + GeneralPreferencesInfo.DefaultBase.FIRST_PARENT, + GeneralPreferencesInfo.DefaultBase.values())); + List<TopMenuItem> items = new ArrayList<>(); for (List<String> v : myMenus.getValues()) { items.add(TopMenuItem.create(v.get(0), v.get(1)));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java index 14f8e2f..e7fa14c5 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
@@ -16,14 +16,16 @@ import com.google.gerrit.client.ErrorDialog; import com.google.gerrit.client.Gerrit; +import com.google.gerrit.client.info.AgreementInfo; import com.google.gerrit.client.rpc.GerritCallback; +import com.google.gerrit.client.rpc.NativeString; +import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.ui.AccountScreen; import com.google.gerrit.client.ui.OnEditEnabler; import com.google.gerrit.client.ui.SmallHeading; import com.google.gerrit.common.PageLinks; -import com.google.gerrit.common.data.AgreementInfo; -import com.google.gerrit.common.data.ContributorAgreement; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JsArray; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.http.client.Request; @@ -41,7 +43,6 @@ import com.google.gwt.user.client.ui.RadioButton; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwtexpui.globalkey.client.NpTextBox; -import com.google.gwtjsonrpc.common.VoidResult; import java.util.HashSet; import java.util.List; @@ -50,8 +51,8 @@ public class NewAgreementScreen extends AccountScreen { private final String nextToken; private Set<String> mySigned; - private List<ContributorAgreement> available; - private ContributorAgreement current; + private List<AgreementInfo> available; + private AgreementInfo current; private VerticalPanel radios; @@ -73,25 +74,22 @@ @Override protected void onLoad() { super.onLoad(); - Util.ACCOUNT_SVC.myAgreements(new GerritCallback<AgreementInfo>() { + AccountApi.getAgreements( + "self", new GerritCallback<JsArray<AgreementInfo>>() { @Override - public void onSuccess(AgreementInfo result) { + public void onSuccess(JsArray<AgreementInfo> result) { if (isAttached()) { - mySigned = new HashSet<>(result.accepted); + mySigned = new HashSet<>(); + for (AgreementInfo info: Natives.asList(result)) { + mySigned.add(info.name()); + } postRPC(); } } }); - Gerrit.SYSTEM_SVC - .contributorAgreements(new GerritCallback<List<ContributorAgreement>>() { - @Override - public void onSuccess(final List<ContributorAgreement> result) { - if (isAttached()) { - available = result; - postRPC(); - } - } - }); + + available = Gerrit.info().auth().contributorAgreements(); + postRPC(); } @Override @@ -158,12 +156,12 @@ } radios.add(hdr); - for (final ContributorAgreement cla : available) { - final RadioButton r = new RadioButton("cla_id", cla.getName()); + for (final AgreementInfo cla : available) { + final RadioButton r = new RadioButton("cla_id", cla.name()); r.addStyleName(Gerrit.RESOURCES.css().contributorAgreementButton()); radios.add(r); - if (mySigned.contains(cla.getName())) { + if (mySigned.contains(cla.name())) { r.setEnabled(false); final Label l = new Label(Util.C.newAgreementAlreadySubmitted()); l.setStyleName(Gerrit.RESOURCES.css().contributorAgreementAlreadySubmitted()); @@ -177,8 +175,8 @@ }); } - if (cla.getDescription() != null && !cla.getDescription().equals("")) { - final Label l = new Label(cla.getDescription()); + if (cla.description() != null && !cla.description().equals("")) { + final Label l = new Label(cla.description()); l.setStyleName(Gerrit.RESOURCES.css().contributorAgreementShortDescription()); radios.add(l); } @@ -199,24 +197,24 @@ } private void doEnterAgreement() { - Util.ACCOUNT_SEC.enterAgreement(current.getName(), - new GerritCallback<VoidResult>() { + AccountApi.enterAgreement("self", current.name(), + new GerritCallback<NativeString>() { @Override - public void onSuccess(final VoidResult result) { + public void onSuccess(NativeString result) { Gerrit.display(nextToken); } @Override - public void onFailure(final Throwable caught) { + public void onFailure(Throwable caught) { yesIAgreeBox.setText(""); super.onFailure(caught); } }); } - private void showCLA(final ContributorAgreement cla) { + private void showCLA(AgreementInfo cla) { current = cla; - String url = cla.getAgreementUrl(); + String url = cla.url(); if (url != null && url.length() > 0) { agreementGroup.setVisible(true); agreementHtml.setText(Gerrit.C.rpcStatusWorking()); @@ -250,7 +248,7 @@ agreementGroup.setVisible(false); } - finalGroup.setVisible(cla.getAutoVerify() != null); + finalGroup.setVisible(cla.autoVerifyGroup() != null); yesIAgreeBox.setText(""); submit.setEnabled(false); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java index c32a846..73557aa 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
@@ -20,7 +20,7 @@ import com.google.gerrit.client.ui.InlineHyperlink; import com.google.gerrit.client.ui.SmallHeading; import com.google.gerrit.common.PageLinks; -import com.google.gerrit.reviewdb.client.Account.FieldName; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FormPanel; @@ -70,7 +70,7 @@ formBody.add(contactGroup); if (Gerrit.getUserAccount().username() == null - && Gerrit.info().auth().canEdit(FieldName.USER_NAME)) { + && Gerrit.info().auth().canEdit(AccountFieldName.USER_NAME)) { final FlowPanel fp = new FlowPanel(); fp.setStyleName(Gerrit.RESOURCES.css().registerScreenSection()); fp.add(new SmallHeading(Util.C.welcomeUsernameHeading()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java index f388436..d70121b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -22,6 +22,7 @@ import com.google.gerrit.client.rpc.NativeString; import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.ui.OnEditEnabler; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.reviewdb.client.Account; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -86,7 +87,7 @@ } private boolean canEditUserName() { - return Gerrit.info().auth().canEdit(Account.FieldName.USER_NAME); + return Gerrit.info().auth().canEdit(AccountFieldName.USER_NAME); } private void confirmSetUserName() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java index a0f36b9..b4b4390 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Util.java
@@ -15,7 +15,6 @@ package com.google.gerrit.client.account; import com.google.gerrit.common.data.AccountSecurity; -import com.google.gerrit.common.data.AccountService; import com.google.gerrit.common.data.ProjectAdminService; import com.google.gwt.core.client.GWT; import com.google.gwtjsonrpc.client.JsonUtil; @@ -23,14 +22,10 @@ public class Util { public static final AccountConstants C = GWT.create(AccountConstants.class); public static final AccountMessages M = GWT.create(AccountMessages.class); - public static final AccountService ACCOUNT_SVC; public static final AccountSecurity ACCOUNT_SEC; public static final ProjectAdminService PROJECT_SVC; static { - ACCOUNT_SVC = GWT.create(AccountService.class); - JsonUtil.bind(ACCOUNT_SVC, "rpc/AccountService"); - ACCOUNT_SEC = GWT.create(AccountSecurity.class); JsonUtil.bind(ACCOUNT_SEC, "rpc/AccountSecurity");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java index 254d3e6..7a32f01 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
@@ -21,8 +21,8 @@ import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.groups.GroupApi; import com.google.gerrit.client.groups.GroupAuditEventInfo; -import com.google.gerrit.client.groups.GroupInfo; import com.google.gerrit.client.info.AccountInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.ui.FancyFlexTable;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java index a71dffe..22a57a4 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -17,7 +17,7 @@ import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.VoidResult; import com.google.gerrit.client.groups.GroupApi; -import com.google.gerrit.client.groups.GroupInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.AccountGroupSuggestOracle; import com.google.gerrit.client.ui.OnEditEnabler;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java index 7c0c8f6..053e7e0c 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -18,8 +18,8 @@ import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.VoidResult; import com.google.gerrit.client.groups.GroupApi; -import com.google.gerrit.client.groups.GroupInfo; import com.google.gerrit.client.info.AccountInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java index 8c00ba7..cbe8a06 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
@@ -17,7 +17,7 @@ import static com.google.gerrit.client.Dispatcher.toGroup; import com.google.gerrit.client.groups.GroupApi; -import com.google.gerrit.client.groups.GroupInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.MenuScreen; import com.google.gerrit.reviewdb.client.AccountGroup;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties index 2fe5978..5e7b873 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -123,7 +123,11 @@ abandon, \ addPatchSet, \ create, \ + createTag, \ + createSignedTag, \ + delete, \ deleteDrafts, \ + editAssignee, \ editHashtags, \ editTopicName, \ forgeAuthor, \ @@ -133,8 +137,6 @@ publishDrafts, \ push, \ pushMerge, \ - pushTag, \ - pushSignedTag, \ read, \ rebase, \ removeReviewer, \ @@ -145,7 +147,11 @@ abandon = Abandon addPatchSet = Add Patch Set create = Create Reference +createTag = Create Annotated Tag +createSignedTag = Create Signed Tag +delete = Delete Reference deleteDrafts = Delete Drafts +editAssignee = Edit Assignee editHashtags = Edit Hashtags editTopicName = Edit Topic Name forgeAuthor = Forge Author Identity @@ -155,8 +161,6 @@ publishDrafts = Publish Drafts push = Push pushMerge = Push Merge Commit -pushTag = Push Annotated Tag -pushSignedTag = Push Signed Tag read = Read rebase = Rebase removeReviewer = Remove Reviewer
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java index a2ba5cd..4efaa61 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
@@ -21,7 +21,7 @@ import com.google.gerrit.client.NotFoundScreen; import com.google.gerrit.client.account.AccountCapabilities; import com.google.gerrit.client.groups.GroupApi; -import com.google.gerrit.client.groups.GroupInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.OnEditEnabler; import com.google.gerrit.client.ui.Screen;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java index 64fc0e5..94d15bd 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
@@ -18,9 +18,9 @@ import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.Gerrit; -import com.google.gerrit.client.groups.GroupInfo; import com.google.gerrit.client.groups.GroupList; import com.google.gerrit.client.groups.GroupMap; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.ui.HighlightingInlineHyperlink; import com.google.gerrit.client.ui.NavigationTable;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java index f66307c..be5bdcb 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
@@ -16,7 +16,6 @@ import static com.google.gerrit.common.data.Permission.EDIT_TOPIC_NAME; import static com.google.gerrit.common.data.Permission.PUSH; -import static com.google.gerrit.common.data.Permission.PUSH_TAG; import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.Gerrit; @@ -143,7 +142,7 @@ initWidget(uiBinder.createAndBindUi(this)); String name = permission.getName(); - boolean canForce = PUSH.equals(name) || PUSH_TAG.equals(name); + boolean canForce = PUSH.equals(name); if (canForce) { String ref = section.getName(); canForce = !ref.startsWith("refs/for/") && !ref.startsWith("^refs/for/");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java index 62c3636..2a8dacf 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.java
@@ -30,4 +30,5 @@ String submittedTogether(int count); String submittedTogether(String count); String editPatchSet(int patchSet); + String failedToLoadFileList(String error); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties index 6461899..743945d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeMessages.properties
@@ -6,3 +6,4 @@ sameTopic = Same Topic ({0}) submittedTogether = Submitted Together ({0}) editPatchSet = edit:{0} +failedToLoadFileList = Failed to load file list: {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java index b2e9f28..55d22a9 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -107,8 +107,13 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; public class ChangeScreen extends Screen { + private static final Logger logger = + Logger.getLogger(ChangeScreen.class.getName()); + interface Binder extends UiBinder<HTMLPanel, ChangeScreen> {} private static final Binder uiBinder = GWT.create(Binder.class); @@ -199,6 +204,7 @@ @UiField FileTable files; @UiField ListBox diffBase; @UiField History history; + @UiField SimplePanel historyExtensionRight; @UiField Button includedIn; @UiField Button patchSets; @@ -282,13 +288,17 @@ info.init(); addExtensionPoints(info, initCurrentRevision(info)); - RevisionInfo rev = info.revision(revision); + final RevisionInfo rev = info.revision(revision); CallbackGroup group = new CallbackGroup(); loadCommit(rev, group); group.addListener(new GerritCallback<Void>() { @Override public void onSuccess(Void result) { + if (base == null && rev.commit().parents().length() > 1) { + base = Gerrit.getUserPreferences() + .defaultBaseForMerges().getBase(); + } loadConfigInfo(info, base); } }); @@ -352,6 +362,9 @@ addExtensionPoint( GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK, commitExtension, change, rev); + addExtensionPoint( + GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS, + historyExtensionRight, change, rev); } private void addExtensionPoint(GerritUiExtensionPoint extensionPoint, @@ -569,35 +582,40 @@ } private void initEditMode(ChangeInfo info, String revision) { - if (Gerrit.isSignedIn() && info.status().isOpen()) { + if (Gerrit.isSignedIn()) { RevisionInfo rev = info.revision(revision); - if (isEditModeEnabled(info, rev)) { - editMode.setVisible(fileTableMode == FileTable.Mode.REVIEW); - addFile.setVisible(!editMode.isVisible()); - deleteFile.setVisible(!editMode.isVisible()); - renameFile.setVisible(!editMode.isVisible()); - reviewMode.setVisible(!editMode.isVisible()); - addFileAction = new AddFileAction( - changeId, info.revision(revision), - style, addFile, files); - deleteFileAction = new DeleteFileAction( - changeId, info.revision(revision), - style, addFile); - renameFileAction = new RenameFileAction( - changeId, info.revision(revision), - style, addFile); - } else { - editMode.setVisible(false); - addFile.setVisible(false); - reviewMode.setVisible(false); - } - - if (rev.isEdit()) { - if (info.hasEditBasedOnCurrentPatchSet()) { - publishEdit.setVisible(true); + if (info.status().isOpen()) { + if (isEditModeEnabled(info, rev)) { + editMode.setVisible(fileTableMode == FileTable.Mode.REVIEW); + addFile.setVisible(!editMode.isVisible()); + deleteFile.setVisible(!editMode.isVisible()); + renameFile.setVisible(!editMode.isVisible()); + reviewMode.setVisible(!editMode.isVisible()); + addFileAction = new AddFileAction( + changeId, info.revision(revision), + style, addFile, files); + deleteFileAction = new DeleteFileAction( + changeId, info.revision(revision), + style, addFile); + renameFileAction = new RenameFileAction( + changeId, info.revision(revision), + style, addFile); } else { - rebaseEdit.setVisible(true); + editMode.setVisible(false); + addFile.setVisible(false); + reviewMode.setVisible(false); } + + if (rev.isEdit()) { + if (info.hasEditBasedOnCurrentPatchSet()) { + publishEdit.setVisible(true); + } else { + rebaseEdit.setVisible(true); + } + deleteEdit.setVisible(true); + } + } else if (rev.isEdit()) { + deleteEdit.setStyleName(style.highlight()); deleteEdit.setVisible(true); } } @@ -616,37 +634,39 @@ @UiHandler("publishEdit") void onPublishEdit(@SuppressWarnings("unused") ClickEvent e) { - EditActions.publishEdit(changeId); + EditActions.publishEdit(changeId, publishEdit, rebaseEdit, deleteEdit); } @UiHandler("rebaseEdit") void onRebaseEdit(@SuppressWarnings("unused") ClickEvent e) { - EditActions.rebaseEdit(changeId); + EditActions.rebaseEdit(changeId, publishEdit, rebaseEdit, deleteEdit); } @UiHandler("deleteEdit") void onDeleteEdit(@SuppressWarnings("unused") ClickEvent e) { if (Window.confirm(Resources.C.deleteChangeEdit())) { - EditActions.deleteEdit(changeId); + EditActions.deleteEdit(changeId, publishEdit, rebaseEdit, deleteEdit); } } @UiHandler("publish") void onPublish(@SuppressWarnings("unused") ClickEvent e) { - DraftActions.publish(changeId, revision); + DraftActions.publish(changeId, revision, publish, deleteRevision, + deleteChange); } @UiHandler("deleteRevision") void onDeleteRevision(@SuppressWarnings("unused") ClickEvent e) { if (Window.confirm(Resources.C.deleteDraftRevision())) { - DraftActions.delete(changeId, revision); + DraftActions.delete(changeId, revision, publish, deleteRevision, + deleteChange); } } @UiHandler("deleteChange") void onDeleteChange(@SuppressWarnings("unused") ClickEvent e) { if (Window.confirm(Resources.C.deleteDraftChange())) { - DraftActions.delete(changeId); + DraftActions.delete(changeId, publish, deleteRevision, deleteChange); } } @@ -918,7 +938,7 @@ } private void loadConfigInfo(final ChangeInfo info, String base) { - RevisionInfo rev = info.revision(revision); + final RevisionInfo rev = info.revision(revision); RevisionInfo b = resolveRevisionOrPatchSetId(info, base, null); CallbackGroup group = new CallbackGroup(); @@ -933,16 +953,32 @@ } else { loadDiff(b, rev, lastReply, group); } + group.addListener(new AsyncCallback<Void>() { + @Override + public void onSuccess(Void result) { + loadConfigInfo(info, rev); + } + @Override + public void onFailure(Throwable caught) { + logger.log(Level.SEVERE, + "Loading file list and inline comments failed: " + + caught.getMessage()); + loadConfigInfo(info, rev); + } + }); + group.done(); + } + + private void loadConfigInfo(final ChangeInfo info, RevisionInfo rev) { if (loaded) { - group.done(); return; } RevisionInfoCache.add(changeId, rev); ConfigInfoCache.add(info); ConfigInfoCache.get(info.projectNameKey(), - group.addFinal(new ScreenLoadCallback<ConfigInfoCache.Entry>(this) { + new ScreenLoadCallback<ConfigInfoCache.Entry>(this) { @Override protected void preDisplay(Entry result) { loaded = true; @@ -951,7 +987,7 @@ renderChangeInfo(info); loadRevisionInfo(); } - })); + }); } static Timestamp myLastReply(ChangeInfo info) { @@ -1012,6 +1048,7 @@ @Override public void onFailure(Throwable caught) { + files.showError(caught); } })); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml index a0d5405..7790044 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
@@ -355,6 +355,11 @@ padding-top: 5px; } + .historyExtension { + display: inline-block; + float: right; + } + .pushCertStatus { padding-left: 5px; } @@ -601,6 +606,7 @@ <ui:attribute name='title'/> <div><ui:msg>Collapse All</ui:msg></div> </g:Button> + <g:SimplePanel ui:field='historyExtensionRight' styleName='{style.historyExtension}'/> </div> </div> <c:History ui:field='history'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java index 634190a2..6787576 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
@@ -21,23 +21,25 @@ import com.google.gerrit.reviewdb.client.Change; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; public class DraftActions { - static void publish(Change.Id id, String revision) { - ChangeApi.publish(id.get(), revision, cs(id)); + static void publish(Change.Id id, String revision, Button... draftButtons) { + ChangeApi.publish(id.get(), revision, cs(id, draftButtons)); } - static void delete(Change.Id id, String revision) { - ChangeApi.deleteRevision(id.get(), revision, cs(id)); + static void delete(Change.Id id, String revision, Button... draftButtons) { + ChangeApi.deleteRevision(id.get(), revision, cs(id, draftButtons)); } - static void delete(Change.Id id) { - ChangeApi.deleteChange(id.get(), mine()); + static void delete(Change.Id id, Button... draftButtons) { + ChangeApi.deleteChange(id.get(), mine(draftButtons)); } public static GerritCallback<JavaScriptObject> cs( - final Change.Id id) { + final Change.Id id, final Button... draftButtons) { + setEnabled(false, draftButtons); return new GerritCallback<JavaScriptObject>() { @Override public void onSuccess(JavaScriptObject result) { @@ -46,6 +48,7 @@ @Override public void onFailure(Throwable err) { + setEnabled(true, draftButtons); if (SubmitFailureDialog.isConflict(err)) { new SubmitFailureDialog(err.getMessage()).center(); Gerrit.display(PageLinks.toChange(id)); @@ -56,7 +59,9 @@ }; } - private static AsyncCallback<JavaScriptObject> mine() { + private static AsyncCallback<JavaScriptObject> mine( + final Button... draftButtons) { + setEnabled(false, draftButtons); return new GerritCallback<JavaScriptObject>() { @Override public void onSuccess(JavaScriptObject result) { @@ -65,6 +70,7 @@ @Override public void onFailure(Throwable err) { + setEnabled(true, draftButtons); if (SubmitFailureDialog.isConflict(err)) { new SubmitFailureDialog(err.getMessage()).center(); Gerrit.display(PageLinks.MINE); @@ -74,4 +80,12 @@ } }; } + + private static void setEnabled(boolean enabled, Button... draftButtons) { + if (draftButtons != null) { + for (Button b : draftButtons) { + b.setEnabled(enabled); + } + } + } }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java index d11cf7e..97abddb 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditActions.java
@@ -20,23 +20,25 @@ import com.google.gerrit.common.PageLinks; import com.google.gerrit.reviewdb.client.Change; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.user.client.ui.Button; public class EditActions { - static void deleteEdit(Change.Id id) { - ChangeApi.deleteEdit(id.get(), cs(id)); + static void deleteEdit(Change.Id id, Button... editButtons) { + ChangeApi.deleteEdit(id.get(), cs(id, editButtons)); } - static void publishEdit(Change.Id id) { - ChangeApi.publishEdit(id.get(), cs(id)); + static void publishEdit(Change.Id id, Button... editButtons) { + ChangeApi.publishEdit(id.get(), cs(id, editButtons)); } - static void rebaseEdit(Change.Id id) { - ChangeApi.rebaseEdit(id.get(), cs(id)); + static void rebaseEdit(Change.Id id, Button... editButtons) { + ChangeApi.rebaseEdit(id.get(), cs(id, editButtons)); } public static GerritCallback<JavaScriptObject> cs( - final Change.Id id) { + final Change.Id id, final Button... editButtons) { + setEnabled(false, editButtons); return new GerritCallback<JavaScriptObject>() { @Override public void onSuccess(JavaScriptObject result) { @@ -45,6 +47,7 @@ @Override public void onFailure(Throwable err) { + setEnabled(true, editButtons); if (SubmitFailureDialog.isConflict(err)) { new SubmitFailureDialog(err.getMessage()).center(); Gerrit.display(PageLinks.toChange(id)); @@ -54,4 +57,12 @@ } }; } + + private static void setEnabled(boolean enabled, Button... editButtons) { + if (editButtons != null) { + for (Button b : editButtons) { + b.setEnabled(enabled); + } + } + } }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java index f0a7ce3..3a85b26 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -60,6 +60,7 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwt.user.client.ui.ImageResourceRenderer; +import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.HyperlinkImpl; import com.google.gwtexpui.globalkey.client.KeyCommand; @@ -94,6 +95,7 @@ String inserted(); String deleted(); String restoreDelete(); + String error(); } public enum Mode { @@ -222,6 +224,13 @@ } } + void showError(Throwable t) { + clear(); + Label l = new Label(Resources.M.failedToLoadFileList(t.getMessage())); + add(l); + l.setStyleName(R.css().error()); + } + void markReviewed(JsArrayString reviewed) { if (table != null) { table.markReviewed(reviewed); @@ -258,11 +267,18 @@ if (table != null) { String self = Gerrit.selfRedirect(null); for (FileInfo info : Natives.asList(table.list)) { - Window.open(self + "#" + url(info), "_blank", null); + if (canOpen(info.path())) { + Window.open(self + "#" + url(info), "_blank", null); + } } } } + private boolean canOpen(String path) { + return mode != Mode.EDIT || !Patch.isMagic(path) + || Patch.COMMIT_MSG.equals(path); + } + private void setTable(MyTable table) { clear(); add(table); @@ -420,7 +436,10 @@ @Override protected void onOpenRow(int row) { if (1 <= row && row <= list.length()) { - Gerrit.display(url(list.get(row - 1))); + FileInfo info = list.get(row - 1); + if (canOpen(info.path())) { + Gerrit.display(url(info)); + } } } @@ -443,7 +462,10 @@ @Override public void onKeyPress(KeyPressEvent event) { - Gerrit.display(url(list.get(index))); + FileInfo info = list.get(index); + if (canOpen(info.path())) { + Gerrit.display(url(info)); + } } } } @@ -529,7 +551,7 @@ bytesDeleted = 0; for (int i = 0; i < list.length(); i++) { FileInfo info = list.get(i); - if (!Patch.COMMIT_MSG.equals(info.path())) { + if (!Patch.isMagic(info.path())) { if (!info.binary()) { hasNonBinaryFile = true; inserted += info.linesInserted(); @@ -619,7 +641,7 @@ private void columnDeleteRestore(SafeHtmlBuilder sb, FileInfo info) { sb.openTd().setStyleName(R.css().restoreDelete()); if (hasUser) { - if (!Patch.COMMIT_MSG.equals(info.path())) { + if (!Patch.isMagic(info.path())) { boolean editable = isEditable(info); sb.openDiv() .openElement("button") @@ -650,7 +672,7 @@ private void columnStatus(SafeHtmlBuilder sb, FileInfo info) { sb.openTd().setStyleName(R.css().status()); - if (!Patch.COMMIT_MSG.equals(info.path()) + if (!Patch.isMagic(info.path()) && info.status() != null && !ChangeType.MODIFIED.matches(info.status())) { sb.append(info.status()); @@ -659,20 +681,43 @@ } private void columnPath(SafeHtmlBuilder sb, FileInfo info) { - sb.openTd() - .setStyleName(R.css().pathColumn()) - .openAnchor(); - String path = info.path(); + + sb.openTd() + .setStyleName(R.css().pathColumn()); + + if (!canOpen(path)) { + sb.openDiv(); + appendPath(path); + sb.closeDiv(); + sb.closeTd(); + return; + } + + sb.openAnchor(); + if (mode == Mode.EDIT && !isEditable(info)) { sb.setAttribute("onclick", RESTORE + "(event," + info._row() + ")"); } else { sb.setAttribute("href", "#" + url(info)) .setAttribute("onclick", OPEN + "(event," + info._row() + ")"); } + appendPath(path); + sb.closeAnchor(); + if (info.oldPath() != null) { + sb.br(); + sb.openSpan().setStyleName(R.css().renameCopySource()) + .append(info.oldPath()) + .closeSpan(); + } + sb.closeTd(); + } + private void appendPath(String path) { if (Patch.COMMIT_MSG.equals(path)) { sb.append(Util.C.commitMessage()); + } else if (Patch.MERGE_LIST.equals(path)) { + sb.append(Util.C.mergeList()); } else if (Gerrit.getUserPreferences().muteCommonPathPrefixes()) { int commonPrefixLen = commonPrefix(path); if (commonPrefixLen > 0) { @@ -685,15 +730,6 @@ } else { sb.append(path); } - - sb.closeAnchor(); - if (info.oldPath() != null) { - sb.br(); - sb.openSpan().setStyleName(R.css().renameCopySource()) - .append(info.oldPath()) - .closeSpan(); - } - sb.closeTd(); } private int commonPrefix(String path) { @@ -775,7 +811,7 @@ private void columnDelta1(SafeHtmlBuilder sb, FileInfo info) { sb.openTd().setStyleName(R.css().deltaColumn1()); - if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()) { + if (!Patch.isMagic(info.path()) && !info.binary()) { if (showChangeSizeBars) { sb.append(info.linesInserted() + info.linesDeleted()); } else if (!ChangeType.DELETED.matches(info.status())) { @@ -804,7 +840,7 @@ private void columnDelta2(SafeHtmlBuilder sb, FileInfo info) { sb.openTd().setStyleName(R.css().deltaColumn2()); if (showChangeSizeBars - && !Patch.COMMIT_MSG.equals(info.path()) && !info.binary() + && !Patch.isMagic(info.path()) && !info.binary() && (info.linesInserted() != 0 || info.linesDeleted() != 0)) { int w = 80; int t = inserted + deleted;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java index 6c27ed9..c8735b7 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
@@ -176,6 +176,10 @@ if (l != null) { comments.add(new FileComments(clp, ps, Util.C.commitMessage(), l)); } + l = m.remove(Patch.MERGE_LIST); + if (l != null) { + comments.add(new FileComments(clp, ps, Util.C.mergeList(), l)); + } for (Map.Entry<String, List<CommentInfo>> e : m.entrySet()) { comments.add(new FileComments(clp, ps, e.getKey(), e.getValue())); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java index cc5c9b7..2d868da 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -215,10 +215,11 @@ EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT), new TabChangeListCallback(Tab.CHERRY_PICKS, info.project(), revision)); - // TODO(sbeller): show only on latest revision - ChangeApi.change(info.legacyId().get()).view("submitted_together") - .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER, - info.project(), revision)); + if (info.currentRevision().equals(revision)) { + ChangeApi.change(info.legacyId().get()).view("submitted_together") + .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER, + info.project(), revision)); + } if (!Gerrit.info().change().isSubmitWholeTopicEnabled() && info.topic() != null && !"".equals(info.topic())) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java index 791effc..846ad53 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
@@ -178,7 +178,7 @@ rows = new ArrayList<>(changes.length()); connectedPos = changes.length() - 1; connected = showIndirectAncestors - ? new HashSet<String>(Math.max(changes.length() * 4 / 3, 16)) + ? new HashSet<>(Math.max(changes.length() * 4 / 3, 16)) : null; }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java index e29048a..b985f43 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -422,12 +422,17 @@ comments.add(new FileComments(clp, psId, Util.C.commitMessage(), copyPath(Patch.COMMIT_MSG, l))); } + l = m.get(Patch.MERGE_LIST); + if (l != null) { + comments.add(new FileComments(clp, psId, Util.C.commitMessage(), + copyPath(Patch.MERGE_LIST, l))); + } List<String> paths = new ArrayList<>(m.keySet()); Collections.sort(paths); for (String path : paths) { - if (!path.equals(Patch.COMMIT_MSG)) { + if (!Patch.isMagic(path)) { comments.add(new FileComments(clp, psId, path, copyPath(path, m.get(path)))); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java index a852fa0..2188c03 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
@@ -16,8 +16,8 @@ import com.google.gerrit.client.admin.Util; import com.google.gerrit.client.changes.ChangeApi; -import com.google.gerrit.client.groups.GroupBaseInfo; import com.google.gerrit.client.info.AccountInfo; +import com.google.gerrit.client.info.GroupBaseInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.ui.AccountSuggestOracle;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css index bde9755..6f514df 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -109,3 +109,7 @@ white-space: nowrap; } +.error { + color: #D33D3D; + font-weight: bold; +}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java index b2334d1d..4a9eea4 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -63,6 +63,7 @@ String patchTableColumnComments(); String patchTableColumnSize(); String commitMessage(); + String mergeList(); String patchTablePrev(); String patchTableNext();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties index b7e2677..675cd07 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -45,6 +45,7 @@ patchTableColumnComments = Comments patchTableColumnSize = Size commitMessage = Commit Message +mergeList = Merge List patchTablePrev = Previous file patchTableNext = Next file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java index 8935e36..de19b35 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffScreen.java
@@ -639,7 +639,7 @@ } private void toggleShowIntraline() { - prefs.intralineDifference(!prefs.intralineDifference()); + prefs.intralineDifference(!Boolean.valueOf(prefs.intralineDifference())); setShowIntraline(prefs.intralineDifference()); prefsAction.update(); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java index f377038..3033cbb1 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
@@ -122,6 +122,8 @@ SafeHtmlBuilder b = new SafeHtmlBuilder(); if (Patch.COMMIT_MSG.equals(path)) { return b.append(Util.C.commitMessage()); + } else if (Patch.MERGE_LIST.equals(path)) { + return b.append(Util.C.mergeList()); } int s = path.lastIndexOf('/') + 1;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java index bc37abb..d45b8d8 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
@@ -128,7 +128,7 @@ if (meta == null) { return; } - if (!Patch.COMMIT_MSG.equals(path)) { + if (!Patch.isMagic(path)) { linkPanel.add(createDownloadLink()); } if (!binary && open && idActive != null && Gerrit.isSignedIn()) { @@ -147,7 +147,7 @@ void setUpBlame(final CodeMirror cm, final boolean isBase, final PatchSet.Id rev, final String path) { - if (!Patch.COMMIT_MSG.equals(path) && Gerrit.isSignedIn() + if (!Patch.isMagic(path) && Gerrit.isSignedIn() && Gerrit.info().change().allowBlame()) { Anchor blameIcon = createBlameIcon(); blameIcon.addClickHandler(new ClickHandler() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java index 78d01db..ef1d4bd 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -260,7 +260,7 @@ @UiHandler("intralineDifference") void onIntralineDifference(ValueChangeEvent<Boolean> e) { - prefs.intralineDifference(e.getValue()); + prefs.intralineDifference(Boolean.valueOf(e.getValue())); if (view != null) { view.setShowIntraline(prefs.intralineDifference()); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java index da7ca44..490e028 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -99,7 +99,6 @@ String hideBase(); } - private final PatchSet.Id base; private final PatchSet.Id revision; private final String path; private final int startLine; @@ -130,8 +129,7 @@ private HandlerRegistration closeHandler; private int generation; - public EditScreen(PatchSet.Id base, Patch.Key patch, int startLine) { - this.base = base; + public EditScreen(Patch.Key patch, int startLine) { this.revision = patch.getParentKey(); this.path = patch.get(); this.startLine = startLine - 1; @@ -232,7 +230,6 @@ // TODO(davido): We probably want to create dedicated GET EditScreenMeta // REST endpoint. Abuse GET diff for now, as it retrieves links we need. DiffApi.diff(revision, path) - .base(base) .webLinksOnly() .get(group1.addFinal(new AsyncCallback<DiffInfo>() { @Override @@ -614,7 +611,7 @@ sbs.setHTML(new ImageResourceRenderer() .render(Gerrit.RESOURCES.sideBySideDiff())); sbs.setTargetHistoryToken( - Dispatcher.toPatch("sidebyside", base, new Patch.Key(revision, path))); + Dispatcher.toPatch("sidebyside", null, new Patch.Key(revision, path))); sbs.setTitle(PatchUtil.C.sideBySideDiff()); linkPanel.add(sbs); @@ -622,7 +619,7 @@ unified.setHTML(new ImageResourceRenderer() .render(Gerrit.RESOURCES.unifiedDiff())); unified.setTargetHistoryToken( - Dispatcher.toPatch("unified", base, new Patch.Key(revision, path))); + Dispatcher.toPatch("unified", null, new Patch.Key(revision, path))); unified.setTitle(PatchUtil.C.unifiedDiff()); linkPanel.add(unified); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java index 93be87b..760f06d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java
@@ -16,6 +16,7 @@ import com.google.gerrit.client.VoidResult; import com.google.gerrit.client.info.AccountInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.NativeString; import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.rpc.RestApi;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java index ed41b65..5bcdc6b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java
@@ -15,6 +15,7 @@ package com.google.gerrit.client.groups; import com.google.gerrit.client.info.AccountInfo; +import com.google.gerrit.client.info.GroupInfo; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupList.java index a24e1dc..f51ecb8 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupList.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupList.java
@@ -14,6 +14,7 @@ package com.google.gerrit.client.groups; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java index 5532285..5e23049 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java
@@ -14,6 +14,7 @@ package com.google.gerrit.client.groups; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.NativeMap; import com.google.gerrit.client.rpc.RestApi; import com.google.gwt.user.client.rpc.AsyncCallback;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java index a96624a..983d48c 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
@@ -14,8 +14,8 @@ package com.google.gerrit.client.ui; -import com.google.gerrit.client.groups.GroupInfo; import com.google.gerrit.client.groups.GroupMap; +import com.google.gerrit.client.info.GroupInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.reviewdb.client.AccountGroup;
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java index 943be7e..3d99883 100644 --- a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java +++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
@@ -82,7 +82,6 @@ Modes.I.htmlmixed(), Modes.I.http(), Modes.I.idl(), - Modes.I.jade(), Modes.I.javascript(), Modes.I.jinja2(), Modes.I.jsx(), @@ -110,6 +109,7 @@ Modes.I.powershell(), Modes.I.properties(), Modes.I.protobuf(), + Modes.I.pug(), Modes.I.puppet(), Modes.I.python(), Modes.I.q(),
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java index 668a57f..218b96c 100644 --- a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java +++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
@@ -67,7 +67,6 @@ @Source("htmlmixed.js") @DoNotEmbed DataResource htmlmixed(); @Source("http.js") @DoNotEmbed DataResource http(); @Source("idl.js") @DoNotEmbed DataResource idl(); - @Source("jade.js") @DoNotEmbed DataResource jade(); @Source("javascript.js") @DoNotEmbed DataResource javascript(); @Source("jinja2.js") @DoNotEmbed DataResource jinja2(); @Source("jsx.js") @DoNotEmbed DataResource jsx(); @@ -95,6 +94,7 @@ @Source("powershell.js") @DoNotEmbed DataResource powershell(); @Source("properties.js") @DoNotEmbed DataResource properties(); @Source("protobuf.js") @DoNotEmbed DataResource protobuf(); + @Source("pug.js") @DoNotEmbed DataResource pug(); @Source("puppet.js") @DoNotEmbed DataResource puppet(); @Source("python.js") @DoNotEmbed DataResource python(); @Source("q.js") @DoNotEmbed DataResource q();
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK index d52963a..0b0499c 100644 --- a/gerrit-httpd/BUCK +++ b/gerrit-httpd/BUCK
@@ -73,7 +73,6 @@ '//lib/jgit/org.eclipse.jgit.junit:junit', '//lib/joda:joda-time', ], - source_under_test = [':httpd'], # TODO(sop) Remove after Buck supports Eclipse visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java deleted file mode 100644 index c1a0f44..0000000 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java +++ /dev/null
@@ -1,49 +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.httpd; - -import org.eclipse.jgit.lib.Config; - -public class GerritOptions { - private final boolean headless; - private final boolean slave; - private final boolean enablePolyGerrit; - private final boolean forcePolyGerritDev; - - public GerritOptions(Config cfg, boolean headless, boolean slave, - boolean forcePolyGerritDev) { - this.headless = headless; - this.slave = slave; - this.enablePolyGerrit = forcePolyGerritDev - || cfg.getBoolean("gerrit", null, "enablePolyGerrit", false); - this.forcePolyGerritDev = forcePolyGerritDev; - } - - public boolean enableDefaultUi() { - return !headless && !enablePolyGerrit; - } - - public boolean enableMasterFeatures() { - return !slave; - } - - public boolean enablePolyGerrit() { - return !headless && enablePolyGerrit; - } - - public boolean forcePolyGerritDev() { - return !headless && forcePolyGerritDev; - } -}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java index 5146b31..7935bb6 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -14,7 +14,7 @@ package com.google.gerrit.httpd; -import static com.google.gerrit.reviewdb.client.AuthType.OAUTH; +import static com.google.gerrit.extensions.client.AuthType.OAUTH; import com.google.gerrit.reviewdb.client.CoreDownloadSchemes; import com.google.gerrit.server.config.AuthConfig;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java index 2c67182..8d8937c 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -18,6 +18,7 @@ import com.google.common.base.Strings; import com.google.gerrit.common.PageLinks; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.httpd.raw.CatServlet; import com.google.gerrit.httpd.raw.HostPageServlet; import com.google.gerrit.httpd.raw.LegacyGerritServlet; @@ -30,10 +31,10 @@ import com.google.gerrit.httpd.restapi.GroupsRestApiServlet; import com.google.gerrit.httpd.restapi.ProjectsRestApiServlet; import com.google.gerrit.httpd.rpc.doc.QueryDocumentationFilter; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.config.AuthConfig; +import com.google.gerrit.server.config.GerritOptions; import com.google.gwtexpui.server.CacheControlFilter; import com.google.inject.Key; import com.google.inject.Provider; @@ -62,8 +63,9 @@ filter("/*").through(Key.get(CacheControlFilter.class)); bind(Key.get(CacheControlFilter.class)).in(SINGLETON); - if (options.enableDefaultUi()) { + if (options.enableGwtUi()) { filter("/").through(XsrfCookieFilter.class); + filter("/accounts/self/detail").through(XsrfCookieFilter.class); serve("/").with(HostPageServlet.class); serve("/Gerrit").with(LegacyGerritServlet.class); serve("/Gerrit/*").with(legacyGerritScreen()); @@ -97,6 +99,11 @@ filter("/a/*").through(RequireIdentifiedUserFilter.class); serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class); + + // Bind servlets for REST root collections. + // The '/plugins/' root collection is already handled by HttpPluginServlet + // which is bound in HttpPluginModule. We cannot bind it here again although + // this means that plugins can't add REST views on PLUGIN_KIND. serveRegex("^/(?:a/)?access/(.*)$").with(AccessRestApiServlet.class); serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class); serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java index 3e3b7c4..fa551e8 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -25,6 +25,7 @@ import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.server.RemotePeer; import com.google.gerrit.server.config.AuthConfig; +import com.google.gerrit.server.config.GerritOptions; import com.google.gerrit.server.config.GerritRequestModule; import com.google.gerrit.server.config.GitwebCgiConfig; import com.google.gerrit.server.git.AsyncReceiveCommits;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java index fe556ac..ac7c7e7 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -609,45 +609,39 @@ final OutputStream dst) throws IOException { final int contentLength = req.getContentLength(); final InputStream src = req.getInputStream(); - new Thread(new Runnable() { - @Override - public void run() { + new Thread(() -> { + try { try { - try { - final byte[] buf = new byte[bufferSize]; - int remaining = contentLength; - while (0 < remaining) { - final int max = Math.max(buf.length, remaining); - final int n = src.read(buf, 0, max); - if (n < 0) { - throw new EOFException("Expected " + remaining + " more bytes"); - } - dst.write(buf, 0, n); - remaining -= n; + final byte[] buf = new byte[bufferSize]; + int remaining = contentLength; + while (0 < remaining) { + final int max = Math.max(buf.length, remaining); + final int n = src.read(buf, 0, max); + if (n < 0) { + throw new EOFException("Expected " + remaining + " more bytes"); } - } finally { - dst.close(); + dst.write(buf, 0, n); + remaining -= n; } - } catch (IOException e) { - log.debug("Unexpected error copying input to CGI", e); + } finally { + dst.close(); } + } catch (IOException e) { + log.debug("Unexpected error copying input to CGI", e); } }, "Gitweb-InputFeeder").start(); } private void copyStderrToLog(final InputStream in) { - new Thread(new Runnable() { - @Override - public void run() { - try (BufferedReader br = - new BufferedReader(new InputStreamReader(in, ISO_8859_1.name()))) { - String line; - while ((line = br.readLine()) != null) { - log.error("CGI: " + line); - } - } catch (IOException e) { - log.debug("Unexpected error copying stderr from CGI", e); + new Thread(() -> { + try (BufferedReader br = + new BufferedReader(new InputStreamReader(in, ISO_8859_1.name()))) { + String line; + while ((line = br.readLine()) != null) { + log.error("CGI: " + line); } + } catch (IOException e) { + log.debug("Unexpected error copying stderr from CGI", e); } }, "Gitweb-ErrorLogger").start(); }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java index 8594e30..cd70143 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -18,14 +18,13 @@ import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CHARACTER_ENCODING; import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CONTENT_TYPE; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toList; import com.google.common.base.CharMatcher; import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.cache.Cache; -import com.google.common.collect.FluentIterable; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.ByteStreams; @@ -74,6 +73,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; +import java.util.function.Predicate; import java.util.jar.Attributes; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -359,7 +359,6 @@ if (Strings.isNullOrEmpty(entryTitle)) { entryTitle = rsrc.substring(nameOffset, rsrc.length() - 3).replace('-', ' '); } - rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html"; } else { entryTitle = rsrc.substring(nameOffset).replace('-', ' '); } @@ -379,34 +378,30 @@ List<PluginEntry> docs = new ArrayList<>(); PluginEntry about = null; - Predicate<PluginEntry> filter = new Predicate<PluginEntry>() { - @Override - public boolean apply(PluginEntry entry) { - String name = entry.getName(); - Optional<Long> size = entry.getSize(); - if (name.startsWith(prefix) - && (name.endsWith(".md") || name.endsWith(".html")) - && size.isPresent()) { - if (size.get() <= 0 || size.get() > SMALL_RESOURCE) { - log.warn(String.format( - "Plugin %s: %s omitted from document index. " - + "Size %d out of range (0,%d).", - pluginName, - name.substring(prefix.length()), - size.get(), - SMALL_RESOURCE)); - return false; + Predicate<PluginEntry> filter = + entry -> { + String name = entry.getName(); + Optional<Long> size = entry.getSize(); + if (name.startsWith(prefix) + && (name.endsWith(".md") || name.endsWith(".html")) + && size.isPresent()) { + if (size.get() <= 0 || size.get() > SMALL_RESOURCE) { + log.warn(String.format( + "Plugin %s: %s omitted from document index. " + + "Size %d out of range (0,%d).", + pluginName, + name.substring(prefix.length()), + size.get(), + SMALL_RESOURCE)); + return false; + } + return true; } - return true; - } - return false; - } - }; + return false; + }; - List<PluginEntry> entries = FluentIterable - .from(Collections.list(scanner.entries())) - .filter(filter) - .toList(); + List<PluginEntry> entries = Collections.list(scanner.entries()).stream() + .filter(filter).collect(toList()); for (PluginEntry entry: entries) { String name = entry.getName().substring(prefix.length()); if (name.startsWith("cmd-")) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.java index ef55e34..3e0f833 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.java
@@ -36,7 +36,7 @@ } else { bowerComponents = GerritLauncher .newZipFileSystem(zip) - .getPath("bower_components/"); + .getPath("/"); } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java index 4f07ac2..c35738b 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
@@ -294,17 +294,14 @@ } private Callable<Resource> newLoader(final Path p) { - return new Callable<Resource>() { - @Override - public Resource call() throws IOException { - try { - return new Resource( - getLastModifiedTime(p), - contentType(p.toString()), - Files.readAllBytes(p)); - } catch (NoSuchFileException e) { - return Resource.NOT_FOUND; - } + return () -> { + try { + return new Resource( + getLastModifiedTime(p), + contentType(p.toString()), + Files.readAllBytes(p)); + } catch (NoSuchFileException e) { + return Resource.NOT_FOUND; } }; }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java index 7916ed0..31e337e 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -15,16 +15,18 @@ package com.google.gerrit.httpd.raw; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import static java.nio.file.Files.exists; import static java.nio.file.Files.isReadable; import com.google.common.cache.Cache; import com.google.common.collect.ImmutableList; -import com.google.gerrit.httpd.GerritOptions; +import com.google.gerrit.extensions.client.UiType; import com.google.gerrit.httpd.XsrfCookieFilter; import com.google.gerrit.httpd.raw.ResourceServlet.Resource; import com.google.gerrit.launcher.GerritLauncher; import com.google.gerrit.server.cache.CacheModule; +import com.google.gerrit.server.config.GerritOptions; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; @@ -46,8 +48,16 @@ import java.nio.file.FileSystem; import java.nio.file.Path; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; public class StaticModule extends ServletModule { @@ -55,20 +65,42 @@ LoggerFactory.getLogger(StaticModule.class); public static final String CACHE = "static_content"; + public static final String GERRIT_UI_COOKIE = "GERRIT_UI"; + /** + * Paths at which we should serve the main PolyGerrit application {@code + * index.html}. + * <p> + * Supports {@code "/*"} as a trailing wildcard. + */ public static final ImmutableList<String> POLYGERRIT_INDEX_PATHS = ImmutableList.of( - "/", - "/c/*", - "/q/*", - "/x/*", - "/admin/*", - "/dashboard/*", - "/settings/*", - // TODO(dborowitz): These fragments conflict with the REST API - // namespace, so they will need to use a different path. - "/groups/*", - "/projects/*"); + "/", + "/c/*", + "/q/*", + "/x/*", + "/admin/*", + "/dashboard/*", + "/settings/*"); + // TODO(dborowitz): These fragments conflict with the REST API + // namespace, so they will need to use a different path. + //"/groups/*", + //"/projects/*"); + // + + /** + * Paths that should be treated as static assets when serving PolyGerrit. + * <p> + * Supports {@code "/*"} as a trailing wildcard. + */ + private static final ImmutableList<String> POLYGERRIT_ASSET_PATHS = + ImmutableList.of( + "/behaviors/*", + "/bower_components/*", + "/elements/*", + "/fonts/*", + "/scripts/*", + "/styles/*"); private static final String DOC_SERVLET = "DocServlet"; private static final String FAVICON_SERVLET = "FaviconServlet"; @@ -77,6 +109,8 @@ "PolyGerritUiIndexServlet"; private static final String ROBOTS_TXT_SERVLET = "RobotsTxtServlet"; + private static final int GERRIT_UI_COOKIE_MAX_AGE = 60 * 60 * 24 * 365; + private final GerritOptions options; private Paths paths; @@ -85,9 +119,11 @@ this.options = options; } + @Provides + @Singleton private Paths getPaths() { if (paths == null) { - paths = new Paths(); + paths = new Paths(options); } return paths; } @@ -104,11 +140,13 @@ .weigher(ResourceServlet.Weigher.class); } }); + if (!options.headless()) { + install(new CoreStaticModule()); + } if (options.enablePolyGerrit()) { - install(new CoreStaticModule()); - install(new PolyGerritUiModule()); - } else if (options.enableDefaultUi()) { - install(new CoreStaticModule()); + install(new PolyGerritModule()); + } + if (options.enableGwtUi()) { install(new GwtUiModule()); } } @@ -211,25 +249,17 @@ } } - private class PolyGerritUiModule extends ServletModule { + private class PolyGerritModule extends ServletModule { @Override public void configureServlets() { - Path buckOut = getPaths().buckOut; - if (buckOut != null) { - serve("/bower_components/*").with(BowerComponentsServlet.class); - serve("/fonts/*").with(FontsServlet.class); - } else { - // In the war case, bower_components and fonts are either inlined - // by vulcanize, or live under /polygerrit_ui in the war file, - // so we don't need a separate servlet. - } - - Key<HttpServlet> indexKey = named(POLYGERRIT_INDEX_SERVLET); for (String p : POLYGERRIT_INDEX_PATHS) { - filter(p).through(XsrfCookieFilter.class); - serve(p).with(indexKey); + // Skip XsrfCookieFilter for /, since that is already done in the GWT UI + // path (UrlModule). + if (!p.equals("/")) { + filter(p).through(XsrfCookieFilter.class); + } } - serve("/*").with(PolyGerritUiServlet.class); + filter("/*").through(PolyGerritFilter.class); } @Provides @@ -281,13 +311,13 @@ } } - private class Paths { + private static class Paths { private final FileSystem warFs; private final Path buckOut; private final Path unpackedWar; private final boolean development; - private Paths() { + private Paths(GerritOptions options) { try { File launcherLoadedFrom = getLauncherLoadedFrom(); if (launcherLoadedFrom != null @@ -393,4 +423,200 @@ private static Key<HttpServlet> named(String name) { return Key.get(HttpServlet.class, Names.named(name)); } + + @Singleton + private static class PolyGerritFilter implements Filter { + private final GerritOptions options; + private final Paths paths; + private final HttpServlet polyGerritIndex; + private final PolyGerritUiServlet polygerritUI; + private final BowerComponentsServlet bowerComponentServlet; + private final FontsServlet fontServlet; + + @Inject + PolyGerritFilter(GerritOptions options, + Paths paths, + @Named(POLYGERRIT_INDEX_SERVLET) HttpServlet polyGerritIndex, + PolyGerritUiServlet polygerritUI, + BowerComponentsServlet bowerComponentServlet, + FontsServlet fontServlet) { + this.paths = paths; + this.options = options; + this.polyGerritIndex = polyGerritIndex; + this.polygerritUI = polygerritUI; + this.bowerComponentServlet = bowerComponentServlet; + this.fontServlet = fontServlet; + checkState(options.enablePolyGerrit(), + "can't install PolyGerritFilter when PolyGerrit is disabled"); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse res = (HttpServletResponse) response; + if (handlePolyGerritParam(req, res)) { + return; + } + if (!isPolyGerritEnabled(req)) { + chain.doFilter(req, res); + return; + } + + GuiceFilterRequestWrapper reqWrapper = + new GuiceFilterRequestWrapper(req); + String path = pathInfo(req); + + // Special case assets during development that are built by Buck and not + // served out of the source tree. + // + // In the war case, these are either inlined by vulcanize, or live under + // /polygerrit_ui in the war file, so we can just treat them as normal + // assets. + if (paths.isDev()) { + if (path.startsWith("/bower_components/")) { + bowerComponentServlet.service(reqWrapper, res); + return; + } else if (path.startsWith("/fonts/")) { + fontServlet.service(reqWrapper, res); + return; + } + } + + if (isPolyGerritIndex(path)) { + polyGerritIndex.service(reqWrapper, res); + return; + } + if (isPolyGerritAsset(path)) { + polygerritUI.service(reqWrapper, res); + return; + } + + chain.doFilter(req, res); + } + + private static String pathInfo(HttpServletRequest req) { + String uri = req.getRequestURI(); + String ctx = req.getContextPath(); + return uri.startsWith(ctx) ? uri.substring(ctx.length()) : uri; + } + + private boolean handlePolyGerritParam(HttpServletRequest req, + HttpServletResponse res) throws IOException { + if (!options.enableGwtUi()) { + return false; + } + boolean redirect = false; + String param = req.getParameter("polygerrit"); + if ("1".equals(param)) { + setPolyGerritCookie(req, res, UiType.POLYGERRIT); + redirect = true; + } else if ("0".equals(param)) { + setPolyGerritCookie(req, res, UiType.GWT); + redirect = true; + } + if (redirect) { + // Strip polygerrit param from URL. This actually strips all params, + // which is a similar behavior to the JS PolyGerrit redirector code. + // Stripping just one param is frustratingly difficult without the use + // of Apache httpclient, which is a dep we don't want here: + // https://gerrit-review.googlesource.com/#/c/57570/57/gerrit-httpd/BUCK@32 + res.sendRedirect(req.getRequestURL().toString()); + } + return redirect; + } + + private boolean isPolyGerritEnabled(HttpServletRequest req) { + return !options.enableGwtUi() || isPolyGerritCookie(req); + } + + private boolean isPolyGerritCookie(HttpServletRequest req) { + UiType type = options.defaultUi(); + Cookie[] all = req.getCookies(); + if (all != null) { + for (Cookie c : all) { + if (GERRIT_UI_COOKIE.equals(c.getName())) { + UiType t = UiType.parse(c.getValue()); + if (t != null) { + type = t; + break; + } + } + } + } + return type == UiType.POLYGERRIT; + } + + private void setPolyGerritCookie(HttpServletRequest req, + HttpServletResponse res, UiType pref) { + // Only actually set a cookie if both UIs are enabled in the server; + // otherwise clear it. + Cookie cookie = new Cookie(GERRIT_UI_COOKIE, pref.name()); + if (options.enablePolyGerrit() && options.enableGwtUi()) { + cookie.setPath("/"); + cookie.setSecure(isSecure(req)); + cookie.setMaxAge(GERRIT_UI_COOKIE_MAX_AGE); + } else { + cookie.setValue(""); + cookie.setMaxAge(0); + } + res.addCookie(cookie); + } + + private static boolean isSecure(HttpServletRequest req) { + return req.isSecure() || "https".equals(req.getScheme()); + } + + private static boolean isPolyGerritAsset(String path) { + return matchPath(POLYGERRIT_ASSET_PATHS, path); + } + + private static boolean isPolyGerritIndex(String path) { + return matchPath(POLYGERRIT_INDEX_PATHS, path); + } + + private static boolean matchPath(Iterable<String> paths, String path) { + for (String p : paths) { + if (p.endsWith("/*")) { + if (path.regionMatches(0, p, 0, p.length() - 1)) { + return true; + } + } else if(p.equals(path)) { + return true; + } + } + return false; + } + } + + private static class GuiceFilterRequestWrapper + extends HttpServletRequestWrapper { + GuiceFilterRequestWrapper(HttpServletRequest req) { + super(req); + } + + @Override + public String getPathInfo() { + String uri = getRequestURI(); + String ctx = getContextPath(); + // This is a workaround for long standing guice filter bug: + // https://github.com/google/guice/issues/807 + String res = uri.startsWith(ctx) ? uri.substring(ctx.length()) : uri; + + // Match the logic in the ResourceServlet, that re-add "/" + // for null path info + if ("/".equals(res)) { + return null; + } + return res; + } + } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java index 943d824..c1a3eec 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -15,9 +15,18 @@ package com.google.gerrit.httpd.restapi; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS; +import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD; +import static com.google.common.net.HttpHeaders.ORIGIN; +import static com.google.common.net.HttpHeaders.VARY; import static java.math.RoundingMode.CEILING; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; @@ -33,11 +42,12 @@ import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED; import com.google.common.base.CharMatcher; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; @@ -85,6 +95,7 @@ import com.google.gerrit.server.OptionUtil; import com.google.gerrit.server.OutputFormat; import com.google.gerrit.server.account.CapabilityUtils; +import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.util.http.RequestUtil; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; @@ -103,6 +114,7 @@ import com.google.inject.Provider; import com.google.inject.util.Providers; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.TemporaryBuffer.Heap; import org.slf4j.Logger; @@ -131,6 +143,8 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.stream.StreamSupport; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletException; @@ -150,6 +164,9 @@ // HTTP 422 Unprocessable Entity. // TODO: Remove when HttpServletResponse.SC_UNPROCESSABLE_ENTITY is available private static final int SC_UNPROCESSABLE_ENTITY = 422; + private static final String X_REQUESTED_WITH = "X-Requested-With"; + private static final ImmutableSet<String> ALLOWED_CORS_REQUEST_HEADERS = + ImmutableSet.of(X_REQUESTED_WITH); private static final int HEAP_EST_SIZE = 10 * 8 * 1024; // Presize 10 blocks. @@ -174,18 +191,29 @@ final Provider<ParameterParser> paramParser; final AuditService auditService; final RestApiMetrics metrics; + final Pattern allowOrigin; @Inject Globals(Provider<CurrentUser> currentUser, DynamicItem<WebSession> webSession, Provider<ParameterParser> paramParser, AuditService auditService, - RestApiMetrics metrics) { + RestApiMetrics metrics, + @GerritServerConfig Config cfg) { this.currentUser = currentUser; this.webSession = webSession; this.paramParser = paramParser; this.auditService = auditService; this.metrics = metrics; + allowOrigin = makeAllowOrigin(cfg); + } + + private static Pattern makeAllowOrigin(Config cfg) { + String[] allow = cfg.getStringList("site", null, "allowOriginRegex"); + if (allow.length > 0) { + return Pattern.compile(Joiner.on('|').join(allow)); + } + return null; } } @@ -222,6 +250,11 @@ ViewData viewData = null; try { + if (isCorsPreflight(req)) { + doCorsPreflight(req, res); + return; + } + checkCors(req, res); checkUserSession(req); List<IdString> path = splitPath(req); @@ -232,7 +265,7 @@ viewData = new ViewData(null, null); if (path.isEmpty()) { - if (isGetOrHead(req)) { + if (isRead(req)) { viewData = new ViewData(null, rc.list()); } else if (rc instanceof AcceptsPost && "POST".equals(req.getMethod())) { @SuppressWarnings("unchecked") @@ -273,7 +306,7 @@ (RestCollection<RestResource, RestResource>) viewData.view; if (path.isEmpty()) { - if (isGetOrHead(req)) { + if (isRead(req)) { viewData = new ViewData(null, c.list()); } else if (c instanceof AcceptsPost && "POST".equals(req.getMethod())) { @SuppressWarnings("unchecked") @@ -330,7 +363,7 @@ return; } - if (viewData.view instanceof RestReadView<?> && isGetOrHead(req)) { + if (viewData.view instanceof RestReadView<?> && isRead(req)) { result = ((RestReadView<RestResource>) viewData.view).apply(rsrc); } else if (viewData.view instanceof RestModifyView<?, ?>) { @SuppressWarnings("unchecked") @@ -428,6 +461,74 @@ } } + private void checkCors(HttpServletRequest req, HttpServletResponse res) { + String origin = req.getHeader(ORIGIN); + if (isRead(req) + && !Strings.isNullOrEmpty(origin) + && isOriginAllowed(origin)) { + res.addHeader(VARY, ORIGIN); + setCorsHeaders(res, origin); + } + } + + private static boolean isCorsPreflight(HttpServletRequest req) { + return "OPTIONS".equals(req.getMethod()) + && !Strings.isNullOrEmpty(req.getHeader(ORIGIN)) + && !Strings.isNullOrEmpty(req.getHeader(ACCESS_CONTROL_REQUEST_METHOD)); + } + + private void doCorsPreflight(HttpServletRequest req, + HttpServletResponse res) throws BadRequestException { + CacheHeaders.setNotCacheable(res); + res.setHeader(VARY, Joiner.on(", ").join(ImmutableList.of( + ORIGIN, + ACCESS_CONTROL_REQUEST_METHOD))); + + String origin = req.getHeader(ORIGIN); + if (Strings.isNullOrEmpty(origin) || !isOriginAllowed(origin)) { + throw new BadRequestException("CORS not allowed"); + } + + String method = req.getHeader(ACCESS_CONTROL_REQUEST_METHOD); + if (!"GET".equals(method) && !"HEAD".equals(method)) { + throw new BadRequestException(method + " not allowed in CORS"); + } + + String headers = req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS); + if (headers != null) { + res.addHeader(VARY, ACCESS_CONTROL_REQUEST_HEADERS); + String badHeader = + StreamSupport.stream( + Splitter.on(',').trimResults().split(headers).spliterator(), + false) + .filter(h -> !ALLOWED_CORS_REQUEST_HEADERS.contains(h)) + .findFirst() + .orElse(null); + if (badHeader != null) { + throw new BadRequestException(badHeader + " not allowed in CORS"); + } + } + + res.setStatus(SC_OK); + setCorsHeaders(res, origin); + res.setContentType("text/plain"); + res.setContentLength(0); + } + + private void setCorsHeaders(HttpServletResponse res, String origin) { + res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin); + res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET, OPTIONS"); + res.setHeader( + ACCESS_CONTROL_ALLOW_HEADERS, + Joiner.on(", ").join(ALLOWED_CORS_REQUEST_HEADERS)); + } + + private boolean isOriginAllowed(String origin) { + return globals.allowOrigin != null + && globals.allowOrigin.matcher(origin).matches(); + } + private static String messageOr(Throwable t, String defaultMessage) { if (!Strings.isNullOrEmpty(t.getMessage())) { return t.getMessage(); @@ -438,7 +539,7 @@ @SuppressWarnings({"unchecked", "rawtypes"}) private static boolean notModified(HttpServletRequest req, RestResource rsrc, RestView<RestResource> view) { - if (!isGetOrHead(req)) { + if (!isRead(req)) { return false; } @@ -469,7 +570,7 @@ private static <R extends RestResource> void configureCaching( HttpServletRequest req, HttpServletResponse res, R rsrc, RestView<R> view, CacheControl c) { - if (isGetOrHead(req)) { + if (isRead(req)) { switch (c.getType()) { case NONE: default: @@ -935,16 +1036,12 @@ } else if (r.isEmpty()) { throw new ResourceNotFoundException(projection); } else { - throw new AmbiguousViewException(String.format( - "Projection %s is ambiguous: %s", - name, - Joiner.on(", ").join( - Iterables.transform(r.keySet(), new Function<String, String>() { - @Override - public String apply(String in) { - return in + "~" + projection; - } - })))); + throw new AmbiguousViewException( + String.format( + "Projection %s is ambiguous: %s", + name, + r.keySet().stream().map(in -> in + "~" + projection) + .collect(joining(", ")))); } } @@ -972,25 +1069,20 @@ private void checkUserSession(HttpServletRequest req) throws AuthException { CurrentUser user = globals.currentUser.get(); - if (isStateChange(req)) { - if (user instanceof AnonymousUser) { - throw new AuthException("Authentication required"); - } else if (!globals.webSession.get().isAccessPathOk(AccessPath.REST_API)) { - throw new AuthException("Invalid authentication method. In order to authenticate, " - + "prefix the REST endpoint URL with /a/ (e.g. http://example.com/a/projects/)."); - } + if (isRead(req)) { + user.setAccessPath(AccessPath.REST_API); + } else if (user instanceof AnonymousUser) { + throw new AuthException("Authentication required"); + } else if (!globals.webSession.get().isAccessPathOk(AccessPath.REST_API)) { + throw new AuthException("Invalid authentication method. In order to authenticate, " + + "prefix the REST endpoint URL with /a/ (e.g. http://example.com/a/projects/)."); } - user.setAccessPath(AccessPath.REST_API); } - private static boolean isGetOrHead(HttpServletRequest req) { + private static boolean isRead(HttpServletRequest req) { return "GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod()); } - private static boolean isStateChange(HttpServletRequest req) { - return !isGetOrHead(req); - } - private void checkRequiresCapability(ViewData viewData) throws AuthException { CapabilityUtils.checkRequiresCapability(globals.currentUser, viewData.pluginName, viewData.view.getClass()); @@ -1029,7 +1121,7 @@ static long replyText(@Nullable HttpServletRequest req, HttpServletResponse res, String text) throws IOException { - if ((req == null || isGetOrHead(req)) && isMaybeHTML(text)) { + if ((req == null || isRead(req)) && isMaybeHTML(text)) { return replyJson(req, res, ImmutableMultimap.of("pp", "0"), new JsonPrimitive(text)); } if (!text.endsWith("\n")) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java index c0fb86b..bda2d91 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SystemInfoServiceImpl.java
@@ -14,11 +14,8 @@ package com.google.gerrit.httpd.rpc; -import com.google.common.collect.Lists; -import com.google.gerrit.common.data.ContributorAgreement; import com.google.gerrit.common.data.SshHostKey; import com.google.gerrit.common.data.SystemInfoService; -import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.ssh.SshInfo; import com.google.gwtjsonrpc.common.AsyncCallback; import com.google.gwtjsonrpc.common.VoidResult; @@ -32,7 +29,6 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -45,28 +41,12 @@ private final List<HostKey> hostKeys; private final Provider<HttpServletRequest> httpRequest; - private final ProjectCache projectCache; @Inject SystemInfoServiceImpl(SshInfo daemon, - Provider<HttpServletRequest> hsr, - ProjectCache pc) { + Provider<HttpServletRequest> hsr) { hostKeys = daemon.getHostKeys(); httpRequest = hsr; - projectCache = pc; - } - - @Override - public void contributorAgreements( - final AsyncCallback<List<ContributorAgreement>> callback) { - Collection<ContributorAgreement> agreements = - projectCache.getAllProjects().getConfig().getContributorAgreements(); - List<ContributorAgreement> cas = - Lists.newArrayListWithCapacity(agreements.size()); - for (ContributorAgreement ca : agreements) { - cas.add(ca.forUi()); - } - callback.onSuccess(cas); } @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java index 62778eb..d32fdaf 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
@@ -28,12 +28,10 @@ install(new FactoryModule() { @Override protected void configure() { - factory(AgreementInfoFactory.Factory.class); factory(DeleteExternalIds.Factory.class); factory(ExternalIdDetailFactory.Factory.class); } }); rpc(AccountSecurityImpl.class); - rpc(AccountServiceImpl.class); } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java index 8fcf9ea..3d05548 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -14,74 +14,31 @@ package com.google.gerrit.httpd.rpc.account; -import com.google.common.base.Strings; -import com.google.gerrit.audit.AuditService; import com.google.gerrit.common.data.AccountSecurity; -import com.google.gerrit.common.data.ContributorAgreement; -import com.google.gerrit.common.errors.NoSuchEntityException; -import com.google.gerrit.common.errors.PermissionDeniedException; import com.google.gerrit.httpd.rpc.BaseServiceImplementation; -import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; -import com.google.gerrit.reviewdb.client.AccountGroup; -import com.google.gerrit.reviewdb.client.AccountGroupMember; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.account.AccountByEmailCache; -import com.google.gerrit.server.account.AccountCache; -import com.google.gerrit.server.account.GroupCache; -import com.google.gerrit.server.account.Realm; -import com.google.gerrit.server.extensions.events.AgreementSignup; -import com.google.gerrit.server.project.ProjectCache; import com.google.gwtjsonrpc.common.AsyncCallback; -import com.google.gwtjsonrpc.common.VoidResult; -import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; -import java.io.IOException; -import java.util.Collections; import java.util.List; import java.util.Set; class AccountSecurityImpl extends BaseServiceImplementation implements AccountSecurity { - private final Realm realm; - private final ProjectCache projectCache; - private final Provider<IdentifiedUser> user; - private final AccountByEmailCache byEmailCache; - private final AccountCache accountCache; - private final DeleteExternalIds.Factory deleteExternalIdsFactory; private final ExternalIdDetailFactory.Factory externalIdDetailFactory; - private final GroupCache groupCache; - private final AuditService auditService; - private final AgreementSignup agreementSignup; - @Inject AccountSecurityImpl(final Provider<ReviewDb> schema, final Provider<CurrentUser> currentUser, - final Realm r, final Provider<IdentifiedUser> u, - final ProjectCache pc, - final AccountByEmailCache abec, final AccountCache uac, final DeleteExternalIds.Factory deleteExternalIdsFactory, - final ExternalIdDetailFactory.Factory externalIdDetailFactory, - final GroupCache groupCache, - final AuditService auditService, - AgreementSignup agreementSignup) { + final ExternalIdDetailFactory.Factory externalIdDetailFactory) { super(schema, currentUser); - realm = r; - user = u; - projectCache = pc; - byEmailCache = abec; - accountCache = uac; - this.auditService = auditService; this.deleteExternalIdsFactory = deleteExternalIdsFactory; this.externalIdDetailFactory = externalIdDetailFactory; - this.groupCache = groupCache; - this.agreementSignup = agreementSignup; } @Override @@ -94,84 +51,4 @@ final AsyncCallback<Set<AccountExternalId.Key>> callback) { deleteExternalIdsFactory.create(keys).to(callback); } - - @Override - public void updateContact(final String name, final String emailAddr, - final AsyncCallback<Account> callback) { - run(callback, new Action<Account>() { - @Override - public Account run(ReviewDb db) - throws OrmException, Failure, IOException { - IdentifiedUser self = user.get(); - final Account me = db.accounts().get(self.getAccountId()); - final String oldEmail = me.getPreferredEmail(); - if (realm.allowsEdit(Account.FieldName.FULL_NAME)) { - me.setFullName(Strings.emptyToNull(name)); - } - if (!Strings.isNullOrEmpty(emailAddr) - && !self.hasEmailAddress(emailAddr)) { - throw new Failure(new PermissionDeniedException("Email address must be verified")); - } - me.setPreferredEmail(Strings.emptyToNull(emailAddr)); - db.accounts().update(Collections.singleton(me)); - if (!eq(oldEmail, me.getPreferredEmail())) { - byEmailCache.evict(oldEmail); - byEmailCache.evict(me.getPreferredEmail()); - } - accountCache.evict(me.getId()); - return me; - } - }); - } - - private static boolean eq(final String a, final String b) { - if (a == null && b == null) { - return true; - } - return a != null && a.equals(b); - } - - @Override - public void enterAgreement(final String agreementName, - final AsyncCallback<VoidResult> callback) { - run(callback, new Action<VoidResult>() { - @Override - public VoidResult run(final ReviewDb db) - throws OrmException, Failure, IOException { - ContributorAgreement ca = projectCache.getAllProjects().getConfig() - .getContributorAgreement(agreementName); - if (ca == null) { - throw new Failure(new NoSuchEntityException()); - } - - if (ca.getAutoVerify() == null) { - throw new Failure(new IllegalStateException( - "cannot enter a non-autoVerify agreement")); - } else if (ca.getAutoVerify().getUUID() == null) { - throw new Failure(new NoSuchEntityException()); - } - - AccountGroup group = groupCache.get(ca.getAutoVerify().getUUID()); - if (group == null) { - throw new Failure(new NoSuchEntityException()); - } - - Account account = user.get().getAccount(); - agreementSignup.fire(account, ca.getName()); - - final AccountGroupMember.Key key = - new AccountGroupMember.Key(account.getId(), group.getId()); - AccountGroupMember m = db.accountGroupMembers().get(key); - if (m == null) { - m = new AccountGroupMember(key); - auditService.dispatchAddAccountsToGroup(account.getId(), Collections - .singleton(m)); - db.accountGroupMembers().insert(Collections.singleton(m)); - accountCache.evict(m.getAccountId()); - } - - return VoidResult.INSTANCE; - } - }); - } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java deleted file mode 100644 index 8fba47d..0000000 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java +++ /dev/null
@@ -1,42 +0,0 @@ -// Copyright (C) 2008 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.httpd.rpc.account; - -import com.google.gerrit.common.data.AccountService; -import com.google.gerrit.common.data.AgreementInfo; -import com.google.gerrit.httpd.rpc.BaseServiceImplementation; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gwtjsonrpc.common.AsyncCallback; -import com.google.inject.Inject; -import com.google.inject.Provider; - -class AccountServiceImpl extends BaseServiceImplementation implements - AccountService { - private final AgreementInfoFactory.Factory agreementInfoFactory; - - @Inject - AccountServiceImpl(final Provider<ReviewDb> schema, - final Provider<IdentifiedUser> identifiedUser, - final AgreementInfoFactory.Factory agreementInfoFactory) { - super(schema, identifiedUser); - this.agreementInfoFactory = agreementInfoFactory; - } - - @Override - public void myAgreements(final AsyncCallback<AgreementInfo> callback) { - agreementInfoFactory.create().to(callback); - } -}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java deleted file mode 100644 index 91afd97..0000000 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java +++ /dev/null
@@ -1,85 +0,0 @@ -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.httpd.rpc.account; - -import com.google.gerrit.common.data.AgreementInfo; -import com.google.gerrit.common.data.ContributorAgreement; -import com.google.gerrit.common.data.PermissionRule; -import com.google.gerrit.common.data.PermissionRule.Action; -import com.google.gerrit.httpd.rpc.Handler; -import com.google.gerrit.reviewdb.client.AccountGroup; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.project.ProjectCache; -import com.google.inject.Inject; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -class AgreementInfoFactory extends Handler<AgreementInfo> { - private static final Logger log = LoggerFactory.getLogger(AgreementInfoFactory.class); - - interface Factory { - AgreementInfoFactory create(); - } - - private final IdentifiedUser user; - private final ProjectCache projectCache; - - private AgreementInfo info; - - @Inject - AgreementInfoFactory(final IdentifiedUser user, - final ProjectCache projectCache) { - this.user = user; - this.projectCache = projectCache; - } - - @Override - public AgreementInfo call() throws Exception { - List<String> accepted = new ArrayList<>(); - Map<String, ContributorAgreement> agreements = new HashMap<>(); - Collection<ContributorAgreement> cas = - projectCache.getAllProjects().getConfig().getContributorAgreements(); - for (ContributorAgreement ca : cas) { - agreements.put(ca.getName(), ca.forUi()); - - List<AccountGroup.UUID> groupIds = new ArrayList<>(); - for (PermissionRule rule : ca.getAccepted()) { - if ((rule.getAction() == Action.ALLOW) && (rule.getGroup() != null)) { - if (rule.getGroup().getUUID() == null) { - log.warn("group \"" + rule.getGroup().getName() + "\" does not " + - " exist, referenced in CLA \"" + ca.getName() + "\""); - } else { - groupIds.add(new AccountGroup.UUID(rule.getGroup().getUUID().get())); - } - } - } - if (user.getEffectiveGroups().containsAnyOf(groupIds)) { - accepted.add(ca.getName()); - } - } - - info = new AgreementInfo(); - info.setAccepted(accepted); - info.setAgreements(agreements); - return info; - } -}
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 ed2a4f9..bd88e6a 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
@@ -14,7 +14,6 @@ package com.google.gerrit.httpd.rpc.project; -import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.Maps; import com.google.gerrit.common.data.AccessSection; @@ -238,14 +237,7 @@ } } } - return Maps.filterEntries( - infos, - new Predicate<Map.Entry<AccountGroup.UUID, GroupInfo>>() { - @Override - public boolean apply(Map.Entry<AccountGroup.UUID, GroupInfo> in) { - return in.getValue() != null; - } - }); + return Maps.filterEntries(infos, in -> in.getValue() != null); } private ProjectControl open() throws NoSuchProjectException {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java index 2a506ba..29b2918 100644 --- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java +++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -24,7 +24,6 @@ import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES; import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES; -import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.FluentIterable; @@ -585,17 +584,12 @@ cd.setStars(stars); } - private void decodeReviewers(Multimap<String, IndexableField> doc, ChangeData cd) { + private void decodeReviewers(Multimap<String, IndexableField> doc, + ChangeData cd) { cd.setReviewers( ChangeField.parseReviewerFieldValues( FluentIterable.from(doc.get(REVIEWER_FIELD)) - .transform( - new Function<IndexableField, String>() { - @Override - public String apply(IndexableField in) { - return in.stringValue(); - } - }))); + .transform(IndexableField::stringValue))); } private static <T> List<T> decodeProtos(Multimap<String, IndexableField> doc,
diff --git a/gerrit-main/BUILD b/gerrit-main/BUILD new file mode 100644 index 0000000..67863c5 --- /dev/null +++ b/gerrit-main/BUILD
@@ -0,0 +1,13 @@ +java_binary( + name = 'main_bin', + main_class = 'Main', + runtime_deps = [':main_lib'], + visibility = ['//visibility:public'], +) + +java_library( + name = 'main_lib', + srcs = ['src/main/java/Main.java'], + deps = ['//gerrit-launcher:launcher'], + visibility = ['//visibility:public'], +)
diff --git a/gerrit-main/src/main/java/Main.java b/gerrit-main/src/main/java/Main.java index a29f1c6..58de6a4 100644 --- a/gerrit-main/src/main/java/Main.java +++ b/gerrit-main/src/main/java/Main.java
@@ -31,11 +31,11 @@ private static boolean onSupportedJavaVersion() { final String version = System.getProperty("java.specification.version"); - if (1.7 <= parse(version)) { + if (1.8 <= parse(version)) { return true; } - System.err.println("fatal: Gerrit Code Review requires Java 7 or later"); + System.err.println("fatal: Gerrit Code Review requires Java 8 or later"); System.err.println(" (trying to run on Java " + version + ")"); return false; }
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java index 3a40252..791f9fd 100644 --- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java +++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -24,12 +24,12 @@ import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.auth.openid.OpenIdUrls; import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.httpd.HtmlDomUtil; import com.google.gerrit.httpd.LoginUrlToken; import com.google.gerrit.httpd.template.SiteHeaderFooter; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.CanonicalWebUrl;
diff --git a/gerrit-patch-jgit/BUCK b/gerrit-patch-jgit/BUCK index 09ccf9c..4a4929e 100644 --- a/gerrit-patch-jgit/BUCK +++ b/gerrit-patch-jgit/BUCK
@@ -33,7 +33,7 @@ 'org/eclipse/jgit/diff/Edit.java;' + 'cd $TMP;' + 'zip -Dq $OUT org/eclipse/jgit/diff/Edit.java', - out = 'edit.src.zip', + out = 'edit-sources.jar', ) java_library( @@ -61,6 +61,5 @@ '//lib/jgit/org.eclipse.jgit:jgit', '//lib:junit', ], - source_under_test = [':server'], visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK index 4be941c..8852133 100644 --- a/gerrit-pgm/BUCK +++ b/gerrit-pgm/BUCK
@@ -180,5 +180,4 @@ '//lib/jgit/org.eclipse.jgit:jgit', '//lib/jgit/org.eclipse.jgit.junit:junit', ], - source_under_test = [':pgm'], )
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD index 59b371a..ec86c9a 100644 --- a/gerrit-pgm/BUILD +++ b/gerrit-pgm/BUILD
@@ -19,6 +19,7 @@ '//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 7ee3cde..078fdaf 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
@@ -20,9 +20,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.gerrit.common.EventBroker; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.gpg.GpgModule; import com.google.gerrit.httpd.AllRequestFilter; -import com.google.gerrit.httpd.GerritOptions; import com.google.gerrit.httpd.GetUserFilter; import com.google.gerrit.httpd.GitOverHttpModule; import com.google.gerrit.httpd.H2CacheBasedWebSession; @@ -46,7 +46,6 @@ import com.google.gerrit.pgm.util.LogFileCompressor; import com.google.gerrit.pgm.util.RuntimeShutdown; import com.google.gerrit.pgm.util.SiteProgram; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.account.InternalAccountDirectory; import com.google.gerrit.server.cache.h2.DefaultCacheFactory; import com.google.gerrit.server.change.ChangeCleanupRunner; @@ -56,6 +55,7 @@ import com.google.gerrit.server.config.CanonicalWebUrlProvider; import com.google.gerrit.server.config.DownloadConfig; import com.google.gerrit.server.config.GerritGlobalModule; +import com.google.gerrit.server.config.GerritOptions; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.RestCacheAdminModule; import com.google.gerrit.server.events.StreamEventsApiListener;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java index 9ae2d6c..2ed4c36 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -14,9 +14,8 @@ package com.google.gerrit.pgm; -import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.google.gerrit.common.IoUtil; import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.PluginData; @@ -42,6 +41,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; /** Initialize a new Gerrit installation. */ public class Init extends BaseInit { @@ -237,16 +237,10 @@ if (nullOrEmpty(installPlugins) || nullOrEmpty(plugins)) { return; } - ArrayList<String> copy = Lists.newArrayList(installPlugins); - List<String> pluginNames = Lists.transform(plugins, new Function<PluginData, String>() { - @Override - public String apply(PluginData input) { - return input.name; - } - }); - copy.removeAll(pluginNames); - if (!copy.isEmpty()) { - ui.message("Cannot find plugin(s): %s\n", Joiner.on(", ").join(copy)); + Set<String> missing = Sets.newHashSet(installPlugins); + plugins.stream().forEach(p -> missing.remove(p.name)); + if (!missing.isEmpty()) { + ui.message("Cannot find plugin(s): %s\n", Joiner.on(", ").join(missing)); listPlugins = true; } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java index 0adb1af..030ac30 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java
@@ -17,7 +17,6 @@ import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb; import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER; -import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.base.Stopwatch; import com.google.common.collect.ArrayListMultimap; @@ -47,8 +46,8 @@ import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.index.DummyIndexModule; import com.google.gerrit.server.index.change.ReindexAfterUpdate; -import com.google.gerrit.server.notedb.ChangeRebuilder; import com.google.gerrit.server.notedb.NotesMigration; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject; @@ -234,13 +233,8 @@ ArrayListMultimap.create(); try (ReviewDb db = schemaFactory.open()) { if (projects.isEmpty() && !changes.isEmpty()) { - Iterable<Change> todo = unwrapDb(db).changes().get( - Iterables.transform(changes, new Function<Integer, Change.Id>() { - @Override - public Change.Id apply(Integer in) { - return new Change.Id(in); - } - })); + Iterable<Change> todo = unwrapDb(db).changes() + .get(Iterables.transform(changes, Change.Id::new)); for (Change c : todo) { changesByProject.put(c.getProject(), c.getId()); }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java index 2e7d88a..501b115 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -16,10 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER; +import static java.util.stream.Collectors.toSet; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.gerrit.common.Die; import com.google.gerrit.extensions.config.FactoryModule; @@ -134,14 +132,8 @@ } checkNotNull(indexDefs, "Called this method before injectMembers?"); - Set<String> valid = FluentIterable.from(indexDefs).transform( - new Function<IndexDefinition<?, ?, ?>, String>() { - @Override - public String apply(IndexDefinition<?, ?, ?> input) { - return input.getName(); - } - }).toSortedSet(Ordering.natural()); - + Set<String> valid = indexDefs.stream() + .map(IndexDefinition::getName).sorted().collect(toSet()); Set<String> invalid = Sets.difference(Sets.newHashSet(indices), valid); if (invalid.isEmpty()) { return;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java index 9d27170..f5212ab 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -18,9 +18,9 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.base.Strings; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.pgm.http.jetty.HttpLog.HttpLogFactory; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.ThreadSettingsConfig;
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 2de71cc..136ec5a 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
@@ -18,6 +18,7 @@ import com.google.common.base.Strings; import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.pgm.init.api.ConsoleUI; import com.google.gerrit.pgm.init.api.InitFlags; import com.google.gerrit.pgm.init.api.InitStep; @@ -27,7 +28,6 @@ import com.google.gerrit.reviewdb.client.AccountGroupMember; import com.google.gerrit.reviewdb.client.AccountGroupName; import com.google.gerrit.reviewdb.client.AccountSshKey; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java index 6b30f80..f4bcd86 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -16,11 +16,11 @@ import static com.google.gerrit.pgm.init.api.InitUtil.dnOf; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.pgm.init.api.ConsoleUI; import com.google.gerrit.pgm.init.api.InitFlags; import com.google.gerrit.pgm.init.api.InitStep; import com.google.gerrit.pgm.init.api.Section; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gwtjsonrpc.server.SignedToken; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -135,8 +135,4 @@ libraries.bouncyCastlePGP.downloadRequired(); } } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java index 4e5d044..b5b230d 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
@@ -54,8 +54,4 @@ Path loc = site.resolve(path); FileUtil.mkdirsOrDie(loc, "cannot create cache.directory"); } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java index 36754a1..03ddd7b 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -117,8 +117,4 @@ private static String javaHome() { return System.getProperty("java.home"); } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java index 7e4d3c1..47783e4 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -102,8 +102,4 @@ GerritServerIdProvider.KEY, GerritServerIdProvider.generate()); } } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java index d8fd509..19eaa3c 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
@@ -47,8 +47,4 @@ } FileUtil.mkdirsOrDie(d, "Cannot create"); } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java index a907d46..72a70c9 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -202,8 +202,4 @@ throw die("Cannot delete " + tmpdir, e); } } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java index 018211b..185063b 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -87,8 +87,4 @@ return true; } } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java index 5c7eefd..ed5f7d2 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -64,8 +64,4 @@ sendemail.string("SMTP username", "smtpUser", username); sendemail.password("smtpUser", "smtpPass"); } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java index cb4439a..63d4883 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -163,8 +163,4 @@ System.err.println(" done"); } } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java index f16e2ec..1d7195b 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -1,4 +1,4 @@ -// Copyright (C) 2009 The Android Open Source Project +// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -99,21 +99,31 @@ chmod(0755, site.gerrit_sh); chmod(0700, site.tmp_dir); - extractMailExample("Abandoned.vm"); - extractMailExample("AddKey.vm"); - extractMailExample("ChangeFooter.vm"); - extractMailExample("ChangeSubject.vm"); - extractMailExample("Comment.vm"); - extractMailExample("CommentFooter.vm"); - extractMailExample("DeleteReviewer.vm"); - extractMailExample("DeleteVote.vm"); - extractMailExample("Footer.vm"); - extractMailExample("Merged.vm"); - extractMailExample("NewChange.vm"); - extractMailExample("RegisterNewEmail.vm"); - extractMailExample("ReplacePatchSet.vm"); - extractMailExample("Restored.vm"); - extractMailExample("Reverted.vm"); + extractMailExample("Abandoned.soy"); + extractMailExample("AbandonedHtml.soy"); + extractMailExample("AddKey.soy"); + extractMailExample("ChangeFooter.soy"); + extractMailExample("ChangeFooterHtml.soy"); + extractMailExample("ChangeSubject.soy"); + extractMailExample("Comment.soy"); + extractMailExample("CommentFooter.soy"); + extractMailExample("DeleteReviewer.soy"); + extractMailExample("DeleteReviewerHtml.soy"); + extractMailExample("DeleteVote.soy"); + extractMailExample("DeleteVoteHtml.soy"); + extractMailExample("Footer.soy"); + extractMailExample("FooterHtml.soy"); + extractMailExample("HeaderHtml.soy"); + extractMailExample("Merged.soy"); + extractMailExample("NewChange.soy"); + extractMailExample("NewChangeHtml.soy"); + extractMailExample("RegisterNewEmail.soy"); + extractMailExample("ReplacePatchSet.soy"); + extractMailExample("ReplacePatchSetHtml.soy"); + extractMailExample("Restored.soy"); + extractMailExample("RestoredHtml.soy"); + extractMailExample("Reverted.soy"); + extractMailExample("RevertedHtml.soy"); if (!ui.isBatch()) { System.err.println();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java index 52f9096..87b24f9 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -286,8 +286,4 @@ } return null; } - - @Override - public void postRun() throws Exception { - } }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java index fd28399..9d4becc 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
@@ -19,5 +19,5 @@ void run() throws Exception; /** Executed after the site has been initialized */ - void postRun() throws Exception; + default void postRun() throws Exception {} }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java index 0360cd6..d39c2fd 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java
@@ -19,7 +19,6 @@ import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.server.git.GitModule; import com.google.gerrit.server.git.validators.CommitValidationListener; -import com.google.gerrit.server.git.validators.CommitValidators; /** Module for batch programs that need git access. */ public class BatchGitModule extends FactoryModule { @@ -27,7 +26,6 @@ protected void configure() { DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class); DynamicSet.setOf(binder(), CommitValidationListener.class); - factory(CommitValidators.Factory.class); install(new GitModule()); } }
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK index c18e497..7e38726 100644 --- a/gerrit-plugin-api/BUCK +++ b/gerrit-plugin-api/BUCK
@@ -13,10 +13,20 @@ java_binary( name = 'plugin-api', + merge_manifests = False, + manifest_file = ':manifest', deps = [':lib'], visibility = ['PUBLIC'], ) +genrule( + name = 'manifest', + cmd = 'echo "Manifest-Version: 1.0" >$OUT;' + + 'echo "Implementation-Title: Gerrit Plugin API" >>$OUT;' + + 'echo "Implementation-Vendor: Gerrit Code Review Project" >>$OUT', + out = 'manifest.txt', +) + java_library( name = 'lib', exported_deps = PLUGIN_API + [ @@ -32,20 +42,30 @@ '//lib:gson', '//lib:guava', '//lib:gwtorm', + '//lib:icu4j', '//lib:jsch', + '//lib:jsr305', '//lib:mime-util', + '//lib:protobuf', '//lib:servlet-api-3_1', + '//lib:soy', '//lib:velocity', '//lib/commons:lang', '//lib/dropwizard:dropwizard-core', '//lib/guice:guice', '//lib/guice:guice-assistedinject', + '//lib/guice:javax-inject', + '//lib/guice:multibindings', '//lib/guice:guice-servlet', '//lib/jgit/org.eclipse.jgit:jgit', '//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet', '//lib/joda:joda-time', '//lib/log:api', '//lib/mina:sshd', + '//lib/ow2:ow2-asm', + '//lib/ow2:ow2-asm-analysis', + '//lib/ow2:ow2-asm-commons', + '//lib/ow2:ow2-asm-util', '//lib/prolog:compiler', ], visibility = ['PUBLIC'], @@ -63,7 +83,7 @@ name = 'plugin-api-javadoc', title = 'Gerrit Review Plugin API Documentation', pkgs = ['com.google.gerrit'], - paths = [n for n in SRCS], + source_jar = ':plugin-api-src', srcs = glob([n + '**/*.java' for n in SRCS]), deps = [ ':plugin-api', @@ -72,5 +92,4 @@ '//lib/bouncycastle:bcpkix', ], visibility = ['PUBLIC'], - do_it_wrong = True, )
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD index 2c18ca6..9404acc 100644 --- a/gerrit-plugin-api/BUILD +++ b/gerrit-plugin-api/BUILD
@@ -28,24 +28,68 @@ '//gerrit-extension-api:api', '//gerrit-gwtexpui:server', '//gerrit-reviewdb:server', - '//lib:args4j', - '//lib:blame-cache', - '//lib/dropwizard:dropwizard-core', - '//lib:guava', - '//lib:gwtorm', - '//lib:jsch', - '//lib:mime-util', - '//lib:servlet-api-3_1', - '//lib:velocity', '//lib/commons:lang', + '//lib/dropwizard:dropwizard-core', '//lib/guice:guice', '//lib/guice:guice-assistedinject', '//lib/guice:guice-servlet', - '//lib/jgit/org.eclipse.jgit:jgit', + '//lib/guice:javax-inject', + '//lib/guice:multibindings', '//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet', + '//lib/jgit/org.eclipse.jgit:jgit', '//lib/joda:joda-time', '//lib/log:api', '//lib/mina:sshd', + '//lib/ow2:ow2-asm', + '//lib/ow2:ow2-asm-analysis', + '//lib/ow2:ow2-asm-commons', + '//lib/ow2:ow2-asm-util', + '//lib:args4j', + '//lib:blame-cache', + '//lib:guava', + '//lib:gwtorm', + '//lib:icu4j', + '//lib:jsch', + '//lib:mime-util', + '//lib:protobuf', + '//lib:servlet-api-3_1', + '//lib:soy', + '//lib:velocity', ], visibility = ['//visibility:public'], ) + +java_binary( + name = 'plugin-api-sources', + main_class = 'Dummy', + runtime_deps = [ + '//gerrit-antlr:libquery_exception-src.jar', + '//gerrit-antlr:libquery_parser-src.jar', + '//gerrit-common:libannotations-src.jar', + '//gerrit-extension-api:libapi-src.jar', + '//gerrit-gwtexpui:libserver-src.jar', + '//gerrit-httpd:libhttpd-src.jar', + '//gerrit-pgm:libinit-api-src.jar', + '//gerrit-reviewdb:libserver-src.jar', + '//gerrit-server:libserver-src.jar', + '//gerrit-sshd:libsshd-src.jar', + ], + visibility = ['//visibility:public'], +) + +load('//tools/bzl:javadoc.bzl', 'java_doc') + +java_doc( + name = 'plugin-api-javadoc', + title = 'Gerrit Review Plugin API Documentation', + pkgs = ['com.google.gerrit'], + libs = PLUGIN_API + [ + '//gerrit-antlr:query_exception', + '//gerrit-antlr:query_parser', + '//gerrit-common:annotations', + '//gerrit-common:server', + '//gerrit-extension-api:api', + '//gerrit-gwtexpui:server', + '//gerrit-reviewdb:server', + ], +)
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml index c797738..9309921 100644 --- a/gerrit-plugin-api/pom.xml +++ b/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.13</version> + <version>2.14-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Plugin API</name> <description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml index 4f20618..e69da7c 100644 --- a/gerrit-plugin-archetype/pom.xml +++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@ <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-archetype</artifactId> - <version>2.13</version> + <version>2.14-SNAPSHOT</version> <name>Gerrit Code Review - Plugin Archetype</name> <description>Maven Archetype for Gerrit Plugins</description> <url>https://www.gerritcodereview.com/</url>
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs index 2a585e4..602b029 100644 --- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs +++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
@@ -4,8 +4,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning @@ -85,7 +85,7 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml index 026e21d..f0cc120 100644 --- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml +++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
@@ -66,8 +66,8 @@ <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> - <source>1.7</source> - <target>1.7</target> + <source>1.8</source> + <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml index 9d4a1f5..5e9bc33 100644 --- a/gerrit-plugin-gwt-archetype/pom.xml +++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@ <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-gwt-archetype</artifactId> - <version>2.13</version> + <version>2.14-SNAPSHOT</version> <name>Gerrit Code Review - Web UI GWT Plugin Archetype</name> <description>Maven Archetype for Gerrit Web UI GWT Plugins</description> <url>https://www.gerritcodereview.com/</url>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs index 2a585e4..602b029 100644 --- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs +++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
@@ -4,8 +4,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning @@ -85,7 +85,7 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK index 511a8ec..21bc45c 100644 --- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK +++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/gwt/BUCK
@@ -13,20 +13,5 @@ name = 'dev', id = 'com.google.gwt:gwt-dev:' + VERSION, license = 'Apache2.0', - deps = [ - ':javax-validation', - ':javax-validation_src', - ], attach_source = False, - exclude = ['org/eclipse/jetty/*'], ) - -maven_jar( - name = 'javax-validation', - id = 'javax.validation:validation-api:1.0.0.GA', - bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e', - src_sha1 = '7a561191db2203550fbfa40d534d4997624cd369', - license = 'Apache2.0', - visibility = [], -) -
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml index 2c7fe88..baec648 100644 --- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml +++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
@@ -61,8 +61,8 @@ <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> - <source>1.7</source> - <target>1.7</target> + <source>1.8</source> + <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin>
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK index 2ee0e19..575ebfc 100644 --- a/gerrit-plugin-gwtui/BUCK +++ b/gerrit-plugin-gwtui/BUCK
@@ -1,8 +1,4 @@ -COMMON = ['gerrit-gwtui-common/src/main/java/'] -GWTEXPUI = ['gerrit-gwtexpui/src/main/java/'] -SRC = 'src/main/java/com/google/gerrit/' -SRCS = glob([SRC + '**/*.java']) - +SRCS = glob(['src/main/java/com/google/gerrit/**/*.java']) DEPS = ['//lib/gwt:user'] java_binary( @@ -50,7 +46,7 @@ 'com.google.gwtexpui.safehtml', 'com.google.gwtexpui.user', ], - paths = COMMON + GWTEXPUI, + source_jar = ':gwtui-api-src', srcs = SRCS, deps = DEPS + [ '//lib:gwtjsonrpc', @@ -61,5 +57,4 @@ '//gerrit-reviewdb:client', ], visibility = ['PUBLIC'], - do_it_wrong = True, )
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml index 15d06d9..4b104c6 100644 --- a/gerrit-plugin-gwtui/pom.xml +++ b/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.13</version> + <version>2.14-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-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml index f3682f9..d31b455 100644 --- a/gerrit-plugin-js-archetype/pom.xml +++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@ <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-js-archetype</artifactId> - <version>2.13</version> + <version>2.14-SNAPSHOT</version> <name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name> <description>Maven Archetype for Gerrit Web UI JavaScript Plugins</description> <url>https://www.gerritcodereview.com/</url>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs index 2a585e4..602b029 100644 --- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs +++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
@@ -4,8 +4,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning @@ -85,7 +85,7 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml index 8f4aadd..7a38260 100644 --- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml +++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
@@ -60,8 +60,8 @@ <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> - <source>1.7</source> - <target>1.7</target> + <source>1.8</source> + <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin>
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK index 82e0135..a5fb1f5 100644 --- a/gerrit-reviewdb/BUCK +++ b/gerrit-reviewdb/BUCK
@@ -33,6 +33,5 @@ '//lib:gwtorm', '//lib:truth', ], - source_under_test = [':client'], visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java index 9e36fc1..de2134b 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
@@ -55,10 +55,6 @@ * </ul> */ public final class Account { - public enum FieldName { - FULL_NAME, USER_NAME, REGISTER_NEW_EMAIL - } - public static final String USER_NAME_PATTERN_FIRST = "[a-zA-Z0-9]"; public static final String USER_NAME_PATTERN_REST = "[a-zA-Z0-9._-]"; public static final String USER_NAME_PATTERN_LAST = "[a-zA-Z0-9]";
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java index 41336791..5ae8847 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
@@ -14,6 +14,7 @@ package com.google.gerrit.reviewdb.client; +import com.google.gerrit.extensions.client.AuthType; import com.google.gwtorm.client.Column; import com.google.gwtorm.client.StringKey;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java index 6a55965..309bda4 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
@@ -22,6 +22,22 @@ /** Magical file name which represents the commit message. */ public static final String COMMIT_MSG = "/COMMIT_MSG"; + /** Magical file name which represents the merge list of a merge commit. */ + public static final String MERGE_LIST = "/MERGE_LIST"; + + /** + * Checks if the given path represents a magic file. A magic file is a + * generated file that is automatically included into changes. It does not + * exist in the commit of the patch set. + * + * @param path the file path + * @return {@code true} if the path represents a magic file, otherwise + * {@code false}. + */ + public static boolean isMagic(String path) { + return COMMIT_MSG.equals(path) || MERGE_LIST.equals(path); + } + public static class Key extends StringKey<PatchSet.Id> { private static final long serialVersionUID = 1L;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java index 42d0993..7e2a9b0 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
@@ -14,41 +14,37 @@ package com.google.gerrit.reviewdb.server; -import com.google.common.base.Function; import com.google.common.collect.Ordering; -import com.google.gerrit.reviewdb.client.Change; import com.google.gwtorm.client.IntKey; /** Static utilities for ReviewDb types. */ public class ReviewDbUtil { - public static final Function<IntKey<?>, Integer> INT_KEY_FUNCTION = - new Function<IntKey<?>, Integer>() { - @Override - public Integer apply(IntKey<?> in) { - return in.get(); - } - }; - - private static final Function<Change, Change.Id> CHANGE_ID_FUNCTION = - new Function<Change, Change.Id>() { - @Override - public Change.Id apply(Change in) { - return in.getId(); - } - }; - private static final Ordering<? extends IntKey<?>> INT_KEY_ORDERING = - Ordering.natural().nullsFirst().onResultOf(INT_KEY_FUNCTION).nullsFirst(); + Ordering.natural() + .nullsFirst() + .<IntKey<?>>onResultOf(IntKey::get) + .nullsFirst(); + /** + * Null-safe ordering over arbitrary subclass of {@code IntKey}. + * <p> + * In some cases, {@code Comparator.comparing(Change.Id::get)} may be shorter + * and cleaner. However, this method may be preferable in some cases: + * <ul> + * <li>This ordering is null-safe over both input and the result of {@link + * IntKey#get()}; {@code comparing} is only a good idea if all inputs are + * obviously non-null.</li> + * <li>{@code intKeyOrdering().sortedCopy(iterable)} is shorter than the + * stream equivalent.</li> + * <li>Creating derived comparators may be more readable with {@link Ordering} + * method chaining rather than static {@code Comparator} methods. + * </ul> + */ @SuppressWarnings("unchecked") public static <K extends IntKey<?>> Ordering<K> intKeyOrdering() { return (Ordering<K>) INT_KEY_ORDERING; } - public static Function<Change, Change.Id> changeIdFunction() { - return CHANGE_ID_FUNCTION; - } - public static ReviewDb unwrapDb(ReviewDb db) { if (db instanceof DisabledChangesReviewDbWrapper) { return ((DisabledChangesReviewDbWrapper) db).unsafeGetDelegate();
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK index 4fc578c..66fc545 100644 --- a/gerrit-server/BUCK +++ b/gerrit-server/BUCK
@@ -46,6 +46,7 @@ '//lib:mime-util', '//lib:pegdown', '//lib:protobuf', + '//lib:soy', '//lib:tukaani-xz', '//lib:velocity', '//lib/antlr:java_runtime', @@ -180,7 +181,6 @@ '//gerrit-server/src/main/prolog:common', '//lib/antlr:java_runtime', ], - source_under_test = [':server'], ) java_test( @@ -203,11 +203,9 @@ '//lib:guava', '//lib:guava-retrying', '//lib:protobuf', - '//lib/commons:validator', '//lib/dropwizard:dropwizard-core', '//lib/guice:guice-assistedinject', '//lib/prolog:runtime', ], - source_under_test = [':server'], visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD index 5a6b50f..6c7ab3e 100644 --- a/gerrit-server/BUILD +++ b/gerrit-server/BUILD
@@ -48,6 +48,7 @@ '//lib:pegdown', '//lib:protobuf', '//lib:servlet-api-3_1', + '//lib:soy', '//lib:tukaani-xz', '//lib:velocity', '//lib/antlr:java_runtime', @@ -188,6 +189,7 @@ ['src/test/java/**/*.java'], exclude = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE + QUERY_TESTS ), + resources = glob(['src/test/resources/com/google/gerrit/server/mail/*']), deps = TESTUTIL_DEPS + [ ':testutil', '//gerrit-antlr:query_exception', @@ -206,3 +208,12 @@ ], visibility = ['//visibility:public'], ) + +load('//tools/bzl:javadoc.bzl', 'java_doc') + +java_doc( + name = 'doc', + title = 'Gerrit Review Server Documentation', + libs = [':server'], + pkgs = ['com.google.gerrit'], +)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java index 364f4f8..95fbf04 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java
@@ -161,17 +161,10 @@ private static <T> Function<T, String> initFormatter(Class<T> keyType) { if (keyType == String.class) { return (Function<T, String>) Functions.<String> identity(); - } else if (keyType == Integer.class || keyType == Boolean.class) { return (Function<T, String>) Functions.toStringFunction(); - } else if (Enum.class.isAssignableFrom(keyType)) { - return new Function<T, String>() { - @Override - public String apply(T in) { - return ((Enum<?>) in).name(); - } - }; + return in -> ((Enum<?>) in).name(); } throw new IllegalStateException("unsupported type " + keyType.getName()); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java index e7ab75c..d3fe6ed 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
@@ -14,7 +14,6 @@ package com.google.gerrit.metrics.dropwizard; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.gerrit.metrics.Description; @@ -124,14 +123,7 @@ @Override public Map<Object, Metric> getCells() { - return Maps.transformValues( - cells, - new Function<ValueGauge, Metric> () { - @Override - public Metric apply(ValueGauge in) { - return in; - } - }); + return Maps.transformValues(cells, in -> (Metric) in); } final class ValueGauge implements Gauge<V> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java index 10b92e6..7894a84 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java
@@ -14,7 +14,6 @@ package com.google.gerrit.metrics.dropwizard; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.gerrit.metrics.Description; @@ -98,13 +97,6 @@ @Override public Map<Object, Metric> getCells() { - return Maps.transformValues( - cells, - new Function<CounterImpl, Metric> () { - @Override - public Metric apply(CounterImpl in) { - return in.metric; - } - }); + return Maps.transformValues(cells, c -> c.metric); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java index 071c678..ff38cd4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java
@@ -14,7 +14,6 @@ package com.google.gerrit.metrics.dropwizard; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.gerrit.metrics.Description; @@ -96,13 +95,6 @@ @Override public Map<Object, Metric> getCells() { - return Maps.transformValues( - cells, - new Function<HistogramImpl, Metric> () { - @Override - public Metric apply(HistogramImpl in) { - return in.metric; - } - }); + return Maps.transformValues(cells, h -> h.metric); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java index 6981ef1..aff6c4a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
@@ -14,7 +14,6 @@ package com.google.gerrit.metrics.dropwizard; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.gerrit.metrics.Description; @@ -96,13 +95,6 @@ @Override public Map<Object, Metric> getCells() { - return Maps.transformValues( - cells, - new Function<TimerImpl, Metric> () { - @Override - public Metric apply(TimerImpl in) { - return in.metric; - } - }); + return Maps.transformValues(cells, t -> t.metric); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java index e159c82..ee2ce29 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
@@ -18,7 +18,6 @@ import static com.google.gerrit.metrics.dropwizard.MetricResource.METRIC_KIND; import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND; -import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -304,14 +303,8 @@ @Override public synchronized RegistrationHandle newTrigger( Set<CallbackMetric<?>> metrics, Runnable trigger) { - final ImmutableSet<CallbackMetricGlue> all = FluentIterable.from(metrics) - .transform( - new Function<CallbackMetric<?>, CallbackMetricGlue>() { - @Override - public CallbackMetricGlue apply(CallbackMetric<?> input) { - return (CallbackMetricGlue) input; - } - }) + ImmutableSet<CallbackMetricGlue> all = FluentIterable.from(metrics) + .transform(m -> (CallbackMetricGlue) m) .toSet(); trigger = new CallbackGroup(trigger, all);
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 bc2ec06..d69ad3f 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
@@ -77,22 +77,54 @@ this.psUtil = psUtil; } + /** + * Apply approval copy settings from prior PatchSets to a new PatchSet. + * + * @param db review database. + * @param ctl change control for user uploading PatchSet + * @param ps new PatchSet + * @throws OrmException + */ public void copy(ReviewDb db, ChangeControl ctl, PatchSet ps) throws OrmException { - db.patchSetApprovals().insert(getForPatchSet(db, ctl, ps)); + copy(db, ctl, ps, Collections.<PatchSetApproval>emptyList()); + } + + /** + * Apply approval copy settings from prior PatchSets to a new PatchSet. + * + * @param db review database. + * @param ctl change control for user uploading PatchSet + * @param ps new PatchSet + * @param dontCopy PatchSetApprovals indicating which (account, label) pairs + * should not be copied + * @throws OrmException + */ + public void copy(ReviewDb db, ChangeControl ctl, PatchSet ps, + Iterable<PatchSetApproval> dontCopy) throws OrmException { + db.patchSetApprovals().insert( + getForPatchSet(db, ctl, ps, dontCopy)); } Iterable<PatchSetApproval> getForPatchSet(ReviewDb db, ChangeControl ctl, PatchSet.Id psId) throws OrmException { + return getForPatchSet(db, ctl, psId, + Collections.<PatchSetApproval>emptyList()); + } + + Iterable<PatchSetApproval> getForPatchSet(ReviewDb db, + ChangeControl ctl, PatchSet.Id psId, + Iterable<PatchSetApproval> dontCopy) throws OrmException { PatchSet ps = psUtil.get(db, ctl.getNotes(), psId); if (ps == null) { return Collections.emptyList(); } - return getForPatchSet(db, ctl, ps); + return getForPatchSet(db, ctl, ps, dontCopy); } private Iterable<PatchSetApproval> getForPatchSet(ReviewDb db, - ChangeControl ctl, PatchSet ps) throws OrmException { + ChangeControl ctl, PatchSet ps, + Iterable<PatchSetApproval> dontCopy) throws OrmException { checkNotNull(ps, "ps should not be null"); ChangeData cd = changeDataFactory.create(db, ctl); try { @@ -103,10 +135,16 @@ Table<String, Account.Id, PatchSetApproval> wontCopy = HashBasedTable.create(); + for (PatchSetApproval psa : dontCopy) { + wontCopy.put(psa.getLabel(), psa.getAccountId(), psa); + } + Table<String, Account.Id, PatchSetApproval> byUser = HashBasedTable.create(); for (PatchSetApproval psa : all.get(ps.getId())) { - byUser.put(psa.getLabel(), psa.getAccountId(), psa); + if (!wontCopy.contains(psa.getLabel(), psa.getAccountId())) { + byUser.put(psa.getLabel(), psa.getAccountId(), psa); + } } TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java index e0526e4..97af09e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -16,10 +16,9 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; +import static java.util.Comparator.comparing; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Iterables; @@ -43,12 +42,15 @@ import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.notedb.ReviewerStateInternal; import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.util.LabelVote; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.sql.Timestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -73,15 +75,11 @@ */ @Singleton public class ApprovalsUtil { + private static final Logger log = + LoggerFactory.getLogger(ApprovalsUtil.class); + private static final Ordering<PatchSetApproval> SORT_APPROVALS = - Ordering.natural() - .onResultOf( - new Function<PatchSetApproval, Timestamp>() { - @Override - public Timestamp apply(PatchSetApproval a) { - return a.getGranted(); - } - }); + Ordering.from(comparing(PatchSetApproval::getGranted)); public static List<PatchSetApproval> sortApprovals( Iterable<PatchSetApproval> approvals) { @@ -90,22 +88,24 @@ private static Iterable<PatchSetApproval> filterApprovals( Iterable<PatchSetApproval> psas, final Account.Id accountId) { - return Iterables.filter(psas, new Predicate<PatchSetApproval>() { - @Override - public boolean apply(PatchSetApproval input) { - return Objects.equals(input.getAccountId(), accountId); - } - }); + return Iterables.filter( + psas, a -> Objects.equals(a.getAccountId(), accountId)); } private final NotesMigration migration; + private final IdentifiedUser.GenericFactory userFactory; + private final ChangeControl.GenericFactory changeControlFactory; private final ApprovalCopier copier; @VisibleForTesting @Inject public ApprovalsUtil(NotesMigration migration, + IdentifiedUser.GenericFactory userFactory, + ChangeControl.GenericFactory changeControlFactory, ApprovalCopier copier) { this.migration = migration; + this.userFactory = userFactory; + this.changeControlFactory = changeControlFactory; this.copier = copier; } @@ -164,8 +164,8 @@ PatchSetInfo info, Iterable<Account.Id> wantReviewers, Collection<Account.Id> existingReviewers) throws OrmException { return addReviewers(db, update, labelTypes, change, ps.getId(), - ps.isDraft(), info.getAuthor().getAccount(), - info.getCommitter().getAccount(), wantReviewers, existingReviewers); + info.getAuthor().getAccount(), info.getCommitter().getAccount(), + wantReviewers, existingReviewers); } public List<PatchSetApproval> addReviewers(ReviewDb db, ChangeNotes notes, @@ -189,12 +189,12 @@ existingReviewers.add(entry.getKey()); } } - return addReviewers(db, update, labelTypes, change, psId, false, null, null, + return addReviewers(db, update, labelTypes, change, psId, null, null, wantReviewers, existingReviewers); } private List<PatchSetApproval> addReviewers(ReviewDb db, ChangeUpdate update, - LabelTypes labelTypes, Change change, PatchSet.Id psId, boolean isDraft, + LabelTypes labelTypes, Change change, PatchSet.Id psId, Account.Id authorId, Account.Id committerId, Iterable<Account.Id> wantReviewers, Collection<Account.Id> existingReviewers) throws OrmException { @@ -204,11 +204,11 @@ } Set<Account.Id> need = Sets.newLinkedHashSet(wantReviewers); - if (authorId != null && !isDraft) { + if (authorId != null && canSee(db, update.getNotes(), authorId)) { need.add(authorId); } - if (committerId != null && !isDraft) { + if (committerId != null && canSee(db, update.getNotes(), committerId)) { need.add(committerId); } need.remove(change.getOwner()); @@ -229,6 +229,17 @@ return Collections.unmodifiableList(cells); } + private boolean canSee(ReviewDb db, ChangeNotes notes, Account.Id accountId) { + try { + IdentifiedUser user = userFactory.create(accountId); + return changeControlFactory.controlFor(notes, user).isVisible(db); + } catch (OrmException | NoSuchChangeException e) { + log.warn(String.format("Failed to check if account %d can see change %d", + accountId.get(), notes.getChangeId().get()), e); + return false; + } + } + /** * Adds accounts to a change as reviewers in the CC state. * @@ -254,25 +265,40 @@ return need; } - public void addApprovals(ReviewDb db, ChangeUpdate update, + /** + * Adds approvals to ChangeUpdate and writes to ReviewDb. + * + * @param db review database. + * @param update change update. + * @param labelTypes label types for the containing project. + * @param ps patch set being approved. + * @param changeCtl change control for user adding approvals. + * @param approvals approvals to add. + * @throws OrmException + */ + public Iterable<PatchSetApproval> addApprovals(ReviewDb db, ChangeUpdate update, LabelTypes labelTypes, PatchSet ps, ChangeControl changeCtl, Map<String, Short> approvals) throws OrmException { - if (!approvals.isEmpty()) { - checkApprovals(approvals, changeCtl); - List<PatchSetApproval> cells = new ArrayList<>(approvals.size()); - Date ts = update.getWhen(); - for (Map.Entry<String, Short> vote : approvals.entrySet()) { - LabelType lt = labelTypes.byLabel(vote.getKey()); - cells.add(new PatchSetApproval(new PatchSetApproval.Key( - ps.getId(), - ps.getUploader(), - lt.getLabelId()), - vote.getValue(), - ts)); - update.putApproval(vote.getKey(), vote.getValue()); - } - db.patchSetApprovals().insert(cells); + if (approvals.isEmpty()) { + return Collections.emptyList(); } + checkApprovals(approvals, changeCtl); + List<PatchSetApproval> cells = new ArrayList<>(approvals.size()); + Date ts = update.getWhen(); + for (Map.Entry<String, Short> vote : approvals.entrySet()) { + LabelType lt = labelTypes.byLabel(vote.getKey()); + cells.add(new PatchSetApproval(new PatchSetApproval.Key( + ps.getId(), + ps.getUploader(), + lt.getLabelId()), + vote.getValue(), + ts)); + } + for (PatchSetApproval psa : cells) { + update.putApproval(psa.getLabel(), psa.getValue()); + } + db.patchSetApprovals().insert(cells); + return cells; } public static void checkLabel(LabelTypes labelTypes, String name, Short value) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java index 11a3d81..7866ed3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -14,7 +14,8 @@ package com.google.gerrit.server; -import com.google.common.base.Function; +import static java.util.Comparator.comparingInt; + import com.google.common.collect.Ordering; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.server.ReviewDb; @@ -40,16 +41,8 @@ private static final String SUBJECT_CROP_APPENDIX = "..."; private static final int SUBJECT_CROP_RANGE = 10; - public static final Function<PatchSet, Integer> TO_PS_ID = - new Function<PatchSet, Integer>() { - @Override - public Integer apply(PatchSet in) { - return in.getId().get(); - } - }; - - public static final Ordering<PatchSet> PS_ID_ORDER = Ordering.natural() - .onResultOf(TO_PS_ID); + public static final Ordering<PatchSet> PS_ID_ORDER = + Ordering.from(comparingInt(PatchSet::getPatchSetId)); /** * Generate a new unique identifier for change message entities.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/OptionUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/OptionUtil.java index 24d10f7..c050a61 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/OptionUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/OptionUtil.java
@@ -15,7 +15,6 @@ package com.google.gerrit.server; import com.google.common.base.CharMatcher; -import com.google.common.base.Function; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; @@ -24,16 +23,10 @@ private static final Splitter COMMA_OR_SPACE = Splitter.on(CharMatcher.anyOf(", ")).omitEmptyStrings().trimResults(); - private static final Function<String, String> TO_LOWER_CASE = - new Function<String, String>() { - @Override - public String apply(String input) { - return input.toLowerCase(); - } - }; - public static Iterable<String> splitOptionValue(String value) { - return Iterables.transform(COMMA_OR_SPACE.split(value), TO_LOWER_CASE); + return Iterables.transform( + COMMA_OR_SPACE.split(value), + String::toLowerCase); } private OptionUtil() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java index 603f528..68065cb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
@@ -16,9 +16,9 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.stream.Collectors.toList; import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.google.common.collect.ComparisonChain; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; @@ -28,6 +28,7 @@ import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.PatchLineComment.Status; import com.google.gerrit.reviewdb.client.PatchSet; @@ -38,7 +39,6 @@ import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeUpdate; -import com.google.gerrit.server.notedb.DraftCommentNotes; import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.patch.PatchListCache; import com.google.gerrit.server.patch.PatchListNotAvailableException; @@ -60,6 +60,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.StreamSupport; /** * Utility functions to manipulate PatchLineComments. @@ -112,17 +113,14 @@ private final GitRepositoryManager repoManager; private final AllUsersName allUsers; - private final DraftCommentNotes.Factory draftFactory; private final NotesMigration migration; @Inject PatchLineCommentsUtil(GitRepositoryManager repoManager, AllUsersName allUsers, - DraftCommentNotes.Factory draftFactory, NotesMigration migration) { this.repoManager = repoManager; this.allUsers = allUsers; - this.draftFactory = draftFactory; this.migration = migration; } @@ -178,13 +176,7 @@ ResultSet<PatchLineComment> comments, final PatchLineComment.Status status) { return Lists.newArrayList( - Iterables.filter(comments, new Predicate<PatchLineComment>() { - @Override - public boolean apply(PatchLineComment input) { - return (input.getStatus() == status); - } - }) - ); + Iterables.filter(comments, c -> c.getStatus() == status)); } public List<PatchLineComment> byPatchSet(ReviewDb db, @@ -216,10 +208,27 @@ public List<PatchLineComment> publishedByPatchSet(ReviewDb db, ChangeNotes notes, PatchSet.Id psId) throws OrmException { if (!migration.readChanges()) { - return sort( - db.patchComments().publishedByPatchSet(psId).toList()); + return removeCommentsOnAncestorOfCommitMessage(sort( + db.patchComments().publishedByPatchSet(psId).toList())); } - return commentsOnPatchSet(notes.load().getComments().values(), psId); + return removeCommentsOnAncestorOfCommitMessage( + commentsOnPatchSet(notes.load().getComments().values(), psId)); + } + + /** + * For the commit message the A side in a diff view is always empty when a + * comparison against an ancestor is done, so there can't be any comments on + * this ancestor. However earlier we showed the auto-merge commit message on + * side A when for a merge commit a comparison against the auto-merge was + * done. From that time there may still be comments on the auto-merge commit + * message and those we want to filter out. + */ + private List<PatchLineComment> removeCommentsOnAncestorOfCommitMessage( + List<PatchLineComment> list) { + return list.stream() + .filter(c -> c.getSide() != 0 + || !Patch.COMMIT_MSG.equals(c.getKey().getParentKey().get())) + .collect(toList()); } public List<PatchLineComment> draftByPatchSetAuthor(ReviewDb db, @@ -251,16 +260,11 @@ throws OrmException { if (!migration.readChanges()) { final Change.Id matchId = notes.getChangeId(); - return FluentIterable - .from(db.patchComments().draftByAuthor(author)) - .filter(new Predicate<PatchLineComment>() { - @Override - public boolean apply(PatchLineComment in) { - Change.Id changeId = - in.getKey().getParentKey().getParentKey().getParentKey(); - return changeId.equals(matchId); - } - }).toSortedList(PLC_ORDER); + return StreamSupport.stream( + db.patchComments().draftByAuthor(author).spliterator(), false) + .filter(c -> c.getPatchSetId().getParentKey().equals(matchId)) + .sorted(PLC_ORDER) + .collect(toList()); } List<PatchLineComment> comments = new ArrayList<>(); comments.addAll(notes.getDraftComments(author).values()); @@ -268,13 +272,14 @@ } @Deprecated // To be used only by HasDraftByLegacyPredicate. - public List<PatchLineComment> draftByAuthor(ReviewDb db, + public List<Change.Id> changesWithDraftsByAuthor(ReviewDb db, Account.Id author) throws OrmException { if (!migration.readChanges()) { - return sort(db.patchComments().draftByAuthor(author).toList()); + return FluentIterable.from(db.patchComments().draftByAuthor(author)) + .transform(plc -> plc.getPatchSetId().getParentKey()).toList(); } - List<PatchLineComment> comments = new ArrayList<>(); + List<Change.Id> changes = new ArrayList<>(); try (Repository repo = repoManager.openRepository(allUsers)) { for (String refName : repo.getRefDatabase() .getRefs(RefNames.REFS_DRAFT_COMMENTS).keySet()) { @@ -283,17 +288,12 @@ if (accountId == null || changeId == null) { continue; } - // Avoid loading notes for all affected changes just to be able to auto- - // rebuild. This is only used in a corner case in the search codepath, - // so returning slightly stale values is ok. - DraftCommentNotes notes = - draftFactory.createWithAutoRebuildingDisabled(changeId, author); - comments.addAll(notes.load().getComments().values()); + changes.add(changeId); } } catch (IOException e) { throw new OrmException(e); } - return sort(comments); + return changes; } public void putComments(ReviewDb db, ChangeUpdate update, @@ -359,7 +359,7 @@ return sort(result); } - public static RevId setCommentRevId(PatchLineComment c, + public static void setCommentRevId(PatchLineComment c, PatchListCache cache, Change change, PatchSet ps) throws OrmException { checkArgument(c.getPatchSetId().equals(ps.getId()), "cannot set RevId for patch set %s on comment %s", ps.getId(), c); @@ -380,7 +380,6 @@ throw new OrmException(e); } } - return c.getRevId(); } public Collection<Ref> getDraftRefs(Change.Id changeId)
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 e1f786b..5941c10 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
@@ -14,14 +14,14 @@ package com.google.gerrit.server; -import com.google.common.base.Function; +import static java.util.Comparator.comparing; + import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; -import com.google.gerrit.common.Nullable; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.extensions.common.AccountInfo; @@ -68,19 +68,17 @@ public class ReviewersUtil { private static final String MAX_SUFFIX = "\u9fa5"; private static final Ordering<SuggestedReviewerInfo> ORDERING = - Ordering.natural().onResultOf(new Function<SuggestedReviewerInfo, String>() { - @Nullable - @Override - public String apply(@Nullable SuggestedReviewerInfo suggestedReviewerInfo) { - if (suggestedReviewerInfo == null) { - return null; - } - return suggestedReviewerInfo.account != null - ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email, - Strings.nullToEmpty(suggestedReviewerInfo.account.name)) - : Strings.nullToEmpty(suggestedReviewerInfo.group.name); - } - }); + Ordering.<SuggestedReviewerInfo> from(comparing( + suggestedReviewerInfo -> { + if (suggestedReviewerInfo == null) { + return null; + } + return suggestedReviewerInfo.account != null + ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email, + Strings.nullToEmpty(suggestedReviewerInfo.account.name)) + : Strings.nullToEmpty(suggestedReviewerInfo.group.name); + })); + private final AccountLoader accountLoader; private final AccountCache accountCache; private final AccountIndexCollection indexes;
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 5a89afa..8f25e43 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
@@ -15,14 +15,12 @@ package com.google.gerrit.server; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toSet; import com.google.auto.value.AutoValue; import com.google.common.base.CharMatcher; -import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.common.base.Predicate; import com.google.common.base.Splitter; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; @@ -249,29 +247,11 @@ public Set<Account.Id> byChange(final Change.Id changeId, final String label) throws OrmException { try (final Repository repo = repoManager.openRepository(allUsers)) { - return FluentIterable - .from(getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId))) - .transform(new Function<String, Account.Id>() { - @Override - public Account.Id apply(String refPart) { - return Account.Id.parse(refPart); - } - }) - .filter(new Predicate<Account.Id>() { - @Override - public boolean apply(Account.Id accountId) { - try { - return readLabels(repo, - RefNames.refsStarredChanges(changeId, accountId)) - .contains(label); - } catch (IOException e) { - log.error(String.format( - "Cannot query stars by account %d on change %d", - accountId.get(), changeId.get()), e); - return false; - } - } - }).toSet(); + return getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId)) + .stream() + .map(Account.Id::parse) + .filter(accountId -> hasStar(repo, changeId, accountId, label)) + .collect(toSet()); } catch (IOException e) { throw new OrmException( String.format("Get accounts that starred change %d failed", @@ -283,36 +263,12 @@ // To be used only for IsStarredByLegacyPredicate. public Set<Change.Id> byAccount(final Account.Id accountId, final String label) throws OrmException { - try (final Repository repo = repoManager.openRepository(allUsers)) { - return FluentIterable - .from(getRefNames(repo, RefNames.REFS_STARRED_CHANGES)) - .filter(new Predicate<String>() { - @Override - public boolean apply(String refPart) { - return refPart.endsWith("/" + accountId.get()); - } - }) - .transform(new Function<String, Change.Id>() { - @Override - public Change.Id apply(String refPart) { - return Change.Id.fromRefPart(refPart); - } - }) - .filter(new Predicate<Change.Id>() { - @Override - public boolean apply(Change.Id changeId) { - try { - return readLabels(repo, - RefNames.refsStarredChanges(changeId, accountId)) - .contains(label); - } catch (IOException e) { - log.error(String.format( - "Cannot query stars by account %d on change %d", - accountId.get(), changeId.get()), e); - return false; - } - } - }).toSet(); + try (Repository repo = repoManager.openRepository(allUsers)) { + return getRefNames(repo, RefNames.REFS_STARRED_CHANGES).stream() + .filter(refPart -> refPart.endsWith("/" + accountId.get())) + .map(Change.Id::fromRefPart) + .filter(changeId -> hasStar(repo, changeId, accountId, label)) + .collect(toSet()); } catch (IOException e) { throw new OrmException( String.format("Get changes that were starred by %d failed", @@ -320,6 +276,20 @@ } } + private boolean hasStar(Repository repo, Change.Id changeId, + Account.Id accountId, String label) { + try { + return readLabels(repo, + RefNames.refsStarredChanges(changeId, accountId)) + .contains(label); + } catch (IOException e) { + log.error(String.format( + "Cannot query stars by account %d on change %d", + accountId.get(), changeId.get()), e); + return false; + } + } + public ImmutableMultimap<Account.Id, String> byChangeFromIndex( Change.Id changeId) throws OrmException, NoSuchChangeException { Set<String> fields = ImmutableSet.of(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java index 761f2a3..6dccbc2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
@@ -39,37 +39,31 @@ @Singleton public class WebLinks { private static final Logger log = LoggerFactory.getLogger(WebLinks.class); + private static final Predicate<WebLinkInfo> INVALID_WEBLINK = - new Predicate<WebLinkInfo>() { - - @Override - public boolean apply(WebLinkInfo link) { - if (link == null) { - return false; - } else if (Strings.isNullOrEmpty(link.name) - || Strings.isNullOrEmpty(link.url)) { - log.warn(String.format("%s is missing name and/or url", - link.getClass().getName())); - return false; - } - return true; + link -> { + if (link == null) { + return false; + } else if (Strings.isNullOrEmpty(link.name) + || Strings.isNullOrEmpty(link.url)) { + log.warn(String.format("%s is missing name and/or url", + link.getClass().getName())); + return false; } + return true; }; - private static final Predicate<WebLinkInfoCommon> INVALID_WEBLINK_COMMON = - new Predicate<WebLinkInfoCommon>() { - @Override - public boolean apply(WebLinkInfoCommon link) { - if (link == null) { - return false; - } else if (Strings.isNullOrEmpty(link.name) - || Strings.isNullOrEmpty(link.url)) { - log.warn(String.format("%s is missing name and/or url", link - .getClass().getName())); - return false; - } - return true; + private static final Predicate<WebLinkInfoCommon> INVALID_WEBLINK_COMMON = + link -> { + if (link == null) { + return false; + } else if (Strings.isNullOrEmpty(link.name) + || Strings.isNullOrEmpty(link.url)) { + log.warn(String.format("%s is missing name and/or url", link + .getClass().getName())); + return false; } + return true; }; private final DynamicSet<PatchSetWebLink> patchSetLinks; @@ -85,8 +79,7 @@ DynamicSet<FileHistoryWebLink> fileLogLinks, DynamicSet<DiffWebLink> diffLinks, DynamicSet<ProjectWebLink> projectLinks, - DynamicSet<BranchWebLink> branchLinks - ) { + DynamicSet<BranchWebLink> branchLinks) { this.patchSetLinks = patchSetLinks; this.fileLinks = fileLinks; this.fileHistoryLinks = fileLogLinks; @@ -101,15 +94,11 @@ * @param commit SHA1 of commit. * @return Links for patch sets. */ - public FluentIterable<WebLinkInfo> getPatchSetLinks(final Project.NameKey project, - final String commit) { - return filterLinks(patchSetLinks, new Function<WebLink, WebLinkInfo>() { - - @Override - public WebLinkInfo apply(WebLink webLink) { - return ((PatchSetWebLink)webLink).getPatchSetWebLink(project.get(), commit); - } - }); + public FluentIterable<WebLinkInfo> getPatchSetLinks(Project.NameKey project, + String commit) { + return filterLinks( + patchSetLinks, + webLink -> webLink.getPatchSetWebLink(project.get(), commit)); } /** @@ -119,15 +108,11 @@ * @param file File name. * @return Links for files. */ - public FluentIterable<WebLinkInfo> getFileLinks(final String project, final String revision, - final String file) { - return filterLinks(fileLinks, new Function<WebLink, WebLinkInfo>() { - - @Override - public WebLinkInfo apply(WebLink webLink) { - return ((FileWebLink)webLink).getFileWebLink(project, revision, file); - } - }); + public FluentIterable<WebLinkInfo> getFileLinks(String project, + String revision, String file) { + return filterLinks( + fileLinks, + webLink -> webLink.getFileWebLink(project, revision, file)); } /** @@ -137,39 +122,31 @@ * @param file File name. * @return Links for file history */ - public FluentIterable<WebLinkInfo> getFileHistoryLinks(final String project, - final String revision, final String file) { - return filterLinks(fileHistoryLinks, new Function<WebLink, WebLinkInfo>() { - - @Override - public WebLinkInfo apply(WebLink webLink) { - return ((FileHistoryWebLink) webLink).getFileHistoryWebLink(project, - revision, file); - } - }); + public FluentIterable<WebLinkInfo> getFileHistoryLinks(String project, + String revision, String file) { + return filterLinks( + fileHistoryLinks, + webLink -> webLink.getFileHistoryWebLink(project, revision, file)); } public FluentIterable<WebLinkInfoCommon> getFileHistoryLinksCommon( - final String project, final String revision, final String file) { + String project, String revision, String file) { return FluentIterable .from(fileHistoryLinks) - .transform(new Function<WebLink, WebLinkInfoCommon>() { - @Override - public WebLinkInfoCommon apply(WebLink webLink) { - WebLinkInfo info = - ((FileHistoryWebLink) webLink).getFileHistoryWebLink(project, - revision, file); - if (info == null) { - return null; - } - WebLinkInfoCommon commonInfo = new WebLinkInfoCommon(); - commonInfo.name = info.name; - commonInfo.imageUrl = info.imageUrl; - commonInfo.url = info.url; - commonInfo.target = info.target; - return commonInfo; - } - }) + .transform( + webLink -> { + WebLinkInfo info = + webLink.getFileHistoryWebLink(project, revision, file); + if (info == null) { + return null; + } + WebLinkInfoCommon commonInfo = new WebLinkInfoCommon(); + commonInfo.name = info.name; + commonInfo.imageUrl = info.imageUrl; + commonInfo.url = info.url; + commonInfo.target = info.target; + return commonInfo; + }) .filter(INVALID_WEBLINK_COMMON); } @@ -190,14 +167,10 @@ final int patchSetIdB, final String revisionB, final String fileB) { return FluentIterable .from(diffLinks) - .transform(new Function<WebLink, DiffWebLinkInfo>() { - @Override - public DiffWebLinkInfo apply(WebLink webLink) { - return ((DiffWebLink) webLink).getDiffLink(project, changeId, + .transform(webLink -> + webLink.getDiffLink(project, changeId, patchSetIdA, revisionA, fileA, - patchSetIdB, revisionB, fileB); - } - }) + patchSetIdB, revisionB, fileB)) .filter(INVALID_WEBLINK); } @@ -207,13 +180,9 @@ * @return Links for projects. */ public FluentIterable<WebLinkInfo> getProjectLinks(final String project) { - return filterLinks(projectLinks, new Function<WebLink, WebLinkInfo>() { - - @Override - public WebLinkInfo apply(WebLink webLink) { - return ((ProjectWebLink)webLink).getProjectWeblink(project); - } - }); + return filterLinks( + projectLinks, + webLink -> webLink.getProjectWeblink(project)); } /** @@ -223,17 +192,13 @@ * @return Links for branches. */ public FluentIterable<WebLinkInfo> getBranchLinks(final String project, final String branch) { - return filterLinks(branchLinks, new Function<WebLink, WebLinkInfo>() { - - @Override - public WebLinkInfo apply(WebLink webLink) { - return ((BranchWebLink)webLink).getBranchWebLink(project, branch); - } - }); + return filterLinks( + branchLinks, + webLink -> webLink.getBranchWebLink(project, branch)); } - private FluentIterable<WebLinkInfo> filterLinks(DynamicSet<? extends WebLink> links, - Function<WebLink, WebLinkInfo> transformer) { + private <T extends WebLink> FluentIterable<WebLinkInfo> filterLinks(DynamicSet<T> links, + Function<T, WebLinkInfo> transformer) { return FluentIterable .from(links) .transform(transformer)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java index 30420e0..a0c6118 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java
@@ -16,8 +16,7 @@ import com.google.common.base.Strings; import com.google.common.collect.Sets; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Account.FieldName; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.mail.EmailSender; @@ -37,11 +36,11 @@ } @Override - public Set<FieldName> getEditableFields() { - Set<Account.FieldName> fields = new HashSet<>(); - for (Account.FieldName n : Account.FieldName.values()) { + public Set<AccountFieldName> getEditableFields() { + Set<AccountFieldName> fields = new HashSet<>(); + for (AccountFieldName n : AccountFieldName.values()) { if (allowsEdit(n)) { - if (n == Account.FieldName.REGISTER_NEW_EMAIL) { + if (n == AccountFieldName.REGISTER_NEW_EMAIL) { if (emailSender != null && emailSender.isEnabled()) { fields.add(n); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java index c5b0699..db2a98f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.account; -import com.google.common.base.Predicate; -import com.google.common.collect.Sets; +import static java.util.stream.Collectors.toSet; + import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.reviewdb.client.Account; @@ -28,7 +28,6 @@ import com.google.inject.Inject; import com.google.inject.Provider; -import java.util.HashSet; import java.util.Set; /** Access control management for one account's access to other accounts. */ @@ -186,14 +185,9 @@ } private Set<AccountGroup.UUID> groupsOf(IdentifiedUser user) { - return new HashSet<>(Sets.filter( - user.getEffectiveGroups().getKnownGroups(), - new Predicate<AccountGroup.UUID>() { - @Override - public boolean apply(AccountGroup.UUID in) { - return !SystemGroupBackend.isSystemGroup(in); - } - })); + return user.getEffectiveGroups().getKnownGroups().stream() + .filter(a -> !SystemGroupBackend.isSystemGroup(a)) + .collect(toSet()); } private abstract static class OtherUser {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountJson.java new file mode 100644 index 0000000..7193564 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountJson.java
@@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.account; + +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.reviewdb.client.Account; + +public class AccountJson { + + public static AccountInfo toAccountInfo(Account account) { + if (account == null || account.getId() == null) { + return null; + } + AccountInfo accountInfo = new AccountInfo(account.getId().get()); + accountInfo.email = account.getPreferredEmail(); + accountInfo.name = account.getFullName(); + accountInfo.username = account.getUserName(); + return accountInfo; + } + +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java index 9cbd1b0..7c69224 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -21,6 +21,7 @@ import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.errors.NameAlreadyUsedException; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; @@ -201,14 +202,14 @@ db.accountExternalIds().update(Collections.singleton(extId)); } - if (!realm.allowsEdit(Account.FieldName.FULL_NAME) + if (!realm.allowsEdit(AccountFieldName.FULL_NAME) && !Strings.isNullOrEmpty(who.getDisplayName()) && !eq(user.getAccount().getFullName(), who.getDisplayName())) { toUpdate = load(toUpdate, user.getAccountId(), db); toUpdate.setFullName(who.getDisplayName()); } - if (!realm.allowsEdit(Account.FieldName.USER_NAME) + if (!realm.allowsEdit(AccountFieldName.USER_NAME) && who.getUserName() != null && !eq(user.getUserName(), who.getUserName())) { log.warn(String.format("Not changing already set username %s to %s", @@ -340,7 +341,7 @@ } else { log.error(errorMessage); } - if (!realm.allowsEdit(Account.FieldName.USER_NAME)) { + if (!realm.allowsEdit(AccountFieldName.USER_NAME)) { // setting the given user name has failed, but the realm does not // allow the user to manually set a user name, // this means we would end with an account without user name
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java index 5a18269..b400eb7 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.account; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; +import static java.util.stream.Collectors.toSet; + import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.server.ReviewDb; @@ -191,14 +191,9 @@ // At this point we have no clue. Just perform a whole bunch of suggestions // and pray we come up with a reasonable result list. - return FluentIterable - .from(accountQueryProvider.get().byDefault(nameOrEmail)) - .transform(new Function<AccountState, Account.Id>() { - @Override - public Account.Id apply(AccountState accountState) { - return accountState.getAccount().getId(); - } - }).toSet(); + return accountQueryProvider.get().byDefault(nameOrEmail).stream() + .map(a -> a.getAccount().getId()) + .collect(toSet()); } List<Account> m = db.accounts().byFullName(nameOrEmail).toList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java index 05a7179..ed99266 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -36,12 +36,7 @@ public class AccountState { public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION = - new Function<AccountState, Account.Id>() { - @Override - public Account.Id apply(AccountState in) { - return in.getAccount().getId(); - } - }; + a -> a.getAccount().getId(); private final Account account; private final Set<AccountGroup.UUID> internalGroups;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java index e348e73..d86d27c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -14,15 +14,14 @@ package com.google.gerrit.server.account; -import com.google.common.base.Function; +import static com.google.common.base.Predicates.not; + import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.Iterables; +import com.google.common.collect.FluentIterable; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.common.data.PermissionRule.Action; -import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.PeerDaemonUser; import com.google.gerrit.server.git.QueueProvider; @@ -32,6 +31,7 @@ import com.google.inject.assistedinject.Assisted; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -98,7 +98,7 @@ if (canEmailReviewers == null) { canEmailReviewers = matchAny(capabilities.emailReviewers, ALLOWED_RULE) - || !matchAny(capabilities.emailReviewers, Predicates.not(ALLOWED_RULE)); + || !matchAny(capabilities.emailReviewers, not(ALLOWED_RULE)); } return canEmailReviewers; @@ -279,23 +279,16 @@ return mine; } - private static final Predicate<PermissionRule> ALLOWED_RULE = new Predicate<PermissionRule>() { - @Override - public boolean apply(PermissionRule rule) { - return rule.getAction() == Action.ALLOW; - } - }; + private static final Predicate<PermissionRule> ALLOWED_RULE = + r -> r.getAction() == Action.ALLOW; - private boolean matchAny(Iterable<PermissionRule> rules, Predicate<PermissionRule> predicate) { - Iterable<AccountGroup.UUID> ids = Iterables.transform( - Iterables.filter(rules, predicate), - new Function<PermissionRule, AccountGroup.UUID>() { - @Override - public AccountGroup.UUID apply(PermissionRule rule) { - return rule.getGroup().getUUID(); - } - }); - return user.getEffectiveGroups().containsAnyOf(ids); + private boolean matchAny(Collection<PermissionRule> rules, + Predicate<PermissionRule> predicate) { + return user.getEffectiveGroups() + .containsAnyOf( + FluentIterable.from(rules) + .filter(predicate) + .transform(r -> r.getGroup().getUUID())); } private static boolean match(GroupMembership groups,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java index 713154c..c4ab7a5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -14,8 +14,11 @@ package com.google.gerrit.server.account; +import static com.google.gerrit.extensions.client.AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT; + import com.google.gerrit.common.errors.EmailException; import com.google.gerrit.extensions.api.accounts.EmailInput; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; @@ -23,8 +26,6 @@ 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.FieldName; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.GetEmails.EmailInfo; @@ -50,11 +51,11 @@ private final Provider<CurrentUser> self; private final Realm realm; - private final AuthConfig authConfig; private final AccountManager accountManager; private final RegisterNewEmailSender.Factory registerNewEmailFactory; private final PutPreferred putPreferred; private final String email; + private final boolean isDevMode; @Inject CreateEmail(Provider<CurrentUser> self, @@ -66,11 +67,11 @@ @Assisted String email) { this.self = self; this.realm = realm; - this.authConfig = authConfig; this.accountManager = accountManager; this.registerNewEmailFactory = registerNewEmailFactory; this.putPreferred = putPreferred; this.email = email; + this.isDevMode = authConfig.getAuthType() == DEVELOPMENT_BECOME_ANY_ACCOUNT; } @Override @@ -96,7 +97,7 @@ throw new AuthException("not allowed to use no_confirmation"); } - if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) { + if (!realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) { throw new MethodNotAllowedException("realm does not allow adding emails"); } @@ -113,8 +114,10 @@ EmailInfo info = new EmailInfo(); info.email = email; - if (input.noConfirmation - || authConfig.getAuthType() == AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) { + if (input.noConfirmation || isDevMode) { + if (isDevMode) { + log.warn("skipping email validation in developer mode"); + } try { accountManager.link(user.getAccountId(), AuthRequest.forEmail(email));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java index eb3c9a0..57af333 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -15,8 +15,9 @@ package com.google.gerrit.server.account; import com.google.common.base.Strings; +import com.google.gerrit.extensions.client.AccountFieldName; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.config.AuthConfig; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -39,7 +40,7 @@ } @Override - public boolean allowsEdit(final Account.FieldName field) { + public boolean allowsEdit(final AccountFieldName field) { if (authConfig.getAuthType() == AuthType.HTTP) { switch (field) { case USER_NAME:
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 f6c48af..94c099e 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,11 +16,14 @@ import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; 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.reviewdb.client.Account; import com.google.gerrit.reviewdb.server.ReviewDb; +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; @@ -38,22 +41,28 @@ private final Provider<ReviewDb> dbProvider; private final AccountCache byIdCache; + private final Provider<IdentifiedUser> self; @Inject - DeleteActive(Provider<ReviewDb> dbProvider, AccountCache byIdCache) { + DeleteActive(Provider<ReviewDb> dbProvider, AccountCache byIdCache, + Provider<IdentifiedUser> self) { this.dbProvider = dbProvider; this.byIdCache = byIdCache; + this.self = self; } @Override public Response<?> apply(AccountResource rsrc, Input input) - throws ResourceNotFoundException, OrmException, IOException { + throws RestApiException, OrmException, IOException { Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId()); if (a == null) { throw new ResourceNotFoundException("account not found"); } if (!a.isActive()) { - throw new ResourceNotFoundException(); + throw new ResourceConflictException("account not active"); + } + if (self.get() == rsrc.getUser()) { + throw new ResourceConflictException("cannot deactivate own account"); } a.setActive(false); dbProvider.get().accounts().update(Collections.singleton(a));
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 76f63b7..1f073ae 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
@@ -14,13 +14,13 @@ package com.google.gerrit.server.account; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.extensions.restapi.AuthException; 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.Response; import com.google.gerrit.extensions.restapi.RestModifyView; -import com.google.gerrit.reviewdb.client.Account.FieldName; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; @@ -67,7 +67,7 @@ public Response<?> apply(IdentifiedUser user, String email) throws ResourceNotFoundException, ResourceConflictException, MethodNotAllowedException, OrmException, IOException { - if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) { + if (!realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) { throw new MethodNotAllowedException("realm does not allow deleting emails"); } AccountExternalId.Key key = new AccountExternalId.Key(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java index e2fbc3c..0e9bc2e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.account; -import com.google.common.base.Function; -import com.google.common.collect.Lists; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.extensions.client.ProjectWatchInfo; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.Response; @@ -105,13 +105,10 @@ private void deleteFromGit(Account.Id accountId, List<ProjectWatchInfo> input) throws IOException, ConfigInvalidException { - watchConfig.deleteProjectWatches(accountId, Lists.transform(input, - new Function<ProjectWatchInfo, ProjectWatchKey>() { - @Override - public ProjectWatchKey apply(ProjectWatchInfo info) { - return ProjectWatchKey.create(new Project.NameKey(info.project), - info.filter); - } - })); + watchConfig.deleteProjectWatches( + accountId, + input.stream().map(w -> ProjectWatchKey.create( + new Project.NameKey(w.project), w.filter)) + .collect(toList())); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java index d3b938f..a53f64e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/FakeRealm.java
@@ -14,13 +14,13 @@ package com.google.gerrit.server.account; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Account.FieldName; /** Fake implementation of {@link Realm} that does not communicate. */ public class FakeRealm extends AbstractRealm { @Override - public boolean allowsEdit(FieldName field) { + public boolean allowsEdit(AccountFieldName field) { return false; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java index 10b6df9..9864b45 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java
@@ -14,7 +14,6 @@ package com.google.gerrit.server.account; -import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.inject.Singleton; @@ -22,9 +21,9 @@ @Singleton public class GetActive implements RestReadView<AccountResource> { @Override - public Object apply(AccountResource rsrc) { + public Response<String> apply(AccountResource rsrc) { if (rsrc.getUser().getAccount().isActive()) { - return BinaryResult.create("ok\n"); + return Response.ok("ok"); } return Response.none(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAgreements.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAgreements.java index 9e1201a..46d6f11 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAgreements.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAgreements.java
@@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.config.AgreementJson; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.project.ProjectCache; import com.google.inject.Inject; @@ -46,14 +47,17 @@ private final Provider<CurrentUser> self; private final ProjectCache projectCache; + private final AgreementJson agreementJson; private final boolean agreementsEnabled; @Inject GetAgreements(Provider<CurrentUser> self, ProjectCache projectCache, + AgreementJson agreementJson, @GerritServerConfig Config config) { this.self = self; this.projectCache = projectCache; + this.agreementJson = agreementJson; this.agreementsEnabled = config.getBoolean("auth", "contributorAgreements", false); } @@ -85,17 +89,13 @@ groupIds.add(rule.getGroup().getUUID()); } else { log.warn("group \"" + rule.getGroup().getName() + "\" does not " + - " exist, referenced in CLA \"" + ca.getName() + "\""); + "exist, referenced in CLA \"" + ca.getName() + "\""); } } } if (user.getEffectiveGroups().containsAnyOf(groupIds)) { - AgreementInfo info = new AgreementInfo(); - info.name = ca.getName(); - info.description = ca.getDescription(); - info.url = ca.getAgreementUrl(); - results.add(info); + results.add(agreementJson.format(ca)); } } return results;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java index bf1a3af..df125e0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -14,7 +14,6 @@ package com.google.gerrit.server.account; -import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.gerrit.extensions.common.SshKeyInfo; @@ -60,13 +59,9 @@ public List<SshKeyInfo> apply(IdentifiedUser user) throws RepositoryNotFoundException, IOException, ConfigInvalidException { - return Lists.transform(authorizedKeys.getKeys(user.getAccountId()), - new Function<AccountSshKey, SshKeyInfo>() { - @Override - public SshKeyInfo apply(AccountSshKey key) { - return newSshKeyInfo(key); - } - }); + return Lists.transform( + authorizedKeys.getKeys(user.getAccountId()), + GetSshKeys::newSshKeyInfo); } public static SshKeyInfo newSshKeyInfo(AccountSshKey sshKey) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java index c47d6f8..84660ec 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -14,10 +14,8 @@ package com.google.gerrit.server.account; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.common.data.GroupDescriptions; import com.google.gerrit.common.data.GroupReference; @@ -30,18 +28,11 @@ import org.eclipse.jgit.lib.ObjectId; import java.util.Collection; +import java.util.stream.StreamSupport; /** Implementation of GroupBackend for the internal group system. */ @Singleton public class InternalGroupBackend implements GroupBackend { - private static final Function<AccountGroup, GroupReference> ACT_GROUP_TO_GROUP_REF = - new Function<AccountGroup, GroupReference>() { - @Override - public GroupReference apply(AccountGroup group) { - return GroupReference.forGroup(group); - } - }; - private final GroupControl.Factory groupControlFactory; private final GroupCache groupCache; private final IncludingGroupMembership.Factory groupMembershipFactory; @@ -77,16 +68,13 @@ @Override public Collection<GroupReference> suggest(final String name, final ProjectControl project) { - Iterable<AccountGroup> filtered = Iterables.filter(groupCache.all(), - new Predicate<AccountGroup>() { - @Override - public boolean apply(AccountGroup group) { + return StreamSupport.stream(groupCache.all().spliterator(), false) + .filter(group -> // startsWithIgnoreCase && isVisible - return group.getName().regionMatches(true, 0, name, 0, name.length()) - && groupControlFactory.controlFor(group).isVisible(); - } - }); - return Lists.newArrayList(Iterables.transform(filtered, ACT_GROUP_TO_GROUP_REF)); + group.getName().regionMatches(true, 0, name, 0, name.length()) + && groupControlFactory.controlFor(group).isVisible()) + .map(GroupReference::forGroup) + .collect(toList()); } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.java index 9197011..239b954 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.java
@@ -15,7 +15,9 @@ package com.google.gerrit.server.account; import com.google.gerrit.extensions.api.accounts.AccountInput; +import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.inject.Singleton; @@ -23,7 +25,7 @@ public class PutAccount implements RestModifyView<AccountResource, AccountInput> { @Override - public Object apply(AccountResource resource, AccountInput input) + public Response<AccountInfo> apply(AccountResource resource, AccountInput input) throws ResourceConflictException { throw new ResourceConflictException("account exists"); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAgreement.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAgreement.java index 2fdf666..b8b902f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAgreement.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAgreement.java
@@ -22,6 +22,7 @@ import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.UnprocessableEntityException; @@ -68,7 +69,7 @@ } @Override - public Object apply(AccountResource resource, AgreementInput input) + public Response<String> apply(AccountResource resource, AgreementInput input) throws IOException, OrmException, RestApiException { if (!agreementsEnabled) { throw new MethodNotAllowedException("contributor agreements disabled"); @@ -103,7 +104,7 @@ addMembers.addMembers(group.getId(), ImmutableList.of(account.getId())); agreementSignup.fire(account, agreementName); - return agreementName; + return Response.ok(agreementName); } }
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 e0b69a6..74c07e8 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
@@ -15,6 +15,7 @@ package com.google.gerrit.server.account; import com.google.common.base.Strings; +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; @@ -22,7 +23,6 @@ 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.reviewdb.client.Account.FieldName; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; @@ -74,7 +74,7 @@ input = new Input(); } - if (!realm.allowsEdit(FieldName.FULL_NAME)) { + if (!realm.allowsEdit(AccountFieldName.FULL_NAME)) { throw new MethodNotAllowedException("realm does not allow editing name"); }
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 e9dc393..29168ed 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,13 +15,13 @@ package com.google.gerrit.server.account; import com.google.gerrit.common.errors.NameAlreadyUsedException; +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.reviewdb.client.Account; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.account.PutUsername.Input; @@ -64,7 +64,7 @@ throw new AuthException("not allowed to set username"); } - if (!realm.allowsEdit(Account.FieldName.USER_NAME)) { + if (!realm.allowsEdit(AccountFieldName.USER_NAME)) { throw new MethodNotAllowedException("realm does not allow editing username"); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java index 85fde4e..627f529 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.account; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.IdentifiedUser; @@ -21,10 +22,10 @@ public interface Realm { /** Can the end-user modify this field of their own account? */ - boolean allowsEdit(Account.FieldName field); + boolean allowsEdit(AccountFieldName field); /** Returns the account fields that the end-user can modify. */ - Set<Account.FieldName> getEditableFields(); + Set<AccountFieldName> getEditableFields(); AuthRequest authenticate(AuthRequest who) throws AccountException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java index 30a3bdf..871b1cd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -15,8 +15,8 @@ package com.google.gerrit.server.account; import static com.google.common.base.Preconditions.checkState; +import static java.util.Comparator.comparing; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -278,13 +278,7 @@ * @param newKeys the new public SSH keys */ public void setKeys(Collection<AccountSshKey> newKeys) { - Ordering<AccountSshKey> o = - Ordering.natural().onResultOf(new Function<AccountSshKey, Integer>() { - @Override - public Integer apply(AccountSshKey sshKey) { - return sshKey.getKey().get(); - } - }); + Ordering<AccountSshKey> o = Ordering.from(comparing(k -> k.getKey().get())); keys = new ArrayList<>(Collections.nCopies(o.max(newKeys).getKey().get(), Optional.<AccountSshKey> absent())); for (AccountSshKey key : newKeys) {
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 2af9f1d..3533fe8 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
@@ -14,6 +14,8 @@ package com.google.gerrit.server.api.accounts; +import static javax.servlet.http.HttpServletResponse.SC_OK; + import com.google.gerrit.common.RawInputUtil; import com.google.gerrit.common.errors.EmailException; import com.google.gerrit.extensions.api.accounts.AccountApi; @@ -31,6 +33,7 @@ import com.google.gerrit.extensions.common.GpgKeyInfo; import com.google.gerrit.extensions.common.SshKeyInfo; import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.server.GpgException; @@ -38,8 +41,10 @@ import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.AddSshKey; import com.google.gerrit.server.account.CreateEmail; +import com.google.gerrit.server.account.DeleteActive; import com.google.gerrit.server.account.DeleteSshKey; import com.google.gerrit.server.account.DeleteWatchedProjects; +import com.google.gerrit.server.account.GetActive; import com.google.gerrit.server.account.GetAgreements; import com.google.gerrit.server.account.GetAvatar; import com.google.gerrit.server.account.GetDiffPreferences; @@ -48,6 +53,7 @@ import com.google.gerrit.server.account.GetSshKeys; import com.google.gerrit.server.account.GetWatchedProjects; import com.google.gerrit.server.account.PostWatchedProjects; +import com.google.gerrit.server.account.PutActive; import com.google.gerrit.server.account.PutAgreement; import com.google.gerrit.server.account.SetDiffPreferences; import com.google.gerrit.server.account.SetEditPreferences; @@ -99,6 +105,9 @@ private final SshKeys sshKeys; private final GetAgreements getAgreements; private final PutAgreement putAgreement; + private final GetActive getActive; + private final PutActive putActive; + private final DeleteActive deleteActive; @Inject AccountApiImpl(AccountLoader.Factory ailf, @@ -126,6 +135,9 @@ SshKeys sshKeys, GetAgreements getAgreements, PutAgreement putAgreement, + GetActive getActive, + PutActive putActive, + DeleteActive deleteActive, @Assisted AccountResource account) { this.account = account; this.accountLoaderFactory = ailf; @@ -153,6 +165,9 @@ this.gpgApiAdapter = gpgApiAdapter; this.getAgreements = getAgreements; this.putAgreement = putAgreement; + this.getActive = getActive; + this.putActive = putActive; + this.deleteActive = deleteActive; } @Override @@ -169,6 +184,25 @@ } @Override + public boolean getActive() throws RestApiException { + Response<String> result = getActive.apply(account); + return result.statusCode() == SC_OK && result.value().equals("ok"); + } + + @Override + public void setActive(boolean active) throws RestApiException { + try { + if (active) { + putActive.apply(account, new PutActive.Input()); + } else { + deleteActive.apply(account, new DeleteActive.Input()); + } + } catch (OrmException | IOException e) { + throw new RestApiException("Cannot set active", e); + } + } + + @Override public String getAvatarUrl(int size) throws RestApiException { getAvatar.setSize(size); return getAvatar.apply(account).location();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java index a18c575..afda5fa 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.api.changes; +import com.google.gerrit.extensions.api.changes.DeleteReviewerInput; import com.google.gerrit.extensions.api.changes.DeleteVoteInput; import com.google.gerrit.extensions.api.changes.ReviewerApi; import com.google.gerrit.extensions.restapi.RestApiException; @@ -79,8 +80,13 @@ @Override public void remove() throws RestApiException { + remove(new DeleteReviewerInput()); + } + + @Override + public void remove(DeleteReviewerInput input) throws RestApiException { try { - deleteReviewer.apply(reviewer, null); + deleteReviewer.apply(reviewer, input); } catch (UpdateException e) { throw new RestApiException("Cannot remove reviewer", 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 6b5e83c..fed792b 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
@@ -30,6 +30,7 @@ import com.google.gerrit.extensions.client.SubmitType; 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.FileInfo; import com.google.gerrit.extensions.common.MergeableInfo; import com.google.gerrit.extensions.common.TestSubmitRuleInput; @@ -44,12 +45,14 @@ import com.google.gerrit.server.change.DraftComments; import com.google.gerrit.server.change.FileResource; import com.google.gerrit.server.change.Files; +import com.google.gerrit.server.change.GetMergeList; import com.google.gerrit.server.change.GetPatch; import com.google.gerrit.server.change.GetRevisionActions; import com.google.gerrit.server.change.ListRevisionComments; import com.google.gerrit.server.change.ListRevisionDrafts; import com.google.gerrit.server.change.Mergeable; import com.google.gerrit.server.change.PostReview; +import com.google.gerrit.server.change.PreviewSubmit; import com.google.gerrit.server.change.PublishDraftPatchSet; import com.google.gerrit.server.change.Rebase; import com.google.gerrit.server.change.RebaseUtil; @@ -59,9 +62,11 @@ import com.google.gerrit.server.change.TestSubmitType; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.UpdateException; +import com.google.gerrit.server.patch.PatchListNotAvailableException; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; +import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; import org.eclipse.jgit.lib.Repository; @@ -84,6 +89,7 @@ private final Rebase rebase; private final RebaseUtil rebaseUtil; private final Submit submit; + private final PreviewSubmit submitPreview; private final PublishDraftPatchSet publish; private final Reviewed.PutReviewed putReviewed; private final Reviewed.DeleteReviewed deleteReviewed; @@ -104,6 +110,7 @@ private final GetRevisionActions revisionActions; private final TestSubmitType testSubmitType; private final TestSubmitType.Get getSubmitType; + private final Provider<GetMergeList> getMergeList; @Inject RevisionApiImpl(GitRepositoryManager repoManager, @@ -113,6 +120,7 @@ Rebase rebase, RebaseUtil rebaseUtil, Submit submit, + PreviewSubmit submitPreview, PublishDraftPatchSet publish, Reviewed.PutReviewed putReviewed, Reviewed.DeleteReviewed deleteReviewed, @@ -132,6 +140,7 @@ GetRevisionActions revisionActions, TestSubmitType testSubmitType, TestSubmitType.Get getSubmitType, + Provider<GetMergeList> getMergeList, @Assisted RevisionResource r) { this.repoManager = repoManager; this.changes = changes; @@ -141,6 +150,7 @@ this.rebaseUtil = rebaseUtil; this.review = review; this.submit = submit; + this.submitPreview = submitPreview; this.publish = publish; this.files = files; this.putReviewed = putReviewed; @@ -159,6 +169,7 @@ this.revisionActions = revisionActions; this.testSubmitType = testSubmitType; this.getSubmitType = getSubmitType; + this.getMergeList = getMergeList; this.revision = r; } @@ -187,6 +198,12 @@ } @Override + public BinaryResult submitPreview() throws RestApiException { + submitPreview.setFormat("zip"); + return submitPreview.apply(revision); + } + + @Override public void publish() throws RestApiException { try { publish.apply(revision, new PublishDraftPatchSet.Input()); @@ -264,7 +281,7 @@ return ImmutableSet.copyOf((Iterable<String>) listFiles .setReviewed(true) .apply(revision).value()); - } catch (OrmException | IOException e) { + } catch (OrmException | IOException | PatchListNotAvailableException e) { throw new RestApiException("Cannot list reviewed files", e); } } @@ -293,7 +310,7 @@ public Map<String, FileInfo> files() throws RestApiException { try { return (Map<String, FileInfo>)listFiles.apply(revision).value(); - } catch (OrmException | IOException e) { + } catch (OrmException | IOException | PatchListNotAvailableException e) { throw new RestApiException("Cannot retrieve files", e); } } @@ -304,7 +321,7 @@ try { return (Map<String, FileInfo>) listFiles.setBase(base) .apply(revision).value(); - } catch (OrmException | IOException e) { + } catch (OrmException | IOException | PatchListNotAvailableException e) { throw new RestApiException("Cannot retrieve files", e); } } @@ -315,7 +332,7 @@ try { return (Map<String, FileInfo>) listFiles.setParent(parentNum) .apply(revision).value(); - } catch (OrmException | IOException e) { + } catch (OrmException | IOException | PatchListNotAvailableException e) { throw new RestApiException("Cannot retrieve files", e); } } @@ -427,4 +444,21 @@ throw new RestApiException("Cannot test submit type", e); } } + + @Override + public MergeListRequest getMergeList() throws RestApiException { + return new MergeListRequest() { + @Override + public List<CommitInfo> get() throws RestApiException { + try { + GetMergeList gml = getMergeList.get(); + gml.setUninterestingParent(getUninterestingParent()); + gml.setAddLinks(getAddLinks()); + return gml.apply(revision).value(); + } catch (IOException e) { + throw new RestApiException("Cannot get merge list", e); + } + } + }; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java index 8339ecf..f433d2b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java
@@ -18,10 +18,12 @@ import com.google.gerrit.extensions.api.config.Server; import com.google.gerrit.extensions.client.DiffPreferencesInfo; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; +import com.google.gerrit.extensions.common.ServerInfo; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.server.config.ConfigResource; import com.google.gerrit.server.config.GetDiffPreferences; import com.google.gerrit.server.config.GetPreferences; +import com.google.gerrit.server.config.GetServerInfo; import com.google.gerrit.server.config.SetDiffPreferences; import com.google.gerrit.server.config.SetPreferences; import com.google.inject.Inject; @@ -37,16 +39,19 @@ private final SetPreferences setPreferences; private final GetDiffPreferences getDiffPreferences; private final SetDiffPreferences setDiffPreferences; + private final GetServerInfo getServerInfo; @Inject ServerImpl(GetPreferences getPreferences, SetPreferences setPreferences, GetDiffPreferences getDiffPreferences, - SetDiffPreferences setDiffPreferences) { + SetDiffPreferences setDiffPreferences, + GetServerInfo getServerInfo) { this.getPreferences = getPreferences; this.setPreferences = setPreferences; this.getDiffPreferences = getDiffPreferences; this.setDiffPreferences = setDiffPreferences; + this.getServerInfo = getServerInfo; } @Override @@ -55,6 +60,15 @@ } @Override + public ServerInfo getInfo() throws RestApiException { + try { + return getServerInfo.apply(new ConfigResource()); + } catch (IOException e) { + throw new RestApiException("Cannot get server info", e); + } + } + + @Override public GeneralPreferencesInfo getDefaultPreferences() throws RestApiException { try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java index 7f5f2d2..4cb96b3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.args4j; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.account.AccountException; import com.google.gerrit.server.account.AccountManager;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java index 8dc7177..3dddf4d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
@@ -14,7 +14,7 @@ package com.google.gerrit.server.auth.ldap; -import com.google.gerrit.reviewdb.client.AuthType; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.server.account.AccountException; import com.google.gerrit.server.auth.AuthBackend; import com.google.gerrit.server.auth.AuthException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java index 30b08a6..603efe0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -21,10 +21,11 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.gerrit.common.data.ParameterizedString; +import com.google.gerrit.extensions.client.AccountFieldName; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.account.AbstractRealm; import com.google.gerrit.server.account.AccountException; @@ -67,7 +68,7 @@ private final AuthConfig authConfig; private final EmailExpander emailExpander; private final LoadingCache<String, Optional<Account.Id>> usernameCache; - private final Set<Account.FieldName> readOnlyAccountFields; + private final Set<AccountFieldName> readOnlyAccountFields; private final boolean fetchMemberOfEagerly; private final Config config; @@ -91,13 +92,13 @@ this.readOnlyAccountFields = new HashSet<>(); if (optdef(config, "accountFullName", "DEFAULT") != null) { - readOnlyAccountFields.add(Account.FieldName.FULL_NAME); + readOnlyAccountFields.add(AccountFieldName.FULL_NAME); } if (optdef(config, "accountSshUserName", "DEFAULT") != null) { - readOnlyAccountFields.add(Account.FieldName.USER_NAME); + readOnlyAccountFields.add(AccountFieldName.USER_NAME); } if (!authConfig.isAllowRegisterNewEmail()) { - readOnlyAccountFields.add(Account.FieldName.REGISTER_NEW_EMAIL); + readOnlyAccountFields.add(AccountFieldName.REGISTER_NEW_EMAIL); } fetchMemberOfEagerly = optional(config, "fetchMemberOfEagerly", true); @@ -196,7 +197,7 @@ } @Override - public boolean allowsEdit(final Account.FieldName field) { + public boolean allowsEdit(final AccountFieldName field) { return !readOnlyAccountFields.contains(field); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java index cf9000d..94a3ac2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
@@ -17,9 +17,9 @@ import com.google.common.base.Strings; import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider; import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo; +import com.google.gerrit.extensions.client.AccountFieldName; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Account.FieldName; import com.google.gerrit.server.account.AbstractRealm; import com.google.gerrit.server.account.AccountException; import com.google.gerrit.server.account.AccountManager; @@ -37,7 +37,7 @@ @Singleton public class OAuthRealm extends AbstractRealm { private final DynamicMap<OAuthLoginProvider> loginProviders; - private final Set<FieldName> editableAccountFields; + private final Set<AccountFieldName> editableAccountFields; @Inject OAuthRealm(DynamicMap<OAuthLoginProvider> loginProviders, @@ -45,15 +45,15 @@ this.loginProviders = loginProviders; this.editableAccountFields = new HashSet<>(); if (config.getBoolean("oauth", null, "allowEditFullName", false)) { - editableAccountFields.add(FieldName.FULL_NAME); + editableAccountFields.add(AccountFieldName.FULL_NAME); } if (config.getBoolean("oauth", null, "allowRegisterNewEmail", false)) { - editableAccountFields.add(FieldName.REGISTER_NEW_EMAIL); + editableAccountFields.add(AccountFieldName.REGISTER_NEW_EMAIL); } } @Override - public boolean allowsEdit(FieldName field) { + public boolean allowsEdit(AccountFieldName field) { return editableAccountFields.contains(field); } @@ -105,12 +105,12 @@ } if (!Strings.isNullOrEmpty(userInfo.getEmailAddress()) && (Strings.isNullOrEmpty(who.getUserName()) - || !allowsEdit(FieldName.REGISTER_NEW_EMAIL))) { + || !allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL))) { who.setEmailAddress(userInfo.getEmailAddress()); } if (!Strings.isNullOrEmpty(userInfo.getDisplayName()) && (Strings.isNullOrEmpty(who.getDisplayName()) - || !allowsEdit(FieldName.FULL_NAME))) { + || !allowsEdit(AccountFieldName.FULL_NAME))) { who.setDisplayName(userInfo.getDisplayName()); } return who;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java index adbcf22..c4bd68d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -28,6 +28,7 @@ import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; 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.ChangeMessagesUtil; import com.google.gerrit.server.ChangeUtil; @@ -50,6 +51,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; + @Singleton public class Abandon implements RestModifyView<ChangeResource, AbandonInput>, UiAction<ChangeResource> { @@ -91,6 +94,11 @@ return json.create(ChangeJson.NO_OPTIONS).format(change); } + public Change abandon(ChangeControl control) + throws RestApiException, UpdateException { + return abandon(control, "", NotifyHandling.ALL); + } + public Change abandon(ChangeControl control, String msgTxt) throws RestApiException, UpdateException { return abandon(control, msgTxt, NotifyHandling.ALL); @@ -98,31 +106,76 @@ public Change abandon(ChangeControl control, String msgTxt, NotifyHandling notifyHandling) throws RestApiException, UpdateException { - CurrentUser user = control.getUser(); - Account account = user.isIdentifiedUser() - ? user.asIdentifiedUser().getAccount() - : null; - Op op = new Op(msgTxt, account, notifyHandling); - try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(), - control.getProject().getNameKey(), user, TimeUtil.nowTs())) { + Op op = new Op(control.getUser(), msgTxt, notifyHandling); + try (BatchUpdate u = + batchUpdateFactory.create( + dbProvider.get(), + control.getProject().getNameKey(), + control.getUser(), + TimeUtil.nowTs())) { u.addOp(control.getId(), op).execute(); } return op.change; } + /** + * If an extension has more than one changes to abandon that belong to the + * same project, they should use the batch instead of abandoning one by one. + * <p> + * It's the caller's responsibility to ensure that all jobs inside the same + * batch have the matching project from its ChangeControl. Violations will + * result in a ResourceConflictException. + */ + public void batchAbandon(Project.NameKey project, CurrentUser user, + Collection<ChangeControl> controls, String msgTxt, + NotifyHandling notifyHandling) throws RestApiException, UpdateException { + if (controls.isEmpty()) { + return; + } + try (BatchUpdate u = batchUpdateFactory.create( + dbProvider.get(), project, user, TimeUtil.nowTs())) { + for (ChangeControl control : controls) { + if (!project.equals(control.getProject().getNameKey())) { + throw new ResourceConflictException( + String.format( + "Project name \"%s\" doesn't match \"%s\"", + control.getProject().getNameKey().get(), + project.get())); + } + u.addOp( + control.getId(), new Op(control.getUser(), msgTxt, notifyHandling)); + } + u.execute(); + } + } + + public void batchAbandon(Project.NameKey project, CurrentUser user, + Collection<ChangeControl> controls, String msgTxt) + throws RestApiException, UpdateException { + batchAbandon(project, user, controls, msgTxt, NotifyHandling.ALL); + } + + public void batchAbandon(Project.NameKey project, CurrentUser user, + Collection<ChangeControl> controls) + throws RestApiException, UpdateException { + batchAbandon(project, user, controls, "", NotifyHandling.ALL); + } + private class Op extends BatchUpdate.Op { - private final Account account; private final String msgTxt; + private final NotifyHandling notifyHandling; + private final Account account; private Change change; private PatchSet patchSet; private ChangeMessage message; - private NotifyHandling notifyHandling; - private Op(String msgTxt, Account account, NotifyHandling notifyHandling) { - this.account = account; + private Op(CurrentUser user, String msgTxt, NotifyHandling notifyHandling) { this.msgTxt = msgTxt; this.notifyHandling = notifyHandling; + account = user.isIdentifiedUser() + ? user.asIdentifiedUser().getAccount() + : null; } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java index 60d9c08..205d959 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
@@ -14,7 +14,9 @@ package com.google.gerrit.server.change; -import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.InternalUser; import com.google.gerrit.server.config.ChangeCleanupConfig; import com.google.gerrit.server.project.ChangeControl; @@ -29,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; @@ -37,10 +40,10 @@ private static final Logger log = LoggerFactory.getLogger(AbandonUtil.class); private final ChangeCleanupConfig cfg; - private final InternalUser.Factory internalUserFactory; private final ChangeQueryProcessor queryProcessor; private final ChangeQueryBuilder queryBuilder; private final Abandon abandon; + private final InternalUser internalUser; @Inject AbandonUtil( @@ -50,10 +53,10 @@ ChangeQueryBuilder queryBuilder, Abandon abandon) { this.cfg = cfg; - this.internalUserFactory = internalUserFactory; this.queryProcessor = queryProcessor; this.queryBuilder = queryBuilder; this.abandon = abandon; + internalUser = internalUserFactory.create(); } public void abandonInactiveOpenChanges() { @@ -68,42 +71,42 @@ if (!cfg.getAbandonIfMergeable()) { query += " -is:mergeable"; } - List<ChangeData> changesToAbandon = queryProcessor.enforceVisibility(false) - .query(queryBuilder.parse(query)).entities(); - int count = 0; + + List<ChangeData> changesToAbandon = + queryProcessor + .enforceVisibility(false) + .query(queryBuilder.parse(query)) + .entities(); + ImmutableMultimap.Builder<Project.NameKey, ChangeControl> builder = + ImmutableMultimap.builder(); for (ChangeData cd : changesToAbandon) { + ChangeControl control = cd.changeControl(internalUser); + builder.put(control.getProject().getNameKey(), control); + } + + int count = 0; + Multimap<Project.NameKey, ChangeControl> abandons = builder.build(); + String message = cfg.getAbandonMessage(); + for (Project.NameKey project : abandons.keySet()) { + Collection<ChangeControl> changes = abandons.get(project); try { - if (noNeedToAbandon(cd, query)){ - log.debug("Change data \"{}\" does not satisfy the query \"{}\" any" - + " more, hence skipping it in clean up", cd, query); - continue; - } - abandon.abandon(changeControl(cd), cfg.getAbandonMessage()); - count++; - } catch (ResourceConflictException e) { - // Change was already merged or abandoned. + abandon.batchAbandon(project, internalUser, changes, message); + count += changes.size(); } catch (Throwable e) { - log.error(String.format( - "Failed to auto-abandon inactive open change %d.", - cd.getId().get()), e); + StringBuilder msg = + new StringBuilder("Failed to auto-abandon inactive change(s):"); + for (ChangeControl change : changes) { + msg.append(" ").append(change.getId().get()); + } + msg.append("."); + log.error(msg.toString(), e); } } log.info(String.format("Auto-Abandoned %d of %d changes.", count, changesToAbandon.size())); } catch (QueryParseException | OrmException e) { - log.error("Failed to query inactive open changes for auto-abandoning.", e); + log.error( + "Failed to query inactive open changes for auto-abandoning.", e); } } - - private boolean noNeedToAbandon(ChangeData cd, String query) - throws OrmException, QueryParseException { - String newQuery = query + " change:" + cd.getId(); - List<ChangeData> changesToAbandon = queryProcessor.enforceVisibility(false) - .query(queryBuilder.parse(newQuery)).entities(); - return changesToAbandon.isEmpty(); - } - - private ChangeControl changeControl(ChangeData cd) throws OrmException { - return cd.changeControl(internalUserFactory.create()); - } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/AllowedFormats.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/AllowedFormats.java new file mode 100644 index 0000000..756ce88 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/AllowedFormats.java
@@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.gerrit.server.config.DownloadConfig; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@Singleton +public class AllowedFormats { + final ImmutableMap<String, ArchiveFormat> extensions; + final ImmutableSet<ArchiveFormat> allowed; + + @Inject + AllowedFormats(DownloadConfig cfg) { + Map<String, ArchiveFormat> exts = new HashMap<>(); + for (ArchiveFormat format : cfg.getArchiveFormats()) { + for (String ext : format.getSuffixes()) { + exts.put(ext, format); + } + exts.put(format.name().toLowerCase(), format); + } + extensions = ImmutableMap.copyOf(exts); + + // Zip is not supported because it may be interpreted by a Java plugin as a + // valid JAR file, whose code would have access to cookies on the domain. + allowed = Sets.immutableEnumSet( + Iterables.filter(cfg.getArchiveFormats(), f -> f != ArchiveFormat.ZIP)); + } + + public Set<ArchiveFormat> getAllowed() { + return allowed; + } + + public ImmutableMap<String, ArchiveFormat> getExtensions() { + return extensions; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java index 335f201..9b8f2b8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
@@ -14,6 +14,10 @@ package com.google.gerrit.server.change; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.archive.TarFormat; import org.eclipse.jgit.archive.Tbz2Format; @@ -21,12 +25,40 @@ import org.eclipse.jgit.archive.TxzFormat; import org.eclipse.jgit.archive.ZipFormat; +import java.io.IOException; +import java.io.OutputStream; + public enum ArchiveFormat { - TGZ("application/x-gzip", new TgzFormat()), - TAR("application/x-tar", new TarFormat()), - TBZ2("application/x-bzip2", new Tbz2Format()), - TXZ("application/x-xz", new TxzFormat()), - ZIP("application/x-zip", new ZipFormat()); + TGZ("application/x-gzip", new TgzFormat()) { + @Override + public ArchiveEntry prepareArchiveEntry(String fileName) { + return new TarArchiveEntry(fileName); + } + }, + TAR("application/x-tar", new TarFormat()) { + @Override + public ArchiveEntry prepareArchiveEntry(String fileName) { + return new TarArchiveEntry(fileName); + } + }, + TBZ2("application/x-bzip2", new Tbz2Format()) { + @Override + public ArchiveEntry prepareArchiveEntry(String fileName) { + return new TarArchiveEntry(fileName); + } + }, + TXZ("application/x-xz", new TxzFormat()) { + @Override + public ArchiveEntry prepareArchiveEntry(String fileName) { + return new TarArchiveEntry(fileName); + } + }, + ZIP("application/x-zip", new ZipFormat()) { + @Override + public ArchiveEntry prepareArchiveEntry(String fileName) { + return new ZipArchiveEntry(fileName); + } + }; private final ArchiveCommand.Format<?> format; private final String mimeType; @@ -52,4 +84,11 @@ Iterable<String> getSuffixes() { return format.suffixes(); } -} + + public ArchiveOutputStream createArchiveOutputStream(OutputStream o) + throws IOException { + return (ArchiveOutputStream)this.format.createArchiveOutputStream(o); + } + + public abstract ArchiveEntry prepareArchiveEntry(final String fileName); +} \ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java index 6333809..6906fb2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID; +import static java.util.stream.Collectors.toSet; import com.google.common.base.MoreObjects; import com.google.gerrit.common.FooterConstants; @@ -34,11 +35,11 @@ import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.ChangeMessagesUtil; import com.google.gerrit.server.ChangeUtil; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.events.CommitReceivedEvent; import com.google.gerrit.server.extensions.events.CommentAdded; import com.google.gerrit.server.extensions.events.RevisionCreated; -import com.google.gerrit.server.git.BanCommit; import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.BatchUpdate.ChangeContext; import com.google.gerrit.server.git.BatchUpdate.Context; @@ -48,6 +49,7 @@ import com.google.gerrit.server.git.validators.CommitValidationException; import com.google.gerrit.server.git.validators.CommitValidators; import com.google.gerrit.server.mail.CreateChangeSender; +import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gerrit.server.patch.PatchSetInfoFactory; import com.google.gerrit.server.project.ChangeControl; @@ -62,7 +64,6 @@ import com.google.inject.assistedinject.Assisted; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.util.ChangeIdUtil; @@ -86,6 +87,7 @@ LoggerFactory.getLogger(ChangeInserter.class); private final ProjectControl.GenericFactory projectControlFactory; + private final IdentifiedUser.GenericFactory userFactory; private final ChangeControl.GenericFactory changeControlFactory; private final PatchSetInfoFactory patchSetInfoFactory; private final PatchSetUtil psUtil; @@ -128,6 +130,7 @@ @Inject ChangeInserter(ProjectControl.GenericFactory projectControlFactory, + IdentifiedUser.GenericFactory userFactory, ChangeControl.GenericFactory changeControlFactory, PatchSetInfoFactory patchSetInfoFactory, PatchSetUtil psUtil, @@ -142,6 +145,7 @@ @Assisted RevCommit commit, @Assisted String refName) { this.projectControlFactory = projectControlFactory; + this.userFactory = userFactory; this.changeControlFactory = changeControlFactory; this.patchSetInfoFactory = patchSetInfoFactory; this.psUtil = psUtil; @@ -353,8 +357,10 @@ update.fixStatus(change.getStatus()); LabelTypes labelTypes = ctl.getProjectControl().getLabelTypes(); - approvalsUtil.addReviewers(db, update, labelTypes, change, - patchSet, patchSetInfo, reviewers, Collections.<Account.Id> emptySet()); + approvalsUtil.addReviewers(db, update, labelTypes, change, patchSet, + patchSetInfo, + filterOnChangeVisibility(db, ctx.getNotes(), reviewers), + Collections.<Account.Id> emptySet()); approvalsUtil.addApprovals(db, update, labelTypes, patchSet, ctx.getControl(), approvals); if (message != null) { @@ -368,6 +374,27 @@ return true; } + private Set<Account.Id> filterOnChangeVisibility(final ReviewDb db, + final ChangeNotes notes, Set<Account.Id> accounts) { + return accounts.stream() + .filter( + accountId -> { + try { + IdentifiedUser user = userFactory.create(accountId); + return changeControlFactory.controlFor(notes, user) + .isVisible(db); + } catch (OrmException | NoSuchChangeException e) { + log.warn( + String.format( + "Failed to check if account %d can see change %d", + accountId.get(), notes.getChangeId().get()), + e); + return false; + } + }) + .collect(toSet()); + } + @Override public void postUpdate(Context ctx) throws OrmException, NoSuchChangeException { if (sendMail) { @@ -440,9 +467,6 @@ try { RefControl refControl = projectControlFactory .controlFor(ctx.getProject(), ctx.getUser()).controlForRef(refName); - CommitValidators cv = commitValidatorsFactory.create( - refControl, new NoSshInfo(), ctx.getRepository()); - String refName = psId.toRefName(); CommitReceivedEvent event = new CommitReceivedEvent( new ReceiveCommand( @@ -453,19 +477,10 @@ change.getDest().get(), commit, ctx.getIdentifiedUser()); - - switch (validatePolicy) { - case RECEIVE_COMMITS: - NoteMap rejectCommits = BanCommit.loadRejectCommitsMap( - ctx.getRepository(), ctx.getRevWalk()); - cv.validateForReceiveCommits(event, rejectCommits); - break; - case GERRIT: - cv.validateForGerritCommits(event); - break; - case NONE: - break; - } + commitValidatorsFactory + .create( + validatePolicy, refControl, new NoSshInfo(), ctx.getRepository()) + .validate(event); } catch (CommitValidationException e) { throw new ResourceConflictException(e.getFullMessage()); } catch (NoSuchProjectException e) {
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 07714dc..d8078ca 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
@@ -34,9 +34,9 @@ import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES; import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS; import static com.google.gerrit.server.CommonConverters.toGitPerson; +import static java.util.stream.Collectors.toList; import com.google.auto.value.AutoValue; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; @@ -45,6 +45,7 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -141,6 +142,9 @@ public static final Set<ListChangesOption> NO_OPTIONS = Collections.emptySet(); + public static final ImmutableSet<ListChangesOption> REQUIRE_LAZY_LOAD = + ImmutableSet.of(ALL_REVISIONS, MESSAGES); + public interface Factory { ChangeJson create(Set<ListChangesOption> options); } @@ -168,7 +172,9 @@ private final ChangeResource.Factory changeResourceFactory; private final ChangeKindCache changeKindCache; + private boolean lazyLoad = true; private AccountLoader accountLoader; + private boolean includeSubmittable; private Map<Change.Id, List<SubmitRecord>> submitRecords; private FixInput fix; @@ -222,6 +228,16 @@ : EnumSet.copyOf(options); } + public ChangeJson includeSubmittable(boolean include) { + includeSubmittable = include; + return this; + } + + public ChangeJson lazyLoad(boolean load) { + lazyLoad = load; + return this; + } + public ChangeJson fix(FixInput fix) { this.fix = fix; return this; @@ -282,13 +298,8 @@ public List<List<ChangeInfo>> formatQueryResults( List<QueryResult<ChangeData>> in) throws OrmException { accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); - ensureLoaded(FluentIterable.from(in).transformAndConcat( - new Function<QueryResult<ChangeData>, List<ChangeData>>() { - @Override - public List<ChangeData> apply(QueryResult<ChangeData> in) { - return in.entities(); - } - })); + ensureLoaded( + FluentIterable.from(in).transformAndConcat(QueryResult::entities)); List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size()); Map<Change.Id, ChangeInfo> out = new HashMap<>(); @@ -316,16 +327,22 @@ } private void ensureLoaded(Iterable<ChangeData> all) throws OrmException { - ChangeData.ensureChangeLoaded(all); - if (has(ALL_REVISIONS)) { - ChangeData.ensureAllPatchSetsLoaded(all); - } else if (has(CURRENT_REVISION) || has(MESSAGES)) { - ChangeData.ensureCurrentPatchSetLoaded(all); + if (lazyLoad) { + ChangeData.ensureChangeLoaded(all); + if (has(ALL_REVISIONS)) { + ChangeData.ensureAllPatchSetsLoaded(all); + } else if (has(CURRENT_REVISION) || has(MESSAGES)) { + ChangeData.ensureCurrentPatchSetLoaded(all); + } + if (has(REVIEWED) && userProvider.get().isIdentifiedUser()) { + ChangeData.ensureReviewedByLoadedForOpenChanges(all); + } + ChangeData.ensureCurrentApprovalsLoaded(all); + } else { + for (ChangeData cd : all) { + cd.setLazyLoad(false); + } } - if (has(REVIEWED) && userProvider.get().isIdentifiedUser()) { - ChangeData.ensureReviewedByLoadedForOpenChanges(all); - } - ChangeData.ensureCurrentApprovalsLoaded(all); } private boolean has(ListChangesOption option) { @@ -421,14 +438,16 @@ out.topic = in.getTopic(); out.hashtags = cd.hashtags(); out.changeId = in.getKey().get(); - if (in.getStatus() != Change.Status.MERGED) { + if (in.getStatus().isOpen()) { SubmitTypeRecord str = cd.submitTypeRecord(); if (str.isOk()) { out.submitType = str.type; } out.mergeable = cd.isMergeable(); + if (includeSubmittable) { + out.submittable = submittable(cd); + } } - out.submittable = Submit.submittable(cd); Optional<ChangedLines> changedLines = cd.changedLines(); if (changedLines.isPresent()) { out.insertions = changedLines.get().insertions; @@ -529,6 +548,18 @@ return result; } + private boolean submittable(ChangeData cd) throws OrmException { + List<SubmitRecord> records = new SubmitRuleEvaluator(cd) + .setFastEvalLabels(true) + .evaluate(); + for (SubmitRecord sr : records) { + if (sr.status == SubmitRecord.Status.OK) { + return true; + } + } + return false; + } + private List<SubmitRecord> submitRecords(ChangeData cd) throws OrmException { // Maintain our own cache rather than using cd.getSubmitRecords(), // since the latter may not have used the same values for @@ -563,7 +594,7 @@ ? labelsForOpenChange(ctl, cd, labelTypes, standard, detailed) : labelsForClosedChange(cd, labelTypes, standard, detailed); return ImmutableMap.copyOf( - Maps.transformValues(withStatus, LabelWithStatus.TO_LABEL_INFO)); + Maps.transformValues(withStatus, LabelWithStatus::label)); } private Map<String, LabelWithStatus> labelsForOpenChange(ChangeControl ctl, @@ -663,7 +694,7 @@ // - They are an explicit reviewer. // - They ever voted on this change. Set<Account.Id> allUsers = new HashSet<>(); - allUsers.addAll(cd.reviewers().all()); + allUsers.addAll(cd.reviewers().byState(ReviewerStateInternal.REVIEWER)); for (PatchSetApproval psa : cd.approvals().values()) { allUsers.add(psa.getAccountId()); } @@ -916,22 +947,25 @@ private Collection<AccountInfo> toAccountInfo( Collection<Account.Id> accounts) { - return FluentIterable.from(accounts) - .transform(new Function<Account.Id, AccountInfo>() { - @Override - public AccountInfo apply(Account.Id id) { - return accountLoader.get(id); - } - }) - .toSortedList(AccountInfoComparator.ORDER_NULLS_FIRST); + return accounts.stream() + .map(accountLoader::get) + .sorted(AccountInfoComparator.ORDER_NULLS_FIRST) + .collect(toList()); + } + + @Nullable + private Repository openRepoIfNecessary(ChangeControl ctl) throws IOException { + if (has(ALL_COMMITS) || has(CURRENT_COMMIT) || has(COMMIT_FOOTERS)) { + return repoManager.openRepository(ctl.getProject().getNameKey()); + } + return null; } private Map<String, RevisionInfo> revisions(ChangeControl ctl, ChangeData cd, Map<PatchSet.Id, PatchSet> map) throws PatchListNotAvailableException, GpgException, OrmException, IOException { Map<String, RevisionInfo> res = new LinkedHashMap<>(); - try (Repository repo = - repoManager.openRepository(ctl.getProject().getNameKey())) { + try (Repository repo = openRepoIfNecessary(ctl)) { for (PatchSet in : map.values()) { if ((has(ALL_REVISIONS) || in.getId().equals(ctl.getChange().currentPatchSetId())) @@ -975,8 +1009,7 @@ throws PatchListNotAvailableException, GpgException, OrmException, IOException { accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); - try (Repository repo = - repoManager.openRepository(ctl.getProject().getNameKey())) { + try (Repository repo = openRepoIfNecessary(ctl)) { RevisionInfo rev = toRevisionInfo( ctl, changeDataFactory.create(db.get(), ctl), in, repo, true); accountLoader.fill(); @@ -985,7 +1018,7 @@ } private RevisionInfo toRevisionInfo(ChangeControl ctl, ChangeData cd, - PatchSet in, Repository repo, boolean fillCommit) + PatchSet in, @Nullable Repository repo, boolean fillCommit) throws PatchListNotAvailableException, GpgException, OrmException, IOException { Change c = ctl.getChange(); @@ -1022,6 +1055,7 @@ if (has(ALL_FILES) || (out.isCurrent && has(CURRENT_FILES))) { out.files = fileInfoJson.toFileInfoMap(c, in); out.files.remove(Patch.COMMIT_MSG); + out.files.remove(Patch.MERGE_LIST); } if ((out.isCurrent || (out.draft != null && out.draft)) @@ -1143,14 +1177,6 @@ @AutoValue abstract static class LabelWithStatus { - private static final Function<LabelWithStatus, LabelInfo> TO_LABEL_INFO = - new Function<LabelWithStatus, LabelInfo>() { - @Override - public LabelInfo apply(LabelWithStatus in) { - return in.label(); - } - }; - private static LabelWithStatus create(LabelInfo label, SubmitRecord.Label.Status status) { return new AutoValue_ChangeJson_LabelWithStatus(label, status);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java index 2302b70..f0075ee 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.change; +import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.client.ChangeKind; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.PatchSet; @@ -31,10 +32,11 @@ * implementation changes, which might invalidate old entries). */ public interface ChangeKindCache { - ChangeKind getChangeKind(ProjectState project, Repository repo, + ChangeKind getChangeKind(ProjectState project, @Nullable Repository repo, ObjectId prior, ObjectId next); ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch); - ChangeKind getChangeKind(Repository repo, ChangeData cd, PatchSet patch); + ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd, + PatchSet patch); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java index edc1b12..b23bcf8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -22,9 +22,11 @@ import com.google.common.cache.Cache; import com.google.common.cache.Weigher; import com.google.common.collect.FluentIterable; +import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.client.ChangeKind; 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.cache.CacheModule; import com.google.gerrit.server.config.GerritServerConfig; @@ -100,11 +102,13 @@ } @Override - public ChangeKind getChangeKind(ProjectState project, Repository repo, - ObjectId prior, ObjectId next) { + public ChangeKind getChangeKind(ProjectState project, + @Nullable Repository repo, ObjectId prior, ObjectId next) { try { Key key = new Key(prior, next, useRecursiveMerge); - return new Loader(key, repo).call(); + return new Loader( + key, repoManager, project.getProject().getNameKey(), repo) + .call(); } catch (IOException e) { log.warn("Cannot check trivial rebase of new patch set " + next.name() + " in " + project.getProject().getName(), e); @@ -120,7 +124,7 @@ } @Override - public ChangeKind getChangeKind(Repository repo, ChangeData cd, + public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd, PatchSet patch) { return getChangeKindInternal(this, repo, cd, patch, projectCache); } @@ -191,11 +195,16 @@ private static class Loader implements Callable<ChangeKind> { private final Key key; - private final Repository repo; + private final GitRepositoryManager repoManager; + private final Project.NameKey projectName; + private final Repository alreadyOpenRepo; - private Loader(Key key, Repository repo) { + private Loader(Key key, GitRepositoryManager repoManager, + Project.NameKey projectName, @Nullable Repository alreadyOpenRepo) { this.key = key; - this.repo = repo; + this.repoManager = repoManager; + this.projectName = projectName; + this.alreadyOpenRepo = alreadyOpenRepo; } @Override @@ -204,6 +213,12 @@ return ChangeKind.NO_CODE_CHANGE; } + Repository repo = alreadyOpenRepo; + boolean close = false; + if (repo == null) { + repo = repoManager.openRepository(projectName); + close = true; + } try (RevWalk walk = new RevWalk(repo)) { RevCommit prior = walk.parseCommit(key.prior); walk.parseBody(prior); @@ -246,6 +261,10 @@ // it was a rework. } return ChangeKind.REWORK; + } finally { + if (close) { + repo.close(); + } } } @@ -321,11 +340,14 @@ } @Override - public ChangeKind getChangeKind(ProjectState project, Repository repo, - ObjectId prior, ObjectId next) { + public ChangeKind getChangeKind(ProjectState project, + @Nullable Repository repo, ObjectId prior, ObjectId next) { try { Key key = new Key(prior, next, useRecursiveMerge); - return cache.get(key, new Loader(key, repo)); + return cache.get( + key, + new Loader( + key, repoManager, project.getProject().getNameKey(), repo)); } catch (ExecutionException e) { log.warn("Cannot check trivial rebase of new patch set " + next.name() + " in " + project.getProject().getName(), e); @@ -340,14 +362,14 @@ } @Override - public ChangeKind getChangeKind(Repository repo, ChangeData cd, + public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd, PatchSet patch) { return getChangeKindInternal(this, repo, cd, patch, projectCache); } private static ChangeKind getChangeKindInternal( ChangeKindCache cache, - Repository repo, + @Nullable Repository repo, ChangeData change, PatchSet patch, ProjectCache projectCache) {
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 db18ba2..0c620e2 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
@@ -17,6 +17,7 @@ import com.google.common.base.Strings; import com.google.gerrit.common.FooterConstants; import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.restapi.MergeConflictException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.reviewdb.client.Branch; @@ -236,7 +237,7 @@ bu.addOp(destChange.getId(), inserter .setMessage("Uploaded patch set " + newPatchSetId.get() + ".") .setDraft(current.isDraft()) - .setSendMail(false)); + .setNotify(NotifyHandling.NONE)); return destChange.getId(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java index d1ce453..2948fa6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentJson.java
@@ -16,7 +16,6 @@ import static com.google.gerrit.server.PatchLineCommentsUtil.COMMENT_INFO_ORDER; -import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.gerrit.extensions.client.Comment.Range; @@ -100,17 +99,13 @@ List<CommentInfo> formatAsList(Iterable<PatchLineComment> l) throws OrmException { - final AccountLoader accountLoader = fillAccounts + AccountLoader accountLoader = fillAccounts ? accountLoaderFactory.create(true) : null; List<CommentInfo> out = FluentIterable .from(l) - .transform(new Function<PatchLineComment, CommentInfo>() { - @Override - public CommentInfo apply(PatchLineComment c) { - return toCommentInfo(c, accountLoader); - } - }).toSortedList(COMMENT_INFO_ORDER); + .transform(c -> toCommentInfo(c, accountLoader)) + .toSortedList(COMMENT_INFO_ORDER); if (accountLoader != null) { accountLoader.fill();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java index 287c3ed..30ed82f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -19,19 +19,17 @@ import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES; import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering; import static com.google.gerrit.server.ChangeUtil.PS_ID_ORDER; -import static com.google.gerrit.server.ChangeUtil.TO_PS_ID; import com.google.auto.value.AutoValue; -import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.gerrit.common.FooterConstants; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.TimeUtil; import com.google.gerrit.extensions.api.changes.FixInput; +import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.common.ProblemInfo; import com.google.gerrit.extensions.common.ProblemInfo.Status; import com.google.gerrit.extensions.registration.DynamicItem; @@ -254,13 +252,10 @@ Map<String, Ref> refs; try { - refs = repo.getRefDatabase().exactRef( - Lists.transform(all, new Function<PatchSet, String>() { - @Override - public String apply(PatchSet ps) { - return ps.getId().toRefName(); - } - }).toArray(new String[all.size()])); + refs = repo.getRefDatabase().exactRef( + all.stream() + .map(ps -> ps.getId().toRefName()) + .toArray(String[]::new)); } catch (IOException e) { error("error reading refs", e); refs = Collections.emptyMap(); @@ -318,7 +313,7 @@ if (e.getValue().size() > 1) { problem(String.format("Multiple patch sets pointing to %s: %s", e.getKey().name(), - Collections2.transform(e.getValue(), TO_PS_ID))); + Collections2.transform(e.getValue(), PatchSet::getPatchSetId))); } } @@ -530,7 +525,7 @@ bu.addOp(ctl.getId(), inserter .setValidatePolicy(CommitValidators.Policy.NONE) .setFireRevisionCreated(false) - .setSendMail(false) + .setNotify(NotifyHandling.NONE) .setAllowClosed(true) .setMessage( "Patch set for merged commit inserted by consistency checker"));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java index 7cb2aac..1af87b3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
@@ -15,7 +15,6 @@ package com.google.gerrit.server.change; import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId; -import static com.google.gerrit.server.change.PutDraftComment.side; import com.google.common.base.Strings; import com.google.gerrit.common.TimeUtil; @@ -119,7 +118,7 @@ ChangeUtil.messageUUID(ctx.getDb())), line, ctx.getAccountId(), Url.decode(in.inReplyTo), ctx.getWhen()); - comment.setSide(side(in)); + comment.setSide(in.side()); comment.setMessage(in.message.trim()); comment.setRange(in.range); comment.setTag(in.tag);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java index bdefa93..099f8e0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -14,12 +14,13 @@ package com.google.gerrit.server.change; -import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelTypes; +import com.google.gerrit.extensions.api.changes.DeleteReviewerInput; +import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; @@ -38,7 +39,6 @@ import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PatchSetUtil; -import com.google.gerrit.server.change.DeleteReviewer.Input; import com.google.gerrit.server.extensions.events.ReviewerDeleted; import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.BatchUpdate.ChangeContext; @@ -62,13 +62,11 @@ import java.util.Map; @Singleton -public class DeleteReviewer implements RestModifyView<ReviewerResource, Input> { +public class DeleteReviewer + implements RestModifyView<ReviewerResource, DeleteReviewerInput> { private static final Logger log = LoggerFactory .getLogger(DeleteReviewer.class); - public static class Input { - } - private final Provider<ReviewDb> dbProvider; private final ApprovalsUtil approvalsUtil; private final PatchSetUtil psUtil; @@ -104,12 +102,19 @@ } @Override - public Response<?> apply(ReviewerResource rsrc, Input input) + public Response<?> apply(ReviewerResource rsrc, DeleteReviewerInput input) throws RestApiException, UpdateException { + if (input == null) { + input = new DeleteReviewerInput(); + } + if (input.notify == null) { + input.notify = NotifyHandling.ALL; + } + try (BatchUpdate bu = batchUpdateFactory.create(dbProvider.get(), rsrc.getChangeResource().getProject(), rsrc.getChangeResource().getUser(), TimeUtil.nowTs())) { - Op op = new Op(rsrc.getReviewerUser().getAccount()); + Op op = new Op(rsrc.getReviewerUser().getAccount(), input); bu.addOp(rsrc.getChange().getId(), op); bu.execute(); } @@ -119,6 +124,7 @@ private class Op extends BatchUpdate.Op { private final Account reviewer; + private final DeleteReviewerInput input; ChangeMessage changeMessage; Change currChange; PatchSet currPs; @@ -126,8 +132,9 @@ Map<String, Short> newApprovals = new HashMap<>(); Map<String, Short> oldApprovals = new HashMap<>(); - Op(Account reviewerAccount) { + Op(Account reviewerAccount, DeleteReviewerInput input) { this.reviewer = reviewerAccount; + this.input = input; } @Override @@ -148,57 +155,61 @@ } StringBuilder msg = new StringBuilder(); + msg.append("Removed reviewer " + reviewer.getFullName()); + StringBuilder removedVotesMsg = new StringBuilder(); + removedVotesMsg.append(" with the following votes:\n\n"); + boolean votesRemoved = false; for (PatchSetApproval a : approvals(ctx, reviewerId)) { if (ctx.getControl().canRemoveReviewer(a)) { del.add(a); if (a.getPatchSetId().equals(currPs.getId()) && a.getValue() != 0) { oldApprovals.put(a.getLabel(), a.getValue()); - if (msg.length() == 0) { - msg.append("Removed reviewer ").append(reviewer.getFullName()) - .append(" with the following votes:\n\n"); - } - msg.append("* ").append(a.getLabel()) + removedVotesMsg.append("* ").append(a.getLabel()) .append(formatLabelValue(a.getValue())).append(" by ") .append(userFactory.create(a.getAccountId()).getNameEmail()) .append("\n"); + votesRemoved = true; } } else { throw new AuthException("delete reviewer not permitted"); } } + if (votesRemoved) { + msg.append(removedVotesMsg); + } else { + msg.append("."); + } + ctx.getDb().patchSetApprovals().delete(del); ChangeUpdate update = ctx.getUpdate(currPs.getId()); update.removeReviewer(reviewerId); - if (msg.length() > 0) { - changeMessage = new ChangeMessage( - new ChangeMessage.Key(currChange.getId(), - ChangeUtil.messageUUID(ctx.getDb())), - ctx.getAccountId(), ctx.getWhen(), currPs.getId()); - changeMessage.setMessage(msg.toString()); - cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage); - } + changeMessage = new ChangeMessage( + new ChangeMessage.Key(currChange.getId(), + ChangeUtil.messageUUID(ctx.getDb())), + ctx.getAccountId(), ctx.getWhen(), currPs.getId()); + changeMessage.setMessage(msg.toString()); + cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage); return true; } @Override public void postUpdate(Context ctx) { - if (changeMessage == null) { - return; + if (input.notify.compareTo(NotifyHandling.NONE) > 0) { + emailReviewers(ctx.getProject(), currChange, del, changeMessage); } - - emailReviewers(ctx.getProject(), currChange, del, changeMessage); reviewerDeleted.fire(currChange, currPs, reviewer, ctx.getAccount(), changeMessage.getMessage(), newApprovals, oldApprovals, + input.notify, ctx.getWhen()); } private Iterable<PatchSetApproval> approvals(ChangeContext ctx, - final Account.Id accountId) throws OrmException { + Account.Id accountId) throws OrmException { Change.Id changeId = ctx.getNotes().getChangeId(); Iterable<PatchSetApproval> approvals; @@ -218,13 +229,7 @@ } return Iterables.filter( - approvals, - new Predicate<PatchSetApproval>() { - @Override - public boolean apply(PatchSetApproval input) { - return accountId.equals(input.getAccountId()); - } - }); + approvals, psa -> accountId.equals(psa.getAccountId())); } private String formatLabelValue(short value) { @@ -233,30 +238,31 @@ } return Short.toString(value); } - } - private void emailReviewers(Project.NameKey projectName, Change change, - List<PatchSetApproval> dels, ChangeMessage changeMessage) { + private void emailReviewers(Project.NameKey projectName, Change change, + List<PatchSetApproval> dels, ChangeMessage changeMessage) { - // The user knows they removed themselves, don't bother emailing them. - List<Account.Id> toMail = Lists.newArrayListWithCapacity(dels.size()); - Account.Id userId = user.get().getAccountId(); - for (PatchSetApproval psa : dels) { - if (!psa.getAccountId().equals(userId)) { - toMail.add(psa.getAccountId()); + // The user knows they removed themselves, don't bother emailing them. + List<Account.Id> toMail = Lists.newArrayListWithCapacity(dels.size()); + Account.Id userId = user.get().getAccountId(); + for (PatchSetApproval psa : dels) { + if (!psa.getAccountId().equals(userId)) { + toMail.add(psa.getAccountId()); + } } - } - if (!toMail.isEmpty()) { - try { - DeleteReviewerSender cm = - deleteReviewerSenderFactory.create(projectName, change.getId()); - cm.setFrom(userId); - cm.addReviewers(toMail); - cm.setChangeMessage(changeMessage.getMessage(), - changeMessage.getWrittenOn()); - cm.send(); - } catch (Exception err) { - log.error("Cannot email update for change " + change.getId(), err); + if (!toMail.isEmpty()) { + try { + DeleteReviewerSender cm = + deleteReviewerSenderFactory.create(projectName, change.getId()); + cm.setFrom(userId); + cm.addReviewers(toMail); + cm.setChangeMessage(changeMessage.getMessage(), + changeMessage.getWrittenOn()); + cm.setNotify(input.notify); + cm.send(); + } catch (Exception err) { + log.error("Cannot email update for change " + change.getId(), err); + } } } }
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 f1bdba5..e25e2292 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
@@ -14,8 +14,9 @@ package com.google.gerrit.server.change; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.gerrit.common.TimeUtil; -import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.extensions.api.changes.DeleteVoteInput; import com.google.gerrit.extensions.api.changes.NotifyHandling; @@ -28,6 +29,7 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; 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.server.ReviewDb; @@ -44,6 +46,7 @@ import com.google.gerrit.server.mail.DeleteVoteSender; import com.google.gerrit.server.mail.ReplyToChangeSender; import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.util.LabelVote; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -137,64 +140,66 @@ PatchSet.Id psId = change.currentPatchSetId(); ps = psUtil.current(db.get(), ctl.getNotes()); - PatchSetApproval psa = null; - StringBuilder msg = new StringBuilder(); - - // get all of the current approvals + boolean found = false; LabelTypes labelTypes = ctx.getControl().getLabelTypes(); - Map<String, Short> currentApprovals = new HashMap<>(); - for (LabelType lt : labelTypes.getLabelTypes()) { - currentApprovals.put(lt.getName(), (short) 0); - for (PatchSetApproval a : approvalsUtil.byPatchSetUser( - ctx.getDb(), ctl, psId, accountId)) { - if (lt.getLabelId().equals(a.getLabelId())) { - currentApprovals.put(lt.getName(), a.getValue()); - } - } - } - // removing votes so we need to determine the new set of approval scores - newApprovals.putAll(currentApprovals); + for (PatchSetApproval a : approvalsUtil.byPatchSetUser( - ctx.getDb(), ctl, psId, accountId)) { - if (ctl.canRemoveReviewer(a)) { - if (a.getLabel().equals(label)) { - // set the approval to 0 if vote is being removed - newApprovals.put(a.getLabel(), (short) 0); - // set old value only if the vote changed - oldApprovals.put(a.getLabel(), a.getValue()); - msg.append("Removed ") - .append(a.getLabel()).append(formatLabelValue(a.getValue())) - .append(" by ").append(userFactory.create(a.getAccountId()) - .getNameEmail()) - .append("\n"); - psa = a; - a.setValue((short)0); - ctx.getUpdate(psId).removeApprovalFor(a.getAccountId(), label); - break; - } - } else { + ctx.getDb(), ctl, psId, accountId)) { + if (labelTypes.byLabel(a.getLabelId()) == null) { + continue; // Ignore undefined labels. + } else if (!a.getLabel().equals(label)) { + // Populate map for non-matching labels, needed by VoteDeleted. + newApprovals.put(a.getLabel(), a.getValue()); + continue; + } else if (!ctl.canRemoveReviewer(a)) { throw new AuthException("delete vote not permitted"); } + // Set the approval to 0 if vote is being removed. + newApprovals.put(a.getLabel(), (short) 0); + found = true; + + // Set old value, as required by VoteDeleted. + oldApprovals.put(a.getLabel(), a.getValue()); + break; } - if (psa == null) { + if (!found) { throw new ResourceNotFoundException(); } - ctx.getDb().patchSetApprovals().update(Collections.singleton(psa)); - if (msg.length() > 0) { - changeMessage = - new ChangeMessage(new ChangeMessage.Key(change.getId(), - ChangeUtil.messageUUID(ctx.getDb())), - ctx.getAccountId(), - ctx.getWhen(), - change.currentPatchSetId()); - changeMessage.setMessage(msg.toString()); - cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), - changeMessage); - } + ctx.getUpdate(psId).removeApprovalFor(accountId, label); + ctx.getDb().patchSetApprovals().upsert( + Collections.singleton(deletedApproval(ctx))); + + changeMessage = + new ChangeMessage(new ChangeMessage.Key(change.getId(), + ChangeUtil.messageUUID(ctx.getDb())), + ctx.getAccountId(), + ctx.getWhen(), + change.currentPatchSetId()); + StringBuilder msg = new StringBuilder(); + msg.append("Removed "); + LabelVote.appendTo(msg, label, checkNotNull(oldApprovals.get(label))); + changeMessage.setMessage( + msg.append(" by ") + .append(userFactory.create(accountId).getNameEmail()) + .append("\n") + .toString()); + cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), + changeMessage); + return true; } + private PatchSetApproval deletedApproval(ChangeContext ctx) { + return new PatchSetApproval( + new PatchSetApproval.Key( + ps.getId(), + accountId, + new LabelId(label)), + (short) 0, + ctx.getWhen()); + } + @Override public void postUpdate(Context ctx) { if (changeMessage == null) { @@ -220,11 +225,4 @@ user.getAccount(), ctx.getWhen()); } } - - private static String formatLabelValue(short value) { - if (value > 0) { - return "+" + value; - } - return Short.toString(value); - } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java index d145ddf..d617a70 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -54,6 +54,7 @@ @Singleton public class FileContentUtil { public static final String TEXT_X_GERRIT_COMMIT_MESSAGE = "text/x-gerrit-commit-message"; + public static final String TEXT_X_GERRIT_MERGE_LIST = "text/x-gerrit-merge-list"; private static final String X_GIT_SYMLINK = "x-git/symlink"; private static final String X_GIT_GITLINK = "x-git/gitlink"; private static final int MAX_SIZE = 5 << 20; @@ -264,6 +265,9 @@ if (Patch.COMMIT_MSG.equals(path)) { return TEXT_X_GERRIT_COMMIT_MESSAGE; } + if (Patch.MERGE_LIST.equals(path)) { + return TEXT_X_GERRIT_MERGE_LIST; + } if (project != null) { for (ProjectState p : project.tree()) { String t = p.getConfig().getMimeTypes().getMimeType(path);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java index e0591f4..8e55df5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -14,8 +14,6 @@ package com.google.gerrit.server.change; -import static com.google.gerrit.server.util.GitUtil.getParent; - import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace; import com.google.gerrit.extensions.common.FileInfo; @@ -23,7 +21,6 @@ import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.RevId; -import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.patch.PatchList; import com.google.gerrit.server.patch.PatchListCache; import com.google.gerrit.server.patch.PatchListEntry; @@ -32,24 +29,18 @@ import com.google.inject.Inject; import com.google.inject.Singleton; -import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; -import java.io.IOException; import java.util.Map; import java.util.TreeMap; @Singleton public class FileInfoJson { private final PatchListCache patchListCache; - private final GitRepositoryManager repoManager; @Inject FileInfoJson( - PatchListCache patchListCache, - GitRepositoryManager repoManager) { - this.repoManager = repoManager; + PatchListCache patchListCache) { this.patchListCache = patchListCache; } @@ -64,24 +55,19 @@ ? null : ObjectId.fromString(base.getRevision().get()); ObjectId b = ObjectId.fromString(revision.get()); - return toFileInfoMap(change, a, b); + return toFileInfoMap(change, new PatchListKey(a, b, Whitespace.IGNORE_NONE)); } Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, int parent) - throws RepositoryNotFoundException, IOException, - PatchListNotAvailableException { + throws PatchListNotAvailableException { ObjectId b = ObjectId.fromString(revision.get()); - ObjectId a; - try (Repository git = repoManager.openRepository(change.getProject())) { - a = getParent(git, b, parent); - } - return toFileInfoMap(change, a, b); + return toFileInfoMap(change, + PatchListKey.againstParentNum(parent + 1, b, Whitespace.IGNORE_NONE)); } private Map<String, FileInfo> toFileInfoMap(Change change, - ObjectId a, ObjectId b) throws PatchListNotAvailableException { - PatchList list = patchListCache.get( - new PatchListKey(a, b, Whitespace.IGNORE_NONE), change.getProject()); + PatchListKey key) throws PatchListNotAvailableException { + PatchList list = patchListCache.get(key, change.getProject()); Map<String, FileInfo> files = new TreeMap<>(); for (PatchListEntry e : list.getPatches()) {
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 35dbec1..c077bbb 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
@@ -138,9 +138,10 @@ } @Override - public Response<?> apply(RevisionResource resource) throws AuthException, - BadRequestException, ResourceNotFoundException, OrmException, - RepositoryNotFoundException, IOException { + public Response<?> apply(RevisionResource resource) + throws AuthException, BadRequestException, ResourceNotFoundException, + OrmException, RepositoryNotFoundException, IOException, + PatchListNotAvailableException { checkOptions(); if (reviewed) { return Response.ok(reviewed(resource)); @@ -149,26 +150,22 @@ } Response<Map<String, FileInfo>> r; - try { - if (base != null) { - RevisionResource baseResource = revisions.parse( - resource.getChangeResource(), IdString.fromDecoded(base)); - r = Response.ok(fileInfoJson.toFileInfoMap( - resource.getChange(), - resource.getPatchSet().getRevision(), - baseResource.getPatchSet())); - } else if (parentNum > 0) { - r = Response.ok(fileInfoJson.toFileInfoMap( - resource.getChange(), - resource.getPatchSet().getRevision(), - parentNum - 1)); - } else { - r = Response.ok(fileInfoJson.toFileInfoMap( - resource.getChange(), - resource.getPatchSet())); - } - } catch (PatchListNotAvailableException e) { - throw new ResourceNotFoundException(e.getMessage()); + if (base != null) { + RevisionResource baseResource = revisions.parse( + resource.getChangeResource(), IdString.fromDecoded(base)); + r = Response.ok(fileInfoJson.toFileInfoMap( + resource.getChange(), + resource.getPatchSet().getRevision(), + baseResource.getPatchSet())); + } else if (parentNum > 0) { + r = Response.ok(fileInfoJson.toFileInfoMap( + resource.getChange(), + resource.getPatchSet().getRevision(), + parentNum - 1)); + } else { + r = Response.ok(fileInfoJson.toFileInfoMap( + resource.getChange(), + resource.getPatchSet())); } if (resource.isCacheable()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java index a2fd004..e99eb87 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
@@ -14,18 +14,13 @@ package com.google.gerrit.server.change; -import com.google.common.base.Predicate; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; import com.google.gerrit.extensions.restapi.RestReadView; -import com.google.gerrit.server.config.DownloadConfig; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; -import com.google.inject.Singleton; import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.api.errors.GitAPIException; @@ -37,48 +32,8 @@ import java.io.IOException; import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; public class GetArchive implements RestReadView<RevisionResource> { - @Singleton - public static class AllowedFormats { - final ImmutableMap<String, ArchiveFormat> extensions; - final Set<ArchiveFormat> allowed; - - @Inject - AllowedFormats(DownloadConfig cfg) { - Map<String, ArchiveFormat> exts = new HashMap<>(); - for (ArchiveFormat format : cfg.getArchiveFormats()) { - for (String ext : format.getSuffixes()) { - exts.put(ext, format); - } - exts.put(format.name().toLowerCase(), format); - } - extensions = ImmutableMap.copyOf(exts); - - // Zip is not supported because it may be interpreted by a Java plugin as a - // valid JAR file, whose code would have access to cookies on the domain. - allowed = Sets.filter( - cfg.getArchiveFormats(), - new Predicate<ArchiveFormat>() { - @Override - public boolean apply(ArchiveFormat format) { - return (format != ArchiveFormat.ZIP); - } - }); - } - - public Set<ArchiveFormat> getAllowed() { - return allowed; - } - - public ImmutableMap<String, ArchiveFormat> getExtensions() { - return extensions; - } - } - private final GitRepositoryManager repoManager; private final AllowedFormats allowedFormats;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java index 8c9a0ad..e51d37b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -35,7 +35,7 @@ private final GitRepositoryManager repoManager; private final ChangeJson.Factory json; - @Option(name = "--links", usage = "Add weblinks") + @Option(name = "--links", usage = "Include weblinks") private boolean addLinks; @Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java index 5a546f3..c7044e1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -24,6 +24,8 @@ import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gerrit.server.patch.ComparisonType; +import com.google.gerrit.server.patch.Text; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -68,6 +70,12 @@ return BinaryResult.create(msg) .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE) .base64(); + } else if (Patch.MERGE_LIST.equals(path)) { + byte[] mergeList = getMergeList( + rsrc.getRevision().getChangeResource().getNotes()); + return BinaryResult.create(mergeList) + .setContentType(FileContentUtil.TEXT_X_GERRIT_MERGE_LIST) + .base64(); } return fileContentUtil.getContent( rsrc.getRevision().getControl().getProjectControl().getProjectState(), @@ -92,4 +100,22 @@ throw new NoSuchChangeException(changeId, e); } } + + private byte[] getMergeList(ChangeNotes notes) + throws NoSuchChangeException, OrmException, IOException { + Change.Id changeId = notes.getChangeId(); + PatchSet ps = psUtil.current(db.get(), notes); + if (ps == null) { + throw new NoSuchChangeException(changeId); + } + + try (Repository git = gitManager.openRepository(notes.getProjectName()); + RevWalk revWalk = new RevWalk(git)) { + return Text.forMergeList(ComparisonType.againstAutoMerge(), + revWalk.getObjectReader(), + ObjectId.fromString(ps.getRevision().get())).getContent(); + } catch (RepositoryNotFoundException e) { + throw new NoSuchChangeException(changeId, e); + } + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java new file mode 100644 index 0000000..b15810c --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
@@ -0,0 +1,102 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.common.collect.ImmutableList; +import com.google.gerrit.extensions.common.CommitInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.CacheControl; +import com.google.gerrit.extensions.restapi.Response; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.patch.MergeListBuilder; +import com.google.inject.Inject; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class GetMergeList implements RestReadView<RevisionResource> { + private final GitRepositoryManager repoManager; + private final ChangeJson.Factory json; + + @Option(name = "--parent", usage = "Uninteresting parent (1-based, default = 1)") + private int uninterestingParent = 1; + + @Option(name = "--links", usage = "Include weblinks") + private boolean addLinks; + + @Inject + GetMergeList(GitRepositoryManager repoManager, + ChangeJson.Factory json) { + this.repoManager = repoManager; + this.json = json; + } + + public void setUninterestingParent(int uninterestingParent) { + this.uninterestingParent = uninterestingParent; + } + + public void setAddLinks(boolean addLinks) { + this.addLinks = addLinks; + } + + @Override + public Response<List<CommitInfo>> apply(RevisionResource rsrc) + throws BadRequestException, IOException { + Project.NameKey p = rsrc.getChange().getProject(); + try (Repository repo = repoManager.openRepository(p); + RevWalk rw = new RevWalk(repo)) { + String rev = rsrc.getPatchSet().getRevision().get(); + RevCommit commit = rw.parseCommit(ObjectId.fromString(rev)); + rw.parseBody(commit); + + if (uninterestingParent < 1 + || uninterestingParent > commit.getParentCount()) { + throw new BadRequestException("No such parent: " + uninterestingParent); + } + + if (commit.getParentCount() < 2) { + return createResponse(rsrc, ImmutableList.<CommitInfo> of()); + } + + List<RevCommit> commits = + MergeListBuilder.build(rw, commit, uninterestingParent); + List<CommitInfo> result = new ArrayList<>(commits.size()); + ChangeJson changeJson = json.create(ChangeJson.NO_OPTIONS); + for (RevCommit c : commits) { + result.add(changeJson.toCommit(rsrc.getControl(), rw, c, addLinks, true)); + } + return createResponse(rsrc, result); + } + } + + private static Response<List<CommitInfo>> createResponse( + RevisionResource rsrc, List<CommitInfo> result) { + Response<List<CommitInfo>> r = Response.ok(result); + if (rsrc.isCacheable()) { + r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS)); + } + return r; + } +}
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 12e4276..0a7452b 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
@@ -92,6 +92,9 @@ PatchSet basePs = isEdit ? rsrc.getEdit().get().getBasePatchSet() : rsrc.getPatchSet(); + + reloadChangeIfStale(cds, basePs); + for (PatchSetData d : sorter.sort(cds, basePs)) { PatchSet ps = d.patchSet(); RevCommit commit; @@ -123,6 +126,17 @@ return result; } + private void reloadChangeIfStale(List<ChangeData> cds, PatchSet wantedPs) + throws OrmException { + for (ChangeData cd : cds) { + if (cd.getId().equals(wantedPs.getId().getParentKey())) { + if (cd.patchSet(wantedPs.getId()) == null) { + cd.reloadChange(); + } + } + } + } + public static class RelatedInfo { public List<ChangeAndCommit> changes; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java index eae67a2..478d9c5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -41,14 +41,14 @@ private final ActionJson delegate; private final Config config; private final Provider<ReviewDb> dbProvider; - private final MergeSuperSet mergeSuperSet; + private final Provider<MergeSuperSet> mergeSuperSet; private final ChangeResource.Factory changeResourceFactory; @Inject GetRevisionActions( ActionJson delegate, Provider<ReviewDb> dbProvider, - MergeSuperSet mergeSuperSet, + Provider<MergeSuperSet> mergeSuperSet, ChangeResource.Factory changeResourceFactory, @GerritServerConfig Config config) { this.delegate = delegate; @@ -72,7 +72,7 @@ h.putBoolean(Submit.wholeTopicEnabled(config)); ReviewDb db = dbProvider.get(); ChangeSet cs = - mergeSuperSet.completeChangeSet(db, rsrc.getChange(), user); + mergeSuperSet.get().completeChangeSet(db, rsrc.getChange(), user); for (ChangeData cd : cs.changes()) { changeResourceFactory.create(cd.changeControl()).prepareETag(h, user); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java index 6de7deb..c2793db 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -78,6 +78,7 @@ child(CHANGE_KIND, "reviewers").to(Reviewers.class); get(REVIEWER_KIND).to(GetReviewer.class); delete(REVIEWER_KIND).to(DeleteReviewer.class); + post(REVIEWER_KIND, "delete").to(DeleteReviewer.class); child(REVIEWER_KIND, "votes").to(Votes.class); delete(VOTE_KIND).to(DeleteVote.class); post(VOTE_KIND, "delete").to(DeleteVote.class); @@ -92,6 +93,7 @@ get(REVISION_KIND, "related").to(GetRelated.class); get(REVISION_KIND, "review").to(GetReview.class); post(REVISION_KIND, "review").to(PostReview.class); + get(REVISION_KIND, "preview_submit").to(PreviewSubmit.class); post(REVISION_KIND, "submit").to(Submit.class); post(REVISION_KIND, "rebase").to(Rebase.class); get(REVISION_KIND, "patch").to(GetPatch.class); @@ -99,6 +101,7 @@ post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class); post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class); get(REVISION_KIND, "archive").to(GetArchive.class); + get(REVISION_KIND, "mergelist").to(GetMergeList.class); child(REVISION_KIND, "drafts").to(DraftComments.class); put(REVISION_KIND, "drafts").to(CreateDraftComment.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java index 508d380..3f38fc3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -35,7 +35,6 @@ import com.google.gerrit.server.ReviewerSet; import com.google.gerrit.server.events.CommitReceivedEvent; import com.google.gerrit.server.extensions.events.RevisionCreated; -import com.google.gerrit.server.git.BanCommit; import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.BatchUpdate.ChangeContext; import com.google.gerrit.server.git.BatchUpdate.Context; @@ -47,13 +46,11 @@ import com.google.gerrit.server.patch.PatchSetInfoFactory; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.ssh.NoSshInfo; -import com.google.gerrit.server.ssh.SshInfo; import com.google.gwtorm.server.OrmException; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.ReceiveCommand; import org.slf4j.Logger; @@ -91,14 +88,13 @@ private final ChangeControl origCtl; // Fields exposed as setters. - private SshInfo sshInfo; private String message; private CommitValidators.Policy validatePolicy = CommitValidators.Policy.GERRIT; private boolean draft; private List<String> groups = Collections.emptyList(); private boolean fireRevisionCreated = true; - private boolean sendMail = true; + private NotifyHandling notify = NotifyHandling.ALL; private boolean allowClosed; private boolean copyApprovals = true; @@ -144,11 +140,6 @@ return this; } - public PatchSetInserter setSshInfo(SshInfo sshInfo) { - this.sshInfo = sshInfo; - return this; - } - public PatchSetInserter setValidatePolicy(CommitValidators.Policy validate) { this.validatePolicy = checkNotNull(validate); return this; @@ -170,8 +161,8 @@ return this; } - public PatchSetInserter setSendMail(boolean sendMail) { - this.sendMail = sendMail; + public PatchSetInserter setNotify(NotifyHandling notify) { + this.notify = notify; return this; } @@ -198,7 +189,6 @@ @Override public void updateRepo(RepoContext ctx) throws AuthException, ResourceConflictException, IOException, OrmException { - init(); validate(ctx); ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), commit, getPatchSetId().toRefName(), ReceiveCommand.Type.CREATE)); @@ -230,7 +220,7 @@ patchSet = psUtil.insert(db, ctx.getRevWalk(), ctx.getUpdate(psId), psId, commit, draft, newGroups, null); - if (sendMail) { + if (notify != NotifyHandling.NONE) { oldReviewers = approvalsUtil.getReviewers(db, ctl.getNotes()); } @@ -257,7 +247,7 @@ @Override public void postUpdate(Context ctx) throws OrmException { - if (sendMail) { + if (notify != NotifyHandling.NONE) { try { ReplacePatchSetSender cm = replacePatchSetFactory.create( ctx.getProject(), change.getId()); @@ -266,6 +256,7 @@ cm.setChangeMessage(changeMessage.getMessage(), ctx.getWhen()); cm.addReviewers(oldReviewers.byState(REVIEWER)); cm.addExtraCC(oldReviewers.byState(CC)); + cm.setNotify(notify); cm.send(); } catch (Exception err) { log.error("Cannot send email for new patch set on change " @@ -273,30 +264,21 @@ } } - NotifyHandling notify = sendMail - ? NotifyHandling.ALL - : NotifyHandling.NONE; if (fireRevisionCreated) { revisionCreated.fire(change, patchSet, ctx.getAccountId(), ctx.getWhen(), notify); } } - private void init() { - if (sshInfo == null) { - sshInfo = new NoSshInfo(); - } - } - private void validate(RepoContext ctx) throws AuthException, ResourceConflictException, IOException, OrmException { - CommitValidators cv = commitValidatorsFactory.create( - origCtl.getRefControl(), sshInfo, ctx.getRepository()); - if (!origCtl.canAddPatchSet(ctx.getDb())) { throw new AuthException("cannot add patch set"); } + if (validatePolicy == CommitValidators.Policy.NONE) { + return; + } String refName = getPatchSetId().toRefName(); CommitReceivedEvent event = new CommitReceivedEvent( @@ -309,18 +291,11 @@ commit, ctx.getIdentifiedUser()); try { - switch (validatePolicy) { - case RECEIVE_COMMITS: - NoteMap rejectCommits = BanCommit.loadRejectCommitsMap( - ctx.getRepository(), ctx.getRevWalk()); - cv.validateForReceiveCommits(event, rejectCommits); - break; - case GERRIT: - cv.validateForGerritCommits(event); - break; - case NONE: - break; - } + commitValidatorsFactory + .create( + validatePolicy, origCtl.getRefControl(), new NoSshInfo(), + ctx.getRepository()) + .validate(event); } catch (CommitValidationException e) { throw new ResourceConflictException(e.getFullMessage()); }
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 aa35da8..d6819ba 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
@@ -17,7 +17,6 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId; -import static com.google.gerrit.server.change.PutDraftComment.side; import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import static java.nio.charset.StandardCharsets.UTF_8; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; @@ -67,6 +66,7 @@ import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PatchLineCommentsUtil; import com.google.gerrit.server.PatchSetUtil; +import com.google.gerrit.server.ReviewerSet; import com.google.gerrit.server.account.AccountsCollection; import com.google.gerrit.server.extensions.events.CommentAdded; import com.google.gerrit.server.git.BatchUpdate; @@ -212,7 +212,7 @@ } bu.addOp( revision.getChange().getId(), - new Op(revision.getPatchSet().getId(), input)); + new Op(revision.getPatchSet().getId(), input, reviewerResults)); bu.execute(); for (PostReviewers.Addition reviewerResult : reviewerResults) { @@ -323,11 +323,19 @@ while (mapItr.hasNext()) { Map.Entry<String, List<CommentInput>> ent = mapItr.next(); String path = ent.getKey(); - if (!filePaths.contains(path) && !Patch.COMMIT_MSG.equals(path)) { + if (!filePaths.contains(path) && !Patch.isMagic(path)) { throw new BadRequestException(String.format( "file %s not found in revision %s", path, revision.getChange().currentPatchSetId())); } + if (Patch.isMagic(path)) { + for (CommentInput comment : ent.getValue()) { + if (comment.side == Side.PARENT && comment.parent == null) { + throw new BadRequestException( + String.format("cannot comment on %s on auto-merge", path)); + } + } + } List<CommentInput> list = ent.getValue(); if (list == null) { @@ -387,6 +395,7 @@ private class Op extends BatchUpdate.Op { private final PatchSet.Id psId; private final ReviewInput in; + private final List<PostReviewers.Addition> reviewerResults; private IdentifiedUser user; private ChangeNotes notes; @@ -397,9 +406,11 @@ private Map<String, Short> approvals = new HashMap<>(); private Map<String, Short> oldApprovals = new HashMap<>(); - private Op(PatchSet.Id psId, ReviewInput in) { + private Op(PatchSet.Id psId, ReviewInput in, + List<PostReviewers.Addition> reviewerResults) { this.psId = psId; this.in = in; + this.reviewerResults = reviewerResults; } @Override @@ -472,7 +483,7 @@ } e.setStatus(PatchLineComment.Status.PUBLISHED); e.setWrittenOn(ctx.getWhen()); - e.setSide(side(c)); + e.setSide(c.side()); setCommentRevId(e, patchListCache, ctx.getChange(), ps); e.setMessage(c.message); e.setTag(in.tag); @@ -615,6 +626,28 @@ return previous; } + private boolean isReviewer(ChangeContext ctx) throws OrmException { + if (ctx.getAccountId().equals(ctx.getChange().getOwner())) { + return true; + } + for (PostReviewers.Addition addition : reviewerResults) { + if (addition.op.addedReviewers == null) { + continue; + } + for (PatchSetApproval psa : addition.op.addedReviewers) { + if (psa.getAccountId().equals(ctx.getAccountId())) { + return true; + } + } + } + ChangeData cd = changeDataFactory.create(db.get(), ctx.getControl()); + ReviewerSet reviewers = cd.reviewers(); + if (reviewers.byState(REVIEWER).contains(ctx.getAccountId())) { + return true; + } + return false; + } + private boolean updateLabels(ChangeContext ctx) throws OrmException, ResourceConflictException { Map<String, Short> inLabels = MoreObjects.firstNonNull(in.labels, @@ -689,6 +722,14 @@ && ctx.getChange().getStatus().isClosed()) { throw new ResourceConflictException("change is closed"); } + + // Return early if user is not a reviewer and not posting any labels. + // This allows us to preserve their CC status. + if (current.isEmpty() && del.isEmpty() && ups.isEmpty() && + !isReviewer(ctx)) { + return false; + } + forceCallerAsReviewer(ctx, current, ups, del); ctx.getDb().patchSetApprovals().delete(del); ctx.getDb().patchSetApprovals().upsert(ups);
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 fb37d9d..b64b5a2 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
@@ -40,7 +40,6 @@ import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PatchSetUtil; -import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountLoader; import com.google.gerrit.server.account.AccountsCollection; import com.google.gerrit.server.account.GroupMembers; @@ -96,7 +95,6 @@ private final Provider<IdentifiedUser> user; private final IdentifiedUser.GenericFactory identifiedUserFactory; private final Config cfg; - private final AccountCache accountCache; private final ReviewerJson json; private final ReviewerAdded reviewerAdded; private final NotesMigration migration; @@ -115,7 +113,6 @@ Provider<IdentifiedUser> user, IdentifiedUser.GenericFactory identifiedUserFactory, @GerritServerConfig Config cfg, - AccountCache accountCache, ReviewerJson json, ReviewerAdded reviewerAdded, NotesMigration migration) { @@ -132,7 +129,6 @@ this.user = user; this.identifiedUserFactory = identifiedUserFactory; this.cfg = cfg; - this.accountCache = accountCache; this.json = json; this.reviewerAdded = reviewerAdded; this.migration = migration; @@ -184,7 +180,12 @@ return new Addition(reviewer, rsrc.getChangeResource(), ImmutableMap.of(member.getId(), control), state); } - throw new UnprocessableEntityException("Change not visible to " + reviewer); + if (member.isActive()) { + throw new UnprocessableEntityException( + String.format("Change not visible to %s", reviewer)); + } + throw new UnprocessableEntityException( + String.format("Account of %s is inactive.", reviewer)); } private Addition putGroup(ChangeResource rsrc, AddReviewerInput input) @@ -355,11 +356,10 @@ } emailReviewers(rsrc.getChange(), addedReviewers, addedCCs); if (!addedReviewers.isEmpty()) { - for (PatchSetApproval psa : addedReviewers) { - Account account = accountCache.get(psa.getAccountId()).getAccount(); - reviewerAdded.fire(rsrc.getChange(), patchSet, account, + List<Account.Id> reviewers = + Lists.transform(addedReviewers, PatchSetApproval::getAccountId); + reviewerAdded.fire(rsrc.getChange(), patchSet, reviewers, ctx.getAccount(), ctx.getWhen()); - } } } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java new file mode 100644 index 0000000..b4ed31b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
@@ -0,0 +1,143 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.common.base.Strings; +import com.google.gerrit.extensions.api.changes.SubmitInput; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.BinaryResult; +import com.google.gerrit.extensions.restapi.MethodNotAllowedException; +import com.google.gerrit.extensions.restapi.PreconditionFailedException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.git.MergeOp; +import com.google.gerrit.server.git.MergeOpRepoManager; +import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.transport.BundleWriter; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Set; + +@Singleton +public class PreviewSubmit implements RestReadView<RevisionResource> { + private final Provider<ReviewDb> dbProvider; + private final Provider<MergeOp> mergeOpProvider; + private final AllowedFormats allowedFormats; + + private String format; + + @Option(name = "--format") + public void setFormat(String f) { + this.format = f; + } + + @Inject + PreviewSubmit(Provider<ReviewDb> dbProvider, + Provider<MergeOp> mergeOpProvider, + AllowedFormats allowedFormats) { + this.dbProvider = dbProvider; + this.mergeOpProvider = mergeOpProvider; + this.allowedFormats = allowedFormats; + } + + @Override + public BinaryResult apply(RevisionResource rsrc) throws RestApiException { + if (Strings.isNullOrEmpty(format)) { + throw new BadRequestException("format is not specified"); + } + ArchiveFormat f = allowedFormats.extensions.get("." + format); + if (f == null) { + throw new BadRequestException("unknown archive format"); + } + + Change change = rsrc.getChange(); + if (!change.getStatus().isOpen()) { + throw new PreconditionFailedException("change is " + Submit.status(change)); + } + ChangeControl control = rsrc.getControl(); + if (!control.getUser().isIdentifiedUser()) { + throw new MethodNotAllowedException("Anonymous users cannot submit"); + } + try (BinaryResult b = getBundles(rsrc, f)) { + b.disableGzip() + .setContentType(f.getMimeType()) + .setAttachmentName("submit-preview-" + + change.getChangeId() + "." + format); + return b; + } catch (OrmException | IOException e) { + throw new RestApiException("Error generating submit preview"); + } + } + + private BinaryResult getBundles(RevisionResource rsrc, final ArchiveFormat f) + throws OrmException, RestApiException { + ReviewDb db = dbProvider.get(); + ChangeControl control = rsrc.getControl(); + IdentifiedUser caller = control.getUser().asIdentifiedUser(); + Change change = rsrc.getChange(); + + BinaryResult bin; + try (MergeOp op = mergeOpProvider.get()) { + op.merge(db, change, caller, false, new SubmitInput(), true); + final MergeOpRepoManager orm = op.getMergeOpRepoManager(); + final Set<Project.NameKey> projects = op.getAllProjects(); + + bin = new BinaryResult() { + @Override + public void writeTo(OutputStream out) throws IOException { + ArchiveOutputStream aos = f.createArchiveOutputStream(out); + + for (Project.NameKey p : projects) { + OpenRepo or = orm.getRepo(p); + BundleWriter bw = new BundleWriter(or.getRepo()); + bw.setObjectCountCallback(null); + bw.setPackConfig(null); + Collection<ReceiveCommand> refs = or.getUpdate().getRefUpdates(); + for (ReceiveCommand r : refs) { + bw.include(r.getRefName(), r.getNewId()); + if (!r.getOldId().equals(ObjectId.zeroId())) { + bw.assume(or.getCodeReviewRevWalk().parseCommit(r.getOldId())); + } + } + // This naming scheme cannot produce directory/file conflicts + // as no projects contains ".git/": + aos.putArchiveEntry(f.prepareArchiveEntry(p.get() + ".git")); + bw.writeBundle(NullProgressMonitor.INSTANCE, aos); + aos.closeArchiveEntry(); + } + aos.finish(); + } + }; + } + return bin; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java index c86e98f..76ff15e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
@@ -16,6 +16,7 @@ import com.google.common.base.Optional; import com.google.gerrit.common.data.Capable; +import com.google.gerrit.extensions.api.changes.PublishChangeEditInput; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.restapi.AcceptsPost; import com.google.gerrit.extensions.restapi.AuthException; @@ -71,9 +72,8 @@ } @Singleton - public static class Publish implements RestModifyView<ChangeResource, Publish.Input> { - public static class Input { - } + public static class Publish + implements RestModifyView<ChangeResource, PublishChangeEditInput> { private final ChangeEditUtil editUtil; @@ -83,7 +83,7 @@ } @Override - public Response<?> apply(ChangeResource rsrc, Publish.Input in) + public Response<?> apply(ChangeResource rsrc, PublishChangeEditInput in) throws NoSuchChangeException, IOException, OrmException, RestApiException, UpdateException { Capable r = @@ -98,7 +98,10 @@ "no edit exists for change %s", rsrc.getChange().getChangeId())); } - editUtil.publish(edit.get()); + if (in == null) { + in = new PublishChangeEditInput(); + } + editUtil.publish(edit.get(), in.notify); return Response.none(); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java index 655e07d..e817d93 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -19,8 +19,6 @@ import com.google.common.base.Optional; import com.google.gerrit.common.TimeUtil; import com.google.gerrit.extensions.api.changes.DraftInput; -import com.google.gerrit.extensions.client.Comment; -import com.google.gerrit.extensions.client.Side; import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; @@ -164,7 +162,7 @@ private static PatchLineComment update(PatchLineComment e, DraftInput in, Timestamp when) { if (in.side != null) { - e.setSide(side(in)); + e.setSide(in.side()); } if (in.inReplyTo != null) { e.setParentUuid(Url.decode(in.inReplyTo)); @@ -181,11 +179,4 @@ } return e; } - - static short side(Comment c) { - if (c.side == Side.PARENT) { - return (short) (c.parent == null ? 0 : -c.parent.shortValue()); - } - return 1; - } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java index 8909e60..d3c300e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkState; import com.google.gerrit.common.Nullable; +import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.restapi.MergeConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestApiException; @@ -150,7 +151,7 @@ patchSetInserter = patchSetInserterFactory .create(ctl, rebasedPatchSetId, rebasedCommit) .setDraft(originalPatchSet.isDraft()) - .setSendMail(false) + .setNotify(NotifyHandling.NONE) .setFireRevisionCreated(fireRevisionCreated) .setCopyApprovals(copyApprovals) .setMessage(
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 5fe0e0b..c27fa53 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
@@ -20,8 +20,8 @@ import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.change.Rebuild.Input; -import com.google.gerrit.server.notedb.ChangeRebuilder; import com.google.gerrit.server.notedb.NotesMigration; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject;
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 4750197..b8443be 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
@@ -14,15 +14,13 @@ package com.google.gerrit.server.change; +import static java.util.stream.Collectors.joining; + import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; -import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.gerrit.common.data.ParameterizedString; @@ -134,7 +132,7 @@ private final ChangeMessagesUtil cmUtil; private final ChangeNotes.Factory changeNotesFactory; private final Provider<MergeOp> mergeOpProvider; - private final MergeSuperSet mergeSuperSet; + private final Provider<MergeSuperSet> mergeSuperSet; private final AccountsCollection accounts; private final ChangesCollection changes; private final String label; @@ -154,7 +152,7 @@ ChangeMessagesUtil cmUtil, ChangeNotes.Factory changeNotesFactory, Provider<MergeOp> mergeOpProvider, - MergeSuperSet mergeSuperSet, + Provider<MergeSuperSet> mergeSuperSet, AccountsCollection accounts, ChangesCollection changes, @GerritServerConfig Config cfg, @@ -222,7 +220,7 @@ try (MergeOp op = mergeOpProvider.get()) { ReviewDb db = dbProvider.get(); - op.merge(db, change, caller, true, input); + op.merge(db, change, caller, true, input, false); try { change = changeNotesFactory .createChecked(db, change.getProject(), change.getId()).getChange(); @@ -282,14 +280,10 @@ return CHANGE_UNMERGEABLE; } } - return CHANGES_NOT_MERGEABLE + Joiner.on(", ").join( - Iterables.transform(unmergeable, - new Function<ChangeData, String>() { - @Override - public String apply(ChangeData cd) { - return String.valueOf(cd.getId().get()); - } - })); + return CHANGES_NOT_MERGEABLE + + unmergeable.stream() + .map(c -> c.getId().toString()) + .collect(joining(", ")); } } catch (ResourceConflictException e) { return BLOCKED_SUBMIT_TOOLTIP; @@ -300,22 +294,6 @@ return null; } - /** - * Check if there are any problems with the given change. It doesn't take - * any problems of related changes into account. - * <p> - * @param cd the change to check for submittability - * @return if the change has any problems for submission - */ - public static boolean submittable(ChangeData cd) { - try { - MergeOp.checkSubmitRule(cd); - return true; - } catch (ResourceConflictException | OrmException e) { - return false; - } - } - @Override public UiAction.Description getDescription(RevisionResource resource) { PatchSet.Id current = resource.getChange().currentPatchSetId(); @@ -345,7 +323,7 @@ ChangeSet cs; try { - cs = mergeSuperSet.completeChangeSet( + cs = mergeSuperSet.get().completeChangeSet( db, cd.change(), resource.getControl().getUser()); } catch (OrmException | IOException e) { throw new OrmRuntimeException("Could not determine complete set of " + @@ -421,14 +399,10 @@ */ public ChangeMessage getConflictMessage(RevisionResource rsrc) throws OrmException { - return FluentIterable.from(cmUtil.byPatchSet(dbProvider.get(), rsrc.getNotes(), - rsrc.getPatchSet().getId())) - .filter(new Predicate<ChangeMessage>() { - @Override - public boolean apply(ChangeMessage input) { - return input.getAuthor() == null; - } - }) + return FluentIterable.from( + cmUtil.byPatchSet( + dbProvider.get(), rsrc.getNotes(), rsrc.getPatchSet().getId())) + .filter(cm -> cm.getAuthor() == null) .last() .orNull(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java index c4c0e98..351ebf9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
@@ -54,7 +54,7 @@ private final ChangeJson.Factory json; private final Provider<ReviewDb> dbProvider; private final Provider<InternalChangeQuery> queryProvider; - private final MergeSuperSet mergeSuperSet; + private final Provider<MergeSuperSet> mergeSuperSet; private final Provider<WalkSorter> sorter; @Option(name = "-o", usage = "Output options") @@ -66,7 +66,7 @@ SubmittedTogether(ChangeJson.Factory json, Provider<ReviewDb> dbProvider, Provider<InternalChangeQuery> queryProvider, - MergeSuperSet mergeSuperSet, + Provider<MergeSuperSet> mergeSuperSet, Provider<WalkSorter> sorter) { this.json = json; this.dbProvider = dbProvider; @@ -96,7 +96,7 @@ if (c.getStatus().isOpen()) { ChangeSet cs = - mergeSuperSet.completeChangeSet( + mergeSuperSet.get().completeChangeSet( dbProvider.get(), c, resource.getControl().getUser()); cds = cs.changes().asList(); hidden = cs.nonVisibleChanges().size(); @@ -126,6 +126,7 @@ info.changes = json.create(EnumSet.of( ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT)) + .includeSubmittable(true) .formatChangeDatas(cds); info.nonVisibleChanges = hidden; return info;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java index d31805d..e0959f5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java
@@ -18,7 +18,6 @@ import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -72,21 +71,17 @@ LoggerFactory.getLogger(WalkSorter.class); private static final Ordering<List<PatchSetData>> PROJECT_LIST_SORTER = - Ordering.natural().nullsFirst() - .onResultOf( - new Function<List<PatchSetData>, Project.NameKey>() { - @Override - public Project.NameKey apply(List<PatchSetData> in) { - if (in == null || in.isEmpty()) { - return null; - } - try { - return in.get(0).data().change().getProject(); - } catch (OrmException e) { - throw new IllegalStateException(e); - } - } - }); + Ordering.natural().nullsFirst().onResultOf( + (List<PatchSetData> in) -> { + if (in == null || in.isEmpty()) { + return null; + } + try { + return in.get(0).data().change().getProject(); + } catch (OrmException e) { + throw new IllegalStateException(e); + } + }); private final GitRepositoryManager repoManager; private final Set<PatchSet.Id> includePatchSets;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AgreementJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AgreementJson.java new file mode 100644 index 0000000..3ababbc --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AgreementJson.java
@@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.common.data.ContributorAgreement; +import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.common.errors.NoSuchGroupException; +import com.google.gerrit.extensions.common.AgreementInfo; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.GroupControl; +import com.google.gerrit.server.group.GroupJson; +import com.google.gerrit.server.group.GroupResource; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AgreementJson { + private static final Logger log = + LoggerFactory.getLogger(AgreementJson.class); + + private final Provider<CurrentUser> self; + private final IdentifiedUser.GenericFactory identifiedUserFactory; + private final GroupControl.GenericFactory genericGroupControlFactory; + private final GroupJson groupJson; + + @Inject + AgreementJson(Provider<CurrentUser> self, + IdentifiedUser.GenericFactory identifiedUserFactory, + GroupControl.GenericFactory genericGroupControlFactory, + GroupJson groupJson) { + this.self = self; + this.identifiedUserFactory = identifiedUserFactory; + this.genericGroupControlFactory = genericGroupControlFactory; + this.groupJson = groupJson; + } + + public AgreementInfo format(ContributorAgreement ca) { + AgreementInfo info = new AgreementInfo(); + info.name = ca.getName(); + info.description = ca.getDescription(); + info.url = ca.getAgreementUrl(); + GroupReference autoVerifyGroup = ca.getAutoVerify(); + if (autoVerifyGroup != null && self.get().isIdentifiedUser()) { + IdentifiedUser user = + identifiedUserFactory.create(self.get().getAccountId()); + try { + GroupControl gc = genericGroupControlFactory.controlFor( + user, autoVerifyGroup.getUUID()); + GroupResource group = new GroupResource(gc); + info.autoVerifyGroup = groupJson.format(group); + } catch (NoSuchGroupException | OrmException e) { + log.warn("autoverify group \"" + autoVerifyGroup.getName() + + "\" does not exist, referenced in CLA \"" + ca.getName() + "\""); + } + } + return info; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java index f2fc94e..5a40a31 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.config; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.reviewdb.client.AccountExternalId; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.auth.openid.OpenIdProviderPattern; import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtjsonrpc.server.XsrfException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java index 8e181a9..5b0f73d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.config; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.extensions.registration.DynamicSet; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.account.DefaultRealm; import com.google.gerrit.server.account.Realm; import com.google.gerrit.server.auth.AuthBackend;
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 da1f9a6..c91ca3b 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
@@ -1,4 +1,4 @@ -// Copyright (C) 2009 The Android Open Source Project +// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -116,7 +116,6 @@ import com.google.gerrit.server.git.TransferConfig; import com.google.gerrit.server.git.strategy.SubmitStrategy; import com.google.gerrit.server.git.validators.CommitValidationListener; -import com.google.gerrit.server.git.validators.CommitValidators; import com.google.gerrit.server.git.validators.MergeValidationListener; import com.google.gerrit.server.git.validators.MergeValidators; import com.google.gerrit.server.git.validators.MergeValidators.ProjectConfigValidator; @@ -134,6 +133,8 @@ import com.google.gerrit.server.mail.EmailModule; import com.google.gerrit.server.mail.FromAddressGenerator; import com.google.gerrit.server.mail.FromAddressGeneratorProvider; +import com.google.gerrit.server.mail.MailSoyTofuProvider; +import com.google.gerrit.server.mail.MailTemplates; import com.google.gerrit.server.mail.MergedSender; import com.google.gerrit.server.mail.RegisterNewEmailSender; import com.google.gerrit.server.mail.ReplacePatchSetSender; @@ -170,6 +171,7 @@ import com.google.inject.Inject; import com.google.inject.TypeLiteral; import com.google.inject.internal.UniqueAnnotations; +import com.google.template.soy.tofu.SoyTofu; import org.apache.velocity.runtime.RuntimeInstance; import org.eclipse.jgit.lib.Config; @@ -275,6 +277,9 @@ 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).annotatedWith(DisableReverseDnsLookup.class) @@ -366,7 +371,6 @@ bind(AnonymousUser.class); - factory(CommitValidators.Factory.class); factory(RefOperationValidators.Factory.class); factory(MergeValidators.Factory.class); factory(ProjectConfigValidator.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritOptions.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritOptions.java new file mode 100644 index 0000000..c181f79 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritOptions.java
@@ -0,0 +1,87 @@ +// 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.server.config; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.gerrit.extensions.client.UiType; + +import org.eclipse.jgit.lib.Config; + +public class GerritOptions { + private final boolean headless; + private final boolean slave; + private final boolean enablePolyGerrit; + private final boolean enableGwtUi; + private final boolean forcePolyGerritDev; + private final UiType defaultUi; + + public GerritOptions(Config cfg, boolean headless, boolean slave, + boolean forcePolyGerritDev) { + this.slave = slave; + this.enablePolyGerrit = forcePolyGerritDev + || cfg.getBoolean("gerrit", null, "enablePolyGerrit", false); + this.enableGwtUi = cfg.getBoolean("gerrit", null, "enableGwtUi", true); + this.forcePolyGerritDev = forcePolyGerritDev; + this.headless = headless || (!enableGwtUi && !enablePolyGerrit); + + UiType defaultUi = enablePolyGerrit && !enableGwtUi + ? UiType.POLYGERRIT + : UiType.GWT; + String uiStr = firstNonNull( + cfg.getString("gerrit", null, "ui"), + defaultUi.name()); + this.defaultUi = firstNonNull(UiType.parse(uiStr), UiType.NONE); + + switch (defaultUi) { + case GWT: + checkArgument(enableGwtUi, + "gerrit.ui = %s but GWT UI is disabled", defaultUi); + break; + case POLYGERRIT: + checkArgument(enablePolyGerrit, + "gerrit.ui = %s but PolyGerrit is disabled", defaultUi); + break; + case NONE: + default: + throw new IllegalArgumentException("invalid gerrit.ui: " + uiStr); + } + } + + public boolean headless() { + return headless; + } + + public boolean enableGwtUi() { + return !headless && enableGwtUi; + } + + public boolean enableMasterFeatures() { + return !slave; + } + + public boolean enablePolyGerrit() { + return !headless && enablePolyGerrit; + } + + public boolean forcePolyGerritDev() { + return !headless && forcePolyGerritDev; + } + + public UiType defaultUi() { + return defaultUi; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java index 1dc910c..83e1419 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -14,12 +14,25 @@ package com.google.gerrit.server.config; +import static java.util.stream.Collectors.toList; + import com.google.common.base.CharMatcher; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Strings; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.gerrit.common.data.ContributorAgreement; +import com.google.gerrit.extensions.client.UiType; +import com.google.gerrit.extensions.common.AuthInfo; +import com.google.gerrit.extensions.common.ChangeConfigInfo; +import com.google.gerrit.extensions.common.DownloadInfo; +import com.google.gerrit.extensions.common.DownloadSchemeInfo; +import com.google.gerrit.extensions.common.GerritInfo; +import com.google.gerrit.extensions.common.PluginConfigInfo; +import com.google.gerrit.extensions.common.ReceiveInfo; +import com.google.gerrit.extensions.common.ServerInfo; +import com.google.gerrit.extensions.common.SshdInfo; +import com.google.gerrit.extensions.common.SuggestInfo; +import com.google.gerrit.extensions.common.UserConfigInfo; import com.google.gerrit.extensions.config.CloneCommand; import com.google.gerrit.extensions.config.DownloadCommand; import com.google.gerrit.extensions.config.DownloadScheme; @@ -28,24 +41,24 @@ import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.webui.WebUiPlugin; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.EnableSignedPush; import com.google.gerrit.server.account.Realm; import com.google.gerrit.server.avatar.AvatarProvider; +import com.google.gerrit.server.change.AllowedFormats; import com.google.gerrit.server.change.ArchiveFormat; -import com.google.gerrit.server.change.GetArchive; import com.google.gerrit.server.change.Submit; import com.google.gerrit.server.documentation.QueryDocumentationExecutor; import com.google.gerrit.server.notedb.NotesMigration; +import com.google.gerrit.server.project.ProjectCache; import com.google.inject.Inject; import org.eclipse.jgit.lib.Config; import java.net.MalformedURLException; import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -61,7 +74,7 @@ private final DynamicMap<DownloadCommand> downloadCommands; private final DynamicMap<CloneCommand> cloneCommands; private final DynamicSet<WebUiPlugin> plugins; - private final GetArchive.AllowedFormats archiveFormats; + private final AllowedFormats archiveFormats; private final AllProjectsName allProjectsName; private final AllUsersName allUsersName; private final String anonymousCowardName; @@ -69,6 +82,9 @@ private final boolean enableSignedPush; private final QueryDocumentationExecutor docSearcher; private final NotesMigration migration; + private final ProjectCache projectCache; + private final AgreementJson agreementJson; + private final GerritOptions gerritOptions; @Inject public GetServerInfo( @@ -79,14 +95,17 @@ DynamicMap<DownloadCommand> downloadCommands, DynamicMap<CloneCommand> cloneCommands, DynamicSet<WebUiPlugin> webUiPlugins, - GetArchive.AllowedFormats archiveFormats, + AllowedFormats archiveFormats, AllProjectsName allProjectsName, AllUsersName allUsersName, @AnonymousCowardName String anonymousCowardName, DynamicItem<AvatarProvider> avatar, @EnableSignedPush boolean enableSignedPush, QueryDocumentationExecutor docSearcher, - NotesMigration migration) { + NotesMigration migration, + ProjectCache projectCache, + AgreementJson agreementJson, + GerritOptions gerritOptions) { this.config = config; this.authConfig = authConfig; this.realm = realm; @@ -102,6 +121,9 @@ this.enableSignedPush = enableSignedPush; this.docSearcher = docSearcher; this.migration = migration; + this.projectCache = projectCache; + this.agreementJson = agreementJson; + this.gerritOptions = gerritOptions; } @Override @@ -134,6 +156,18 @@ info.switchAccountUrl = cfg.getSwitchAccountUrl(); info.isGitBasicAuth = toBoolean(cfg.isGitBasicAuth()); + if (info.useContributorAgreements != null) { + Collection<ContributorAgreement> agreements = + projectCache.getAllProjects().getConfig().getContributorAgreements(); + if (!agreements.isEmpty()) { + info.contributorAgreements = + Lists.newArrayListWithCapacity(agreements.size()); + for (ContributorAgreement agreement: agreements) { + info.contributorAgreements.add(agreementJson.format(agreement)); + } + } + } + switch (info.authType) { case LDAP: case LDAP_BIND: @@ -186,7 +220,7 @@ DynamicMap<DownloadScheme> downloadSchemes, DynamicMap<DownloadCommand> downloadCommands, DynamicMap<CloneCommand> cloneCommands, - GetArchive.AllowedFormats archiveFormats) { + AllowedFormats archiveFormats) { DownloadInfo info = new DownloadInfo(); info.schemes = new HashMap<>(); for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) { @@ -196,14 +230,8 @@ getDownloadSchemeInfo(scheme, downloadCommands, cloneCommands)); } } - info.archives = Lists.newArrayList(Iterables.transform( - archiveFormats.getAllowed(), - new Function<ArchiveFormat, String>() { - @Override - public String apply(ArchiveFormat in) { - return in.getShortName(); - } - })); + info.archives = archiveFormats.getAllowed().stream() + .map(ArchiveFormat::getShortName).collect(toList()); return info; } @@ -251,6 +279,13 @@ info.docSearch = docSearcher.isAvailable(); info.editGpgKeys = toBoolean(enableSignedPush && cfg.getBoolean("gerrit", null, "editGpgKeys", true)); + info.webUis = EnumSet.noneOf(UiType.class); + if (gerritOptions.enableGwtUi()) { + info.webUis.add(UiType.GWT); + } + if (gerritOptions.enablePolyGerrit()) { + info.webUis.add(UiType.POLYGERRIT); + } return info; } @@ -322,85 +357,4 @@ private static Boolean toBoolean(boolean v) { return v ? v : null; } - - public static class ServerInfo { - public AuthInfo auth; - public ChangeConfigInfo change; - public DownloadInfo download; - public GerritInfo gerrit; - public Boolean noteDbEnabled; - public PluginConfigInfo plugin; - public SshdInfo sshd; - public SuggestInfo suggest; - public Map<String, String> urlAliases; - public UserConfigInfo user; - public ReceiveInfo receive; - } - - public static class AuthInfo { - public AuthType authType; - public Boolean useContributorAgreements; - public List<Account.FieldName> editableAccountFields; - public String loginUrl; - public String loginText; - public String switchAccountUrl; - public String registerUrl; - public String registerText; - public String editFullNameUrl; - public String httpPasswordUrl; - public Boolean isGitBasicAuth; - } - - public static class ChangeConfigInfo { - public Boolean allowBlame; - public Boolean allowDrafts; - public int largeChange; - public String replyLabel; - public String replyTooltip; - public int updateDelay; - public Boolean submitWholeTopic; - } - - public static class DownloadInfo { - public Map<String, DownloadSchemeInfo> schemes; - public List<String> archives; - } - - public static class DownloadSchemeInfo { - public String url; - public Boolean isAuthRequired; - public Boolean isAuthSupported; - public Map<String, String> commands; - public Map<String, String> cloneCommands; - } - - public static class GerritInfo { - public String allProjects; - public String allUsers; - public Boolean docSearch; - public String docUrl; - public Boolean editGpgKeys; - public String reportBugUrl; - public String reportBugText; - } - - public static class PluginConfigInfo { - public Boolean hasAvatars; - public List<String> jsResourcePaths; - } - - public static class SshdInfo { - } - - public static class SuggestInfo { - public int from; - } - - public static class UserConfigInfo { - public String anonymousCowardName; - } - - public static class ReceiveInfo { - public Boolean enableSignedPush; - } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java index 33a458e..f7968c8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
@@ -67,7 +67,7 @@ } @Override - public Object apply(ConfigResource rsrc, Input input) + public Response<String> apply(ConfigResource rsrc, Input input) throws AuthException, BadRequestException, UnprocessableEntityException { if (input == null || input.operation == null) { throw new BadRequestException("operation must be specified");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java index cc7857c..7d11ff4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.config; -import com.google.common.base.Function; -import com.google.common.collect.Lists; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.extensions.annotations.ExtensionPoint; import com.google.gerrit.extensions.api.projects.ConfigValue; import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType; @@ -137,14 +137,9 @@ T defaultValue, Class<T> permittedValues, boolean inheritable, String description) { this(displayName, defaultValue.name(), ProjectConfigEntryType.LIST, - Lists.transform( - Arrays.asList(permittedValues.getEnumConstants()), - new Function<Enum<?>, String>() { - @Override - public String apply(Enum<?> e) { - return e.name(); - } - }), inheritable, description); + Arrays.stream(permittedValues.getEnumConstants()) + .map(Enum::name).collect(toList()), + inheritable, description); } public ProjectConfigEntry(String displayName, String defaultValue,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java index 6811056..b09e0fa 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -18,6 +18,7 @@ import com.google.common.base.Optional; import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.client.ChangeKind; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.ResourceConflictException; @@ -34,6 +35,7 @@ import com.google.gerrit.server.change.ChangeKindCache; import com.google.gerrit.server.change.PatchSetInserter; import com.google.gerrit.server.git.BatchUpdate; +import com.google.gerrit.server.git.BatchUpdate.RepoContext; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.UpdateException; import com.google.gerrit.server.index.change.ChangeIndexer; @@ -168,24 +170,69 @@ * @throws UpdateException * @throws RestApiException */ - public void publish(ChangeEdit edit) throws NoSuchChangeException, - IOException, OrmException, RestApiException, UpdateException { + public void publish(final ChangeEdit edit, NotifyHandling notify) + throws NoSuchChangeException, IOException, OrmException, RestApiException, + UpdateException { Change change = edit.getChange(); try (Repository repo = gitManager.openRepository(change.getProject()); RevWalk rw = new RevWalk(repo); - ObjectInserter inserter = repo.newObjectInserter()) { + ObjectInserter oi = repo.newObjectInserter()) { PatchSet basePatchSet = edit.getBasePatchSet(); if (!basePatchSet.getId().equals(change.currentPatchSetId())) { throw new ResourceConflictException( "only edit for current patch set can be published"); } - Change updatedChange = - insertPatchSet(edit, change, repo, rw, inserter, basePatchSet, - squashEdit(rw, inserter, edit.getEditCommit(), basePatchSet)); - // TODO(davido): This should happen in the same BatchRefUpdate. - deleteRef(repo, edit); - indexer.index(db.get(), updatedChange); + RevCommit squashed = squashEdit(rw, oi, edit.getEditCommit(), basePatchSet); + ChangeControl ctl = + changeControlFactory.controlFor(db.get(), change, edit.getUser()); + PatchSet.Id psId = + ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId()); + PatchSetInserter inserter = patchSetInserterFactory + .create(ctl, psId, squashed) + .setNotify(notify); + + StringBuilder message = new StringBuilder("Patch Set ") + .append(inserter.getPatchSetId().get()) + .append(": "); + + ProjectState project = projectCache.get(change.getDest().getParentKey()); + // Previously checked that the base patch set is the current patch set. + ObjectId prior = ObjectId.fromString(basePatchSet.getRevision().get()); + ChangeKind kind = changeKindCache.getChangeKind(project, repo, prior, squashed); + if (kind == ChangeKind.NO_CODE_CHANGE) { + message.append("Commit message was updated."); + } else { + message.append("Published edit on patch set ") + .append(basePatchSet.getPatchSetId()) + .append("."); + } + + try (BatchUpdate bu = updateFactory.create( + db.get(), change.getProject(), ctl.getUser(), + TimeUtil.nowTs())) { + bu.setRepository(repo, rw, oi); + bu.addOp(change.getId(), inserter + .setDraft(change.getStatus() == Status.DRAFT || + basePatchSet.isDraft()) + .setMessage(message.toString())); + bu.addOp(change.getId(), new BatchUpdate.Op() { + @Override + public void updateRepo(RepoContext ctx) throws Exception { + deleteRef(ctx.getRepository(), edit); + } + }); + bu.execute(); + } catch (UpdateException e) { + if (e.getCause() instanceof IOException && e.getMessage() + .equals(String.format("%s: Failed to delete ref %s: %s", + IOException.class.getName(), edit.getRefName(), + RefUpdate.Result.LOCK_FAILURE.name()))) { + throw new ResourceConflictException("edit ref was updated"); + } + } + + indexer.index(db.get(), inserter.getChange()); } } @@ -230,47 +277,6 @@ return writeSquashedCommit(rw, inserter, parent, edit); } - private Change insertPatchSet(ChangeEdit edit, Change change, - Repository repo, RevWalk rw, ObjectInserter oi, PatchSet basePatchSet, - RevCommit squashed) throws NoSuchChangeException, RestApiException, - UpdateException, OrmException, IOException { - ChangeControl ctl = - changeControlFactory.controlFor(db.get(), change, edit.getUser()); - PatchSet.Id psId = - ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId()); - PatchSetInserter inserter = - patchSetInserterFactory.create(ctl, psId, squashed); - - StringBuilder message = new StringBuilder("Patch Set ") - .append(inserter.getPatchSetId().get()) - .append(": "); - - ProjectState project = projectCache.get(change.getDest().getParentKey()); - // Previously checked that the base patch set is the current patch set. - ObjectId prior = ObjectId.fromString(basePatchSet.getRevision().get()); - ChangeKind kind = changeKindCache.getChangeKind(project, repo, prior, squashed); - if (kind == ChangeKind.NO_CODE_CHANGE) { - message.append("Commit message was updated."); - } else { - message.append("Published edit on patch set ") - .append(basePatchSet.getPatchSetId()) - .append("."); - } - - try (BatchUpdate bu = updateFactory.create( - db.get(), change.getProject(), ctl.getUser(), - TimeUtil.nowTs())) { - bu.setRepository(repo, rw, oi); - bu.addOp(change.getId(), inserter - .setDraft(change.getStatus() == Status.DRAFT || - basePatchSet.isDraft()) - .setMessage(message.toString())); - bu.execute(); - } - - return inserter.getChange(); - } - private static void deleteRef(Repository repo, ChangeEdit edit) throws IOException { String refName = edit.getRefName();
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 56daccc..44b5979 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
@@ -15,11 +15,10 @@ package com.google.gerrit.server.events; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Comparator.comparing; -import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import com.google.common.collect.Ordering; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelTypes; @@ -298,22 +297,21 @@ } } // Sort by original parent order. - Collections.sort(ca.dependsOn, Ordering.natural().onResultOf( - new Function<DependencyAttribute, Integer>() { - @Override - public Integer apply(DependencyAttribute d) { - for (int i = 0; i < parentNames.size(); i++) { - if (parentNames.get(i).equals(d.revision)) { - return i; + Collections.sort( + ca.dependsOn, + comparing( + (DependencyAttribute d) -> { + for (int i = 0; i < parentNames.size(); i++) { + if (parentNames.get(i).equals(d.revision)) { + return i; + } } - } - return parentNames.size() + 1; - } - })); + return parentNames.size() + 1; + })); } - private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change, - PatchSet currentPs) throws OrmException, IOException { + private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs) + throws OrmException, IOException { if (currentPs.getGroups().isEmpty()) { return; } @@ -500,7 +498,7 @@ List<Patch> list = patchListCache.get(change, patchSet).toPatchList(pId); for (Patch pe : list) { - if (!Patch.COMMIT_MSG.equals(pe.getFileName())) { + if (!Patch.isMagic(pe.getFileName())) { p.sizeDeletions -= pe.getDeletions(); p.sizeInsertions += pe.getInsertions(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java index 1368bf3..8d093f0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -321,7 +321,7 @@ } @Override - public void onReviewerAdded(ReviewerAddedListener.Event ev) { + public void onReviewersAdded(ReviewerAddedListener.Event ev) { try { ChangeNotes notes = getNotes(ev.getChange()); Change change = notes.getChange(); @@ -330,9 +330,10 @@ event.change = changeAttributeSupplier(change); event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); - event.reviewer = accountAttributeSupplier(ev.getReviewer()); - - dispatcher.get().postEvent(change, event); + for (AccountInfo reviewer : ev.getReviewers()) { + event.reviewer = accountAttributeSupplier(reviewer); + dispatcher.get().postEvent(change, event); + } } catch (OrmException e) { log.error("Failed to dispatch event", 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 dd49272..c76e76b 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
@@ -83,22 +83,15 @@ private static class Event extends AbstractRevisionEvent implements ChangeAbandonedListener.Event { - private final AccountInfo abandoner; private final String reason; Event(ChangeInfo change, RevisionInfo revision, AccountInfo abandoner, String reason, Timestamp when, NotifyHandling notifyHandling) { super(change, revision, abandoner, when, notifyHandling); - this.abandoner = abandoner; this.reason = reason; } @Override - public AccountInfo getAbandoner() { - return abandoner; - } - - @Override public String getReason() { return reason; }
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 94df1d1..378f2b7 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
@@ -81,22 +81,15 @@ private static class Event extends AbstractRevisionEvent implements ChangeMergedListener.Event { - private final AccountInfo merger; private final String newRevisionId; Event(ChangeInfo change, RevisionInfo revision, AccountInfo merger, String newRevisionId, Timestamp when) { super(change, revision, merger, when, NotifyHandling.ALL); - this.merger = merger; this.newRevisionId = newRevisionId; } @Override - public AccountInfo getMerger() { - return merger; - } - - @Override public String getNewRevisionId() { return newRevisionId; }
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 c853609..05e0d21 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
@@ -82,22 +82,15 @@ private static class Event extends AbstractRevisionEvent implements ChangeRestoredListener.Event { - private AccountInfo restorer; private String reason; Event(ChangeInfo change, RevisionInfo revision, AccountInfo restorer, String reason, Timestamp when) { super(change, revision, restorer, when, NotifyHandling.ALL); - this.restorer = restorer; this.reason = reason; } @Override - public AccountInfo getRestorer() { - return restorer; - } - - @Override public String getReason() { return reason; }
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 85ad8b6..8e27ce9 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
@@ -90,7 +90,6 @@ private static class Event extends AbstractRevisionEvent implements CommentAddedListener.Event { - private final AccountInfo author; private final String comment; private final Map<String, ApprovalInfo> approvals; private final Map<String, ApprovalInfo> oldApprovals; @@ -99,18 +98,12 @@ String comment, Map<String, ApprovalInfo> approvals, Map<String, ApprovalInfo> oldApprovals, Timestamp when) { super(change, revision, author, when, NotifyHandling.ALL); - this.author = author; this.comment = comment; this.approvals = approvals; this.oldApprovals = oldApprovals; } @Override - public AccountInfo getAuthor() { - return author; - } - - @Override public String getComment() { return comment; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/DraftPublished.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/DraftPublished.java index 0895cb8..6b8ce3d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/DraftPublished.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/DraftPublished.java
@@ -78,17 +78,10 @@ private static class Event extends AbstractRevisionEvent implements DraftPublishedListener.Event { - private final AccountInfo publisher; Event(ChangeInfo change, RevisionInfo revision, AccountInfo publisher, Timestamp when) { super(change, revision, publisher, when, NotifyHandling.ALL); - this.publisher = publisher; - } - - @Override - public AccountInfo getPublisher() { - return publisher; } } }
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 114338d..1c8782b 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
@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.GpgException; import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.AccountJson; import com.google.gerrit.server.change.ChangeJson; import com.google.gerrit.server.patch.PatchListNotAvailableException; import com.google.gerrit.server.project.ChangeControl; @@ -82,11 +83,7 @@ if (a == null || a.getId() == null) { return null; } - AccountInfo ai = new AccountInfo(a.getId().get()); - ai.email = a.getPreferredEmail(); - ai.name = a.getFullName(); - ai.username = a.getUserName(); - return ai; + return AccountJson.toAccountInfo(a); } public AccountInfo accountInfo(Account.Id accountId) { @@ -97,7 +94,7 @@ Map<String, Short> approvals, Timestamp ts) { Map<String, ApprovalInfo> result = new HashMap<>(); for (Map.Entry<String, Short> e : approvals.entrySet()) { - Integer value = e.getValue() != null ? new Integer(e.getValue()) : null; + Integer value = e.getValue() != null ? Integer.valueOf(e.getValue()) : null; result.put(e.getKey(), ChangeJson.getApprovalInfo(a.getId(), value, null, ts)); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java index fe42d02..f18b963 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java
@@ -81,7 +81,6 @@ private static class Event extends AbstractChangeEvent implements HashtagsEditedListener.Event { - private AccountInfo editor; private Collection<String> updatedHashtags; private Collection<String> addedHashtags; private Collection<String> removedHashtags; @@ -89,18 +88,12 @@ Event(ChangeInfo change, AccountInfo editor, Collection<String> updated, Collection<String> added, Collection<String> removed, Timestamp when) { super(change, editor, when, NotifyHandling.ALL); - this.editor = editor; this.updatedHashtags = updated; this.addedHashtags = added; this.removedHashtags = removed; } @Override - public AccountInfo getEditor() { - return editor; - } - - @Override public Collection<String> getHashtags() { return updatedHashtags; }
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 3ae8135..9dbbeef 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
@@ -14,6 +14,7 @@ package com.google.gerrit.server.extensions.events; +import com.google.common.collect.Lists; import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ChangeInfo; @@ -33,6 +34,7 @@ import java.io.IOException; import java.sql.Timestamp; +import java.util.List; public class ReviewerAdded { private static final Logger log = @@ -49,29 +51,30 @@ } public void fire(ChangeInfo change, RevisionInfo revision, - AccountInfo reviewer, AccountInfo adder, Timestamp when) { + List<AccountInfo> reviewers, AccountInfo adder, Timestamp when) { if (!listeners.iterator().hasNext()) { return; } - Event event = new Event(change, revision, reviewer, adder, when); + Event event = new Event(change, revision, reviewers, adder, when); for (ReviewerAddedListener l : listeners) { try { - l.onReviewerAdded(event); + l.onReviewersAdded(event); } catch (Exception e) { log.warn("Error in event listener, e"); } } } - public void fire(Change change, PatchSet patchSet, Account account, + public void fire(Change change, PatchSet patchSet, List<Account.Id> reviewers, Account adder, Timestamp when) { - if (!listeners.iterator().hasNext()) { + if (!listeners.iterator().hasNext() || reviewers.isEmpty()) { return; } + try { fire(util.changeInfo(change), util.revisionInfo(change.getProject(), patchSet), - util.accountInfo(account), + Lists.transform(reviewers, util::accountInfo), util.accountInfo(adder), when); } catch (PatchListNotAvailableException | GpgException | IOException @@ -82,17 +85,17 @@ private static class Event extends AbstractRevisionEvent implements ReviewerAddedListener.Event { - private final AccountInfo reviewer; + private final List<AccountInfo> reviewers; - Event(ChangeInfo change, RevisionInfo revision, AccountInfo reviewer, + Event(ChangeInfo change, RevisionInfo revision, List<AccountInfo> reviewers, AccountInfo adder, Timestamp when) { super(change, revision, adder, when, NotifyHandling.ALL); - this.reviewer = reviewer; + this.reviewers = reviewers; } @Override - public AccountInfo getReviewer() { - return reviewer; + public List<AccountInfo> getReviewers() { + return reviewers; } } }
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 270eb35..b519c46 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
@@ -53,12 +53,13 @@ public void fire(ChangeInfo change, RevisionInfo revision, AccountInfo reviewer, AccountInfo remover, String message, Map<String, ApprovalInfo> newApprovals, - Map<String, ApprovalInfo> oldApprovals, Timestamp when) { + Map<String, ApprovalInfo> oldApprovals, NotifyHandling notify, + Timestamp when) { if (!listeners.iterator().hasNext()) { return; } Event event = new Event(change, revision, reviewer, remover, message, - newApprovals, oldApprovals, when); + newApprovals, oldApprovals, notify, when); for (ReviewerDeletedListener listener : listeners) { try { listener.onReviewerDeleted(event); @@ -69,9 +70,8 @@ } public void fire(Change change, PatchSet patchSet, Account reviewer, - Account remover, String message, - Map<String, Short> newApprovals, - Map<String, Short> oldApprovals, Timestamp when) { + Account remover, String message, Map<String, Short> newApprovals, + Map<String, Short> oldApprovals, NotifyHandling notify, Timestamp when) { if (!listeners.iterator().hasNext()) { return; } @@ -83,6 +83,7 @@ message, util.approvals(reviewer, newApprovals, when), util.approvals(reviewer, oldApprovals, when), + notify, when); } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) { @@ -101,8 +102,9 @@ Event(ChangeInfo change, RevisionInfo revision, AccountInfo reviewer, AccountInfo remover, String comment, Map<String, ApprovalInfo> newApprovals, - Map<String, ApprovalInfo> oldApprovals, Timestamp when) { - super(change, revision, remover, when, NotifyHandling.ALL); + Map<String, ApprovalInfo> oldApprovals, NotifyHandling notify, + Timestamp when) { + super(change, revision, remover, when, notify); this.reviewer = reviewer; this.comment = comment; this.newApprovals = newApprovals;
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 98fa05e..71bc9ec 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
@@ -81,17 +81,10 @@ private static class Event extends AbstractRevisionEvent implements RevisionCreatedListener.Event { - private final AccountInfo uploader; Event(ChangeInfo change, RevisionInfo revision, AccountInfo uploader, Timestamp when, NotifyHandling notify) { super(change, revision, uploader, when, notify); - this.uploader = uploader; - } - - @Override - public AccountInfo getUploader() { - return uploader; } } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/TopicEdited.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/TopicEdited.java index 0a0a8ca..77c1647 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/TopicEdited.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/TopicEdited.java
@@ -75,22 +75,15 @@ private static class Event extends AbstractChangeEvent implements TopicEditedListener.Event { - private final AccountInfo editor; private final String oldTopic; Event(ChangeInfo change, AccountInfo editor, String oldTopic, Timestamp when) { super(change, editor, when, NotifyHandling.ALL); - this.editor = editor; this.oldTopic = oldTopic; } @Override - public AccountInfo getEditor() { - return editor; - } - - @Override public String getOldTopic() { return oldTopic; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java index 601bcc6..3dbd1e3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -14,11 +14,8 @@ package com.google.gerrit.server.extensions.webui; -import com.google.common.base.Function; import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.Iterables; -import com.google.gerrit.common.Nullable; +import com.google.common.collect.FluentIterable; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.RestCollection; @@ -33,16 +30,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Objects; + public class UiActions { private static final Logger log = LoggerFactory.getLogger(UiActions.class); public static Predicate<UiAction.Description> enabled() { - return new Predicate<UiAction.Description>() { - @Override - public boolean apply(UiAction.Description input) { - return input.isEnabled(); - } - }; + return UiAction.Description::isEnabled; } public static <R extends RestResource> Iterable<UiAction.Description> from( @@ -56,58 +50,52 @@ DynamicMap<RestView<R>> views, final R resource, final Provider<CurrentUser> userProvider) { - return Iterables.filter( - Iterables.transform( - views, - new Function<DynamicMap.Entry<RestView<R>>, UiAction.Description> () { - @Override - @Nullable - public UiAction.Description apply(DynamicMap.Entry<RestView<R>> e) { - int d = e.getExportName().indexOf('.'); - if (d < 0) { - return null; - } + return FluentIterable.from(views) + .transform((DynamicMap.Entry<RestView<R>> e) -> { + int d = e.getExportName().indexOf('.'); + if (d < 0) { + return null; + } - RestView<R> view; - try { - view = e.getProvider().get(); - } catch (RuntimeException err) { - log.error(String.format( - "error creating view %s.%s", - e.getPluginName(), e.getExportName()), err); - return null; - } + RestView<R> view; + try { + view = e.getProvider().get(); + } catch (RuntimeException err) { + log.error(String.format( + "error creating view %s.%s", + e.getPluginName(), e.getExportName()), err); + return null; + } - if (!(view instanceof UiAction)) { - return null; - } + if (!(view instanceof UiAction)) { + return null; + } - try { - CapabilityUtils.checkRequiresCapability(userProvider, - e.getPluginName(), view.getClass()); - } catch (AuthException exc) { - return null; - } + try { + CapabilityUtils.checkRequiresCapability(userProvider, + e.getPluginName(), view.getClass()); + } catch (AuthException exc) { + return null; + } - UiAction.Description dsc = - ((UiAction<R>) view).getDescription(resource); - if (dsc == null || !dsc.isVisible()) { - return null; - } + UiAction.Description dsc = + ((UiAction<R>) view).getDescription(resource); + if (dsc == null || !dsc.isVisible()) { + return null; + } - String name = e.getExportName().substring(d + 1); - PrivateInternals_UiActionDescription.setMethod( - dsc, - e.getExportName().substring(0, d)); - PrivateInternals_UiActionDescription.setId( - dsc, - "gerrit".equals(e.getPluginName()) - ? name - : e.getPluginName() + '~' + name); - return dsc; - } - }), - Predicates.notNull()); + String name = e.getExportName().substring(d + 1); + PrivateInternals_UiActionDescription.setMethod( + dsc, + e.getExportName().substring(0, d)); + PrivateInternals_UiActionDescription.setId( + dsc, + "gerrit".equals(e.getPluginName()) + ? name + : e.getPluginName() + '~' + name); + return dsc; + }) + .filter(Objects::nonNull); } private UiActions() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java index 090d99d..8e98562 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.util.Comparator.comparing; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -226,7 +227,7 @@ this.dbWrapper = dbWrapper; this.threadLocalRepo = repo; this.threadLocalRevWalk = rw; - updates = new TreeMap<>(ReviewDbUtil.intKeyOrdering()); + updates = new TreeMap<>(comparing(PatchSet.Id::get)); } @Override @@ -379,7 +380,8 @@ } static void execute(Collection<BatchUpdate> updates, Listener listener, - @Nullable RequestId requestId) throws UpdateException, RestApiException { + @Nullable RequestId requestId, boolean dryrun) + throws UpdateException, RestApiException { if (updates.isEmpty()) { return; } @@ -401,17 +403,17 @@ } listener.afterUpdateRepos(); for (BatchUpdate u : updates) { - u.executeRefUpdates(); + u.executeRefUpdates(dryrun); } listener.afterRefUpdates(); for (BatchUpdate u : updates) { - u.executeChangeOps(updateChangesInParallel); + u.executeChangeOps(updateChangesInParallel, dryrun); } listener.afterUpdateChanges(); break; case DB_BEFORE_REPO: for (BatchUpdate u : updates) { - u.executeChangeOps(updateChangesInParallel); + u.executeChangeOps(updateChangesInParallel, dryrun); } listener.afterUpdateChanges(); for (BatchUpdate u : updates) { @@ -419,7 +421,7 @@ } listener.afterUpdateRepos(); for (BatchUpdate u : updates) { - u.executeRefUpdates(); + u.executeRefUpdates(dryrun); } listener.afterRefUpdates(); break; @@ -445,9 +447,10 @@ u.getUser().isIdentifiedUser() ? u.getUser().getAccountId() : null); } } - - for (BatchUpdate u : updates) { - u.executePostOps(); + if (!dryrun) { + for (BatchUpdate u : updates) { + u.executePostOps(); + } } } catch (UpdateException | RestApiException e) { // Propagate REST API exceptions thrown by operations; they commonly throw @@ -638,13 +641,17 @@ return this; } + public Collection<ReceiveCommand> getRefUpdates() { + return commands.getCommands().values(); + } + public void execute() throws UpdateException, RestApiException { execute(Listener.NONE); } public void execute(Listener listener) throws UpdateException, RestApiException { - execute(ImmutableList.of(this), listener, requestId); + execute(ImmutableList.of(this), listener, requestId, false); } private void executeUpdateRepo() throws UpdateException, RestApiException { @@ -674,7 +681,8 @@ } } - private void executeRefUpdates() throws IOException, UpdateException { + private void executeRefUpdates(boolean dryrun) + throws IOException, UpdateException { if (commands == null || commands.isEmpty()) { logDebug("No ref updates to execute"); return; @@ -685,6 +693,10 @@ commands.addTo(batchRefUpdate); logDebug("Executing batch of {} ref updates", batchRefUpdate.getCommands().size()); + if (dryrun) { + return; + } + batchRefUpdate.execute(revWalk, NullProgressMonitor.INSTANCE); boolean ok = true; for (ReceiveCommand cmd : batchRefUpdate.getCommands()) { @@ -698,8 +710,9 @@ } } - private void executeChangeOps(boolean parallel) - throws UpdateException, RestApiException { + private void executeChangeOps(boolean parallel, + boolean dryrun) throws UpdateException, + RestApiException { logDebug("Executing change ops (parallel? {})", parallel); ListeningExecutorService executor = parallel ? changeUpdateExector @@ -722,7 +735,8 @@ List<ListenableFuture<?>> futures = new ArrayList<>(ops.keySet().size()); for (Map.Entry<Change.Id, Collection<Op>> e : ops.asMap().entrySet()) { ChangeTask task = - new ChangeTask(e.getKey(), e.getValue(), Thread.currentThread()); + new ChangeTask(e.getKey(), e.getValue(), Thread.currentThread(), + dryrun); tasks.add(task); if (!parallel) { logDebug("Direct execution of task for ops: {}", ops); @@ -740,7 +754,9 @@ if (notesMigration.commitChangeWrites()) { startNanos = System.nanoTime(); - executeNoteDbUpdates(tasks); + if (!dryrun) { + executeNoteDbUpdates(tasks); + } maybeLogSlowUpdate(startNanos, "NoteDb"); } } catch (ExecutionException | InterruptedException e) { @@ -872,6 +888,7 @@ final Change.Id id; private final Collection<Op> changeOps; private final Thread mainThread; + private final boolean dryrun; NoteDbUpdateManager.StagedResult noteDbResult; boolean dirty; @@ -879,10 +896,11 @@ private String taskId; private ChangeTask(Change.Id id, Collection<Op> changeOps, - Thread mainThread) { + Thread mainThread, boolean dryrun) { this.id = id; this.changeOps = changeOps; this.mainThread = mainThread; + this.dryrun = dryrun; } @Override @@ -951,7 +969,9 @@ logDebug("Updating change"); db.changes().update(cs); } - db.commit(); + if (!dryrun) { + db.commit(); + } } finally { db.rollback(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java index f07b922..27767c0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -16,7 +16,6 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.base.Function; import com.google.common.collect.Ordering; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.PatchSet; @@ -46,14 +45,11 @@ * AnyObjectId} and only orders on SHA-1. */ public static final Ordering<CodeReviewCommit> ORDER = Ordering.natural() - .onResultOf(new Function<CodeReviewCommit, Integer>() { - @Override - public Integer apply(CodeReviewCommit in) { - return in.getPatchsetId() != null - ? in.getPatchsetId().getParentKey().get() - : null; - } - }).nullsFirst(); + .onResultOf((CodeReviewCommit c) -> + c.getPatchsetId() != null + ? c.getPatchsetId().getParentKey().get() + : null) + .nullsFirst(); public static CodeReviewRevWalk newRevWalk(Repository repo) { return new CodeReviewRevWalk(repo);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java index 0e954f3..26c59c2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java
@@ -67,7 +67,7 @@ logDebug("Loading .gitmodules of {} for project {}", branch, project); OpenRepo or; try { - or = orm.openRepo(project, false); + or = orm.openRepo(project); } catch (NoSuchProjectException e) { throw new IOException(e); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java index d832260..795a838 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java
@@ -18,7 +18,6 @@ import static org.eclipse.jgit.revwalk.RevFlag.UNINTERESTING; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; @@ -158,13 +157,7 @@ private static Multimap<ObjectId, PatchSet.Id> transformRefs( Multimap<ObjectId, Ref> refs) { return Multimaps.transformValues( - refs, - new Function<Ref, PatchSet.Id>() { - @Override - public PatchSet.Id apply(Ref in) { - return PatchSet.Id.fromRef(in.getName()); - } - }); + refs, r -> PatchSet.Id.fromRef(r.getName())); } @VisibleForTesting
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java index 9d62721..71bc9f4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -17,12 +17,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.util.Comparator.comparing; import com.google.auto.value.AutoValue; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; @@ -31,7 +30,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; -import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.TimeUtil; @@ -122,13 +120,10 @@ } byBranch = bb.build(); commits = new HashMap<>(); - problems = MultimapBuilder.treeKeys( - Ordering.natural().onResultOf(new Function<Change.Id, Integer>() { - @Override - public Integer apply(Change.Id in) { - return in.get(); - } - })).arrayListValues(1).build(); + problems = MultimapBuilder + .treeKeys(comparing(Change.Id::get)) + .arrayListValues(1) + .build(); } public ImmutableSet<Change.Id> getChangeIds() { @@ -230,6 +225,8 @@ private CommitStatus commits; private ReviewDb db; private SubmitInput submitInput; + private Set<Project.NameKey> allProjects; + private boolean dryrun; @Inject MergeOp(ChangeMessagesUtil cmUtil, @@ -262,12 +259,7 @@ if (in == null) { return Optional.absent(); } - return Iterables.tryFind(in, new Predicate<SubmitRecord>() { - @Override - public boolean apply(SubmitRecord input) { - return input.status == SubmitRecord.Status.OK; - } - }); + return Iterables.tryFind(in, r -> r.status == SubmitRecord.Status.OK); } public static void checkSubmitRule(ChangeData cd) @@ -400,10 +392,26 @@ } } + /** + * Merges the given change. + * + * Depending on the server configuration, more changes may be affected, e.g. + * by submission of a topic or via superproject subscriptions. All affected + * changes are integrated using the projects integration strategy. + * + * @param db the review database. + * @param change the change to be merged. + * @param caller the identity of the caller + * @param checkSubmitRules whether the prolog submit rules should be evaluated + * @param submitInput parameters regarding the merge + * @throws OrmException an error occurred reading or writing the database. + * @throws RestApiException if an error occurred. + */ public void merge(ReviewDb db, Change change, IdentifiedUser caller, - boolean checkSubmitRules, SubmitInput submitInput) + boolean checkSubmitRules, SubmitInput submitInput, boolean dryrun) throws OrmException, RestApiException { this.submitInput = submitInput; + this.dryrun = dryrun; this.caller = caller; this.ts = TimeUtil.nowTs(); submissionId = RequestId.forChange(change); @@ -412,7 +420,8 @@ logDebug("Beginning integration of {}", change); try { - ChangeSet cs = mergeSuperSet.completeChangeSet(db, change, caller); + ChangeSet cs = mergeSuperSet.setMergeOpRepoManager(orm) + .completeChangeSet(db, change, caller); checkState(cs.ids().contains(change.getId()), "change %s missing from %s", change.getId(), cs); if (cs.furtherHiddenChanges()) { @@ -469,16 +478,17 @@ commits.maybeFailVerbose(); SubmoduleOp submoduleOp = subOpFactory.create(branches, orm); try { - List<SubmitStrategy> strategies = getSubmitStrategies(toSubmit, submoduleOp); + List<SubmitStrategy> strategies = getSubmitStrategies(toSubmit, + submoduleOp, dryrun); Set<Project.NameKey> allProjects = submoduleOp.getProjectsInOrder(); // in case superproject subscription is disabled, allProjects would be null if (allProjects == null) { allProjects = projects; } - BatchUpdate.execute( - orm.batchUpdates(allProjects), + this.allProjects = allProjects; + BatchUpdate.execute(orm.batchUpdates(allProjects), new SubmitStrategyListener(submitInput, strategies, commits), - submissionId); + submissionId, dryrun); } catch (UpdateException | SubmoduleException e) { // BatchUpdate may have inadvertently wrapped an IntegrationException // thrown by some legacy SubmitStrategyOp code that intended the error @@ -498,9 +508,17 @@ } } + public Set<Project.NameKey> getAllProjects() { + return allProjects; + } + + public MergeOpRepoManager getMergeOpRepoManager() { + return orm; + } + private List<SubmitStrategy> getSubmitStrategies( - Map<Branch.NameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp) - throws IntegrationException { + Map<Branch.NameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp, + boolean dryrun) throws IntegrationException { List<SubmitStrategy> strategies = new ArrayList<>(); Set<Branch.NameKey> allBranches = submoduleOp.getBranchesInOrder(); // in case superproject subscription is disabled, allBranches would be null @@ -519,9 +537,13 @@ Set<CodeReviewCommit> commitsToSubmit = commits(submitting.changes()); ob.mergeTip = new MergeTip(ob.oldTip, commitsToSubmit); SubmitStrategy strategy = createStrategy(or, ob.mergeTip, branch, - submitting.submitType(), ob.oldTip, submoduleOp); + submitting.submitType(), ob.oldTip, submoduleOp, dryrun); strategies.add(strategy); strategy.addOps(or.getUpdate(), commitsToSubmit); + if (submitting.submitType().equals(SubmitType.FAST_FORWARD_ONLY) && + submoduleOp.hasSubscription(branch)) { + submoduleOp.addOp(or.getUpdate(), branch); + } } else { // no open change for this branch // add submodule triggered op into BatchUpdate @@ -545,10 +567,12 @@ private SubmitStrategy createStrategy(OpenRepo or, MergeTip mergeTip, Branch.NameKey destBranch, SubmitType submitType, - CodeReviewCommit branchTip, SubmoduleOp submoduleOp) throws IntegrationException { + CodeReviewCommit branchTip, SubmoduleOp submoduleOp, boolean dryrun) + throws IntegrationException { return submitStrategyFactory.create(submitType, db, or.repo, or.rw, or.ins, or.canMergeFlag, getAlreadyAccepted(or, branchTip), destBranch, caller, - mergeTip, commits, submissionId, submitInput.notify, submoduleOp); + mergeTip, commits, submissionId, submitInput.notify, submoduleOp, + dryrun); } private Set<RevCommit> getAlreadyAccepted(OpenRepo or, @@ -727,7 +751,7 @@ throws IntegrationException { for (Project.NameKey project : projects) { try { - orm.openRepo(project, true); + orm.openRepo(project); } catch (NoSuchProjectException noProject) { logWarn("Project " + noProject.project() + " no longer exists, " + "abandoning open changes");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java index fb4c2d4..5196ebe 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java
@@ -90,11 +90,19 @@ return ob; } + public Repository getRepo() { + return repo; + } + Project.NameKey getProjectName() { return project.getProject().getNameKey(); } - BatchUpdate getUpdate() { + public CodeReviewRevWalk getCodeReviewRevWalk() { + return rw; + } + + public BatchUpdate getUpdate() { checkState(db != null, "call setContext before getUpdate"); if (update == null) { update = batchUpdateFactory.create(db, getProjectName(), caller, ts) @@ -187,13 +195,8 @@ return or; } - public OpenRepo openRepo(Project.NameKey project, boolean abortIfOpen) + public OpenRepo openRepo(Project.NameKey project) throws NoSuchProjectException, IOException { - if (abortIfOpen) { - checkState(!openRepos.containsKey(project), - "repo already opened: %s", project); - } - if (openRepos.containsKey(project)) { return openRepos.get(project); }
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 284e9ed..108572f 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
@@ -14,13 +14,16 @@ package com.google.gerrit.server.git; +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.Optional; 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.common.collect.Multimap; import com.google.gerrit.common.data.SubmitTypeRecord; import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.reviewdb.client.Branch; @@ -31,31 +34,33 @@ 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.project.NoSuchChangeException; +import com.google.gerrit.server.project.NoSuchProjectException; 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; import com.google.inject.Inject; import com.google.inject.Provider; -import com.google.inject.Singleton; -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; -import org.eclipse.jgit.revwalk.RevWalk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.Set; /** @@ -68,7 +73,6 @@ * If change.submitWholeTopic is enabled, also all changes of the topic * and their parents are included. */ -@Singleton public class MergeSuperSet { private static final Logger log = LoggerFactory.getLogger(MergeOp.class); @@ -77,46 +81,69 @@ for (ChangeData cd : cs.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(); + } + private final ChangeData.Factory changeDataFactory; private final Provider<InternalChangeQuery> queryProvider; - private final GitRepositoryManager repoManager; + private final Provider<MergeOpRepoManager> repoManagerProvider; private final Config cfg; + private final Map<QueryKey, List<ChangeData>> queryCache; + private final Map<Branch.NameKey, Optional<RevCommit>> heads; + + private MergeOpRepoManager orm; + private boolean closeOrm; @Inject MergeSuperSet(@GerritServerConfig Config cfg, ChangeData.Factory changeDataFactory, Provider<InternalChangeQuery> queryProvider, - GitRepositoryManager repoManager) { + Provider<MergeOpRepoManager> repoManagerProvider) { this.cfg = cfg; this.changeDataFactory = changeDataFactory; this.queryProvider = queryProvider; - this.repoManager = repoManager; + this.repoManagerProvider = repoManagerProvider; + queryCache = new HashMap<>(); + heads = new HashMap<>(); } - public ChangeSet completeChangeSet(ReviewDb db, Change change, CurrentUser user) - throws MissingObjectException, IncorrectObjectTypeException, IOException, - OrmException { - ChangeData cd = - changeDataFactory.create(db, change.getProject(), change.getId()); - cd.changeControl(user); - ChangeSet cs = new ChangeSet(cd, cd.changeControl().isVisible(db, cd)); - if (Submit.wholeTopicEnabled(cfg)) { - return completeChangeSetIncludingTopics(db, cs, user); - } - return completeChangeSetWithoutTopic(db, cs, user); + public MergeSuperSet setMergeOpRepoManager(MergeOpRepoManager orm) { + checkState(this.orm == null); + this.orm = checkNotNull(orm); + closeOrm = false; + return this; } - private static ImmutableListMultimap<Project.NameKey, ChangeData> - byProject(Iterable<ChangeData> changes) throws OrmException { - ImmutableListMultimap.Builder<Project.NameKey, ChangeData> builder = - new ImmutableListMultimap.Builder<>(); - for (ChangeData cd : changes) { - builder.put(cd.change().getProject(), cd); + public ChangeSet completeChangeSet(ReviewDb db, Change change, + CurrentUser user) throws IOException, OrmException { + try { + ChangeData cd = + changeDataFactory.create(db, change.getProject(), change.getId()); + cd.changeControl(user); + ChangeSet cs = new ChangeSet(cd, cd.changeControl().isVisible(db, cd)); + if (Submit.wholeTopicEnabled(cfg)) { + return completeChangeSetIncludingTopics(db, cs, user); + } + return completeChangeSetWithoutTopic(db, cs, user); + } finally { + if (closeOrm && orm != null) { + orm.close(); + orm = null; + } } - return builder.build(); } private SubmitType submitType(ChangeData cd, PatchSet ps, boolean visible) @@ -146,94 +173,175 @@ return str.type; } - private ChangeSet completeChangeSetWithoutTopic(ReviewDb db, ChangeSet changes, - CurrentUser user) throws MissingObjectException, - IncorrectObjectTypeException, IOException, OrmException { - List<ChangeData> visibleChanges = new ArrayList<>(); - List<ChangeData> nonVisibleChanges = new ArrayList<>(); + 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(); + } - Multimap<Project.NameKey, ChangeData> pc = - byProject( - Iterables.concat(changes.changes(), changes.nonVisibleChanges())); - for (Project.NameKey project : pc.keySet()) { - try (Repository repo = repoManager.openRepository(project); - RevWalk rw = CodeReviewCommit.newRevWalk(repo)) { - for (ChangeData cd : pc.get(project)) { - checkState(cd.hasChangeControl(), - "completeChangeSet forgot to set changeControl for current user" - + " at ChangeData creation time"); - boolean visible = changes.ids().contains(cd.getId()); - if (visible && !cd.changeControl().isVisible(db, 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; - } - List<ChangeData> dest = visible ? visibleChanges : nonVisibleChanges; + 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); + } - // 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; - if (!cd.changeControl().isPatchVisible(ps, cd)) { - Iterable<PatchSet> visiblePatchSets = cd.visiblePatchSets(); - if (Iterables.isEmpty(visiblePatchSets)) { - visiblePatchSet = false; - } else { - ps = Iterables.getLast(visiblePatchSets); - } - } + return destHashes; + } - if (submitType(cd, ps, visiblePatchSet) == SubmitType.CHERRY_PICK) { - dest.add(cd); - continue; - } + private ChangeSet completeChangeSetWithoutTopic(ReviewDb db, + ChangeSet changes, CurrentUser user) throws IOException, OrmException { + Collection<ChangeData> visibleChanges = new ArrayList<>(); + Collection<ChangeData> nonVisibleChanges = new ArrayList<>(); - // Get the underlying git commit object - String objIdStr = ps.getRevision().get(); - RevCommit commit = rw.parseCommit(ObjectId.fromString(objIdStr)); + // 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)) { + checkState(cd.hasChangeControl(), + "completeChangeSet forgot to set changeControl for current user" + + " at ChangeData creation time"); - // Collect unmerged ancestors - Branch.NameKey destBranch = cd.change().getDest(); - repo.getRefDatabase().refresh(); - Ref ref = repo.getRefDatabase().getRef(destBranch.get()); + boolean visible = changes.ids().contains(cd.getId()); + if (visible && !cd.changeControl().isVisible(db, 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; + } + Collection<RevCommit> toWalk = visible ? + visibleCommits : nonVisibleCommits; - rw.reset(); - rw.sort(RevSort.TOPO); - rw.markStart(commit); - if (ref != null) { - RevCommit head = rw.parseCommit(ref.getObjectId()); - rw.markUninteresting(head); - } - - List<String> hashes = new ArrayList<>(); - // Always include the input, even if merged. This allows - // SubmitStrategyOp to correct the situation later, assuming it gets - // returned by byCommitsOnBranchNotMerged below. - hashes.add(objIdStr); - for (RevCommit c : rw) { - if (!c.equals(commit)) { - hashes.add(c.name()); - } - } - - if (!hashes.isEmpty()) { - Iterable<ChangeData> destChanges = query() - .byCommitsOnBranchNotMerged( - repo, db, cd.change().getDest(), hashes); - for (ChangeData chd : destChanges) { - chd.changeControl(user); - dest.add(chd); - } + // 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; + if (!cd.changeControl().isPatchVisible(ps, cd)) { + Iterable<PatchSet> visiblePatchSets = cd.visiblePatchSets(); + if (Iterables.isEmpty(visiblePatchSets)) { + visiblePatchSet = false; + } else { + ps = Iterables.getLast(visiblePatchSets); } } + + if (submitType(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. + toWalk.add(commit); } + + Set<String> emptySet = Collections.emptySet(); + Set<String> visibleHashes = + walkChangesByHashes(visibleCommits, emptySet, or, b); + + List<ChangeData> cds = + byCommitsOnBranchNotMerged(or, db, user, b, visibleHashes); + for (ChangeData chd : cds) { + chd.changeControl(user); + visibleChanges.add(chd); + } + + Set<String> nonVisibleHashes = + walkChangesByHashes(nonVisibleCommits, visibleHashes, or, b); + Iterables.addAll(nonVisibleChanges, + byCommitsOnBranchNotMerged(or, db, user, 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.openRepo(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.<RevCommit>of(or.rw.parseCommit(ref.getObjectId())) + : Optional.<RevCommit>absent(); + heads.put(b, head); + } + if (head.isPresent()) { + or.rw.markUninteresting(head.get()); + } + } + + private List<ChangeData> byCommitsOnBranchNotMerged(OpenRepo or, ReviewDb db, + CurrentUser user, 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) { + chd.changeControl(user); + result.add(chd); + } + queryCache.put(k, result); + return result; + } + /** * Completes {@code cs} with any additional changes from its topics * <p> @@ -261,11 +369,19 @@ continue; } for (ChangeData topicCd : query().byTopicOpen(topic)) { - topicCd.changeControl(user); - if (topicCd.changeControl().isVisible(db, topicCd)) { - visibleChanges.add(topicCd); - } else { - nonVisibleChanges.add(topicCd); + try { + topicCd.changeControl(user); + if (topicCd.changeControl().isVisible(db, topicCd)) { + visibleChanges.add(topicCd); + } else { + nonVisibleChanges.add(topicCd); + } + } catch (OrmException e) { + if (e.getCause() instanceof NoSuchChangeException) { + // Ignore and skip this change + } else { + throw e; + } } } topicsSeen.add(topic); @@ -288,8 +404,7 @@ private ChangeSet completeChangeSetIncludingTopics( ReviewDb db, ChangeSet changes, CurrentUser user) - throws MissingObjectException, IncorrectObjectTypeException, IOException, - OrmException { + throws IOException, OrmException { Set<String> topicsSeen = new HashSet<>(); Set<String> visibleTopicsSeen = new HashSet<>(); int oldSeen; @@ -307,13 +422,15 @@ } private InternalChangeQuery query() { - // Request fields required for completing the ChangeSet without having to - // touch the database. 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(). + // 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.PATCH_SET.getName(), + ChangeField.MERGEABLE.getName()); return queryProvider.get().setRequestedFields(fields); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java index 89ec1d6..0667e14 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -16,9 +16,9 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Strings; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; @@ -213,7 +213,8 @@ PersonIdent committerIndent, String commitMsg, RevWalk rw) throws IOException, MergeIdenticalTreeException, MergeConflictException { - if (rw.isMergedInto(originalCommit, mergeTip)) { + if (!MergeStrategy.THEIRS.getName().equals(mergeStrategy) && + rw.isMergedInto(originalCommit, mergeTip)) { throw new ChangeAlreadyMergedException( "'" + originalCommit.getName() + "' has already been merged"); } @@ -598,14 +599,10 @@ Joiner.on("', '").join(topics)); } else { return String.format("Merge changes %s%s", - Joiner.on(',').join(Iterables.transform( - Iterables.limit(merged, 5), - new Function<CodeReviewCommit, String>() { - @Override - public String apply(CodeReviewCommit in) { - return in.change().getKey().abbreviate(); - } - })), + FluentIterable.from(merged) + .limit(5) + .transform(c -> c.change().getKey().abbreviate()) + .join(Joiner.on(',')), merged.size() > 5 ? ", ..." : ""); } }
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 1721ae2..1429079 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
@@ -91,7 +91,7 @@ private static final String PROJECT = "project"; private static final String KEY_DESCRIPTION = "description"; - private static final String ACCESS = "access"; + public static final String ACCESS = "access"; private static final String KEY_INHERIT_FROM = "inheritFrom"; private static final String KEY_GROUP_PERMISSIONS = "exclusiveGroupPermissions"; @@ -151,10 +151,13 @@ private static final String KEY_COPY_ALL_SCORES_IF_NO_CHANGE = "copyAllScoresIfNoChange"; 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 String KEY_BRANCH = "branch"; private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of( "MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp", "PatchSetLock"); + private static final String LEGACY_PERMISSION_PUSH_TAG = "pushTag"; + private static final String LEGACY_PERMISSION_PUSH_SIGNED_TAG = "pushSignedTag"; + private static final String PLUGIN = "plugin"; private static final SubmitType defaultSubmitAction = @@ -180,6 +183,7 @@ private Map<String, Config> pluginConfigs; private boolean checkReceivedObjects; private Set<String> sectionsWithUnknownPermissions; + private boolean hasLegacyPermissions; public static ProjectConfig read(MetaDataUpdate update) throws IOException, ConfigInvalidException { @@ -627,6 +631,7 @@ for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) { for (String n : varName.split("[, \t]{1,}")) { + n = convertLegacyPermission(n); if (isPermission(n)) { as.getPermission(n, true).setExclusiveGroup(true); } @@ -634,10 +639,11 @@ } for (String varName : rc.getNames(ACCESS, refName)) { - if (isPermission(varName)) { - Permission perm = as.getPermission(varName, true); + String convertedName = convertLegacyPermission(varName); + if (isPermission(convertedName)) { + Permission perm = as.getPermission(convertedName, true); loadPermissionRules(rc, ACCESS, refName, varName, groupsByName, - perm, Permission.hasRange(varName)); + perm, Permission.hasRange(convertedName)); } else { sectionsWithUnknownPermissions.add(as.getName()); } @@ -805,7 +811,7 @@ label.setCanOverride( rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, LabelType.DEF_CAN_OVERRIDE)); - label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_Branch)); + label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_BRANCH)); labelSections.put(name, label); } } @@ -1147,7 +1153,8 @@ } for (String varName : rc.getNames(ACCESS, refName)) { - if (isPermission(varName) && !have.contains(varName.toLowerCase())) { + if (isPermission(convertLegacyPermission(varName)) + && !have.contains(varName.toLowerCase())) { rc.unset(ACCESS, refName, varName); } } @@ -1282,4 +1289,21 @@ Collections.sort(r); return r; } + + public boolean hasLegacyPermissions() { + return hasLegacyPermissions; + } + + private String convertLegacyPermission(String permissionName) { + switch(permissionName) { + case LEGACY_PERMISSION_PUSH_TAG: + hasLegacyPermissions = true; + return Permission.CREATE_TAG; + case LEGACY_PERMISSION_PUSH_SIGNED_TAG: + hasLegacyPermissions = true; + return Permission.CREATE_SIGNED_TAG; + default: + return permissionName; + } + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java index 7fadae0..c2c12f0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -21,6 +21,9 @@ import static com.google.gerrit.server.change.HashtagsUtil.cleanupHashtag; import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN; import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters; +import static java.util.Comparator.comparingInt; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.RefDatabase.ALL; import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; @@ -30,24 +33,20 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; -import com.google.common.collect.Collections2; -import com.google.common.collect.FluentIterable; import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Ordering; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.google.common.collect.SortedSetMultimap; @@ -315,6 +314,8 @@ private final RequestId receiveId; private MagicBranchInput magicBranch; private boolean newChangeForAllNotInTarget; + private final ListMultimap<String, String> pushOptions = + LinkedListMultimap.create(); private List<CreateRequest> newChanges = Collections.emptyList(); private final Map<Change.Id, ReplaceRequest> replaceByChange = @@ -490,6 +491,7 @@ advHooks.add(new HackPushNegotiateHook()); rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks)); rp.setPostReceiveHook(lazyPostReceive.get()); + rp.setAllowPushOptions(true); } public void init() { @@ -677,14 +679,9 @@ } private void reportMessages() { - Iterable<CreateRequest> created = - Iterables.filter(newChanges, new Predicate<CreateRequest>() { - @Override - public boolean apply(CreateRequest input) { - return input.change != null; - } - }); - if (!Iterables.isEmpty(created)) { + List<CreateRequest> created = + newChanges.stream().filter(r -> r.change != null).collect(toList()); + if (!created.isEmpty()) { addMessage(""); addMessage("New Changes:"); for (CreateRequest c : created) { @@ -695,21 +692,10 @@ addMessage(""); } - List<ReplaceRequest> updated = FluentIterable - .from(replaceByChange.values()) - .filter(new Predicate<ReplaceRequest>() { - @Override - public boolean apply(ReplaceRequest input) { - return !input.skip && input.inputCommand.getResult() == OK; - } - }) - .toSortedList(Ordering.natural().onResultOf( - new Function<ReplaceRequest, Integer>() { - @Override - public Integer apply(ReplaceRequest in) { - return in.notes.getChangeId().get(); - } - })); + List<ReplaceRequest> updated = replaceByChange.values().stream() + .filter(r -> !r.skip && r.inputCommand.getResult() == OK) + .sorted(comparingInt(r -> r.notes.getChangeId().get())) + .collect(toList()); if (!updated.isEmpty()) { addMessage(""); addMessage("Updated Changes:"); @@ -827,7 +813,7 @@ // One or more new references failed to create. Assume the // system isn't working correctly anymore and abort. reject(magicBranch.cmd, "Unable to create changes: " - + Joiner.on(' ').join(lastCreateChangeErrors)); + + lastCreateChangeErrors.stream().collect(joining(" "))); logError(String.format( "Only %d of %d new change refs created in %s; aborting", okToInsert, replaceCount + newChanges.size(), project.getName())); @@ -915,6 +901,18 @@ } private void parseCommands(Collection<ReceiveCommand> commands) { + List<String> optionList = rp.getPushOptions(); + if (optionList != null) { + for (String option : optionList) { + int e = option.indexOf('='); + if (e > 0) { + pushOptions.put(option.substring(0, e), option.substring(e + 1)); + } else { + pushOptions.put(option, ""); + } + } + } + logDebug("Parsing {} commands", commands.size()); for (ReceiveCommand cmd : commands) { if (cmd.getResult() != NOT_ATTEMPTED) { @@ -1040,11 +1038,12 @@ .getPluginConfig(e.getPluginName()) .getString(e.getExportName()); if (configEntry.getType() == ProjectConfigEntryType.ARRAY) { - List<String> l = - Arrays.asList(projectControl.getProjectState() - .getConfig().getPluginConfig(e.getPluginName()) - .getStringList(e.getExportName())); - oldValue = Joiner.on("\n").join(l); + oldValue = + Arrays.stream( + projectControl.getProjectState() + .getConfig().getPluginConfig(e.getPluginName()) + .getStringList(e.getExportName())) + .collect(joining("\n")); } if ((value == null ? oldValue != null : !value.equals(oldValue)) && @@ -1238,6 +1237,9 @@ @Option(name = "--submit", usage = "immediately submit the change") boolean submit; + @Option(name = "--merged", usage = "create single change for a merged commit") + boolean merged; + @Option(name = "--notify", usage = "Notify handling that defines to whom email notifications " + "should be sent. Allowed values are NONE, OWNER, " @@ -1305,14 +1307,14 @@ return new MailRecipients(reviewer, cc); } - String parse(CmdLineParser clp, Repository repo, Set<String> refs) - throws CmdLineException { + String parse(CmdLineParser clp, Repository repo, Set<String> refs, + ListMultimap<String, String> pushOptions) throws CmdLineException { String ref = RefNames.fullName( MagicBranch.getDestBranchName(cmd.getRefName())); + ListMultimap<String, String> options = LinkedListMultimap.create(pushOptions); int optionStart = ref.indexOf('%'); if (0 < optionStart) { - ListMultimap<String, String> options = LinkedListMultimap.create(); for (String s : COMMAS.split(ref.substring(optionStart + 1))) { int e = s.indexOf('='); if (0 < e) { @@ -1321,10 +1323,13 @@ options.put(s, ""); } } - clp.parseOptionMap(options); ref = ref.substring(0, optionStart); } + if (!options.isEmpty()) { + clp.parseOptionMap(options); + } + // Split the destination branch by branch and topic. The topic // suffix is entirely optional, so it might not even exist. String head = readHEAD(repo); @@ -1347,6 +1352,19 @@ } } + /** + * Gets an unmodifiable view of the pushOptions. + * <p> + * The collection is empty if the client does not support push options, or if + * the client did not send any options. + * + * @return an unmodifiable view of pushOptions. + */ + @Nullable + public ListMultimap<String, String> getPushOptions() { + return ImmutableListMultimap.copyOf(pushOptions); + } + private void parseMagicBranch(ReceiveCommand cmd) { // Permit exactly one new change request per push. if (magicBranch != null) { @@ -1362,8 +1380,10 @@ String ref; CmdLineParser clp = optionParserFactory.create(magicBranch); magicBranch.clp = clp; + try { - ref = magicBranch.parse(clp, repo, rp.getAdvertisedRefs().keySet()); + ref = magicBranch.parse( + clp, repo, rp.getAdvertisedRefs().keySet(), pushOptions); } catch (CmdLineException e) { if (!clp.wasHelpRequestedByOption()) { logDebug("Invalid branch syntax"); @@ -1408,7 +1428,8 @@ errors.put(Error.CODE_REVIEW, ref); reject(cmd, "draft workflow is disabled"); return; - } else if (projectControl.controlForRef("refs/drafts/" + ref) + } else if (projectControl + .controlForRef(MagicBranch.NEW_DRAFT_CHANGE + ref) .isBlocked(Permission.PUSH)) { errors.put(Error.CODE_REVIEW, ref); reject(cmd, "cannot upload drafts"); @@ -1428,7 +1449,7 @@ } if (magicBranch.submit && !projectControl.controlForRef( - MagicBranch.NEW_CHANGE + ref).canSubmit()) { + MagicBranch.NEW_CHANGE + ref).canSubmit(true)) { reject(cmd, "submit not allowed"); return; } @@ -1444,56 +1465,71 @@ return; } - // If tip is a merge commit, or the root commit or - // if %base was specified, ignore newChangeForAllNotInTarget - if (tip.getParentCount() > 1 - || magicBranch.base != null - || tip.getParentCount() == 0) { - logDebug("Forcing newChangeForAllNotInTarget = false"); - newChangeForAllNotInTarget = false; - } - - if (magicBranch.base != null) { - logDebug("Handling %base: {}", magicBranch.base); - magicBranch.baseCommit = Lists.newArrayListWithCapacity( - magicBranch.base.size()); - for (ObjectId id : magicBranch.base) { - try { - magicBranch.baseCommit.add(walk.parseCommit(id)); - } catch (IncorrectObjectTypeException notCommit) { - reject(cmd, "base must be a commit"); + String destBranch = magicBranch.dest.get(); + try { + if (magicBranch.merged) { + if (magicBranch.draft) { + reject(cmd, "cannot be draft & merged"); return; - } catch (MissingObjectException e) { - reject(cmd, "base not found"); + } + if (magicBranch.base != null) { + reject(cmd, "cannot use merged with base"); return; - } catch (IOException e) { - logWarn(String.format( - "Project %s cannot read %s", - project.getName(), id.name()), e); - reject(cmd, "internal server error"); + } + RevCommit branchTip = readBranchTip(cmd, magicBranch.dest); + if (branchTip == null) { + return; // readBranchTip already rejected cmd. + } + if (!walk.isMergedInto(tip, branchTip)) { + reject(cmd, "not merged into branch"); return; } } - } else if (newChangeForAllNotInTarget) { - logDebug("Handling newChangeForAllNotInTarget"); - String destBranch = magicBranch.dest.get(); - try { - Ref r = repo.getRefDatabase().exactRef(destBranch); - if (r == null) { - reject(cmd, destBranch + " not found"); - return; - } - ObjectId baseHead = r.getObjectId(); - magicBranch.baseCommit = - Collections.singletonList(walk.parseCommit(baseHead)); + // If tip is a merge commit, or the root commit or + // if %base or %merged was specified, ignore newChangeForAllNotInTarget. + if (tip.getParentCount() > 1 + || magicBranch.base != null + || magicBranch.merged + || tip.getParentCount() == 0) { + logDebug("Forcing newChangeForAllNotInTarget = false"); + newChangeForAllNotInTarget = false; + } + + if (magicBranch.base != null) { + logDebug("Handling %base: {}", magicBranch.base); + magicBranch.baseCommit = Lists.newArrayListWithCapacity( + magicBranch.base.size()); + for (ObjectId id : magicBranch.base) { + try { + magicBranch.baseCommit.add(walk.parseCommit(id)); + } catch (IncorrectObjectTypeException notCommit) { + reject(cmd, "base must be a commit"); + return; + } catch (MissingObjectException e) { + reject(cmd, "base not found"); + return; + } catch (IOException e) { + logWarn(String.format( + "Project %s cannot read %s", + project.getName(), id.name()), e); + reject(cmd, "internal server error"); + return; + } + } + } else if (newChangeForAllNotInTarget) { + RevCommit branchTip = readBranchTip(cmd, magicBranch.dest); + if (branchTip == null) { + return; // readBranchTip already rejected cmd. + } + magicBranch.baseCommit = Collections.singletonList(branchTip); logDebug("Set baseCommit = {}", magicBranch.baseCommit.get(0).name()); - } catch (IOException ex) { - logWarn(String.format("Project %s cannot read %s", project.getName(), - destBranch), ex); - reject(cmd, "internal server error"); - return; } + } catch (IOException ex) { + logWarn(String.format("Error walking to %s in project %s", + destBranch, project.getName()), ex); + reject(cmd, "internal server error"); + return; } // Validate that the new commits are connected with the target @@ -1540,6 +1576,16 @@ } } + private RevCommit readBranchTip(ReceiveCommand cmd, Branch.NameKey branch) + throws IOException { + Ref r = allRefs.get(branch.get()); + if (r == null) { + reject(cmd, branch.get() + " not found"); + return null; + } + return rp.getRevWalk().parseCommit(r.getObjectId()); + } + private void parseReplaceCommand(ReceiveCommand cmd, Change.Id changeId) { logDebug("Parsing replace command"); if (cmd.getType() != ReceiveCommand.Type.CREATE) { @@ -1604,29 +1650,10 @@ GroupCollector groupCollector = GroupCollector.create(changeRefsById(), db, psUtil, notesFactory, project.getNameKey()); - rp.getRevWalk().reset(); - rp.getRevWalk().sort(RevSort.TOPO); - rp.getRevWalk().sort(RevSort.REVERSE, true); try { - RevCommit start = rp.getRevWalk().parseCommit(magicBranch.cmd.getNewId()); - rp.getRevWalk().markStart(start); - if (magicBranch.baseCommit != null) { - logDebug("Marking {} base commits uninteresting", - magicBranch.baseCommit.size()); - for (RevCommit c : magicBranch.baseCommit) { - rp.getRevWalk().markUninteresting(c); - } - Ref targetRef = allRefs.get(magicBranch.ctl.getRefName()); - if (targetRef != null) { - logDebug("Marking target ref {} ({}) uninteresting", - magicBranch.ctl.getRefName(), targetRef.getObjectId().name()); - rp.getRevWalk().markUninteresting( - rp.getRevWalk().parseCommit(targetRef.getObjectId())); - } - } else { - markHeadsAsUninteresting( - rp.getRevWalk(), - magicBranch.ctl != null ? magicBranch.ctl.getRefName() : null); + RevCommit start = setUpWalkForSelectingChanges(); + if (start == null) { + return; } List<ChangeLookup> pending = new ArrayList<>(); @@ -1636,7 +1663,11 @@ int total = 0; int alreadyTracked = 0; boolean rejectImplicitMerges = start.getParentCount() == 1 - && projectCache.get(project.getNameKey()).isRejectImplicitMerges(); + && projectCache.get(project.getNameKey()).isRejectImplicitMerges() + // Don't worry about implicit merges when creating changes for + // already-merged commits; they're already in history, so it's too + // late. + && !magicBranch.merged; Set<RevCommit> mergedParents; if (rejectImplicitMerges) { mergedParents = new HashSet<>(); @@ -1655,9 +1686,7 @@ Collection<Ref> existingRefs = existing.get(c); if (rejectImplicitMerges) { - for (RevCommit p : c.getParents()) { - mergedParents.add(p); - } + Collections.addAll(mergedParents, c.getParents()); mergedParents.remove(c); } @@ -1750,14 +1779,10 @@ List<ChangeData> changes = p.destChanges; if (changes.size() > 1) { logDebug("Multiple changes in project with Change-Id {}: {}", - p.changeKey, Lists.transform( - changes, - new Function<ChangeData, String>() { - @Override - public String apply(ChangeData in) { - return in.getId().toString(); - } - })); + p.changeKey, + changes.stream() + .map(cd -> cd.getId().toString()) + .collect(joining())); // WTF, multiple changes in this project have the same key? // Since the commit is new, the user should recreate it with // a different Change-Id. In practice, we should never see @@ -1854,8 +1879,46 @@ } } + private RevCommit setUpWalkForSelectingChanges() throws IOException { + RevWalk rw = rp.getRevWalk(); + RevCommit start = rw.parseCommit(magicBranch.cmd.getNewId()); + + rw.reset(); + rw.sort(RevSort.TOPO); + rw.sort(RevSort.REVERSE, true); + rp.getRevWalk().markStart(start); + if (magicBranch.baseCommit != null) { + markExplicitBasesUninteresting(); + } else if (magicBranch.merged) { + logDebug( + "Marking parents of merged commit {} uninteresting", start.name()); + for (RevCommit c : start.getParents()) { + rw.markUninteresting(c); + } + } else { + markHeadsAsUninteresting( + rw, magicBranch.ctl != null ? magicBranch.ctl.getRefName() : null); + } + return start; + } + + private void markExplicitBasesUninteresting() throws IOException { + logDebug("Marking {} base commits uninteresting", + magicBranch.baseCommit.size()); + for (RevCommit c : magicBranch.baseCommit) { + rp.getRevWalk().markUninteresting(c); + } + Ref targetRef = allRefs.get(magicBranch.ctl.getRefName()); + if (targetRef != null) { + logDebug("Marking target ref {} ({}) uninteresting", + magicBranch.ctl.getRefName(), targetRef.getObjectId().name()); + rp.getRevWalk().markUninteresting( + rp.getRevWalk().parseCommit(targetRef.getObjectId())); + } + } + private void rejectImplicitMerges(Set<RevCommit> mergedParents) - throws MissingObjectException, IncorrectObjectTypeException, IOException { + throws IOException { if (!mergedParents.isEmpty()) { Ref targetRef = allRefs.get(magicBranch.ctl.getRefName()); if (targetRef != null) { @@ -1938,10 +2001,15 @@ private void setChangeId(int id) { changeId = new Change.Id(id); ins = changeInserterFactory.create(changeId, commit, refName) - .setDraft(magicBranch.draft) .setTopic(magicBranch.topic) // Changes already validated in validateNewCommits. .setValidatePolicy(CommitValidators.Policy.NONE); + + if (magicBranch.draft) { + ins.setDraft(magicBranch.draft); + } else if (magicBranch.merged) { + ins.setStatus(Change.Status.MERGED); + } cmd = new ReceiveCommand(ObjectId.zeroId(), commit, ins.getPatchSetId().toRefName()); ins.setUpdateRefCommand(cmd); @@ -2035,7 +2103,7 @@ logDebug("Processing submit with tip change {} ({})", tipChange.getId(), magicBranch.cmd.getNewId()); try (MergeOp op = mergeOpProvider.get()) { - op.merge(db, tipChange, user, false, new SubmitInput()); + op.merge(db, tipChange, user, false, new SubmitInput(), false); } } @@ -2099,14 +2167,8 @@ Collection<ChangeNotes> allNotes = notesFactory.create( db, - Collections2.transform( - replaceByChange.values(), - new Function<ReplaceRequest, Change.Id>() { - @Override - public Change.Id apply(ReplaceRequest in) { - return in.ontoChange; - } - })); + replaceByChange.values().stream() + .map(r -> r.ontoChange).collect(toList())); for (ChangeNotes notes : allNotes) { replaceByChange.get(notes.getChangeId()).notes = notes; } @@ -2556,12 +2618,20 @@ rw.parseBody(c); CommitReceivedEvent receiveEvent = new CommitReceivedEvent(cmd, project, ctl.getRefName(), c, user); - CommitValidators commitValidators = - commitValidatorsFactory.create(ctl, sshInfo, repo); + + CommitValidators.Policy policy; + if (magicBranch != null + && cmd.getRefName().equals(magicBranch.cmd.getRefName()) + && magicBranch.merged) { + policy = CommitValidators.Policy.MERGED; + } else { + policy = CommitValidators.Policy.RECEIVE_COMMITS; + } try { - messages.addAll(commitValidators.validateForReceiveCommits( - receiveEvent, rejectCommits)); + messages.addAll( + commitValidatorsFactory.create(policy, ctl, sshInfo, repo) + .validate(receiveEvent)); } catch (CommitValidationException e) { logDebug("Commit validation failed on {}", c.name()); messages.addAll(e.getMessages());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java index fe568c8..a70fa7a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -255,13 +255,15 @@ ChangeData cd = changeDataFactory.create(ctx.getDb(), ctx.getControl()); MailRecipients oldRecipients = getRecipientsFromReviewers(cd.reviewers()); - approvalCopier.copy(ctx.getDb(), ctx.getControl(), newPatchSet); + Iterable<PatchSetApproval> newApprovals = + approvalsUtil.addApprovals(ctx.getDb(), update, + projectControl.getLabelTypes(), newPatchSet, ctx.getControl(), + approvals); + approvalCopier.copy(ctx.getDb(), ctx.getControl(), newPatchSet, + newApprovals); approvalsUtil.addReviewers(ctx.getDb(), update, projectControl.getLabelTypes(), change, newPatchSet, info, recipients.getReviewers(), oldRecipients.getAll()); - approvalsUtil.addApprovals(ctx.getDb(), update, - projectControl.getLabelTypes(), newPatchSet, ctx.getControl(), - approvals); recipients.add(oldRecipients); String approvalMessage = ApprovalsUtil.renderMessageWithApprovals(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java index 01d73ec..a1578ce 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -251,7 +251,7 @@ } OpenRepo or; try { - or = orm.openRepo(s.getProject(), false); + or = orm.openRepo(s.getProject()); } catch (NoSuchProjectException e) { // A project listed a non existent project to be allowed // to subscribe to it. Allow this for now, i.e. no exception is @@ -290,7 +290,7 @@ for (Branch.NameKey targetBranch : branches) { Project.NameKey targetProject = targetBranch.getParentKey(); try { - OpenRepo or = orm.openRepo(targetProject, false); + OpenRepo or = orm.openRepo(targetProject); ObjectId id = or.repo.resolve(targetBranch.get()); if (id == null) { logDebug("The branch " + targetBranch + " doesn't exist."); @@ -327,14 +327,14 @@ if (dst.containsKey(project)) { superProjects.add(project); // get a new BatchUpdate for the super project - OpenRepo or = orm.openRepo(project, false); + OpenRepo or = orm.openRepo(project); for (Branch.NameKey branch : dst.get(project)) { addOp(or.getUpdate(), branch); } } } BatchUpdate.execute(orm.batchUpdates(superProjects), Listener.NONE, - orm.getSubmissionId()); + orm.getSubmissionId(), false); } catch (RestApiException | UpdateException | IOException | NoSuchProjectException e) { throw new SubmoduleException("Cannot update gitlinks", e); @@ -348,18 +348,22 @@ throws IOException, SubmoduleException { OpenRepo or; try { - or = orm.openRepo(subscriber.getParentKey(), false); + or = orm.openRepo(subscriber.getParentKey()); } catch (NoSuchProjectException | IOException e) { throw new SubmoduleException("Cannot access superproject", e); } CodeReviewCommit currentCommit; - Ref r = or.repo.exactRef(subscriber.get()); - if (r == null) { - throw new SubmoduleException( - "The branch was probably deleted from the subscriber repository"); + if (branchTips.containsKey(subscriber)) { + currentCommit = branchTips.get(subscriber); + } else { + Ref r = or.repo.exactRef(subscriber.get()); + if (r == null) { + throw new SubmoduleException( + "The branch was probably deleted from the subscriber repository"); + } + currentCommit = or.rw.parseCommit(r.getObjectId()); } - currentCommit = or.rw.parseCommit(r.getObjectId()); StringBuilder msgbuf = new StringBuilder(""); PersonIdent author = null; @@ -404,7 +408,7 @@ throws IOException, SubmoduleException { OpenRepo or; try { - or = orm.openRepo(subscriber.getParentKey(), false); + or = orm.openRepo(subscriber.getParentKey()); } catch (NoSuchProjectException | IOException e) { throw new SubmoduleException("Cannot access superproject", e); } @@ -436,7 +440,9 @@ commit.setAuthor(currentCommit.getAuthorIdent()); commit.setCommitter(myIdent); ObjectId id = or.ins.insert(commit); - return or.rw.parseCommit(id); + CodeReviewCommit newCommit = or.rw.parseCommit(id); + newCommit.copyFrom(currentCommit); + return newCommit; } private RevCommit updateSubmodule(DirCache dc, DirCacheEditor ed, @@ -444,7 +450,7 @@ throws SubmoduleException, IOException { OpenRepo subOr; try { - subOr = orm.openRepo(s.getSubmodule().getParentKey(), false); + subOr = orm.openRepo(s.getSubmodule().getParentKey()); } catch (NoSuchProjectException | IOException e) { throw new SubmoduleException("Cannot access submodule", e); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java index 5260aab..ad650c3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -14,8 +14,8 @@ package com.google.gerrit.server.git; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.reviewdb.client.Project; import org.eclipse.jgit.lib.Ref; @@ -45,12 +45,7 @@ } TagMatcher matcher(TagCache cache, Repository db, Collection<Ref> include) { - include = FluentIterable.from(include).filter(new Predicate<Ref>() { - @Override - public boolean apply(Ref ref) { - return !TagSet.skip(ref); - } - }).toList(); + include = include.stream().filter(r -> !TagSet.skip(r)).collect(toList()); TagSet tags = this.tags; if (tags == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java index 4a5e94d..31da05c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -48,14 +48,14 @@ @Override public List<SubmitStrategyOp> buildOps( - Collection<CodeReviewCommit> toMerge) { + Collection<CodeReviewCommit> toMerge) throws IntegrationException { List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); boolean first = true; while (!sorted.isEmpty()) { CodeReviewCommit n = sorted.remove(0); if (first && args.mergeTip.getInitialTip() == null) { - ops.add(new CherryPickUnbornRootOp(n)); + ops.add(new FastForwardOp(args, n)); } else if (n.getParentCount() == 0) { ops.add(new CherryPickRootOp(n)); } else if (n.getParentCount() == 1) { @@ -68,21 +68,6 @@ return ops; } - private class CherryPickUnbornRootOp extends SubmitStrategyOp { - private CherryPickUnbornRootOp(CodeReviewCommit toMerge) { - super(CherryPick.this.args, toMerge); - } - - @Override - protected void updateRepoImpl(RepoContext ctx) throws IntegrationException { - // The branch is unborn. Take fast-forward resolution to create the - // branch. - CodeReviewCommit newCommit = amendGitlink(toMerge); - args.mergeTip.moveTipTo(newCommit, toMerge); - newCommit.setStatusCode(CommitMergeStatus.CLEAN_MERGE); - } - } - private class CherryPickRootOp extends SubmitStrategyOp { private CherryPickRootOp(CodeReviewCommit toMerge) { super(CherryPick.this.args, toMerge); @@ -191,8 +176,9 @@ // different first parent. So instead behave as though MERGE_IF_NECESSARY // was configured. MergeTip mergeTip = args.mergeTip; - if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) { - mergeTip.moveTipTo(amendGitlink(toMerge), toMerge); + if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge) && + !args.submoduleOp.hasSubscription(args.destBranch)) { + mergeTip.moveTipTo(toMerge, toMerge); } else { PersonIdent myIdent = new PersonIdent(args.serverIdent, ctx.getWhen()); CodeReviewCommit result = args.mergeUtil.mergeOneCommit(myIdent, @@ -200,9 +186,9 @@ mergeTip.getCurrentTip(), toMerge); result = amendGitlink(result); mergeTip.moveTipTo(result, toMerge); + args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, + mergeTip.getCurrentTip(), args.alreadyAccepted); } - args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, - mergeTip.getCurrentTip(), args.alreadyAccepted); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java index 0e69128..bb58540 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
@@ -25,6 +25,6 @@ @Override protected void updateRepoImpl(RepoContext ctx) throws IntegrationException { - args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge); + args.mergeTip.moveTipTo(toMerge, toMerge); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java index 0e2cbd7..5b2e213 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -32,11 +32,15 @@ List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); - CodeReviewCommit firstFastForward = args.mergeUtil.getFirstFastForward( + + if (args.mergeTip.getInitialTip() == null || !args.submoduleOp + .hasSubscription(args.destBranch)) { + CodeReviewCommit firstFastForward = args.mergeUtil.getFirstFastForward( args.mergeTip.getInitialTip(), args.rw, sorted); - if (firstFastForward != null && - !firstFastForward.equals(args.mergeTip.getInitialTip())) { - ops.add(new FastForwardOp(args, firstFastForward)); + if (firstFastForward != null && + !firstFastForward.equals(args.mergeTip.getInitialTip())) { + ops.add(new FastForwardOp(args, firstFastForward)); + } } // For every other commit do a pair-wise merge.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java index f183772d..3270fc3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -63,7 +63,7 @@ while (!sorted.isEmpty()) { CodeReviewCommit n = sorted.remove(0); if (first && args.mergeTip.getInitialTip() == null) { - ops.add(new RebaseUnbornRootOp(n)); + ops.add(new FastForwardOp(args, n)); } else if (n.getParentCount() == 0) { ops.add(new RebaseRootOp(n)); } else if (n.getParentCount() == 1) { @@ -76,22 +76,6 @@ return ops; } - private class RebaseUnbornRootOp extends SubmitStrategyOp { - private RebaseUnbornRootOp(CodeReviewCommit toMerge) { - super(RebaseIfNecessary.this.args, toMerge); - } - - @Override - public void updateRepoImpl(RepoContext ctx) throws IntegrationException { - // The branch is unborn. Take fast-forward resolution to create the - // branch. - toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE); - CodeReviewCommit newCommit = amendGitlink(toMerge); - args.mergeTip.moveTipTo(newCommit, toMerge); - acceptMergeTip(args.mergeTip); - } - } - private class RebaseRootOp extends SubmitStrategyOp { private RebaseRootOp(CodeReviewCommit toMerge) { super(RebaseIfNecessary.this.args, toMerge); @@ -120,9 +104,11 @@ // TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk. // When hoisting BatchUpdate into MergeOp, we will need to teach // BatchUpdate how to produce CodeReviewRevWalks. - if (args.mergeUtil.canFastForward(args.mergeSorter, - args.mergeTip.getCurrentTip(), args.rw, toMerge)) { + if (args.mergeUtil + .canFastForward(args.mergeSorter, args.mergeTip.getCurrentTip(), + args.rw, toMerge)) { args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge); + toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE); acceptMergeTip(args.mergeTip); return; } @@ -193,9 +179,9 @@ // first parent. So instead behave as though MERGE_IF_NECESSARY was // configured. MergeTip mergeTip = args.mergeTip; - if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) { - mergeTip.moveTipTo(amendGitlink(toMerge), toMerge); - acceptMergeTip(mergeTip); + if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge) && + !args.submoduleOp.hasSubscription(args.destBranch)) { + mergeTip.moveTipTo(toMerge, toMerge); } else { CodeReviewCommit newTip = args.mergeUtil.mergeOneCommit( args.serverIdent, args.serverIdent, args.repo, args.rw,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java index c784379..e60b947 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
@@ -14,7 +14,6 @@ package com.google.gerrit.server.git.strategy; -import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.reviewdb.client.Branch; @@ -70,12 +69,7 @@ return FluentIterable .from(repo.getRefDatabase().getRefs(Constants.R_HEADS).values()) .append(repo.getRefDatabase().getRefs(Constants.R_TAGS).values()) - .transform(new Function<Ref, ObjectId>() { - @Override - public ObjectId apply(Ref r) { - return r.getObjectId(); - } - }); + .transform(Ref::getObjectId); } public static Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw)
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 36de70e..28e170f 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
@@ -96,7 +96,8 @@ Set<RevCommit> alreadyAccepted, RequestId submissionId, NotifyHandling notifyHandling, - SubmoduleOp submoduleOp); + SubmoduleOp submoduleOp, + boolean dryrun); } final AccountCache accountCache; @@ -133,6 +134,7 @@ final ProjectState project; final MergeSorter mergeSorter; final MergeUtil mergeUtil; + final boolean dryrun; @AssistedInject Arguments( @@ -165,7 +167,8 @@ @Assisted RequestId submissionId, @Assisted SubmitType submitType, @Assisted NotifyHandling notifyHandling, - @Assisted SubmoduleOp submoduleOp) { + @Assisted SubmoduleOp submoduleOp, + @Assisted boolean dryrun) { this.accountCache = accountCache; this.approvalsUtil = approvalsUtil; this.batchUpdateFactory = batchUpdateFactory; @@ -196,6 +199,7 @@ this.submitType = submitType; this.notifyHandling = notifyHandling; this.submoduleOp = submoduleOp; + this.dryrun = dryrun; this.project = checkNotNull(projectCache.get(destBranch.getParentKey()), "project not found: %s", destBranch.getParentKey());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java index 6bb6fa6..146eda1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -54,12 +54,12 @@ Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag, Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch, IdentifiedUser caller, MergeTip mergeTip, - CommitStatus commits, RequestId submissionId, NotifyHandling notifyHandling, - SubmoduleOp submoduleOp) + CommitStatus commits, RequestId submissionId, + NotifyHandling notifyHandling, SubmoduleOp submoduleOp, boolean dryrun) throws IntegrationException { SubmitStrategy.Arguments args = argsFactory.create(submitType, destBranch, commits, rw, caller, mergeTip, inserter, repo, canMergeFlag, db, - alreadyAccepted, submissionId, notifyHandling, submoduleOp); + alreadyAccepted, submissionId, notifyHandling, submoduleOp, dryrun); switch (submitType) { case CHERRY_PICK: return new CherryPick(args);
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 72bfedf..b35de7b 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
@@ -182,13 +182,7 @@ } } Collections.sort(commits, ReviewDbUtil.intKeyOrdering().reverse() - .onResultOf( - new Function<CodeReviewCommit, PatchSet.Id>() { - @Override - public PatchSet.Id apply(CodeReviewCommit in) { - return in.getPatchsetId(); - } - })); + .onResultOf(c -> c.getPatchsetId())); CodeReviewCommit result = MergeUtil.findAnyMergedInto(rw, commits, tip); if (result == null) { return null; @@ -385,14 +379,11 @@ private static Function<PatchSetApproval, PatchSetApproval> convertPatchSet(final PatchSet.Id psId) { - return new Function<PatchSetApproval, PatchSetApproval>() { - @Override - public PatchSetApproval apply(PatchSetApproval in) { - if (in.getPatchSetId().equals(psId)) { - return in; - } - return new PatchSetApproval(psId, in); + return psa -> { + if (psa.getPatchSetId().equals(psId)) { + return psa; } + return new PatchSetApproval(psId, psa); }; } @@ -403,14 +394,12 @@ private static Iterable<PatchSetApproval> zero( Iterable<PatchSetApproval> approvals) { - return Iterables.transform(approvals, - new Function<PatchSetApproval, PatchSetApproval>() { - @Override - public PatchSetApproval apply(PatchSetApproval in) { - PatchSetApproval copy = new PatchSetApproval(in.getPatchSetId(), in); - copy.setValue((short) 0); - return copy; - } + return Iterables.transform( + approvals, + a -> { + PatchSetApproval copy = new PatchSetApproval(a.getPatchSetId(), a); + copy.setValue((short) 0); + return copy; }); } @@ -524,7 +513,7 @@ } catch (Exception e) { log.error("Cannot email merged notification for " + getId(), e); } - if (mergeResultRev != null) { + if (mergeResultRev != null && !args.dryrun) { args.changeMerged.fire( updatedChange, mergedPatchSet, @@ -564,26 +553,18 @@ */ protected CodeReviewCommit amendGitlink(CodeReviewCommit commit) throws IntegrationException { - CodeReviewCommit newCommit = commit; - // Modify the commit with gitlink update - if (args.submoduleOp.hasSubscription(args.destBranch)) { - try { - newCommit = - args.submoduleOp.composeGitlinksCommit(args.destBranch, commit); - newCommit.copyFrom(commit); - if (commit.equals(toMerge)) { - newCommit.setPatchsetId(ChangeUtil.nextPatchSetId( - args.repo, toMerge.change().currentPatchSetId())); - args.commits.put(newCommit); - } - } catch (SubmoduleException | IOException e) { - throw new IntegrationException( - "cannot update gitlink for the commit at branch: " - + args.destBranch); - } + if (!args.submoduleOp.hasSubscription(args.destBranch)) { + return commit; } - return newCommit; + // Modify the commit with gitlink update + try { + return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit); + } catch (SubmoduleException | IOException e) { + throw new IntegrationException( + "cannot update gitlink for the commit at branch: " + + args.destBranch); + } } protected final void logDebug(String msg, Object... args) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java index d4956ab..9cb09cc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -16,8 +16,10 @@ import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN; import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG; +import static com.google.gerrit.server.git.ReceiveCommits.NEW_PATCHSET; import com.google.common.base.CharMatcher; +import com.google.common.collect.ImmutableList; import com.google.gerrit.common.FooterConstants; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.PageLinks; @@ -31,15 +33,15 @@ import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.events.CommitReceivedEvent; +import com.google.gerrit.server.git.BanCommit; import com.google.gerrit.server.git.ProjectConfig; -import com.google.gerrit.server.git.ReceiveCommits; import com.google.gerrit.server.git.ValidationError; import com.google.gerrit.server.project.ProjectControl; import com.google.gerrit.server.project.RefControl; import com.google.gerrit.server.ssh.SshInfo; import com.google.gerrit.server.util.MagicBranch; import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import com.google.inject.Singleton; import com.jcraft.jsch.HostKey; @@ -51,6 +53,7 @@ import org.eclipse.jgit.revwalk.FooterKey; import org.eclipse.jgit.revwalk.FooterLine; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,107 +71,124 @@ .getLogger(CommitValidators.class); public enum Policy { - /** Use {@link #validateForGerritCommits}. */ + /** Use {@link Factory#forGerritCommits}. */ GERRIT, - /** Use {@link #validateForReceiveCommits}. */ + /** Use {@link Factory#forReceiveCommits}. */ RECEIVE_COMMITS, + /** Use {@link Factory#forMergedCommits}. */ + MERGED, + /** Do not validate commits. */ NONE } - public interface Factory { - CommitValidators create(RefControl refControl, SshInfo sshInfo, - Repository repo); - } + @Singleton + public static class Factory { + private final PersonIdent gerritIdent; + private final String canonicalWebUrl; + private final DynamicSet<CommitValidationListener> pluginValidators; + private final AllUsersName allUsers; + private final String installCommitMsgHookCommand; - private final PersonIdent gerritIdent; - private final RefControl refControl; - private final String canonicalWebUrl; - private final String installCommitMsgHookCommand; - private final SshInfo sshInfo; - private final Repository repo; - private final DynamicSet<CommitValidationListener> commitValidationListeners; - private final AllUsersName allUsers; - - @Inject - CommitValidators(@GerritPersonIdent PersonIdent gerritIdent, - @CanonicalWebUrl @Nullable String canonicalWebUrl, - @GerritServerConfig Config config, - DynamicSet<CommitValidationListener> commitValidationListeners, - AllUsersName allUsers, - @Assisted SshInfo sshInfo, - @Assisted Repository repo, - @Assisted RefControl refControl) { - this.gerritIdent = gerritIdent; - this.canonicalWebUrl = canonicalWebUrl; - this.installCommitMsgHookCommand = - config.getString("gerrit", null, "installCommitMsgHookCommand"); - this.commitValidationListeners = commitValidationListeners; - this.allUsers = allUsers; - this.sshInfo = sshInfo; - this.repo = repo; - this.refControl = refControl; - } - - public List<CommitValidationMessage> validateForReceiveCommits( - CommitReceivedEvent receiveEvent, NoteMap rejectCommits) - throws CommitValidationException { - - List<CommitValidationListener> validators = new LinkedList<>(); - - validators.add(new UploadMergesPermissionValidator(refControl)); - validators.add(new AmendedGerritMergeCommitValidationListener( - refControl, gerritIdent)); - validators.add(new AuthorUploaderValidator(refControl, canonicalWebUrl)); - validators.add(new CommitterUploaderValidator(refControl, canonicalWebUrl)); - validators.add(new SignedOffByValidator(refControl)); - if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName()) - || ReceiveCommits.NEW_PATCHSET.matcher( - receiveEvent.command.getRefName()).matches()) { - validators.add(new ChangeIdValidator(refControl, canonicalWebUrl, - installCommitMsgHookCommand, sshInfo)); + @Inject + Factory(@GerritPersonIdent PersonIdent gerritIdent, + @CanonicalWebUrl @Nullable String canonicalWebUrl, + @GerritServerConfig Config cfg, + DynamicSet<CommitValidationListener> pluginValidators, + AllUsersName allUsers) { + this.gerritIdent = gerritIdent; + this.canonicalWebUrl = canonicalWebUrl; + this.pluginValidators = pluginValidators; + this.allUsers = allUsers; + this.installCommitMsgHookCommand = cfg != null + ? cfg.getString("gerrit", null, "installCommitMsgHookCommand") : null; } - validators.add(new ConfigValidator(refControl, repo, allUsers)); - validators.add(new BannedCommitsValidator(rejectCommits)); - validators.add(new PluginCommitValidationListener(commitValidationListeners)); - List<CommitValidationMessage> messages = new LinkedList<>(); - - try { - for (CommitValidationListener commitValidator : validators) { - messages.addAll(commitValidator.onCommitReceived(receiveEvent)); + public CommitValidators create(Policy policy, RefControl refControl, + SshInfo sshInfo, Repository repo) throws IOException { + switch (policy) { + case RECEIVE_COMMITS: + return forReceiveCommits(refControl, sshInfo, repo); + case GERRIT: + return forGerritCommits(refControl, sshInfo, repo); + case MERGED: + return forMergedCommits(refControl); + case NONE: + return none(); + default: + throw new IllegalArgumentException("unspported policy: " + policy); } - } catch (CommitValidationException e) { - // Keep the old messages (and their order) in case of an exception - messages.addAll(e.getMessages()); - throw new CommitValidationException(e.getMessage(), messages); } - return messages; + + private CommitValidators forReceiveCommits(RefControl refControl, + SshInfo sshInfo, Repository repo) throws IOException { + try (RevWalk rw = new RevWalk(repo)) { + NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, rw); + return new CommitValidators(ImmutableList.of( + new UploadMergesPermissionValidator(refControl), + new AmendedGerritMergeCommitValidationListener( + refControl, gerritIdent), + new AuthorUploaderValidator(refControl, canonicalWebUrl), + new CommitterUploaderValidator(refControl, canonicalWebUrl), + new SignedOffByValidator(refControl), + new ChangeIdValidator(refControl, canonicalWebUrl, + installCommitMsgHookCommand, sshInfo), + new ConfigValidator(refControl, repo, allUsers), + new BannedCommitsValidator(rejectCommits), + new PluginCommitValidationListener(pluginValidators))); + } + } + + private CommitValidators forGerritCommits(RefControl refControl, + SshInfo sshInfo, Repository repo) { + return new CommitValidators(ImmutableList.of( + new UploadMergesPermissionValidator(refControl), + new AmendedGerritMergeCommitValidationListener( + refControl, gerritIdent), + new AuthorUploaderValidator(refControl, canonicalWebUrl), + new SignedOffByValidator(refControl), + new ChangeIdValidator(refControl, canonicalWebUrl, + installCommitMsgHookCommand, sshInfo), + new ConfigValidator(refControl, repo, allUsers), + new PluginCommitValidationListener(pluginValidators))); + } + + private CommitValidators forMergedCommits(RefControl refControl) { + // Generally only include validators that are based on permissions of the + // user creating a change for a merged commit; generally exclude + // validators that would require amending the change in order to correct. + // + // Examples: + // - Change-Id and Signed-off-by can't be added to an already-merged + // commit. + // - If the commit is banned, we can't ban it here. In fact, creating a + // review of a previously merged and recently-banned commit is a use + // case for post-commit code review: so reviewers have a place to + // discuss what to do about it. + // - Plugin validators may do things like require certain commit message + // formats, so we play it safe and exclude them. + return new CommitValidators(ImmutableList.of( + new UploadMergesPermissionValidator(refControl), + new AuthorUploaderValidator(refControl, canonicalWebUrl), + new CommitterUploaderValidator(refControl, canonicalWebUrl))); + } + + private CommitValidators none() { + return new CommitValidators(ImmutableList.<CommitValidationListener>of()); + } } - public List<CommitValidationMessage> validateForGerritCommits( + private final List<CommitValidationListener> validators; + + CommitValidators(List<CommitValidationListener> validators) { + this.validators = validators; + } + + public List<CommitValidationMessage> validate( CommitReceivedEvent receiveEvent) throws CommitValidationException { - - List<CommitValidationListener> validators = new LinkedList<>(); - - validators.add(new UploadMergesPermissionValidator(refControl)); - validators.add(new AmendedGerritMergeCommitValidationListener( - refControl, gerritIdent)); - validators.add(new AuthorUploaderValidator(refControl, canonicalWebUrl)); - validators.add(new SignedOffByValidator(refControl)); - if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName()) - || ReceiveCommits.NEW_PATCHSET.matcher( - receiveEvent.command.getRefName()).matches()) { - validators.add(new ChangeIdValidator(refControl, canonicalWebUrl, - installCommitMsgHookCommand, sshInfo)); - } - validators.add(new ConfigValidator(refControl, repo, allUsers)); - validators.add(new PluginCommitValidationListener(commitValidationListeners)); - List<CommitValidationMessage> messages = new LinkedList<>(); - try { for (CommitValidationListener commitValidator : validators) { messages.addAll(commitValidator.onCommitReceived(receiveEvent)); @@ -221,6 +241,9 @@ @Override public List<CommitValidationMessage> onCommitReceived( CommitReceivedEvent receiveEvent) throws CommitValidationException { + if (!shouldValidateChangeId(receiveEvent)) { + return Collections.emptyList(); + } RevCommit commit = receiveEvent.commit; List<CommitValidationMessage> messages = new LinkedList<>(); List<String> idList = commit.getFooterLines(FooterConstants.CHANGE_ID); @@ -255,6 +278,11 @@ return Collections.emptyList(); } + private static boolean shouldValidateChangeId(CommitReceivedEvent event) { + return MagicBranch.isMagicBranch(event.command.getRefName()) + || NEW_PATCHSET.matcher(event.command.getRefName()).matches(); + } + private CommitValidationMessage getMissingChangeIdErrorMsg( final String errMsg, final RevCommit c) { StringBuilder sb = new StringBuilder();
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 bd74fff..c10b279 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
@@ -17,6 +17,7 @@ import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.gerrit.audit.AuditService; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.DefaultInput; @@ -27,7 +28,6 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountCache;
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 d5d90d3..6a25862 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
@@ -17,7 +17,6 @@ import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH; import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE; -import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; @@ -110,19 +109,11 @@ accounts, changes); Set<String> expected = FluentIterable.from(ALL_SCHEMA_DEFS) - .transform(new Function<SchemaDefinitions<?>, String>() { - @Override - public String apply(SchemaDefinitions<?> in) { - return in.getName(); - } - }).toSet(); + .transform(SchemaDefinitions::getName) + .toSet(); Set<String> actual = FluentIterable.from(result) - .transform(new Function<IndexDefinition<?, ?, ?>, String>() { - @Override - public String apply(IndexDefinition<?, ?, ?> in) { - return in.getName(); - } - }).toSet(); + .transform(IndexDefinition::getName) + .toSet(); if (!expected.equals(actual)) { throw new ProvisionException( "need index definitions for all schemas: "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java index 824739e..afe3f70 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
@@ -14,14 +14,12 @@ package com.google.gerrit.server.index.account; -import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.server.account.AccountState; -import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey; import com.google.gerrit.server.index.FieldDef; import com.google.gerrit.server.index.FieldType; import com.google.gerrit.server.index.SchemaUtil; @@ -47,13 +45,7 @@ @Override public Iterable<String> get(AccountState input, FillArgs args) { return Iterables.transform( - input.getExternalIds(), - new Function<AccountExternalId, String>() { - @Override - public String apply(AccountExternalId in) { - return in.getKey().get(); - } - }); + input.getExternalIds(), id -> id.getKey().get()); } }; @@ -68,12 +60,7 @@ fullName, Iterables.transform( input.getExternalIds(), - new Function<AccountExternalId, String>() { - @Override - public String apply(AccountExternalId in) { - return in.getEmailAddress(); - } - })); + AccountExternalId::getEmailAddress)); // Additional values not currently added by getPersonParts. // TODO(dborowitz): Move to getPersonParts and remove this hack. @@ -108,23 +95,11 @@ @Override public Iterable<String> get(AccountState input, FillArgs args) { return FluentIterable.from(input.getExternalIds()) - .transform( - new Function<AccountExternalId, String>() { - @Override - public String apply(AccountExternalId in) { - return in.getEmailAddress(); - } - }) + .transform(AccountExternalId::getEmailAddress) .append( Collections.singleton(input.getAccount().getPreferredEmail())) .filter(Predicates.notNull()) - .transform( - new Function<String, String>() { - @Override - public String apply(String in) { - return in.toLowerCase(); - } - }) + .transform(String::toLowerCase) .toSet(); } }; @@ -153,12 +128,8 @@ @Override public Iterable<String> get(AccountState input, FillArgs args) { return FluentIterable.from(input.getProjectWatches().keySet()) - .transform(new Function<ProjectWatchKey, String>() { - @Override - public String apply(ProjectWatchKey in) { - return in.project().get(); - } - }).toSet(); + .transform(k -> k.project().get()) + .toSet(); } };
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java index fe448c6..891a8da 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -16,9 +16,9 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toSet; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; @@ -34,7 +34,6 @@ import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSetApproval; -import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.ReviewerSet; import com.google.gerrit.server.StarredChangesUtil; import com.google.gerrit.server.index.FieldDef; @@ -247,13 +246,9 @@ @Override public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException { - return ImmutableSet.copyOf(Iterables.transform(input.hashtags(), - new Function<String, String>() { - @Override - public String apply(String input) { - return input.toLowerCase(); - } - })); + return input.hashtags().stream() + .map(String::toLowerCase) + .collect(toSet()); } }; @@ -264,13 +259,9 @@ @Override public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException { - return ImmutableSet.copyOf(Iterables.transform(input.hashtags(), - new Function<String, byte[]>() { - @Override - public byte[] apply(String hashtag) { - return hashtag.getBytes(UTF_8); - } - })); + return input.hashtags().stream() + .map(t -> t.getBytes(UTF_8)) + .collect(toSet()); } }; @@ -657,13 +648,7 @@ @Override public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException { - return Iterables.transform(input.starredBy(), - new Function<Account.Id, Integer>() { - @Override - public Integer apply(Account.Id accountId) { - return accountId.get(); - } - }); + return Iterables.transform(input.starredBy(), Account.Id::get); } }; @@ -676,14 +661,12 @@ @Override public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException { - return Iterables.transform(input.stars().entries(), - new Function<Map.Entry<Account.Id, String>, String>() { - @Override - public String apply(Map.Entry<Account.Id, String> e) { - return StarredChangesUtil.StarField.create( - e.getKey(), e.getValue()).toString(); - } - }); + return Iterables.transform( + input.stars().entries(), + (Map.Entry<Account.Id, String> e) -> { + return StarredChangesUtil.StarField.create( + e.getKey(), e.getValue()).toString(); + }); } }; @@ -694,8 +677,7 @@ @Override public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException { - return Iterables.transform(input.stars().keySet(), - ReviewDbUtil.INT_KEY_FUNCTION); + return Iterables.transform(input.stars().keySet(), Account.Id::get); } }; @@ -740,13 +722,9 @@ @Override public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException { - return ImmutableSet.copyOf(Iterables.transform(input.editsByUser(), - new Function<Account.Id, Integer>() { - @Override - public Integer apply(Account.Id account) { - return account.get(); - } - })); + return input.editsByUser().stream() + .map(Account.Id::get) + .collect(toSet()); } }; @@ -758,13 +736,9 @@ @Override public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException { - return ImmutableSet.copyOf(Iterables.transform(input.draftsByUser(), - new Function<Account.Id, Integer>() { - @Override - public Integer apply(Account.Id account) { - return account.get(); - } - })); + return input.draftsByUser().stream() + .map(Account.Id::get) + .collect(toSet()); } };
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java index 996caa7..3e0678d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -19,7 +19,6 @@ import static com.google.gerrit.server.index.change.ChangeField.PROJECT; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.gerrit.reviewdb.client.Change; @@ -94,12 +93,9 @@ public Iterator<ChangeData> iterator() { return Iterables.transform( rs, - new Function<ChangeData, ChangeData>() { - @Override - public ChangeData apply(ChangeData cd) { - fromSource.put(cd, currSource); - return cd; - } + cd -> { + fromSource.put(cd, currSource); + return cd; }).iterator(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java index 1e8bdf4..f1e609e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
@@ -50,6 +50,14 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("Abandoned.vm")); + appendText(textTemplate("Abandoned")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("AbandonedHtml")); + } + } + + @Override + protected boolean supportsHtml() { + return true; } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java index f825d1c..40492bd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
@@ -80,7 +80,10 @@ @Override protected void format() throws EmailException { - appendText(velocifyFile("AddKey.vm")); + appendText(textTemplate("AddKey")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("AddKeyHtml")); + } } public String getEmail() { @@ -110,4 +113,19 @@ } return null; } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData.put("email", getEmail()); + soyContextEmailData.put("gpgKeys", getGpgKeys()); + soyContextEmailData.put("keyType", getKeyType()); + soyContextEmailData.put("sshKey", getSshKey()); + soyContextEmailData.put("userNameEmail", getUserNameEmail()); + } + + @Override + protected boolean supportsHtml() { + return true; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java index badc706..df0f018 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -55,6 +55,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -120,7 +121,10 @@ @Override protected void format() throws EmailException { formatChange(); - appendText(velocifyFile("ChangeFooter.vm")); + appendText(textTemplate("ChangeFooter")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("ChangeFooterHtml")); + } try { TreeSet<String> names = new TreeSet<>(); for (Account.Id who : changeData.reviewers().all()) { @@ -199,7 +203,7 @@ } private void setChangeSubjectHeader() throws EmailException { - setHeader("Subject", velocifyFile("ChangeSubject.vm")); + setHeader("Subject", textTemplate("ChangeSubject")); } /** Get a link to the change; null if the server doesn't know its own address. */ @@ -255,7 +259,7 @@ detail.append("---\n"); PatchList patchList = getPatchList(); for (PatchListEntry p : patchList.getPatches()) { - if (Patch.COMMIT_MSG.equals(p.getNewName())) { + if (Patch.isMagic(p.getNewName())) { continue; } detail.append(p.getChangeType().getCode()) @@ -435,11 +439,48 @@ velocityContext.put("patchSetInfo", patchSetInfo); } + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + + soyContext.put("changeId", change.getKey().get()); + soyContext.put("coverLetter", getCoverLetter()); + soyContext.put("fromName", getNameFor(fromId)); + + soyContextEmailData.put("unifiedDiff", getUnifiedDiff()); + soyContextEmailData.put("changeDetail", getChangeDetail()); + soyContextEmailData.put("changeUrl", getChangeUrl()); + soyContextEmailData.put("includeDiff", getIncludeDiff()); + + Map<String, String> changeData = new HashMap<>(); + changeData.put("subject", change.getSubject()); + changeData.put("originalSubject", change.getOriginalSubject()); + changeData.put("ownerEmail", getNameEmailFor(change.getOwner())); + soyContext.put("change", changeData); + + String subject = change.getSubject(); + changeData.put("subject", subject); + // shortSubject is the subject limited to 63 characters, with an ellipsis if + // it exceeds that. + if (subject.length() < 64) { + changeData.put("shortSubject", subject); + } else { + changeData.put("shortSubject", subject.substring(0, 60) + "..."); + } + + Map<String, Object> patchSetData = new HashMap<>(); + patchSetData.put("patchSetId", patchSet.getPatchSetId()); + patchSetData.put("refName", patchSet.getRefName()); + soyContext.put("patchSet", patchSetData); + + // TODO(wyatta): patchSetInfo + } + public boolean getIncludeDiff() { return args.settings.includeDiff; } - private static int HEAP_EST_SIZE = 32 * 1024; + private static final int HEAP_EST_SIZE = 32 * 1024; /** Show patch set as unified difference. */ public String getUnifiedDiff() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java index b56b737..c4b6869 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -75,7 +75,7 @@ Set<String> paths = new HashSet<>(); for (PatchLineComment c : plc) { Patch.Key p = c.getKey().getParentKey(); - if (!Patch.COMMIT_MSG.equals(p.getFileName())) { + if (!Patch.isMagic(p.getFileName())) { paths.add(p.getFileName()); } } @@ -97,12 +97,12 @@ @Override public void formatChange() throws EmailException { - appendText(velocifyFile("Comment.vm")); + appendText(textTemplate("Comment")); } @Override public void formatFooter() throws EmailException { - appendText(velocifyFile("CommentFooter.vm")); + appendText(textTemplate("CommentFooter")); } public boolean hasInlineComments() { @@ -137,6 +137,8 @@ } if (Patch.COMMIT_MSG.equals(pk.get())) { cmts.append("Commit Message:\n\n"); + } else if (Patch.MERGE_LIST.equals(pk.get())) { + cmts.append("Merge List:\n\n"); } else { cmts.append("File ").append(pk.get()).append(":\n\n"); } @@ -144,8 +146,7 @@ if (patchList != null) { try { - currentFileData = - new PatchFile(repo, patchList, pk.get()); + currentFileData = new PatchFile(repo, patchList, pk.get()); } catch (IOException e) { log.warn(String.format( "Cannot load %s from %s in %s", @@ -283,4 +284,11 @@ return null; } } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData.put("inlineComments", getInlineComments()); + soyContextEmailData.put("hasInlineComments", hasInlineComments()); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java index 75f9f82..ec4a728 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
@@ -65,7 +65,10 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("DeleteReviewer.vm")); + appendText(textTemplate("DeleteReviewer")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("DeleteReviewerHtml")); + } } public List<String> getReviewerNames() { @@ -78,4 +81,15 @@ } return names; } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData.put("reviewerNames", getReviewerNames()); + } + + @Override + protected boolean supportsHtml() { + return true; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java index d861109..6257deb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java
@@ -49,6 +49,14 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("DeleteVote.vm")); + appendText(textTemplate("DeleteVote")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("DeleteVoteHtml")); + } + } + + @Override + protected boolean supportsHtml() { + return true; } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java index 68e5e50..71c294b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -1,4 +1,4 @@ -// Copyright (C) 2010 The Android Open Source Project +// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AnonymousCowardName; import com.google.gerrit.server.config.CanonicalWebUrl; +import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.index.account.AccountIndexCollection; import com.google.gerrit.server.notedb.ChangeNotes; @@ -43,6 +44,7 @@ import com.google.gerrit.server.validators.OutgoingEmailValidationListener; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.template.soy.tofu.SoyTofu; import org.apache.velocity.runtime.RuntimeInstance; import org.eclipse.jgit.lib.PersonIdent; @@ -69,11 +71,13 @@ final Provider<String> urlProvider; final AllProjectsName allProjectsName; final List<String> sshAddresses; + final SitePaths site; final ChangeQueryBuilder queryBuilder; final Provider<ReviewDb> db; final ChangeData.Factory changeDataFactory; final RuntimeInstance velocityRuntime; + final SoyTofu soyTofu; final EmailSettings settings; final DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners; final StarredChangesUtil starredChangesUtil; @@ -100,8 +104,10 @@ Provider<ReviewDb> db, ChangeData.Factory changeDataFactory, RuntimeInstance velocityRuntime, + @MailTemplates SoyTofu soyTofu, EmailSettings settings, @SshAdvertisedAddresses List<String> sshAddresses, + SitePaths site, DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners, StarredChangesUtil starredChangesUtil, AccountIndexCollection accountIndexes, @@ -128,8 +134,10 @@ this.db = db; this.changeDataFactory = changeDataFactory; this.velocityRuntime = velocityRuntime; + this.soyTofu = soyTofu; this.settings = settings; this.sshAddresses = sshAddresses; + this.site = site; this.outgoingEmailValidationListeners = outgoingEmailValidationListeners; this.starredChangesUtil = starredChangesUtil; this.accountIndexes = accountIndexes;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java index 3c14f2f..2f2fd8c5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java
@@ -22,11 +22,13 @@ @Singleton public class EmailSettings { + public final boolean html; public final boolean includeDiff; public final int maximumDiffSize; @Inject EmailSettings(@GerritServerConfig Config cfg) { + html = cfg.getBoolean("sendemail", "html", true); includeDiff = cfg.getBoolean("sendemail", "includeDiff", false); maximumDiffSize = cfg.getInt("sendemail", "maximumDiffSize", 256 << 10); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java index 51f7ad1..0bc65bd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -32,6 +32,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.regex.Pattern; /** Creates a {@link FromAddressGenerator} from the {@link GerritServerConfig} */ @Singleton @@ -53,13 +54,15 @@ generator = new PatternGen(srvAddr, accountCache, anonymousCowardName, name, srvAddr.email); - } else if ("USER".equalsIgnoreCase(from)) { - generator = new UserGen(accountCache, srvAddr); - + String[] domains = cfg.getStringList("sendemail", null, "allowedDomain"); + Pattern domainPattern = MailUtil.glob(domains); + ParameterizedString namePattern = + new ParameterizedString("${user} (Code Review)"); + generator = new UserGen(accountCache, domainPattern, anonymousCowardName, + namePattern, srvAddr); } else if ("SERVER".equalsIgnoreCase(from)) { generator = new ServerGen(srvAddr); - } else { final Address a = Address.parse(from); final ParameterizedString name = a.name != null ? new ParameterizedString(a.name) : null; @@ -84,11 +87,31 @@ static final class UserGen implements FromAddressGenerator { private final AccountCache accountCache; - private final Address srvAddr; + private final Pattern domainPattern; + private final String anonymousCowardName; + private final ParameterizedString nameRewriteTmpl; + private final Address serverAddress; - UserGen(AccountCache accountCache, Address srvAddr) { + /** + * From address generator for USER mode + * + * @param accountCache get user account from id + * @param domainPattern allowed user domain pattern that Gerrit can send as + * the user + * @param anonymousCowardName name used when user's full name is missing + * @param nameRewriteTmpl name template used for rewriting the sender's name + * when Gerrit can not send as the user + * @param serverAddress serverAddress.name is used when fromId is null and + * serverAddress.email is used when Gerrit can not send as the user + */ + UserGen(AccountCache accountCache, Pattern domainPattern, + String anonymousCowardName, ParameterizedString nameRewriteTmpl, + Address serverAddress) { this.accountCache = accountCache; - this.srvAddr = srvAddr; + this.domainPattern = domainPattern; + this.anonymousCowardName = anonymousCowardName; + this.nameRewriteTmpl = nameRewriteTmpl; + this.serverAddress = serverAddress; } @Override @@ -98,14 +121,44 @@ @Override public Address from(final Account.Id fromId) { + String senderName; if (fromId != null) { Account a = accountCache.get(fromId).getAccount(); + String fullName = a.getFullName(); String userEmail = a.getPreferredEmail(); - return new Address( - a.getFullName(), - userEmail != null ? userEmail : srvAddr.getEmail()); + if (canRelay(userEmail)) { + return new Address(fullName, userEmail); + } + + if (fullName == null || "".equals(fullName.trim())) { + fullName = anonymousCowardName; + } + senderName = nameRewriteTmpl.replace("user", fullName).toString(); + } else { + senderName = serverAddress.name; } - return srvAddr; + + String senderEmail; + ParameterizedString senderEmailPattern = + new ParameterizedString(serverAddress.email); + if (senderEmailPattern.getParameterNames().isEmpty()) { + senderEmail = senderEmailPattern.getRawPattern(); + } else { + senderEmail = senderEmailPattern.replace("userHash", hashOf(senderName)) + .toString(); + } + return new Address(senderName, senderEmail); + } + + /** check if Gerrit is allowed to send from {@code userEmail}. */ + private boolean canRelay(String userEmail) { + if (userEmail != null) { + int index = userEmail.indexOf('@'); + if (index > 0 && index < userEmail.length() - 1) { + return domainPattern.matcher(userEmail.substring(index + 1)).matches(); + } + } + return false; } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java new file mode 100644 index 0000000..9aef496 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java
@@ -0,0 +1,104 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.mail; + +import com.google.common.io.CharStreams; +import com.google.common.io.Resources; +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 com.google.template.soy.SoyFileSet; +import com.google.template.soy.tofu.SoyTofu; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +/** Configures Soy Tofu object for rendering email templates. */ +@Singleton +public class MailSoyTofuProvider implements Provider<SoyTofu> { + + // Note: will fail to construct the tofu object if this array is empty. + private static final String[] TEMPLATES = { + "Abandoned.soy", + "AbandonedHtml.soy", + "AddKey.soy", + "AddKeyHtml.soy", + "ChangeFooter.soy", + "ChangeFooterHtml.soy", + "ChangeSubject.soy", + "Comment.soy", + "CommentFooter.soy", + "DeleteReviewer.soy", + "DeleteReviewerHtml.soy", + "DeleteVote.soy", + "DeleteVoteHtml.soy", + "Footer.soy", + "FooterHtml.soy", + "HeaderHtml.soy", + "Merged.soy", + "NewChange.soy", + "NewChangeHtml.soy", + "RegisterNewEmail.soy", + "ReplacePatchSet.soy", + "ReplacePatchSetHtml.soy", + "Restored.soy", + "RestoredHtml.soy", + "Reverted.soy", + "RevertedHtml.soy", + "ViewChangeButton.soy", + }; + + private final SitePaths site; + + @Inject + MailSoyTofuProvider(SitePaths site) { + this.site = site; + } + + @Override + public SoyTofu get() throws ProvisionException { + SoyFileSet.Builder builder = SoyFileSet.builder(); + for (String name : TEMPLATES) { + addTemplate(builder, name); + } + return builder.build().compileToTofu(); + } + + private void addTemplate(SoyFileSet.Builder builder, String name) + throws ProvisionException { + // Load as a file in the mail templates directory if present. + Path tmpl = site.mail_dir.resolve(name); + if (Files.isRegularFile(tmpl)) { + String content; + try (Reader r = Files.newBufferedReader(tmpl, StandardCharsets.UTF_8)) { + content = CharStreams.toString(r); + } catch (IOException err) { + throw new ProvisionException("Failed to read template file " + + tmpl.toAbsolutePath().toString(), err); + } + builder.add(content, tmpl.toAbsolutePath().toString()); + return; + } + + // Otherwise load the template as a resource. + String resourcePath = "com/google/gerrit/server/mail/" + name; + builder.add(Resources.getResource(resourcePath)); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java new file mode 100644 index 0000000..72fdaae --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java
@@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.mail; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.Retention; + +@Retention(RUNTIME) +@BindingAnnotation +public @interface MailTemplates {}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java index 048a4a4..8a132cd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
@@ -32,6 +32,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; public class MailUtil { public static MailRecipients getRecipientsFromFooters( @@ -124,4 +125,19 @@ return Collections.unmodifiableSet(all); } } + + /** allow wildcard matching for {@code domains} */ + public static Pattern glob(String[] domains) { + // if domains is not set, match anything + if (domains == null || domains.length == 0) { + return Pattern.compile(".*"); + } + + StringBuilder sb = new StringBuilder(""); + for (String domain : domains) { + String quoted = "\\Q" + domain.replace("\\E", "\\E\\\\E\\Q") + "\\E|"; + sb.append(quoted.replace("*", "\\E.*\\Q")); + } + return Pattern.compile(sb.substring(0, sb.length() - 1)); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java index f6c3d0f..c2a3cdd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -58,7 +58,7 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("Merged.vm")); + appendText(textTemplate("Merged")); } public String getApprovals() { @@ -123,4 +123,10 @@ txt.append('\n'); return txt.toString(); } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData.put("approvals", getApprovals()); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java index 62385d9..05a709d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
@@ -67,7 +67,10 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("NewChange.vm")); + appendText(textTemplate("NewChange")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("NewChangeHtml")); + } } public List<String> getReviewerNames() { @@ -80,4 +83,15 @@ } return names; } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData.put("reviewerNames", getReviewerNames()); + } + + @Override + protected boolean supportsHtml() { + return true; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java index de338ec..85dd800 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
@@ -25,6 +25,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Map; + /** * Common class for notifications that are related to a project and branch */ @@ -103,4 +106,20 @@ velocityContext.put("projectName", branch.getParentKey().get()); velocityContext.put("branch", branch); } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + + String projectName = branch.getParentKey().get(); + soyContext.put("projectName", projectName); + // shortProjectName is the project name with the path abbreviated. + soyContext.put("shortProjectName", projectName.replaceAll("/.*/", "...")); + + soyContextEmailData.put("sshHost", getSshHost()); + + Map<String, String> branchData = new HashMap<>(); + branchData.put("shortName", branch.getShortName()); + soyContext.put("branch", branchData); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java index 6200688..c3b69e3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -1,4 +1,4 @@ -// Copyright (C) 2009 The Android Open Source Project +// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.io.BaseEncoding; import com.google.gerrit.common.errors.EmailException; import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.client.GeneralPreferencesInfo; @@ -28,6 +29,7 @@ import com.google.gerrit.server.validators.OutgoingEmailValidationListener; import com.google.gerrit.server.validators.ValidationException; import com.google.gwtorm.server.OrmException; +import com.google.template.soy.data.SanitizedContent; import org.apache.commons.lang.StringUtils; import org.apache.velocity.Template; @@ -43,13 +45,17 @@ 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.Collection; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; /** Sends an email to one or more interested parties. */ public abstract class OutgoingEmail { @@ -63,9 +69,11 @@ private final Map<String, EmailHeader> headers; private final Set<Address> smtpRcptTo = new HashSet<>(); private Address smtpFromAddress; - private StringBuilder body; + private StringBuilder textBody; + private StringBuilder htmlBody; protected VelocityContext velocityContext; - + protected Map<String, Object> soyContext; + protected Map<String, Object> soyContextEmailData; protected final EmailArguments args; protected Account.Id fromId; protected NotifyHandling notify = NotifyHandling.ALL; @@ -101,8 +109,14 @@ } init(); + if (useHtml()) { + appendHtml(soyHtmlTemplate("HeaderHtml")); + } format(); - appendText(velocifyFile("Footer.vm")); + appendText(textTemplate("Footer")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("FooterHtml")); + } if (shouldSendMessage()) { if (fromId != null) { final Account fromUser = args.accountCache.get(fromId).getAccount(); @@ -136,12 +150,29 @@ } } + String textPart = textBody.toString(); OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args(); va.messageClass = messageClass; va.smtpFromAddress = smtpFromAddress; va.smtpRcptTo = smtpRcptTo; va.headers = headers; - va.body = body.toString(); + + if (useHtml()) { + String htmlPart = htmlBody.toString(); + String boundary = generateMultipartBoundary(textPart, htmlPart); + + va.body = buildMultipartBody(boundary, textPart, htmlPart); + va.textBody = textPart; + va.htmlBody = htmlPart; + va.headers.put("Content-Type", new EmailHeader.String( + "multipart/alternative; " + + "boundary=\"" + boundary + "\"; " + + "charset=UTF-8")); + } else { + va.body = textPart; + va.textBody = textPart; + } + for (OutgoingEmailValidationListener validator : args.outgoingEmailValidationListeners) { try { validator.validateOutgoingEmail(va); @@ -154,6 +185,49 @@ } } + protected String buildMultipartBody(String boundary, String textPart, + String htmlPart) { + return + // Output the text part: + "--" + boundary + "\r\n" + + "Content-Type: text/plain; charset=UTF-8\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + textPart + "\r\n" + + // Output the HTML part: + + "--" + boundary + "\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + htmlPart + "\r\n" + + // Output the closing boundary. + + "--" + boundary + "--\r\n"; + } + + protected String generateMultipartBoundary(String textBody, String htmlBody) + throws EmailException { + byte[] bytes = new byte[8]; + ThreadLocalRandom rng = ThreadLocalRandom.current(); + + // The probability of the boundary being valid is approximately + // (2^64 - len(message)) / 2^64. + // + // The message is much shorter than 2^64 bytes, so if two tries don't + // suffice, something is seriously wrong. + for (int i = 0; i < 2; i++) { + rng.nextBytes(bytes); + String boundary = BaseEncoding.base64().encode(bytes); + String encBoundary = "--" + boundary; + if (textBody.contains(encBoundary) || htmlBody.contains(encBoundary)) { + continue; + } + return boundary; + } + throw new EmailException("Gave up generating unique MIME boundary"); + } + /** Format the message body by calling {@link #appendText(String)}. */ protected abstract void format() throws EmailException; @@ -164,6 +238,7 @@ */ protected void init() throws EmailException { setupVelocityContext(); + setupSoyContext(); smtpFromAddress = args.fromAddressGenerator.from(fromId); setHeader("Date", new Date()); @@ -185,7 +260,8 @@ } setHeader("X-Gerrit-MessageType", messageClass); - body = new StringBuilder(); + textBody = new StringBuilder(); + htmlBody = new StringBuilder(); if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) { appendText(getFromLine()); @@ -260,7 +336,14 @@ /** Append text to the outgoing email body. */ protected void appendText(final String text) { if (text != null) { - body.append(text); + textBody.append(text); + } + } + + /** Append html to the outgoing email body. */ + protected void appendHtml(String html) { + if (html != null) { + htmlBody.append(html); } } @@ -334,7 +417,7 @@ } protected boolean shouldSendMessage() { - if (body.length() == 0) { + if (textBody.length() == 0) { // If we have no message body, don't send. return false; } @@ -428,6 +511,18 @@ velocityContext.put("StringUtils", StringUtils.class); } + protected void setupSoyContext() { + soyContext = new HashMap<>(); + + soyContext.put("messageClass", messageClass); + + soyContextEmailData = new HashMap<>(); + soyContextEmailData.put("settingsUrl", getSettingsUrl()); + soyContextEmailData.put("gerritHost", getGerritHost()); + soyContextEmailData.put("gerritUrl", getGerritUrl()); + soyContext.put("email", soyContextEmailData); + } + protected String velocify(String template) throws EmailException { try { RuntimeInstance runtime = args.velocityRuntime; @@ -463,6 +558,37 @@ } } + private String soyTemplate(String name, SanitizedContent.ContentKind kind) { + return args.soyTofu + .newRenderer("com.google.gerrit.server.mail.template." + name) + .setContentKind(kind) + .setData(soyContext) + .render(); + } + + protected String soyTextTemplate(String name) { + return soyTemplate(name, SanitizedContent.ContentKind.TEXT); + } + + protected String soyHtmlTemplate(String name) { + 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); } @@ -504,4 +630,13 @@ private static String safeToString(Object obj) { return obj != null ? obj.toString() : ""; } + + protected final boolean useHtml() { + return args.settings.html && supportsHtml(); + } + + /** Override this method to enable HTML in a subclass. */ + protected boolean supportsHtml() { + return false; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java index cfdeb8f..405d9f9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -51,7 +51,7 @@ @Override protected void format() throws EmailException { - appendText(velocifyFile("RegisterNewEmail.vm")); + appendText(textTemplate("RegisterNewEmail")); } public String getUserNameEmail() { @@ -69,4 +69,12 @@ public boolean isAllowed() { return args.emailSender.canEmail(addr); } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData + .put("emailRegistrationToken", getEmailRegistrationToken()); + soyContextEmailData.put("userNameEmail", getUserNameEmail()); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java index df9f20e..3f1e356 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -72,17 +72,34 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("ReplacePatchSet.vm")); + appendText(textTemplate("ReplacePatchSet")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("ReplacePatchSetHtml")); + } } public List<String> getReviewerNames() { - if (reviewers.isEmpty()) { - return null; - } List<String> names = new ArrayList<>(); for (Account.Id id : reviewers) { + if (id.equals(fromId)) { + continue; + } names.add(getNameFor(id)); } + if (names.isEmpty()) { + return null; + } return names; } + + @Override + protected void setupSoyContext() { + super.setupSoyContext(); + soyContextEmailData.put("reviewerNames", getReviewerNames()); + } + + @Override + protected boolean supportsHtml() { + return true; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java index d946eb2..45a45c7 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
@@ -49,6 +49,14 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("Restored.vm")); + appendText(textTemplate("Restored")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("RestoredHtml")); + } + } + + @Override + protected boolean supportsHtml() { + return true; } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java index 2c9c37e..0734a3c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
@@ -47,6 +47,14 @@ @Override protected void formatChange() throws EmailException { - appendText(velocifyFile("Reverted.vm")); + appendText(textTemplate("Reverted")); + if (useHtml()) { + appendHtml(soyHtmlTemplate("RevertedHtml")); + } + } + + @Override + protected boolean supportsHtml() { + return true; } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java index 679a9de..3c669f0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -27,6 +27,7 @@ import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java index e15af9d..fd931f5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -50,9 +50,9 @@ import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSetApproval; -import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.PatchLineCommentsUtil; import com.google.gerrit.server.ReviewerSet; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl; import com.google.gwtorm.client.Column; import com.google.gwtorm.server.OrmException; @@ -83,25 +83,6 @@ REVIEW_DB, NOTE_DB; } - public static ChangeBundle fromReviewDb(ReviewDb db, Change.Id id) - throws OrmException { - db.changes().beginTransaction(id); - try { - List<PatchSetApproval> approvals = - db.patchSetApprovals().byChange(id).toList(); - return new ChangeBundle( - db.changes().get(id), - db.changeMessages().byChange(id), - db.patchSets().byChange(id), - approvals, - db.patchComments().byChange(id), - ReviewerSet.fromApprovals(approvals), - Source.REVIEW_DB); - } finally { - db.rollback(); - } - } - public static ChangeBundle fromNotes(PatchLineCommentsUtil plcUtil, ChangeNotes notes) throws OrmException { return new ChangeBundle( @@ -241,10 +222,7 @@ checkColumns(Change.Id.class, 1); checkColumns(Change.class, - 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, - // TODO(dborowitz): It's potentially possible to compare noteDbState in - // the Change with the state implied by a ChangeNotes. - 101); + 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 101); checkColumns(ChangeMessage.Key.class, 1, 2); checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6); checkColumns(PatchSet.Id.class, 1, 2); @@ -367,54 +345,36 @@ private Map<PatchSetApproval.Key, PatchSetApproval> filterPatchSetApprovals() { - return limitToValidPatchSets(patchSetApprovals, - new Function<PatchSetApproval.Key, PatchSet.Id>() { - @Override - public PatchSet.Id apply(PatchSetApproval.Key in) { - return in.getParentKey(); - } - }); + return limitToValidPatchSets( + patchSetApprovals, PatchSetApproval.Key::getParentKey); } private Map<PatchLineComment.Key, PatchLineComment> filterPatchLineComments() { - return limitToValidPatchSets(patchLineComments, - new Function<PatchLineComment.Key, PatchSet.Id>() { - @Override - public PatchSet.Id apply(PatchLineComment.Key in) { - return in.getParentKey().getParentKey(); - } - }); + return limitToValidPatchSets( + patchLineComments, + k -> k.getParentKey().getParentKey()); } private <K, V> Map<K, V> limitToValidPatchSets(Map<K, V> in, - final Function<K, PatchSet.Id> func) { + Function<K, PatchSet.Id> func) { return Maps.filterKeys( in, Predicates.compose(validPatchSetPredicate(), func)); } private Predicate<PatchSet.Id> validPatchSetPredicate() { - final Predicate<PatchSet.Id> upToCurrent = upToCurrentPredicate(); - return new Predicate<PatchSet.Id>() { - @Override - public boolean apply(PatchSet.Id in) { - return upToCurrent.apply(in) && patchSets.containsKey(in); - } - }; + Predicate<PatchSet.Id> upToCurrent = upToCurrentPredicate(); + return p -> upToCurrent.apply(p) && patchSets.containsKey(p); } private Collection<ChangeMessage> filterChangeMessages() { final Predicate<PatchSet.Id> validPatchSet = validPatchSetPredicate(); - return Collections2.filter(changeMessages, - new Predicate<ChangeMessage>() { - @Override - public boolean apply(ChangeMessage in) { - PatchSet.Id psId = in.getPatchSetId(); - if (psId == null) { - return true; - } - return validPatchSet.apply(psId); + return Collections2.filter(changeMessages, m -> { + PatchSet.Id psId = m.getPatchSetId(); + if (psId == null) { + return true; } + return validPatchSet.apply(psId); }); } @@ -423,13 +383,8 @@ if (current == null) { return Predicates.alwaysFalse(); } - final int max = current.get(); - return new Predicate<PatchSet.Id>() { - @Override - public boolean apply(PatchSet.Id in) { - return in.get() <= max; - } - }; + int max = current.get(); + return p -> p.get() <= max; } private Map<PatchSet.Id, PatchSet> filterPatchSets() { @@ -669,17 +624,31 @@ List<String> tempDiffs = new ArrayList<>(); String temp = "temp"; + // ReviewDb allows timestamps before patch set was created, but NoteDb + // truncates this to the patch set creation timestamp. + Timestamp ta = a.getWrittenOn(); + Timestamp tb = b.getWrittenOn(); + PatchSet psa = bundleA.patchSets.get(a.getPatchSetId()); + PatchSet psb = bundleB.patchSets.get(b.getPatchSetId()); boolean excludePatchSet = false; + boolean excludeWrittenOn = false; if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) { excludePatchSet = a.getPatchSetId() == null; + excludeWrittenOn = psa != null && psb != null + && ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn()); } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) { excludePatchSet = b.getPatchSetId() == null; + excludeWrittenOn = psa != null && psb != null + && tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()); } List<String> exclude = Lists.newArrayList("key"); if (excludePatchSet) { exclude.add("patchset"); } + if (excludeWrittenOn) { + exclude.add("writtenOn"); + } diffColumnsExcluding( tempDiffs, ChangeMessage.class, temp, bundleA, a, bundleB, b, exclude); @@ -718,7 +687,28 @@ PatchSetApproval a = as.get(k); PatchSetApproval b = bs.get(k); String desc = describe(k); - diffColumns(diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b); + + // ReviewDb allows timestamps before patch set was created, but NoteDb + // truncates this to the patch set creation timestamp. + Timestamp ta = a.getGranted(); + Timestamp tb = b.getGranted(); + PatchSet psa = checkNotNull(bundleA.patchSets.get(a.getPatchSetId())); + PatchSet psb = checkNotNull(bundleB.patchSets.get(b.getPatchSetId())); + boolean excludeGranted = false; + List<String> exclude = new ArrayList<>(1); + if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) { + excludeGranted = + ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn()); + } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) { + excludeGranted = + tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()); + } + if (excludeGranted) { + exclude.add("granted"); + } + + diffColumnsExcluding( + diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b, exclude); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundleReader.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundleReader.java new file mode 100644 index 0000000..9e7a1fe1 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundleReader.java
@@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gwtorm.server.OrmException; + +public interface ChangeBundleReader { + ChangeBundle fromReviewDb(ReviewDb db, Change.Id id) throws OrmException; +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java index 7b59a47..ae6c4cf 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -219,7 +219,10 @@ // Even though reading from changes might not be enabled, we need to // parse any existing revision notes so we can merge them. return RevisionNoteMap.parse( - noteUtil, getId(), rw.getObjectReader(), noteMap, true); + noteUtil, getId(), + rw.getObjectReader(), + noteMap, + PatchLineComment.Status.DRAFT); } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java index 4c1a734..960962f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -35,10 +35,14 @@ import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.config.AnonymousCowardName; +import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerId; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.inject.Inject; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.revwalk.FooterKey; import org.eclipse.jgit.util.GitDateFormatter; @@ -61,20 +65,22 @@ import java.util.Set; public class ChangeNoteUtil { - static final FooterKey FOOTER_BRANCH = new FooterKey("Branch"); - static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id"); - static final FooterKey FOOTER_COMMIT = new FooterKey("Commit"); - static final FooterKey FOOTER_GROUPS = new FooterKey("Groups"); - static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags"); - static final FooterKey FOOTER_LABEL = new FooterKey("Label"); - static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set"); - static final FooterKey FOOTER_STATUS = new FooterKey("Status"); - static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject"); - static final FooterKey FOOTER_SUBMISSION_ID = new FooterKey("Submission-id"); - static final FooterKey FOOTER_SUBMITTED_WITH = + public static final FooterKey FOOTER_ASSIGNEE = new FooterKey("Assignee"); + public static final FooterKey FOOTER_BRANCH = new FooterKey("Branch"); + public static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id"); + public static final FooterKey FOOTER_COMMIT = new FooterKey("Commit"); + public static final FooterKey FOOTER_GROUPS = new FooterKey("Groups"); + public static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags"); + public static final FooterKey FOOTER_LABEL = new FooterKey("Label"); + public static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set"); + public static final FooterKey FOOTER_STATUS = new FooterKey("Status"); + public static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject"); + public static final FooterKey FOOTER_SUBMISSION_ID = + new FooterKey("Submission-id"); + public static final FooterKey FOOTER_SUBMITTED_WITH = new FooterKey("Submitted-with"); - static final FooterKey FOOTER_TOPIC = new FooterKey("Topic"); - static final FooterKey FOOTER_TAG = new FooterKey("Tag"); + public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic"); + public static final FooterKey FOOTER_TAG = new FooterKey("Tag"); private static final String AUTHOR = "Author"; private static final String BASE_PATCH_SET = "Base-for-patch-set"; @@ -99,16 +105,20 @@ private final PersonIdent serverIdent; private final String anonymousCowardName; private final String serverId; + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + private final boolean writeJson; @Inject public ChangeNoteUtil(AccountCache accountCache, @GerritPersonIdent PersonIdent serverIdent, @AnonymousCowardName String anonymousCowardName, - @GerritServerId String serverId) { + @GerritServerId String serverId, + @GerritServerConfig Config config) { this.accountCache = accountCache; this.serverIdent = serverIdent; this.anonymousCowardName = anonymousCowardName; this.serverId = serverId; + this.writeJson = config.getBoolean("notedb", "writeJson", false); } @VisibleForTesting @@ -120,6 +130,18 @@ when, serverIdent.getTimeZone()); } + public boolean getWriteJson() { + return writeJson; + } + + public Gson getGson() { + return gson; + } + + public String getServerId() { + return serverId; + } + public Account.Id parseIdent(PersonIdent ident, Change.Id changeId) throws ConfigInvalidException { String email = ident.getEmailAddress();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java index 6327682..9c50c27 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -19,10 +19,9 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef; import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES; +import static java.util.Comparator.comparing; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -52,6 +51,7 @@ import com.google.gerrit.server.ReviewerStatusUpdate; import com.google.gerrit.server.git.RefCache; import com.google.gerrit.server.git.RepoRefCache; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.query.change.ChangeData; @@ -69,7 +69,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -77,28 +76,17 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; /** View of a single {@link Change} based on the log of its notes branch. */ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> { private static final Logger log = LoggerFactory.getLogger(ChangeNotes.class); static final Ordering<PatchSetApproval> PSA_BY_TIME = - Ordering.natural().onResultOf( - new Function<PatchSetApproval, Timestamp>() { - @Override - public Timestamp apply(PatchSetApproval input) { - return input.getGranted(); - } - }); + Ordering.from(comparing(PatchSetApproval::getGranted)); public static final Ordering<ChangeMessage> MESSAGE_BY_TIME = - Ordering.natural().onResultOf( - new Function<ChangeMessage, Timestamp>() { - @Override - public Timestamp apply(ChangeMessage input) { - return input.getWrittenOn(); - } - }); + Ordering.from(comparing(ChangeMessage::getWrittenOn)); public static ConfigInvalidException parseException(Change.Id changeId, String fmt, Object... args) { @@ -248,7 +236,7 @@ if (args.migration.enabled()) { for (Change.Id cid : changeIds) { ChangeNotes cn = create(db, project, cid); - if (cn.getChange() != null && predicate.apply(cn)) { + if (cn.getChange() != null && predicate.test(cn)) { notes.add(cn); } } @@ -258,7 +246,7 @@ for (Change c : ReviewDbUtil.unwrapDb(db).changes().get(changeIds)) { if (c != null && project.equals(c.getDest().getParentKey())) { ChangeNotes cn = createFromChangeOnlyWhenNoteDbDisabled(c); - if (predicate.apply(cn)) { + if (predicate.test(cn)) { notes.add(cn); } } @@ -274,7 +262,7 @@ try (Repository repo = args.repoManager.openRepository(project)) { List<ChangeNotes> changes = scanNoteDb(repo, db, project); for (ChangeNotes cn : changes) { - if (predicate.apply(cn)) { + if (predicate.test(cn)) { m.put(project, cn); } } @@ -283,7 +271,7 @@ } else { for (Change change : ReviewDbUtil.unwrapDb(db).changes().all()) { ChangeNotes notes = createFromChangeOnlyWhenNoteDbDisabled(change); - if (predicate.apply(notes)) { + if (predicate.test(notes)) { m.put(change.getProject(), notes); } } @@ -399,6 +387,13 @@ } /** + * @return an Account.Id of the user assigned to this change. + */ + public Account.Id getAssignee() { + return state.assignee(); + } + + /** * * @return a ImmutableSet of all hashtags for this change sorted in alphabetical order. */ @@ -450,16 +445,13 @@ // failed. Multimap<RevId, PatchLineComment> filtered = Multimaps.filterEntries( draftCommentNotes.getComments(), - new Predicate<Map.Entry<RevId, PatchLineComment>>() { - @Override - public boolean apply(Map.Entry<RevId, PatchLineComment> in) { - for (PatchLineComment c : published.get(in.getKey())) { - if (c.getKey().equals(in.getValue().getKey())) { + (Map.Entry<RevId, PatchLineComment> e) -> { + for (PatchLineComment c : published.get(e.getKey())) { + if (c.getKey().equals(e.getValue().getKey())) { return false; } } return true; - } }); return ImmutableListMultimap.copyOf( filtered);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java index 8272aaf..8bf8ca1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.notedb; +import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT; @@ -28,13 +29,13 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC; import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.joining; +import com.google.auto.value.AutoValue; import com.google.common.base.Enums; -import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Splitter; -import com.google.common.base.Supplier; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableSet; @@ -46,6 +47,7 @@ import com.google.common.collect.Table; import com.google.common.collect.Tables; import com.google.common.primitives.Ints; +import com.google.gerrit.common.Nullable; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.metrics.Timer1; @@ -58,7 +60,6 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RevId; -import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.ReviewerSet; import com.google.gerrit.server.ReviewerStatusUpdate; import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk; @@ -85,7 +86,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.NavigableSet; import java.util.Objects; import java.util.Set; @@ -97,6 +97,20 @@ private static final RevId PARTIAL_PATCH_SET = new RevId("INVALID PARTIAL PATCH SET"); + @AutoValue + abstract static class ApprovalKey { + abstract PatchSet.Id psId(); + abstract Account.Id accountId(); + abstract String label(); + @Nullable abstract String tag(); + + private static ApprovalKey create(PatchSet.Id psId, Account.Id accountId, + String label, @Nullable String tag) { + return new AutoValue_ChangeNotesParser_ApprovalKey( + psId, accountId, label, tag); + } + } + // Private final members initialized in the constructor. private final ChangeNoteUtil noteUtil; private final NoteDbMetrics metrics; @@ -114,8 +128,7 @@ private final TreeMap<PatchSet.Id, PatchSet> patchSets; private final Set<PatchSet.Id> deletedPatchSets; private final Map<PatchSet.Id, PatchSetState> patchSetStates; - private final Map<PatchSet.Id, - Table<Account.Id, Entry<String, String>, Optional<PatchSetApproval>>> approvals; + private final Map<ApprovalKey, PatchSetApproval> approvals; private final List<ChangeMessage> allChangeMessages; private final Multimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet; @@ -123,6 +136,7 @@ private String branch; private Change.Status status; private String topic; + private Account.Id assignee; private Set<String> hashtags; private Timestamp createdOn; private Timestamp lastUpdatedOn; @@ -142,7 +156,7 @@ this.walk = walk; this.noteUtil = noteUtil; this.metrics = metrics; - approvals = new HashMap<>(); + approvals = new LinkedHashMap<>(); reviewers = HashBasedTable.create(); allPastReviewers = new ArrayList<>(); reviewerUpdates = new ArrayList<>(); @@ -150,7 +164,7 @@ allChangeMessages = new ArrayList<>(); changeMessagesByPatchSet = LinkedListMultimap.create(); comments = ArrayListMultimap.create(); - patchSets = Maps.newTreeMap(ReviewDbUtil.intKeyOrdering()); + patchSets = Maps.newTreeMap(comparing(PatchSet.Id::get)); deletedPatchSets = new HashSet<>(); patchSetStates = new HashMap<>(); } @@ -197,6 +211,7 @@ submissionId, status, + assignee, hashtags, patchSets, buildApprovals(), @@ -210,14 +225,15 @@ } private Multimap<PatchSet.Id, PatchSetApproval> buildApprovals() { - Multimap<PatchSet.Id, PatchSetApproval> result = - ArrayListMultimap.create(approvals.keySet().size(), 3); - for (Table<?, ?, Optional<PatchSetApproval>> curr : approvals.values()) { - for (Optional<PatchSetApproval> psa : curr.values()) { - if (psa.isPresent()) { - result.put(psa.get().getPatchSetId(), psa.get()); - } + Multimap<PatchSet.Id, PatchSetApproval> result = ArrayListMultimap.create(); + for (PatchSetApproval a : approvals.values()) { + if (patchSetStates.get(a.getPatchSetId()) == PatchSetState.DELETED) { + continue; // Patch set was explicitly deleted. + } else if (allPastReviewers.contains(a.getAccountId()) + && !reviewers.containsRow(a.getAccountId())) { + continue; // Reviewer was explicitly removed. } + result.put(a.getPatchSetId(), a); } for (Collection<PatchSetApproval> v : result.asMap().values()) { Collections.sort((List<PatchSetApproval>) v, ChangeNotes.PSA_BY_TIME); @@ -303,6 +319,8 @@ parseHashtags(commit); + parseAssignee(commit); + if (submissionId == null) { submissionId = parseSubmissionId(commit); } @@ -459,6 +477,18 @@ } } + private void parseAssignee(ChangeNotesCommit commit) + throws ConfigInvalidException { + if (assignee != null) { + return; + } + String assigneeValue = parseOneFooter(commit, FOOTER_ASSIGNEE); + if (assigneeValue != null) { + PersonIdent ident = RawParseUtils.parsePersonIdent(assigneeValue); + assignee = noteUtil.parseIdent(ident, id); + } + } + private void parseTag(ChangeNotesCommit commit) throws ConfigInvalidException { tag = null; @@ -582,7 +612,8 @@ ObjectReader reader = walk.getObjectReader(); ChangeNotesCommit tipCommit = walk.parseCommit(tip); revisionNoteMap = RevisionNoteMap.parse( - noteUtil, id, reader, NoteMap.read(reader, tipCommit), false); + noteUtil, id, reader, NoteMap.read(reader, tipCommit), + PatchLineComment.Status.PUBLISHED); Map<RevId, RevisionNote> rns = revisionNoteMap.revisionNotes; for (Map.Entry<RevId, RevisionNote> e : rns.entrySet()) { @@ -606,7 +637,7 @@ "patch set %s requires an identified user as uploader", psId.get()); } if (line.startsWith("-")) { - parseRemoveApproval(psId, accountId, line); + parseRemoveApproval(psId, accountId, ts, line); } else { parseAddApproval(psId, accountId, ts, line); } @@ -637,39 +668,37 @@ throw pe; } - Entry<String, String> label = Maps.immutableEntry(l.label(), tag); - Table<Account.Id, Entry<String, String>, Optional<PatchSetApproval>> curr = - getApprovalsTableIfNoVotePresent(psId, accountId, label); - if (curr != null) { - PatchSetApproval psa = new PatchSetApproval( - new PatchSetApproval.Key( - psId, - accountId, - new LabelId(l.label())), - l.value(), - ts); - psa.setTag(tag); - curr.put(accountId, label, Optional.of(psa)); + PatchSetApproval psa = new PatchSetApproval( + new PatchSetApproval.Key( + psId, + accountId, + new LabelId(l.label())), + l.value(), + ts); + psa.setTag(tag); + ApprovalKey k = ApprovalKey.create(psId, accountId, l.label(), tag); + if (!approvals.containsKey(k)) { + approvals.put(k, psa); } } private void parseRemoveApproval(PatchSet.Id psId, Account.Id committerId, - String line) throws ConfigInvalidException { + Timestamp ts, String line) throws ConfigInvalidException { Account.Id accountId; - Entry<String, String> label; + String label; int s = line.indexOf(' '); if (s > 0) { - label = Maps.immutableEntry(line.substring(1, s), tag); + label = line.substring(1, s); PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1)); checkFooter(ident != null, FOOTER_LABEL, line); accountId = noteUtil.parseIdent(ident, id); } else { - label = Maps.immutableEntry(line.substring(1), tag); + label = line.substring(1); accountId = committerId; } try { - LabelType.checkNameInternal(label.getKey()); + LabelType.checkNameInternal(label); } catch (IllegalArgumentException e) { ConfigInvalidException pe = parseException("invalid %s: %s", FOOTER_LABEL, line); @@ -677,38 +706,25 @@ throw pe; } - Table<Account.Id, Entry<String, String>, Optional<PatchSetApproval>> curr = - getApprovalsTableIfNoVotePresent(psId, accountId, label); - if (curr != null) { - curr.put(accountId, label, Optional.<PatchSetApproval> absent()); + // Store an actual 0-vote approval in the map for a removed approval, for + // several reasons: + // - This is closer to the ReviewDb representation, which leads to less + // confusion and special-casing of NoteDb. + // - More importantly, ApprovalCopier needs an actual approval in order to + // block copying an earlier approval over a later delete. + PatchSetApproval remove = new PatchSetApproval( + new PatchSetApproval.Key( + psId, + accountId, + new LabelId(label)), + (short) 0, + ts); + ApprovalKey k = ApprovalKey.create(psId, accountId, label, tag); + if (!approvals.containsKey(k)) { + approvals.put(k, remove); } } - private Table<Account.Id, Entry<String, String>, Optional<PatchSetApproval>> - getApprovalsTableIfNoVotePresent(PatchSet.Id psId, Account.Id accountId, - Entry<String, String> label) { - - Table<Account.Id, Entry<String, String>, Optional<PatchSetApproval>> curr = - approvals.get(psId); - if (curr != null) { - if (curr.contains(accountId, label)) { - return null; - } - } else { - curr = Tables.newCustomTable( - Maps.<Account.Id, Map<Entry<String, String>, Optional<PatchSetApproval>>> - newHashMapWithExpectedSize(2), - new Supplier<Map<Entry<String, String>, Optional<PatchSetApproval>>>() { - @Override - public Map<Entry<String, String>, Optional<PatchSetApproval>> get() { - return new LinkedHashMap<>(); - } - }); - approvals.put(psId, curr); - } - return curr; - } - private void parseSubmitRecords(List<String> lines) throws ConfigInvalidException { SubmitRecord rec = null; @@ -787,9 +803,6 @@ Table.Cell<Account.Id, ReviewerStateInternal, Timestamp> e = rit.next(); if (e.getColumnKey() == ReviewerStateInternal.REMOVED) { rit.remove(); - for (Table<Account.Id, ?, ?> curr : approvals.values()) { - curr.rowKeySet().remove(e.getRowKey()); - } } } } @@ -832,13 +845,14 @@ // Post-process other collections to remove items corresponding to deleted // patch sets. This is safer than trying to prevent insertion, as it will // also filter out items racily added after the patch set was deleted. + // + // Approvals are filtered in buildApprovals(). NavigableSet<PatchSet.Id> all = patchSets.navigableKeySet(); if (!all.isEmpty()) { currentPatchSetId = all.last(); } else { currentPatchSetId = null; } - approvals.keySet().retainAll(all); changeMessagesByPatchSet.keys().retainAll(all); for (Iterator<ChangeMessage> it = allChangeMessages.iterator(); @@ -868,13 +882,8 @@ missing.add(FOOTER_SUBJECT); } if (!missing.isEmpty()) { - throw parseException("Missing footers: " + Joiner.on(", ") - .join(Lists.transform(missing, new Function<FooterKey, String>() { - @Override - public String apply(FooterKey input) { - return input.getName(); - } - }))); + throw parseException("Missing footers: " + + missing.stream().map(FooterKey::getName).collect(joining(", "))); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java index 988184f..4abb8ac 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -15,6 +15,7 @@ package com.google.gerrit.server.notedb; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Comparator.comparing; import com.google.auto.value.AutoValue; import com.google.common.base.Strings; @@ -33,7 +34,6 @@ import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.RevId; -import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.ReviewerSet; import com.google.gerrit.server.ReviewerStatusUpdate; @@ -59,6 +59,7 @@ return new AutoValue_ChangeNotesState( change.getId(), null, + null, ImmutableSet.<String>of(), ImmutableSortedMap.<PatchSet.Id, PatchSet>of(), ImmutableListMultimap.<PatchSet.Id, PatchSetApproval>of(), @@ -84,6 +85,7 @@ @Nullable String originalSubject, @Nullable String submissionId, @Nullable Change.Status status, + @Nullable Account.Id assignee, @Nullable Set<String> hashtags, Map<PatchSet.Id, PatchSet> patchSets, Multimap<PatchSet.Id, PatchSetApproval> approvals, @@ -111,8 +113,9 @@ originalSubject, submissionId, status), + assignee, ImmutableSet.copyOf(hashtags), - ImmutableSortedMap.copyOf(patchSets, ReviewDbUtil.intKeyOrdering()), + ImmutableSortedMap.copyOf(patchSets, comparing(PatchSet.Id::get)), ImmutableListMultimap.copyOf(approvals), reviewers, ImmutableList.copyOf(allPastReviewers), @@ -153,6 +156,7 @@ @Nullable abstract ChangeColumns columns(); // Other related to this Change. + @Nullable abstract Account.Id assignee(); abstract ImmutableSet<String> hashtags(); abstract ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets(); abstract ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java deleted file mode 100644 index 08acbad..0000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java +++ /dev/null
@@ -1,1060 +0,0 @@ -// Copyright (C) 2014 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.notedb; - -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef; -import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId; -import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS; -import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.google.common.base.MoreObjects; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.base.Splitter; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.Ordering; -import com.google.common.collect.Sets; -import com.google.common.collect.Table; -import com.google.common.primitives.Ints; -import com.google.gerrit.common.FormatUtil; -import com.google.gerrit.common.Nullable; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.ChangeMessage; -import com.google.gerrit.reviewdb.client.PatchLineComment; -import com.google.gerrit.reviewdb.client.PatchLineComment.Status; -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.reviewdb.server.ReviewDbUtil; -import com.google.gerrit.server.GerritPersonIdent; -import com.google.gerrit.server.PatchLineCommentsUtil; -import com.google.gerrit.server.account.AccountCache; -import com.google.gerrit.server.config.AnonymousCowardName; -import com.google.gerrit.server.git.ChainedReceiveCommands; -import com.google.gerrit.server.notedb.NoteDbUpdateManager.OpenRepo; -import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result; -import com.google.gerrit.server.patch.PatchListCache; -import com.google.gerrit.server.project.NoSuchChangeException; -import com.google.gerrit.server.project.ProjectCache; -import com.google.gwtorm.server.AtomicUpdate; -import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.OrmRuntimeException; -import com.google.gwtorm.server.SchemaFactory; -import com.google.inject.Inject; - -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.errors.InvalidObjectIdException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.TextProgressMonitor; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.ReceiveCommand; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.PrintWriter; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ChangeRebuilderImpl extends ChangeRebuilder { - private static final Logger log = - LoggerFactory.getLogger(ChangeRebuilderImpl.class); - - /** - * The maximum amount of time between the ReviewDb timestamp of the first and - * last events batched together into a single NoteDb update. - * <p> - * Used to account for the fact that different records with their own - * timestamps (e.g. {@link PatchSetApproval} and {@link ChangeMessage}) - * historically didn't necessarily use the same timestamp, and tended to call - * {@code System.currentTimeMillis()} independently. - */ - static final long MAX_WINDOW_MS = SECONDS.toMillis(3); - - /** - * The maximum amount of time between two consecutive events to consider them - * to be in the same batch. - */ - private static final long MAX_DELTA_MS = SECONDS.toMillis(1); - - private final AccountCache accountCache; - private final ChangeDraftUpdate.Factory draftUpdateFactory; - private final ChangeNoteUtil changeNoteUtil; - private final ChangeUpdate.Factory updateFactory; - private final NoteDbUpdateManager.Factory updateManagerFactory; - private final NotesMigration migration; - private final PatchListCache patchListCache; - private final PersonIdent serverIdent; - private final ProjectCache projectCache; - private final String anonymousCowardName; - - @Inject - ChangeRebuilderImpl(SchemaFactory<ReviewDb> schemaFactory, - AccountCache accountCache, - ChangeDraftUpdate.Factory draftUpdateFactory, - ChangeNoteUtil changeNoteUtil, - ChangeUpdate.Factory updateFactory, - NoteDbUpdateManager.Factory updateManagerFactory, - NotesMigration migration, - PatchListCache patchListCache, - @GerritPersonIdent PersonIdent serverIdent, - @Nullable ProjectCache projectCache, - @AnonymousCowardName String anonymousCowardName) { - super(schemaFactory); - this.accountCache = accountCache; - this.draftUpdateFactory = draftUpdateFactory; - this.changeNoteUtil = changeNoteUtil; - this.updateFactory = updateFactory; - this.updateManagerFactory = updateManagerFactory; - this.migration = migration; - this.patchListCache = patchListCache; - this.serverIdent = serverIdent; - this.projectCache = projectCache; - this.anonymousCowardName = anonymousCowardName; - } - - @Override - public Result rebuild(ReviewDb db, Change.Id changeId) - throws NoSuchChangeException, IOException, OrmException, - ConfigInvalidException { - db = ReviewDbUtil.unwrapDb(db); - Change change = db.changes().get(changeId); - if (change == null) { - throw new NoSuchChangeException(changeId); - } - try (NoteDbUpdateManager manager = - updateManagerFactory.create(change.getProject())) { - buildUpdates(manager, ChangeBundle.fromReviewDb(db, changeId)); - return execute(db, changeId, manager); - } - } - - private static class AbortUpdateException extends OrmRuntimeException { - private static final long serialVersionUID = 1L; - - AbortUpdateException() { - super("aborted"); - } - } - - private static class ConflictingUpdateException extends OrmRuntimeException { - private static final long serialVersionUID = 1L; - - ConflictingUpdateException(Change change, String expectedNoteDbState) { - super(String.format( - "Expected change %s to have noteDbState %s but was %s", - change.getId(), expectedNoteDbState, change.getNoteDbState())); - } - } - - @Override - public Result rebuild(NoteDbUpdateManager manager, - ChangeBundle bundle) throws NoSuchChangeException, IOException, - OrmException, ConfigInvalidException { - Change change = new Change(bundle.getChange()); - buildUpdates(manager, bundle); - return manager.stageAndApplyDelta(change); - } - - @Override - public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId) - throws NoSuchChangeException, IOException, OrmException { - db = ReviewDbUtil.unwrapDb(db); - Change change = db.changes().get(changeId); - if (change == null) { - throw new NoSuchChangeException(changeId); - } - NoteDbUpdateManager manager = - updateManagerFactory.create(change.getProject()); - buildUpdates(manager, ChangeBundle.fromReviewDb(db, changeId)); - manager.stage(); - return manager; - } - - @Override - public Result execute(ReviewDb db, Change.Id changeId, - NoteDbUpdateManager manager) throws NoSuchChangeException, OrmException, - IOException { - db = ReviewDbUtil.unwrapDb(db); - Change change = db.changes().get(changeId); - if (change == null) { - throw new NoSuchChangeException(changeId); - } - - final String oldNoteDbState = change.getNoteDbState(); - Result r = manager.stageAndApplyDelta(change); - final String newNoteDbState = change.getNoteDbState(); - try { - db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() { - @Override - public Change update(Change change) { - String currNoteDbState = change.getNoteDbState(); - if (Objects.equals(currNoteDbState, newNoteDbState)) { - // Another thread completed the same rebuild we were about to. - throw new AbortUpdateException(); - } else if (!Objects.equals(oldNoteDbState, currNoteDbState)) { - // Another thread updated the state to something else. - throw new ConflictingUpdateException(change, oldNoteDbState); - } - change.setNoteDbState(newNoteDbState); - return change; - } - }); - } catch (ConflictingUpdateException e) { - // Rethrow as an OrmException so the caller knows to use staged results. - // Strictly speaking they are not completely up to date, but result we - // send to the caller is the same as if this rebuild had executed before - // the other thread. - throw new OrmException(e.getMessage()); - } catch (AbortUpdateException e) { - if (NoteDbChangeState.parse(changeId, newNoteDbState).isUpToDate( - manager.getChangeRepo().cmds.getRepoRefCache(), - manager.getAllUsersRepo().cmds.getRepoRefCache())) { - // If the state in ReviewDb matches NoteDb at this point, it means - // another thread successfully completed this rebuild. It's ok to not - // execute the update in this case, since the object referenced in the - // Result was flushed to the repo by whatever thread won the race. - return r; - } - // If the state doesn't match, that means another thread attempted this - // rebuild, but failed. Fall through and try to update the ref again. - } - if (migration.failChangeWrites()) { - // Don't even attempt to execute if read-only, it would fail anyway. But - // do throw an exception to the caller so they know to use the staged - // results instead of reading from the repo. - throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY); - } - manager.execute(); - return r; - } - - @Override - public boolean rebuildProject(ReviewDb db, - ImmutableMultimap<Project.NameKey, Change.Id> allChanges, - Project.NameKey project, Repository allUsersRepo) - throws NoSuchChangeException, IOException, OrmException, - ConfigInvalidException { - checkArgument(allChanges.containsKey(project)); - boolean ok = true; - ProgressMonitor pm = new TextProgressMonitor(new PrintWriter(System.out)); - pm.beginTask( - FormatUtil.elide(project.get(), 50), allChanges.get(project).size()); - try (NoteDbUpdateManager manager = updateManagerFactory.create(project); - ObjectInserter allUsersInserter = allUsersRepo.newObjectInserter(); - RevWalk allUsersRw = new RevWalk(allUsersInserter.newReader())) { - manager.setAllUsersRepo(allUsersRepo, allUsersRw, allUsersInserter, - new ChainedReceiveCommands(allUsersRepo)); - for (Change.Id changeId : allChanges.get(project)) { - try { - buildUpdates(manager, ChangeBundle.fromReviewDb(db, changeId)); - } catch (NoPatchSetsException e) { - log.warn(e.getMessage()); - } catch (Throwable t) { - log.error("Failed to rebuild change " + changeId, t); - ok = false; - } - pm.update(1); - } - manager.execute(); - } finally { - pm.endTask(); - } - return ok; - } - - private void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle) - throws IOException, OrmException { - manager.setCheckExpectedState(false); - Change change = new Change(bundle.getChange()); - if (bundle.getPatchSets().isEmpty()) { - throw new NoPatchSetsException(change.getId()); - } - - PatchSet.Id currPsId = change.currentPatchSetId(); - // We will rebuild all events, except for draft comments, in buckets based - // on author and timestamp. - List<Event> events = new ArrayList<>(); - Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents = - ArrayListMultimap.create(); - - events.addAll(getHashtagsEvents(change, manager)); - - // Delete ref only after hashtags have been read - deleteChangeMetaRef(change, manager.getChangeRepo().cmds); - deleteDraftRefs(change, manager.getAllUsersRepo()); - - Integer minPsNum = getMinPatchSetNum(bundle); - Set<PatchSet.Id> psIds = - Sets.newHashSetWithExpectedSize(bundle.getPatchSets().size()); - - for (PatchSet ps : bundle.getPatchSets()) { - if (ps.getId().get() > currPsId.get()) { - log.info( - "Skipping patch set {}, which is higher than current patch set {}", - ps.getId(), currPsId); - continue; - } - psIds.add(ps.getId()); - events.add(new PatchSetEvent( - change, ps, manager.getChangeRepo().rw)); - for (PatchLineComment c : getPatchLineComments(bundle, ps)) { - PatchLineCommentEvent e = - new PatchLineCommentEvent(c, change, ps, patchListCache); - if (c.getStatus() == Status.PUBLISHED) { - events.add(e); - } else { - draftCommentEvents.put(c.getAuthor(), e); - } - } - } - - for (PatchSetApproval psa : bundle.getPatchSetApprovals()) { - if (psIds.contains(psa.getPatchSetId())) { - events.add(new ApprovalEvent(psa, change.getCreatedOn())); - } - } - - for (Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> r : - bundle.getReviewers().asTable().cellSet()) { - events.add(new ReviewerEvent(r, change.getCreatedOn())); - } - - Change noteDbChange = new Change(null, null, null, null, null); - for (ChangeMessage msg : bundle.getChangeMessages()) { - if (msg.getPatchSetId() == null || psIds.contains(msg.getPatchSetId())) { - events.add( - new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn())); - } - } - - sortAndFillEvents(change, noteDbChange, events, minPsNum); - - EventList<Event> el = new EventList<>(); - for (Event e : events) { - if (!el.canAdd(e)) { - flushEventsToUpdate(manager, el, change); - checkState(el.canAdd(e)); - } - el.add(e); - } - flushEventsToUpdate(manager, el, change); - - EventList<PatchLineCommentEvent> plcel = new EventList<>(); - for (Account.Id author : draftCommentEvents.keys()) { - for (PatchLineCommentEvent e : - EVENT_ORDER.sortedCopy(draftCommentEvents.get(author))) { - if (!plcel.canAdd(e)) { - flushEventsToDraftUpdate(manager, plcel, change); - checkState(plcel.canAdd(e)); - } - plcel.add(e); - } - flushEventsToDraftUpdate(manager, plcel, change); - } - } - - private static Integer getMinPatchSetNum(ChangeBundle bundle) { - Integer minPsNum = null; - for (PatchSet ps : bundle.getPatchSets()) { - int n = ps.getId().get(); - if (minPsNum == null || n < minPsNum) { - minPsNum = n; - } - } - return minPsNum; - } - - private static List<PatchLineComment> getPatchLineComments(ChangeBundle bundle, - final PatchSet ps) { - return FluentIterable.from(bundle.getPatchLineComments()) - .filter(new Predicate<PatchLineComment>() { - @Override - public boolean apply(PatchLineComment in) { - return in.getPatchSetId().equals(ps.getId()); - } - }).toSortedList(PatchLineCommentsUtil.PLC_ORDER); - } - - private void sortAndFillEvents(Change change, Change noteDbChange, - List<Event> events, Integer minPsNum) { - Collections.sort(events, EVENT_ORDER); - events.add(new FinalUpdatesEvent(change, noteDbChange)); - - // Ensure the first event in the list creates the change, setting the author - // and any required footers. - Event first = events.get(0); - if (first instanceof PatchSetEvent && change.getOwner().equals(first.who)) { - ((PatchSetEvent) first).createChange = true; - } else { - events.add(0, new CreateChangeEvent(change, minPsNum)); - } - - // Fill in any missing patch set IDs using the latest patch set of the - // change at the time of the event, because NoteDb can't represent actions - // with no associated patch set ID. This workaround is as if a user added a - // ChangeMessage on the change by replying from the latest patch set. - // - // Start with the first patch set that actually exists. If there are no - // patch sets at all, minPsNum will be null, so just bail and use 1 as the - // patch set ID. The corresponding patch set won't exist, but this change is - // probably corrupt anyway, as deleting the last draft patch set should have - // deleted the whole change. - int ps = firstNonNull(minPsNum, 1); - for (Event e : events) { - if (e.psId == null) { - e.psId = new PatchSet.Id(change.getId(), ps); - } else { - ps = Math.max(ps, e.psId.get()); - } - } - } - - private void flushEventsToUpdate(NoteDbUpdateManager manager, - EventList<Event> events, Change change) throws OrmException, IOException { - if (events.isEmpty()) { - return; - } - Comparator<String> labelNameComparator; - if (projectCache != null) { - labelNameComparator = projectCache.get(change.getProject()) - .getLabelTypes().nameComparator(); - } else { - // No project cache available, bail and use natural ordering; there's no - // semantic difference anyway difference. - labelNameComparator = Ordering.natural(); - } - ChangeUpdate update = updateFactory.create( - change, - events.getAccountId(), - events.newAuthorIdent(), - events.getWhen(), - labelNameComparator); - update.setAllowWriteToNewRef(true); - update.setPatchSetId(events.getPatchSetId()); - update.setTag(events.getTag()); - for (Event e : events) { - e.apply(update); - } - manager.add(update); - events.clear(); - } - - private void flushEventsToDraftUpdate(NoteDbUpdateManager manager, - EventList<PatchLineCommentEvent> events, Change change) - throws OrmException { - if (events.isEmpty()) { - return; - } - ChangeDraftUpdate update = draftUpdateFactory.create( - change, - events.getAccountId(), - events.newAuthorIdent(), - events.getWhen()); - update.setPatchSetId(events.getPatchSetId()); - for (PatchLineCommentEvent e : events) { - e.applyDraft(update); - } - manager.add(update); - events.clear(); - } - - private List<HashtagsEvent> getHashtagsEvents(Change change, - NoteDbUpdateManager manager) throws IOException { - String refName = changeMetaRef(change.getId()); - Optional<ObjectId> old = manager.getChangeRepo().getObjectId(refName); - if (!old.isPresent()) { - return Collections.emptyList(); - } - - RevWalk rw = manager.getChangeRepo().rw; - List<HashtagsEvent> events = new ArrayList<>(); - rw.reset(); - rw.markStart(rw.parseCommit(old.get())); - for (RevCommit commit : rw) { - Account.Id authorId; - try { - authorId = - changeNoteUtil.parseIdent(commit.getAuthorIdent(), change.getId()); - } catch (ConfigInvalidException e) { - continue; // Corrupt data, no valid hashtags in this commit. - } - PatchSet.Id psId = parsePatchSetId(change, commit); - Set<String> hashtags = parseHashtags(commit); - if (authorId == null || psId == null || hashtags == null) { - continue; - } - - Timestamp commitTime = - new Timestamp(commit.getCommitterIdent().getWhen().getTime()); - events.add(new HashtagsEvent(psId, authorId, commitTime, hashtags, - change.getCreatedOn())); - } - return events; - } - - private Set<String> parseHashtags(RevCommit commit) { - List<String> hashtagsLines = commit.getFooterLines(FOOTER_HASHTAGS); - if (hashtagsLines.isEmpty() || hashtagsLines.size() > 1) { - return null; - } - - if (hashtagsLines.get(0).isEmpty()) { - return ImmutableSet.of(); - } - return Sets.newHashSet(Splitter.on(',').split(hashtagsLines.get(0))); - } - - private PatchSet.Id parsePatchSetId(Change change, RevCommit commit) { - List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET); - if (psIdLines.size() != 1) { - return null; - } - Integer psId = Ints.tryParse(psIdLines.get(0)); - if (psId == null) { - return null; - } - return new PatchSet.Id(change.getId(), psId); - } - - private void deleteChangeMetaRef(Change change, ChainedReceiveCommands cmds) - throws IOException { - String refName = changeMetaRef(change.getId()); - Optional<ObjectId> old = cmds.get(refName); - if (old.isPresent()) { - cmds.add(new ReceiveCommand(old.get(), ObjectId.zeroId(), refName)); - } - } - - private void deleteDraftRefs(Change change, OpenRepo allUsersRepo) - throws IOException { - for (Ref r : allUsersRepo.repo.getRefDatabase() - .getRefs(RefNames.refsDraftCommentsPrefix(change.getId())).values()) { - allUsersRepo.cmds.add( - new ReceiveCommand(r.getObjectId(), ObjectId.zeroId(), r.getName())); - } - } - - private static final Ordering<Event> EVENT_ORDER = new Ordering<Event>() { - @Override - public int compare(Event a, Event b) { - return ComparisonChain.start() - .compare(a.when, b.when) - .compareTrueFirst(isPatchSet(a), isPatchSet(b)) - .compareTrueFirst(a.predatesChange, b.predatesChange) - .compare(a.who, b.who, ReviewDbUtil.intKeyOrdering()) - .compare(a.psId, b.psId, ReviewDbUtil.intKeyOrdering().nullsLast()) - .result(); - } - - private boolean isPatchSet(Event e) { - return e instanceof PatchSetEvent; - } - }; - - private abstract static class Event { - // NOTE: EventList only supports direct subclasses, not an arbitrary - // hierarchy. - - final Account.Id who; - final Timestamp when; - final String tag; - final boolean predatesChange; - PatchSet.Id psId; - - protected Event(PatchSet.Id psId, Account.Id who, Timestamp when, - Timestamp changeCreatedOn, String tag) { - this.psId = psId; - this.who = who; - this.tag = tag; - // Truncate timestamps at the change's createdOn timestamp. - predatesChange = when.before(changeCreatedOn); - this.when = predatesChange ? changeCreatedOn : when; - } - - protected void checkUpdate(AbstractChangeUpdate update) { - checkState(Objects.equals(update.getPatchSetId(), psId), - "cannot apply event for %s to update for %s", - update.getPatchSetId(), psId); - checkState(when.getTime() - update.getWhen().getTime() <= MAX_WINDOW_MS, - "event at %s outside update window starting at %s", - when, update.getWhen()); - checkState(Objects.equals(update.getNullableAccountId(), who), - "cannot apply event by %s to update by %s", - who, update.getNullableAccountId()); - } - - /** - * @return whether this event type must be unique per {@link ChangeUpdate}, - * i.e. there may be at most one of this type. - */ - abstract boolean uniquePerUpdate(); - - abstract void apply(ChangeUpdate update) throws OrmException, IOException; - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("psId", psId) - .add("who", who) - .add("when", when) - .toString(); - } - } - - private class EventList<E extends Event> extends ArrayList<E> { - private static final long serialVersionUID = 1L; - - private E getLast() { - return get(size() - 1); - } - - private long getLastTime() { - return getLast().when.getTime(); - } - - private long getFirstTime() { - return get(0).when.getTime(); - } - - boolean canAdd(E e) { - if (isEmpty()) { - return true; - } - if (e instanceof FinalUpdatesEvent) { - return false; // FinalUpdatesEvent always gets its own update. - } - - Event last = getLast(); - if (!Objects.equals(e.who, last.who) - || !e.psId.equals(last.psId) - || !Objects.equals(e.tag, last.tag)) { - return false; // Different patch set, author, or tag. - } - - long t = e.when.getTime(); - long tFirst = getFirstTime(); - long tLast = getLastTime(); - checkArgument(t >= tLast, - "event %s is before previous event in list %s", e, last); - if (t - tLast > MAX_DELTA_MS || t - tFirst > MAX_WINDOW_MS) { - return false; // Too much time elapsed. - } - - if (!e.uniquePerUpdate()) { - return true; - } - for (Event o : this) { - if (e.getClass() == o.getClass()) { - return false; // Only one event of this type allowed per update. - } - } - - // TODO(dborowitz): Additional heuristics, like keeping events separate if - // they affect overlapping fields within a single entity. - - return true; - } - - Timestamp getWhen() { - return get(0).when; - } - - PatchSet.Id getPatchSetId() { - PatchSet.Id id = checkNotNull(get(0).psId); - for (int i = 1; i < size(); i++) { - checkState(get(i).psId.equals(id), - "mismatched patch sets in EventList: %s != %s", id, get(i).psId); - } - return id; - } - - Account.Id getAccountId() { - Account.Id id = get(0).who; - for (int i = 1; i < size(); i++) { - checkState(Objects.equals(id, get(i).who), - "mismatched users in EventList: %s != %s", id, get(i).who); - } - return id; - } - - PersonIdent newAuthorIdent() { - Account.Id id = getAccountId(); - if (id == null) { - return new PersonIdent(serverIdent, getWhen()); - } - return changeNoteUtil.newIdent( - accountCache.get(id).getAccount(), getWhen(), serverIdent, - anonymousCowardName); - } - - String getTag() { - return getLast().tag; - } - } - - private static void createChange(ChangeUpdate update, Change change) { - update.setSubjectForCommit("Create change"); - update.setChangeId(change.getKey().get()); - update.setBranch(change.getDest().get()); - update.setSubject(change.getOriginalSubject()); - } - - private static class CreateChangeEvent extends Event { - private final Change change; - - private static PatchSet.Id psId(Change change, Integer minPsNum) { - int n; - if (minPsNum == null) { - // There were no patch sets for the change at all, so something is very - // wrong. Bail and use 1 as the patch set. - n = 1; - } else { - n = minPsNum; - } - return new PatchSet.Id(change.getId(), n); - } - - CreateChangeEvent(Change change, Integer minPsNum) { - super(psId(change, minPsNum), change.getOwner(), change.getCreatedOn(), - change.getCreatedOn(), null); - this.change = change; - } - - @Override - boolean uniquePerUpdate() { - return true; - } - - @Override - void apply(ChangeUpdate update) throws IOException, OrmException { - checkUpdate(update); - createChange(update, change); - } - } - - private static class ApprovalEvent extends Event { - private PatchSetApproval psa; - - ApprovalEvent(PatchSetApproval psa, Timestamp changeCreatedOn) { - super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted(), - changeCreatedOn, psa.getTag()); - this.psa = psa; - } - - @Override - boolean uniquePerUpdate() { - return false; - } - - @Override - void apply(ChangeUpdate update) { - checkUpdate(update); - update.putApproval(psa.getLabel(), psa.getValue()); - } - } - - private static class ReviewerEvent extends Event { - private Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer; - - ReviewerEvent( - Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer, - Timestamp changeCreatedOn) { - super( - // Reviewers aren't generally associated with a particular patch set - // (although as an implementation detail they were in ReviewDb). Just - // use the latest patch set at the time of the event. - null, - reviewer.getColumnKey(), reviewer.getValue(), changeCreatedOn, null); - this.reviewer = reviewer; - } - - @Override - boolean uniquePerUpdate() { - return false; - } - - @Override - void apply(ChangeUpdate update) throws IOException, OrmException { - checkUpdate(update); - update.putReviewer(reviewer.getColumnKey(), reviewer.getRowKey()); - } - } - - private static class PatchSetEvent extends Event { - private final Change change; - private final PatchSet ps; - private final RevWalk rw; - private boolean createChange; - - PatchSetEvent(Change change, PatchSet ps, RevWalk rw) { - super(ps.getId(), ps.getUploader(), ps.getCreatedOn(), - change.getCreatedOn(), null); - this.change = change; - this.ps = ps; - this.rw = rw; - } - - @Override - boolean uniquePerUpdate() { - return true; - } - - @Override - void apply(ChangeUpdate update) throws IOException, OrmException { - checkUpdate(update); - if (createChange) { - createChange(update, change); - } else { - update.setSubject(change.getSubject()); - update.setSubjectForCommit("Create patch set " + ps.getPatchSetId()); - } - setRevision(update, ps); - List<String> groups = ps.getGroups(); - if (!groups.isEmpty()) { - update.setGroups(ps.getGroups()); - } - if (ps.isDraft()) { - update.setPatchSetState(PatchSetState.DRAFT); - } - } - - private void setRevision(ChangeUpdate update, PatchSet ps) - throws IOException { - String rev = ps.getRevision().get(); - String cert = ps.getPushCertificate(); - ObjectId id; - try { - id = ObjectId.fromString(rev); - } catch (InvalidObjectIdException e) { - update.setRevisionForMissingCommit(rev, cert); - return; - } - try { - update.setCommit(rw, id, cert); - } catch (MissingObjectException e) { - update.setRevisionForMissingCommit(rev, cert); - return; - } - } - } - - private static class PatchLineCommentEvent extends Event { - public final PatchLineComment c; - private final Change change; - private final PatchSet ps; - private final PatchListCache cache; - - PatchLineCommentEvent(PatchLineComment c, Change change, PatchSet ps, - PatchListCache cache) { - super(PatchLineCommentsUtil.getCommentPsId(c), c.getAuthor(), - c.getWrittenOn(), change.getCreatedOn(), c.getTag()); - this.c = c; - this.change = change; - this.ps = ps; - this.cache = cache; - } - - @Override - boolean uniquePerUpdate() { - return false; - } - - @Override - void apply(ChangeUpdate update) throws OrmException { - checkUpdate(update); - if (c.getRevId() == null) { - setCommentRevId(c, cache, change, ps); - } - update.putComment(c); - } - - void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException { - if (c.getRevId() == null) { - setCommentRevId(c, cache, change, ps); - } - draftUpdate.putComment(c); - } - } - - private static class HashtagsEvent extends Event { - private final Set<String> hashtags; - - HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when, - Set<String> hashtags, Timestamp changeCreatdOn) { - super(psId, who, when, changeCreatdOn, - // Somewhat confusingly, hashtags do not use the setTag method on - // AbstractChangeUpdate, so pass null as the tag. - null); - this.hashtags = hashtags; - } - - @Override - boolean uniquePerUpdate() { - // Since these are produced from existing commits in the old NoteDb graph, - // we know that there must be one per commit in the rebuilt graph. - return true; - } - - @Override - void apply(ChangeUpdate update) throws OrmException { - update.setHashtags(hashtags); - } - } - - private static class ChangeMessageEvent extends Event { - private static final Pattern TOPIC_SET_REGEXP = - Pattern.compile("^Topic set to (.+)$"); - private static final Pattern TOPIC_CHANGED_REGEXP = - Pattern.compile("^Topic changed from (.+) to (.+)$"); - private static final Pattern TOPIC_REMOVED_REGEXP = - Pattern.compile("^Topic (.+) removed$"); - - private static final Pattern STATUS_ABANDONED_REGEXP = - Pattern.compile("^Abandoned(\n.*)*$"); - private static final Pattern STATUS_RESTORED_REGEXP = - Pattern.compile("^Restored(\n.*)*$"); - - private final ChangeMessage message; - private final Change noteDbChange; - - ChangeMessageEvent(ChangeMessage message, Change noteDbChange, - Timestamp changeCreatedOn) { - super(message.getPatchSetId(), message.getAuthor(), - message.getWrittenOn(), changeCreatedOn, message.getTag()); - this.message = message; - this.noteDbChange = noteDbChange; - } - - @Override - boolean uniquePerUpdate() { - return true; - } - - @Override - void apply(ChangeUpdate update) throws OrmException { - checkUpdate(update); - update.setChangeMessage(message.getMessage()); - setTopic(update); - setStatus(update); - } - - private void setTopic(ChangeUpdate update) { - String msg = message.getMessage(); - if (msg == null) { - return; - } - Matcher m = TOPIC_SET_REGEXP.matcher(msg); - if (m.matches()) { - String topic = m.group(1); - update.setTopic(topic); - noteDbChange.setTopic(topic); - return; - } - - m = TOPIC_CHANGED_REGEXP.matcher(msg); - if (m.matches()) { - String topic = m.group(2); - update.setTopic(topic); - noteDbChange.setTopic(topic); - return; - } - - if (TOPIC_REMOVED_REGEXP.matcher(msg).matches()) { - update.setTopic(null); - noteDbChange.setTopic(null); - } - } - - private void setStatus(ChangeUpdate update) { - String msg = message.getMessage(); - if (msg == null) { - return; - } - if (STATUS_ABANDONED_REGEXP.matcher(msg).matches()) { - update.setStatus(Change.Status.ABANDONED); - noteDbChange.setStatus(Change.Status.ABANDONED); - return; - } - - if (STATUS_RESTORED_REGEXP.matcher(msg).matches()) { - update.setStatus(Change.Status.NEW); - noteDbChange.setStatus(Change.Status.NEW); - } - } - } - - private static class FinalUpdatesEvent extends Event { - private final Change change; - private final Change noteDbChange; - - FinalUpdatesEvent(Change change, Change noteDbChange) { - super(change.currentPatchSetId(), change.getOwner(), - change.getLastUpdatedOn(), change.getCreatedOn(), null); - this.change = change; - this.noteDbChange = noteDbChange; - } - - @Override - boolean uniquePerUpdate() { - return true; - } - - @SuppressWarnings("deprecation") - @Override - void apply(ChangeUpdate update) throws OrmException { - if (!Objects.equals(change.getTopic(), noteDbChange.getTopic())) { - update.setTopic(change.getTopic()); - } - if (!Objects.equals(change.getStatus(), noteDbChange.getStatus())) { - // TODO(dborowitz): Stamp approximate approvals at this time. - update.fixStatus(change.getStatus()); - } - if (change.getSubmissionId() != null) { - update.setSubmissionId(change.getSubmissionId()); - } - if (!update.isEmpty()) { - update.setSubjectForCommit("Final NoteDb migration updates"); - } - } - } -}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java index 77b8dc0..39ca57c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef; +import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT; @@ -32,6 +33,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG; import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC; +import static java.util.Comparator.comparing; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import com.google.common.annotations.VisibleForTesting; @@ -48,7 +50,6 @@ import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RevId; -import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.config.AnonymousCowardName; @@ -56,6 +57,7 @@ import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.util.LabelVote; import com.google.gerrit.server.util.RequestId; +import com.google.gwtorm.client.IntKey; import com.google.gwtorm.server.OrmException; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; @@ -122,6 +124,7 @@ private String submissionId; private String topic; private String commit; + private Account.Id assignee; private Set<String> hashtags; private String changeMessage; private String tag; @@ -173,7 +176,7 @@ private static Table<String, Account.Id, Optional<Short>> approvals( Comparator<String> nameComparator) { - return TreeBasedTable.create(nameComparator, ReviewDbUtil.intKeyOrdering()); + return TreeBasedTable.create(nameComparator, comparing(IntKey::get)); } @AssistedInject @@ -284,7 +287,7 @@ this.commitSubject = commitSubject; } - void setSubject(String subject) { + public void setSubject(String subject) { this.subject = subject; } @@ -379,6 +382,10 @@ this.hashtags = hashtags; } + public void setAssignee(Account.Id assignee) { + this.assignee = assignee; + } + public Map<Account.Id, ReviewerStateInternal> getReviewers() { return reviewers; } @@ -452,7 +459,11 @@ // Even though reading from changes might not be enabled, we need to // parse any existing revision notes so we can merge them. return RevisionNoteMap.parse( - noteUtil, getId(), rw.getObjectReader(), noteMap, false); + noteUtil, + getId(), + rw.getObjectReader(), + noteMap, + PatchLineComment.Status.PUBLISHED); } private void checkComments(Map<RevId, RevisionNote> existingNotes, @@ -543,6 +554,11 @@ addFooter(msg, FOOTER_COMMIT, commit); } + if (assignee != null) { + addFooter(msg, FOOTER_ASSIGNEE); + addIdent(msg, assignee).append('\n'); + } + Joiner comma = Joiner.on(','); if (hashtags != null) { addFooter(msg, FOOTER_HASHTAGS, comma.join(hashtags)); @@ -643,6 +659,7 @@ && status == null && submissionId == null && submitRecords == null + && assignee == null && hashtags == null && topic == null && commit == null
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java index 08195e4..933b4ae 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.git.RepoRefCache; import com.google.gerrit.server.notedb.NoteDbUpdateManager.StagedResult; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gwtorm.server.OrmException; import com.google.inject.assistedinject.Assisted; @@ -140,7 +141,7 @@ ObjectReader reader = handle.walk().getObjectReader(); revisionNoteMap = RevisionNoteMap.parse( args.noteUtil, getChangeId(), reader, NoteMap.read(reader, tipCommit), - true); + PatchLineComment.Status.DRAFT); Multimap<RevId, PatchLineComment> cs = ArrayListMultimap.create(); for (RevisionNote rn : revisionNoteMap.revisionNotes.values()) { for (PatchLineComment c : rn.comments) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/GwtormChangeBundleReader.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/GwtormChangeBundleReader.java new file mode 100644 index 0000000..e401a52 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/GwtormChangeBundleReader.java
@@ -0,0 +1,53 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb; + +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.ReviewerSet; +import com.google.gerrit.server.notedb.ChangeBundle.Source; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.util.List; + +@Singleton +public class GwtormChangeBundleReader implements ChangeBundleReader { + @Inject + GwtormChangeBundleReader() { + } + + @Override + public ChangeBundle fromReviewDb(ReviewDb db, Change.Id id) + throws OrmException { + db.changes().beginTransaction(id); + try { + List<PatchSetApproval> approvals = + db.patchSetApprovals().byChange(id).toList(); + return new ChangeBundle( + db.changes().get(id), + db.changeMessages().byChange(id), + db.patchSets().byChange(id), + approvals, + db.patchComments().byChange(id), + ReviewerSet.fromApprovals(approvals), + Source.REVIEW_DB); + } finally { + db.rollback(); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java index 4a7a781..4aed71e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef; import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments; +import static java.util.Comparator.comparing; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; @@ -30,7 +31,6 @@ import com.google.gerrit.common.Nullable; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.git.RefCache; import org.eclipse.jgit.lib.ObjectId; @@ -78,7 +78,7 @@ } @VisibleForTesting - static NoteDbChangeState parse(Change.Id id, String str) { + public static NoteDbChangeState parse(Change.Id id, String str) { if (str == null) { return null; } @@ -163,7 +163,7 @@ public static String toString(ObjectId changeMetaId, Map<Account.Id, ObjectId> draftIds) { List<Account.Id> accountIds = Lists.newArrayList(draftIds.keySet()); - Collections.sort(accountIds, ReviewDbUtil.intKeyOrdering()); + Collections.sort(accountIds, comparing(Account.Id::get)); StringBuilder sb = new StringBuilder(changeMetaId.name()); for (Account.Id id : accountIds) { sb.append(',') @@ -204,7 +204,7 @@ return id.get().equals(draftIds.get(accountId)); } - boolean isUpToDate(RefCache changeRepoRefs, RefCache draftsRepoRefs) + public boolean isUpToDate(RefCache changeRepoRefs, RefCache draftsRepoRefs) throws IOException { if (!isChangeUpToDate(changeRepoRefs)) { return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java index ff3b4b8..876f47f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
@@ -23,6 +23,8 @@ import com.google.gerrit.reviewdb.client.Project.NameKey; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl; import com.google.inject.TypeLiteral; import com.google.inject.name.Names;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java index cad531f..7f6eb5c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -119,10 +119,10 @@ @Nullable abstract NoteDbUpdateManager.StagedResult staged(); } - static class OpenRepo implements AutoCloseable { - final Repository repo; - final RevWalk rw; - final ChainedReceiveCommands cmds; + public static class OpenRepo implements AutoCloseable { + public final Repository repo; + public final RevWalk rw; + public final ChainedReceiveCommands cmds; private final InMemoryInserter tempIns; @Nullable private final ObjectInserter finalIns; @@ -143,7 +143,7 @@ this.close = close; } - Optional<ObjectId> getObjectId(String refName) throws IOException { + public Optional<ObjectId> getObjectId(String refName) throws IOException { return cmds.get(refName); } @@ -233,17 +233,17 @@ return this; } - NoteDbUpdateManager setCheckExpectedState(boolean checkExpectedState) { + public NoteDbUpdateManager setCheckExpectedState(boolean checkExpectedState) { this.checkExpectedState = checkExpectedState; return this; } - OpenRepo getChangeRepo() throws IOException { + public OpenRepo getChangeRepo() throws IOException { initChangeRepo(); return changeRepo; } - OpenRepo getAllUsersRepo() throws IOException { + public OpenRepo getAllUsersRepo() throws IOException { initAllUsersRepo(); return allUsersRepo; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java index 73ad68e..4c9f702 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
@@ -28,7 +28,11 @@ import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.RawParseUtils; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; class RevisionNote { static final int MAX_NOTE_SZ = 25 << 20; @@ -65,21 +69,51 @@ final String pushCert; RevisionNote(ChangeNoteUtil noteUtil, Change.Id changeId, - ObjectReader reader, ObjectId noteId, boolean draftsOnly) + ObjectReader reader, ObjectId noteId, PatchLineComment.Status status) throws ConfigInvalidException, IOException { + raw = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ); MutableInteger p = new MutableInteger(); trimLeadingEmptyLines(raw, p); - if (!draftsOnly) { + if (p.value >= raw.length) { + comments = null; + pushCert = null; + return; + } + + if (isJson(raw, p.value)) { + RevisionNoteData data = parseJson(noteUtil, p.value); + comments = data.exportComments(changeId, status); + if (status == PatchLineComment.Status.PUBLISHED) { + pushCert = data.pushCert; + } else { + pushCert = null; + } + return; + } + + if (status == PatchLineComment.Status.PUBLISHED) { pushCert = parsePushCert(changeId, raw, p); trimLeadingEmptyLines(raw, p); } else { pushCert = null; } - PatchLineComment.Status status = draftsOnly - ? PatchLineComment.Status.DRAFT - : PatchLineComment.Status.PUBLISHED; comments = ImmutableList.copyOf( noteUtil.parseNote(raw, p, changeId, status)); } + + private static boolean isJson(byte[] raw, int offset) { + return raw[offset] == '{' || raw[offset] == '['; + } + + private RevisionNoteData parseJson(ChangeNoteUtil noteUtil, int offset) + throws IOException{ + RevisionNoteData data; + try (InputStream is = new ByteArrayInputStream( + raw, offset, raw.length - offset); + Reader r = new InputStreamReader(is)) { + data = noteUtil.getGson().fromJson(r, RevisionNoteData.class); + } + return data; + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java index c8364d3..be0a689 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -15,7 +15,9 @@ package com.google.gerrit.server.notedb; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.gerrit.server.PatchLineCommentsUtil.PLC_ORDER; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toList; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; @@ -25,6 +27,9 @@ import com.google.gerrit.reviewdb.client.RevId; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -79,6 +84,16 @@ delete = new HashSet<>(); } + public byte[] build(ChangeNoteUtil noteUtil) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (noteUtil.getWriteJson()) { + buildNoteJson(noteUtil, out); + } else { + buildNoteLegacy(noteUtil, out); + } + return out.toByteArray(); + } + void putComment(PatchLineComment comment) { checkArgument(!delete.contains(comment.getKey()), "cannot both delete and put %s", comment.getKey()); @@ -94,15 +109,9 @@ this.pushCert = pushCert; } - byte[] build(ChangeNoteUtil noteUtil) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (pushCert != null) { - byte[] certBytes = pushCert.getBytes(UTF_8); - out.write(certBytes, 0, trimTrailingNewlines(certBytes)); - out.write('\n'); - } - + private Multimap<PatchSet.Id, PatchLineComment> buildCommentMap() { Multimap<PatchSet.Id, PatchLineComment> all = ArrayListMultimap.create(); + for (PatchLineComment c : baseComments) { if (!delete.contains(c.getKey()) && !put.containsKey(c.getKey())) { all.put(c.getPatchSetId(), c); @@ -113,8 +122,36 @@ all.put(c.getPatchSetId(), c); } } - noteUtil.buildNote(all, out); - return out.toByteArray(); + return all; + } + + private void buildNoteJson(final ChangeNoteUtil noteUtil, OutputStream out) + throws IOException { + Multimap<PatchSet.Id, PatchLineComment> comments = buildCommentMap(); + if (comments.isEmpty() && pushCert == null) { + return; + } + + RevisionNoteData data = new RevisionNoteData(); + data.comments = comments.values().stream() + .sorted(PLC_ORDER) + .map(plc -> new RevisionNoteData.Comment(plc, noteUtil.getServerId())) + .collect(toList()); + data.pushCert = pushCert; + + try (OutputStreamWriter osw = new OutputStreamWriter(out)) { + noteUtil.getGson().toJson(data, osw); + } + } + + private void buildNoteLegacy(ChangeNoteUtil noteUtil, OutputStream out) + throws IOException { + if (pushCert != null) { + byte[] certBytes = pushCert.getBytes(UTF_8); + out.write(certBytes, 0, trimTrailingNewlines(certBytes)); + out.write('\n'); + } + noteUtil.buildNote(buildCommentMap(), out); } private static int trimTrailingNewlines(byte[] bytes) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java new file mode 100644 index 0000000..73083b7 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
@@ -0,0 +1,141 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb; + +import static java.util.stream.Collectors.toList; + +import com.google.common.collect.ImmutableList; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.Patch; +import com.google.gerrit.reviewdb.client.PatchLineComment; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.reviewdb.client.RevId; + +import java.sql.Timestamp; +import java.util.List; + +/** + * Holds the raw data of a RevisionNote. + * <p>It is intended for (de)serialization to JSON only. + */ +class RevisionNoteData { + static class Identity { + int id; + + Identity(Account.Id id) { + this.id = id.get(); + } + + Account.Id export() { + return new Account.Id(id); + } + } + + static class CommentKey { + String uuid; + String filename; + int patchSetId; + + CommentKey(PatchLineComment.Key k) { + uuid = k.get(); + filename = k.getParentKey().getFileName(); + patchSetId = k.getParentKey().getParentKey().get(); + } + + PatchLineComment.Key export(Change.Id changeId) { + return new PatchLineComment.Key( + new Patch.Key( + new PatchSet.Id(changeId, patchSetId), + filename), + uuid); + } + } + + static class CommentRange { + int startLine; + int startChar; + int endLine; + int endChar; + + CommentRange(com.google.gerrit.reviewdb.client.CommentRange cr) { + startLine = cr.getStartLine(); + startChar = cr.getStartCharacter(); + endLine = cr.getEndLine(); + endChar = cr.getEndCharacter(); + } + + com.google.gerrit.reviewdb.client.CommentRange export() { + return new com.google.gerrit.reviewdb.client.CommentRange( + startLine, startChar, endLine, endChar); + } + } + + static class Comment { + CommentKey key; + int lineNbr; + Identity author; + Timestamp writtenOn; + short side; + String message; + String parentUuid; + CommentRange range; + String tag; + String revId; + String serverId; + + Comment(PatchLineComment plc, String serverId) { + key = new CommentKey(plc.getKey()); + lineNbr = plc.getLine(); + author = new Identity(plc.getAuthor()); + writtenOn = plc.getWrittenOn(); + side = plc.getSide(); + message = plc.getMessage(); + parentUuid = plc.getParentUuid(); + range = plc.getRange() != null ? new CommentRange(plc.getRange()) : null; + tag = plc.getTag(); + revId = plc.getRevId().get(); + this.serverId = serverId; + } + + PatchLineComment export(Change.Id changeId, + PatchLineComment.Status status) { + PatchLineComment plc = new PatchLineComment( + key.export(changeId), lineNbr, author.export(), parentUuid, writtenOn); + plc.setSide(side); + plc.setMessage(message); + if (range != null) { + plc.setRange(range.export()); + } + plc.setTag(tag); + plc.setRevId(new RevId(revId)); + plc.setStatus(status); + return plc; + } + } + + + String pushCert; + List<Comment> comments; + + + ImmutableList<PatchLineComment> exportComments(Change.Id changeId, + PatchLineComment.Status status) { + return ImmutableList.copyOf( + comments.stream() + .map(c -> c.export(changeId, status)) + .collect(toList())); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java index cd70528..1783c1a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
@@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableMap; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.RevId; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -33,11 +34,12 @@ static RevisionNoteMap parse(ChangeNoteUtil noteUtil, Change.Id changeId, ObjectReader reader, NoteMap noteMap, - boolean draftsOnly) throws ConfigInvalidException, IOException { + PatchLineComment.Status status) + throws ConfigInvalidException, IOException { Map<RevId, RevisionNote> result = new HashMap<>(); for (Note note : noteMap) { RevisionNote rn = new RevisionNote( - noteUtil, changeId, reader, note.getData(), draftsOnly); + noteUtil, changeId, reader, note.getData(), status); result.put(new RevId(note.name()), rn); } return new RevisionNoteMap(noteMap, ImmutableMap.copyOf(result));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java index c0bb8ab..7bfb20c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java
@@ -20,6 +20,8 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.SchemaFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java new file mode 100644 index 0000000..0e6d3e9 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java
@@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gwtorm.server.OrmRuntimeException; + +class AbortUpdateException extends OrmRuntimeException { + private static final long serialVersionUID = 1L; + + AbortUpdateException() { + super("aborted"); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java new file mode 100644 index 0000000..a00334d --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java
@@ -0,0 +1,41 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.PatchSetApproval; +import com.google.gerrit.server.notedb.ChangeUpdate; + +import java.sql.Timestamp; + +class ApprovalEvent extends Event { + private PatchSetApproval psa; + + ApprovalEvent(PatchSetApproval psa, Timestamp changeCreatedOn) { + super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted(), + changeCreatedOn, psa.getTag()); + this.psa = psa; + } + + @Override + boolean uniquePerUpdate() { + return false; + } + + @Override + void apply(ChangeUpdate update) { + checkUpdate(update); + update.putApproval(psa.getLabel(), psa.getValue()); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java new file mode 100644 index 0000000..bc8af82 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
@@ -0,0 +1,83 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.ChangeMessage; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gwtorm.server.OrmException; + +import java.sql.Timestamp; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class ChangeMessageEvent extends Event { + private static final Pattern TOPIC_SET_REGEXP = + Pattern.compile("^Topic set to (.+)$"); + private static final Pattern TOPIC_CHANGED_REGEXP = + Pattern.compile("^Topic changed from (.+) to (.+)$"); + private static final Pattern TOPIC_REMOVED_REGEXP = + Pattern.compile("^Topic (.+) removed$"); + + private final ChangeMessage message; + private final Change noteDbChange; + + ChangeMessageEvent(ChangeMessage message, Change noteDbChange, + Timestamp changeCreatedOn) { + super(message.getPatchSetId(), message.getAuthor(), + message.getWrittenOn(), changeCreatedOn, message.getTag()); + this.message = message; + this.noteDbChange = noteDbChange; + } + + @Override + boolean uniquePerUpdate() { + return true; + } + + @Override + void apply(ChangeUpdate update) throws OrmException { + checkUpdate(update); + update.setChangeMessage(message.getMessage()); + setTopic(update); + } + + private void setTopic(ChangeUpdate update) { + String msg = message.getMessage(); + if (msg == null) { + return; + } + Matcher m = TOPIC_SET_REGEXP.matcher(msg); + if (m.matches()) { + String topic = m.group(1); + update.setTopic(topic); + noteDbChange.setTopic(topic); + return; + } + + m = TOPIC_CHANGED_REGEXP.matcher(msg); + if (m.matches()) { + String topic = m.group(2); + update.setTopic(topic); + noteDbChange.setTopic(topic); + return; + } + + if (TOPIC_REMOVED_REGEXP.matcher(msg).matches()) { + update.setTopic(null); + noteDbChange.setTopic(null); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java similarity index 94% rename from gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java rename to gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java index 679b5e2..ea96012 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.server.notedb; +package com.google.gerrit.server.notedb.rebuild; import com.google.common.collect.ImmutableMultimap; import com.google.common.util.concurrent.ListenableFuture; @@ -20,6 +20,8 @@ import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.notedb.ChangeBundle; +import com.google.gerrit.server.notedb.NoteDbUpdateManager; import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java new file mode 100644 index 0000000..3d240c6 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -0,0 +1,612 @@ +// Copyright (C) 2014 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.notedb.rebuild; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef; +import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS; +import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.toList; + +import com.google.common.base.Optional; +import com.google.common.base.Splitter; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; +import com.google.common.collect.Table; +import com.google.common.primitives.Ints; +import com.google.gerrit.common.FormatUtil; +import com.google.gerrit.common.Nullable; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.ChangeMessage; +import com.google.gerrit.reviewdb.client.PatchLineComment; +import com.google.gerrit.reviewdb.client.PatchLineComment.Status; +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.reviewdb.server.ReviewDbUtil; +import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.PatchLineCommentsUtil; +import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.config.AnonymousCowardName; +import com.google.gerrit.server.git.ChainedReceiveCommands; +import com.google.gerrit.server.notedb.ChangeBundle; +import com.google.gerrit.server.notedb.ChangeBundleReader; +import com.google.gerrit.server.notedb.ChangeDraftUpdate; +import com.google.gerrit.server.notedb.ChangeNoteUtil; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gerrit.server.notedb.NoteDbChangeState; +import com.google.gerrit.server.notedb.NoteDbUpdateManager; +import com.google.gerrit.server.notedb.NoteDbUpdateManager.OpenRepo; +import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result; +import com.google.gerrit.server.notedb.NotesMigration; +import com.google.gerrit.server.notedb.ReviewerStateInternal; +import com.google.gerrit.server.patch.PatchListCache; +import com.google.gerrit.server.project.NoSuchChangeException; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gwtorm.server.AtomicUpdate; +import com.google.gwtorm.server.OrmException; +import com.google.gwtorm.server.SchemaFactory; +import com.google.inject.Inject; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.PrintWriter; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class ChangeRebuilderImpl extends ChangeRebuilder { + private static final Logger log = + LoggerFactory.getLogger(ChangeRebuilderImpl.class); + + /** + * The maximum amount of time between the ReviewDb timestamp of the first and + * last events batched together into a single NoteDb update. + * <p> + * Used to account for the fact that different records with their own + * timestamps (e.g. {@link PatchSetApproval} and {@link ChangeMessage}) + * historically didn't necessarily use the same timestamp, and tended to call + * {@code System.currentTimeMillis()} independently. + */ + public static final long MAX_WINDOW_MS = SECONDS.toMillis(3); + + /** + * The maximum amount of time between two consecutive events to consider them + * to be in the same batch. + */ + static final long MAX_DELTA_MS = SECONDS.toMillis(1); + + private final AccountCache accountCache; + private final ChangeBundleReader bundleReader; + private final ChangeDraftUpdate.Factory draftUpdateFactory; + private final ChangeNoteUtil changeNoteUtil; + private final ChangeUpdate.Factory updateFactory; + private final NoteDbUpdateManager.Factory updateManagerFactory; + private final NotesMigration migration; + private final PatchListCache patchListCache; + private final PersonIdent serverIdent; + private final ProjectCache projectCache; + private final String anonymousCowardName; + + @Inject + ChangeRebuilderImpl(SchemaFactory<ReviewDb> schemaFactory, + AccountCache accountCache, + ChangeBundleReader bundleReader, + ChangeDraftUpdate.Factory draftUpdateFactory, + ChangeNoteUtil changeNoteUtil, + ChangeUpdate.Factory updateFactory, + NoteDbUpdateManager.Factory updateManagerFactory, + NotesMigration migration, + PatchListCache patchListCache, + @GerritPersonIdent PersonIdent serverIdent, + @Nullable ProjectCache projectCache, + @AnonymousCowardName String anonymousCowardName) { + super(schemaFactory); + this.accountCache = accountCache; + this.bundleReader = bundleReader; + this.draftUpdateFactory = draftUpdateFactory; + this.changeNoteUtil = changeNoteUtil; + this.updateFactory = updateFactory; + this.updateManagerFactory = updateManagerFactory; + this.migration = migration; + this.patchListCache = patchListCache; + this.serverIdent = serverIdent; + this.projectCache = projectCache; + this.anonymousCowardName = anonymousCowardName; + } + + @Override + public Result rebuild(ReviewDb db, Change.Id changeId) + throws NoSuchChangeException, IOException, OrmException, + ConfigInvalidException { + db = ReviewDbUtil.unwrapDb(db); + Change change = db.changes().get(changeId); + if (change == null) { + throw new NoSuchChangeException(changeId); + } + try (NoteDbUpdateManager manager = + updateManagerFactory.create(change.getProject())) { + buildUpdates(manager, bundleReader.fromReviewDb(db, changeId)); + return execute(db, changeId, manager); + } + } + + @Override + public Result rebuild(NoteDbUpdateManager manager, + ChangeBundle bundle) throws NoSuchChangeException, IOException, + OrmException, ConfigInvalidException { + Change change = new Change(bundle.getChange()); + buildUpdates(manager, bundle); + return manager.stageAndApplyDelta(change); + } + + @Override + public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId) + throws NoSuchChangeException, IOException, OrmException { + db = ReviewDbUtil.unwrapDb(db); + Change change = db.changes().get(changeId); + if (change == null) { + throw new NoSuchChangeException(changeId); + } + NoteDbUpdateManager manager = + updateManagerFactory.create(change.getProject()); + buildUpdates(manager, bundleReader.fromReviewDb(db, changeId)); + manager.stage(); + return manager; + } + + @Override + public Result execute(ReviewDb db, Change.Id changeId, + NoteDbUpdateManager manager) throws NoSuchChangeException, OrmException, + IOException { + db = ReviewDbUtil.unwrapDb(db); + Change change = db.changes().get(changeId); + if (change == null) { + throw new NoSuchChangeException(changeId); + } + + final String oldNoteDbState = change.getNoteDbState(); + Result r = manager.stageAndApplyDelta(change); + final String newNoteDbState = change.getNoteDbState(); + try { + db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() { + @Override + public Change update(Change change) { + String currNoteDbState = change.getNoteDbState(); + if (Objects.equals(currNoteDbState, newNoteDbState)) { + // Another thread completed the same rebuild we were about to. + throw new AbortUpdateException(); + } else if (!Objects.equals(oldNoteDbState, currNoteDbState)) { + // Another thread updated the state to something else. + throw new ConflictingUpdateException(change, oldNoteDbState); + } + change.setNoteDbState(newNoteDbState); + return change; + } + }); + } catch (ConflictingUpdateException e) { + // Rethrow as an OrmException so the caller knows to use staged results. + // Strictly speaking they are not completely up to date, but result we + // send to the caller is the same as if this rebuild had executed before + // the other thread. + throw new OrmException(e.getMessage()); + } catch (AbortUpdateException e) { + if (NoteDbChangeState.parse(changeId, newNoteDbState).isUpToDate( + manager.getChangeRepo().cmds.getRepoRefCache(), + manager.getAllUsersRepo().cmds.getRepoRefCache())) { + // If the state in ReviewDb matches NoteDb at this point, it means + // another thread successfully completed this rebuild. It's ok to not + // execute the update in this case, since the object referenced in the + // Result was flushed to the repo by whatever thread won the race. + return r; + } + // If the state doesn't match, that means another thread attempted this + // rebuild, but failed. Fall through and try to update the ref again. + } + if (migration.failChangeWrites()) { + // Don't even attempt to execute if read-only, it would fail anyway. But + // do throw an exception to the caller so they know to use the staged + // results instead of reading from the repo. + throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY); + } + manager.execute(); + return r; + } + + @Override + public boolean rebuildProject(ReviewDb db, + ImmutableMultimap<Project.NameKey, Change.Id> allChanges, + Project.NameKey project, Repository allUsersRepo) + throws NoSuchChangeException, IOException, OrmException, + ConfigInvalidException { + checkArgument(allChanges.containsKey(project)); + boolean ok = true; + ProgressMonitor pm = new TextProgressMonitor(new PrintWriter(System.out)); + pm.beginTask( + FormatUtil.elide(project.get(), 50), allChanges.get(project).size()); + try (NoteDbUpdateManager manager = updateManagerFactory.create(project); + ObjectInserter allUsersInserter = allUsersRepo.newObjectInserter(); + RevWalk allUsersRw = new RevWalk(allUsersInserter.newReader())) { + manager.setAllUsersRepo(allUsersRepo, allUsersRw, allUsersInserter, + new ChainedReceiveCommands(allUsersRepo)); + for (Change.Id changeId : allChanges.get(project)) { + try { + buildUpdates(manager, bundleReader.fromReviewDb(db, changeId)); + } catch (NoPatchSetsException e) { + log.warn(e.getMessage()); + } catch (Throwable t) { + log.error("Failed to rebuild change " + changeId, t); + ok = false; + } + pm.update(1); + } + manager.execute(); + } finally { + pm.endTask(); + } + return ok; + } + + private void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle) + throws IOException, OrmException { + manager.setCheckExpectedState(false); + Change change = new Change(bundle.getChange()); + if (bundle.getPatchSets().isEmpty()) { + throw new NoPatchSetsException(change.getId()); + } + + PatchSet.Id currPsId = change.currentPatchSetId(); + // We will rebuild all events, except for draft comments, in buckets based + // on author and timestamp. + List<Event> events = new ArrayList<>(); + Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents = + ArrayListMultimap.create(); + + events.addAll(getHashtagsEvents(change, manager)); + + // Delete ref only after hashtags have been read + deleteChangeMetaRef(change, manager.getChangeRepo().cmds); + deleteDraftRefs(change, manager.getAllUsersRepo()); + + Integer minPsNum = getMinPatchSetNum(bundle); + Map<PatchSet.Id, PatchSetEvent> patchSetEvents = + Maps.newHashMapWithExpectedSize(bundle.getPatchSets().size()); + + for (PatchSet ps : bundle.getPatchSets()) { + if (ps.getId().get() > currPsId.get()) { + log.info( + "Skipping patch set {}, which is higher than current patch set {}", + ps.getId(), currPsId); + continue; + } + PatchSetEvent pse = + new PatchSetEvent(change, ps, manager.getChangeRepo().rw); + patchSetEvents.put(ps.getId(), pse); + events.add(pse); + for (PatchLineComment c : getPatchLineComments(bundle, ps)) { + PatchLineCommentEvent e = + new PatchLineCommentEvent(c, change, ps, patchListCache); + if (c.getStatus() == Status.PUBLISHED) { + events.add(e.addDep(pse)); + } else { + draftCommentEvents.put(c.getAuthor(), e); + } + } + } + + for (PatchSetApproval psa : bundle.getPatchSetApprovals()) { + PatchSetEvent pse = patchSetEvents.get(psa.getPatchSetId()); + if (pse != null) { + events.add(new ApprovalEvent(psa, change.getCreatedOn()).addDep(pse)); + } + } + + for (Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> r : + bundle.getReviewers().asTable().cellSet()) { + events.add(new ReviewerEvent(r, change.getCreatedOn())); + } + + Change noteDbChange = new Change(null, null, null, null, null); + for (ChangeMessage msg : bundle.getChangeMessages()) { + List<Event> msgEvents = parseChangeMessage(msg, change, noteDbChange); + if (msg.getPatchSetId() != null) { + PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId()); + if (pse != null) { + for (Event e : msgEvents) { + e.addDep(pse); + } + } + } + events.addAll(msgEvents); + } + + sortAndFillEvents(change, noteDbChange, events, minPsNum); + + EventList<Event> el = new EventList<>(); + for (Event e : events) { + if (!el.canAdd(e)) { + flushEventsToUpdate(manager, el, change); + checkState(el.canAdd(e)); + } + el.add(e); + } + flushEventsToUpdate(manager, el, change); + + EventList<PatchLineCommentEvent> plcel = new EventList<>(); + for (Account.Id author : draftCommentEvents.keys()) { + for (PatchLineCommentEvent e : + Ordering.natural().sortedCopy(draftCommentEvents.get(author))) { + if (!plcel.canAdd(e)) { + flushEventsToDraftUpdate(manager, plcel, change); + checkState(plcel.canAdd(e)); + } + plcel.add(e); + } + flushEventsToDraftUpdate(manager, plcel, change); + } + } + + private List<Event> parseChangeMessage(ChangeMessage msg, Change change, + Change noteDbChange) { + List<Event> events = new ArrayList<>(2); + events.add(new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn())); + Optional<StatusChangeEvent> sce = + StatusChangeEvent.parseFromMessage(msg, change, noteDbChange); + if (sce.isPresent()) { + events.add(sce.get()); + } + return events; + } + + private static Integer getMinPatchSetNum(ChangeBundle bundle) { + Integer minPsNum = null; + for (PatchSet ps : bundle.getPatchSets()) { + int n = ps.getId().get(); + if (minPsNum == null || n < minPsNum) { + minPsNum = n; + } + } + return minPsNum; + } + + private static List<PatchLineComment> getPatchLineComments(ChangeBundle bundle, + final PatchSet ps) { + return bundle.getPatchLineComments().stream() + .filter(c -> c.getPatchSetId().equals(ps.getId())) + .sorted(PatchLineCommentsUtil.PLC_ORDER) + .collect(toList()); + } + + private void sortAndFillEvents(Change change, Change noteDbChange, + List<Event> events, Integer minPsNum) { + events.add(new FinalUpdatesEvent(change, noteDbChange)); + new EventSorter(events).sort(); + + // Ensure the first event in the list creates the change, setting the author + // and any required footers. + Event first = events.get(0); + if (first instanceof PatchSetEvent && change.getOwner().equals(first.who)) { + ((PatchSetEvent) first).createChange = true; + } else { + events.add(0, new CreateChangeEvent(change, minPsNum)); + } + + // Final pass to correct some inconsistencies. + // + // First, fill in any missing patch set IDs using the latest patch set of + // the change at the time of the event, because NoteDb can't represent + // actions with no associated patch set ID. This workaround is as if a user + // added a ChangeMessage on the change by replying from the latest patch + // set. + // + // Start with the first patch set that actually exists. If there are no + // patch sets at all, minPsNum will be null, so just bail and use 1 as the + // patch set ID. The corresponding patch set won't exist, but this change is + // probably corrupt anyway, as deleting the last draft patch set should have + // deleted the whole change. + // + // Second, ensure timestamps are nondecreasing, by copying the previous + // timestamp if this happens. This assumes that the only way this can happen + // is due to dependency constraints, and it is ok to give an event the same + // timestamp as one of its dependencies. + int ps = firstNonNull(minPsNum, 1); + for (int i = 0; i < events.size(); i++) { + Event e = events.get(i); + if (e.psId == null) { + e.psId = new PatchSet.Id(change.getId(), ps); + } else { + ps = Math.max(ps, e.psId.get()); + } + + if (i > 0) { + Event p = events.get(i - 1); + if (e.when.before(p.when)) { + e.when = p.when; + } + } + } + } + + private void flushEventsToUpdate(NoteDbUpdateManager manager, + EventList<Event> events, Change change) throws OrmException, IOException { + if (events.isEmpty()) { + return; + } + Comparator<String> labelNameComparator; + if (projectCache != null) { + labelNameComparator = projectCache.get(change.getProject()) + .getLabelTypes().nameComparator(); + } else { + // No project cache available, bail and use natural ordering; there's no + // semantic difference anyway difference. + labelNameComparator = Ordering.natural(); + } + ChangeUpdate update = updateFactory.create( + change, + events.getAccountId(), + newAuthorIdent(events), + events.getWhen(), + labelNameComparator); + update.setAllowWriteToNewRef(true); + update.setPatchSetId(events.getPatchSetId()); + update.setTag(events.getTag()); + for (Event e : events) { + e.apply(update); + } + manager.add(update); + events.clear(); + } + + private void flushEventsToDraftUpdate(NoteDbUpdateManager manager, + EventList<PatchLineCommentEvent> events, Change change) + throws OrmException { + if (events.isEmpty()) { + return; + } + ChangeDraftUpdate update = draftUpdateFactory.create( + change, + events.getAccountId(), + newAuthorIdent(events), + events.getWhen()); + update.setPatchSetId(events.getPatchSetId()); + for (PatchLineCommentEvent e : events) { + e.applyDraft(update); + } + manager.add(update); + events.clear(); + } + + private PersonIdent newAuthorIdent(EventList<?> events) { + Account.Id id = events.getAccountId(); + if (id == null) { + return new PersonIdent(serverIdent, events.getWhen()); + } + return changeNoteUtil.newIdent( + accountCache.get(id).getAccount(), events.getWhen(), serverIdent, + anonymousCowardName); + } + + private List<HashtagsEvent> getHashtagsEvents(Change change, + NoteDbUpdateManager manager) throws IOException { + String refName = changeMetaRef(change.getId()); + Optional<ObjectId> old = manager.getChangeRepo().getObjectId(refName); + if (!old.isPresent()) { + return Collections.emptyList(); + } + + RevWalk rw = manager.getChangeRepo().rw; + List<HashtagsEvent> events = new ArrayList<>(); + rw.reset(); + rw.markStart(rw.parseCommit(old.get())); + for (RevCommit commit : rw) { + Account.Id authorId; + try { + authorId = + changeNoteUtil.parseIdent(commit.getAuthorIdent(), change.getId()); + } catch (ConfigInvalidException e) { + continue; // Corrupt data, no valid hashtags in this commit. + } + PatchSet.Id psId = parsePatchSetId(change, commit); + Set<String> hashtags = parseHashtags(commit); + if (authorId == null || psId == null || hashtags == null) { + continue; + } + + Timestamp commitTime = + new Timestamp(commit.getCommitterIdent().getWhen().getTime()); + events.add(new HashtagsEvent(psId, authorId, commitTime, hashtags, + change.getCreatedOn())); + } + return events; + } + + private Set<String> parseHashtags(RevCommit commit) { + List<String> hashtagsLines = commit.getFooterLines(FOOTER_HASHTAGS); + if (hashtagsLines.isEmpty() || hashtagsLines.size() > 1) { + return null; + } + + if (hashtagsLines.get(0).isEmpty()) { + return ImmutableSet.of(); + } + return Sets.newHashSet(Splitter.on(',').split(hashtagsLines.get(0))); + } + + private PatchSet.Id parsePatchSetId(Change change, RevCommit commit) { + List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET); + if (psIdLines.size() != 1) { + return null; + } + Integer psId = Ints.tryParse(psIdLines.get(0)); + if (psId == null) { + return null; + } + return new PatchSet.Id(change.getId(), psId); + } + + private void deleteChangeMetaRef(Change change, ChainedReceiveCommands cmds) + throws IOException { + String refName = changeMetaRef(change.getId()); + Optional<ObjectId> old = cmds.get(refName); + if (old.isPresent()) { + cmds.add(new ReceiveCommand(old.get(), ObjectId.zeroId(), refName)); + } + } + + private void deleteDraftRefs(Change change, OpenRepo allUsersRepo) + throws IOException { + for (Ref r : allUsersRepo.repo.getRefDatabase() + .getRefs(RefNames.refsDraftCommentsPrefix(change.getId())).values()) { + allUsersRepo.cmds.add( + new ReceiveCommand(r.getObjectId(), ObjectId.zeroId(), r.getName())); + } + } + + static void createChange(ChangeUpdate update, Change change) { + update.setSubjectForCommit("Create change"); + update.setChangeId(change.getKey().get()); + update.setBranch(change.getDest().get()); + update.setSubject(change.getOriginalSubject()); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java new file mode 100644 index 0000000..2098727 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java
@@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gwtorm.server.OrmRuntimeException; + +class ConflictingUpdateException extends OrmRuntimeException { + private static final long serialVersionUID = 1L; + + ConflictingUpdateException(Change change, String expectedNoteDbState) { + super(String.format( + "Expected change %s to have noteDbState %s but was %s", + change.getId(), expectedNoteDbState, change.getNoteDbState())); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java new file mode 100644 index 0000000..886f6c4 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java
@@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gwtorm.server.OrmException; + +import java.io.IOException; + +class CreateChangeEvent extends Event { + private final Change change; + + private static PatchSet.Id psId(Change change, Integer minPsNum) { + int n; + if (minPsNum == null) { + // There were no patch sets for the change at all, so something is very + // wrong. Bail and use 1 as the patch set. + n = 1; + } else { + n = minPsNum; + } + return new PatchSet.Id(change.getId(), n); + } + + CreateChangeEvent(Change change, Integer minPsNum) { + super(psId(change, minPsNum), change.getOwner(), change.getCreatedOn(), + change.getCreatedOn(), null); + this.change = change; + } + + @Override + boolean uniquePerUpdate() { + return true; + } + + @Override + void apply(ChangeUpdate update) throws IOException, OrmException { + checkUpdate(update); + ChangeRebuilderImpl.createChange(update, change); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java new file mode 100644 index 0000000..b7b08b0 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
@@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl.MAX_WINDOW_MS; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ComparisonChain; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.reviewdb.server.ReviewDbUtil; +import com.google.gerrit.server.notedb.AbstractChangeUpdate; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gwtorm.server.OrmException; + +import java.io.IOException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +abstract class Event implements Comparable<Event> { + // NOTE: EventList only supports direct subclasses, not an arbitrary + // hierarchy. + + final Account.Id who; + final String tag; + final boolean predatesChange; + final List<Event> deps; + Timestamp when; + PatchSet.Id psId; + + protected Event(PatchSet.Id psId, Account.Id who, Timestamp when, + Timestamp changeCreatedOn, String tag) { + this.psId = psId; + this.who = who; + this.tag = tag; + // Truncate timestamps at the change's createdOn timestamp. + predatesChange = when.before(changeCreatedOn); + this.when = predatesChange ? changeCreatedOn : when; + deps = new ArrayList<>(); + } + + protected void checkUpdate(AbstractChangeUpdate update) { + checkState(Objects.equals(update.getPatchSetId(), psId), + "cannot apply event for %s to update for %s", + update.getPatchSetId(), psId); + checkState(when.getTime() - update.getWhen().getTime() <= MAX_WINDOW_MS, + "event at %s outside update window starting at %s", + when, update.getWhen()); + checkState(Objects.equals(update.getNullableAccountId(), who), + "cannot apply event by %s to update by %s", + who, update.getNullableAccountId()); + } + + Event addDep(Event e) { + deps.add(e); + return this; + } + + /** + * @return whether this event type must be unique per {@link ChangeUpdate}, + * i.e. there may be at most one of this type. + */ + abstract boolean uniquePerUpdate(); + + abstract void apply(ChangeUpdate update) throws OrmException, IOException; + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("psId", psId) + .add("who", who) + .add("when", when) + .toString(); + } + + @Override + public int compareTo(Event other) { + return ComparisonChain.start() + .compareFalseFirst(this.isFinalUpdates(), other.isFinalUpdates()) + .compare(this.when, other.when) + .compareTrueFirst(isPatchSet(), isPatchSet()) + .compareTrueFirst(this.predatesChange, other.predatesChange) + .compare(this.who, other.who, ReviewDbUtil.intKeyOrdering()) + .compare(this.psId, other.psId, + ReviewDbUtil.intKeyOrdering().nullsLast()) + .result(); + } + + private boolean isPatchSet() { + return this instanceof PatchSetEvent; + } + + private boolean isFinalUpdates() { + return this instanceof FinalUpdatesEvent; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java new file mode 100644 index 0000000..398657b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
@@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.PatchSet; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Objects; + +class EventList<E extends Event> extends ArrayList<E> { + private static final long serialVersionUID = 1L; + + private E getLast() { + return get(size() - 1); + } + + private long getLastTime() { + return getLast().when.getTime(); + } + + private long getFirstTime() { + return get(0).when.getTime(); + } + + boolean canAdd(E e) { + if (isEmpty()) { + return true; + } + if (e instanceof FinalUpdatesEvent) { + return false; // FinalUpdatesEvent always gets its own update. + } + + Event last = getLast(); + if (!Objects.equals(e.who, last.who) + || !e.psId.equals(last.psId) + || !Objects.equals(e.tag, last.tag)) { + return false; // Different patch set, author, or tag. + } + + long t = e.when.getTime(); + long tFirst = getFirstTime(); + long tLast = getLastTime(); + checkArgument(t >= tLast, + "event %s is before previous event in list %s", e, last); + if (t - tLast > ChangeRebuilderImpl.MAX_DELTA_MS || t - tFirst > ChangeRebuilderImpl.MAX_WINDOW_MS) { + return false; // Too much time elapsed. + } + + if (!e.uniquePerUpdate()) { + return true; + } + for (Event o : this) { + if (e.getClass() == o.getClass()) { + return false; // Only one event of this type allowed per update. + } + } + + // TODO(dborowitz): Additional heuristics, like keeping events separate if + // they affect overlapping fields within a single entity. + + return true; + } + + Timestamp getWhen() { + return get(0).when; + } + + PatchSet.Id getPatchSetId() { + PatchSet.Id id = checkNotNull(get(0).psId); + for (int i = 1; i < size(); i++) { + checkState(get(i).psId.equals(id), + "mismatched patch sets in EventList: %s != %s", id, get(i).psId); + } + return id; + } + + Account.Id getAccountId() { + Account.Id id = get(0).who; + for (int i = 1; i < size(); i++) { + checkState(Objects.equals(id, get(i).who), + "mismatched users in EventList: %s != %s", id, get(i).who); + } + return id; + } + + String getTag() { + return getLast().tag; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java new file mode 100644 index 0000000..2ab4c00 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java
@@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.SetMultimap; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * Helper to sort a list of events. + * <p> + * Events are sorted in two passes: + * <ol> + * <li>Sort by natural order (timestamp, patch set, author, etc.)</li> + * <li>Postpone any events with dependencies to occur only after all of their + * dependencies, where this violates natural order.</li> + * </ol> + * + * {@link #sort()} modifies the event list in place (similar to {@link + * Collections#sort(List)}), but does not modify any event. In particular, + * events might end up out of order with respect to timestamp; callers are + * responsible for adjusting timestamps later if they prefer monotonicity. + */ +class EventSorter { + private final List<Event> out; + private final LinkedHashSet<Event> sorted; + private ListMultimap<Event, Event> waiting; + private SetMultimap<Event, Event> deps; + + EventSorter(List<Event> events) { + LinkedHashSet<Event> all = new LinkedHashSet<>(events); + out = events; + + for (Event e : events) { + for (Event d : e.deps) { + checkArgument(all.contains(d), "dep %s of %s not in input list", d, e); + } + } + + all.clear(); + sorted = all; // Presized. + } + + void sort() { + // First pass: sort by natural order. + Collections.sort(out); + + // Populate waiting map after initial sort to preserve natural order. + waiting = ArrayListMultimap.create(); + deps = HashMultimap.create(); + for (Event e : out) { + for (Event d : e.deps) { + deps.put(e, d); + waiting.put(d, e); + } + } + + // Second pass: enforce dependencies. + int size = out.size(); + for (Event e : out) { + process(e); + } + checkState(sorted.size() == size, + "event sort expected %s elements, got %s", size, sorted.size()); + + // Modify out in-place a la Collections#sort. + out.clear(); + out.addAll(sorted); + } + + void process(Event e) { + if (sorted.contains(e)) { + return; + } + // If all events that e depends on have been emitted: + // - e can be emitted. + // - remove e from the dependency set of all events waiting on e, and then + // re-process those events in case they can now be emitted. + if (deps.get(e).isEmpty()) { + sorted.add(e); + for (Event w : waiting.get(e)) { + deps.get(w).remove(e); + process(w); + } + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java new file mode 100644 index 0000000..4e82635 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
@@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gwtorm.server.OrmException; + +import java.util.Objects; + +class FinalUpdatesEvent extends Event { + private final Change change; + private final Change noteDbChange; + + FinalUpdatesEvent(Change change, Change noteDbChange) { + super(change.currentPatchSetId(), change.getOwner(), + change.getLastUpdatedOn(), change.getCreatedOn(), null); + this.change = change; + this.noteDbChange = noteDbChange; + } + + @Override + boolean uniquePerUpdate() { + return true; + } + + @SuppressWarnings("deprecation") + @Override + void apply(ChangeUpdate update) throws OrmException { + if (!Objects.equals(change.getTopic(), noteDbChange.getTopic())) { + update.setTopic(change.getTopic()); + } + if (!Objects.equals(change.getStatus(), noteDbChange.getStatus())) { + // TODO(dborowitz): Stamp approximate approvals at this time. + update.fixStatus(change.getStatus()); + } + if (change.getSubmissionId() != null + && noteDbChange.getSubmissionId() == null) { + update.setSubmissionId(change.getSubmissionId()); + } + if (!update.isEmpty()) { + update.setSubjectForCommit("Final NoteDb migration updates"); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java new file mode 100644 index 0000000..21b3b6e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java
@@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gwtorm.server.OrmException; + +import java.sql.Timestamp; +import java.util.Set; + +class HashtagsEvent extends Event { + private final Set<String> hashtags; + + HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when, + Set<String> hashtags, Timestamp changeCreatdOn) { + super(psId, who, when, changeCreatdOn, + // Somewhat confusingly, hashtags do not use the setTag method on + // AbstractChangeUpdate, so pass null as the tag. + null); + this.hashtags = hashtags; + } + + @Override + boolean uniquePerUpdate() { + // Since these are produced from existing commits in the old NoteDb graph, + // we know that there must be one per commit in the rebuilt graph. + return true; + } + + @Override + void apply(ChangeUpdate update) throws OrmException { + update.setHashtags(hashtags); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchLineCommentEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchLineCommentEvent.java new file mode 100644 index 0000000..8d962be --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchLineCommentEvent.java
@@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.PatchLineComment; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.server.PatchLineCommentsUtil; +import com.google.gerrit.server.notedb.ChangeDraftUpdate; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gerrit.server.patch.PatchListCache; +import com.google.gwtorm.server.OrmException; + +class PatchLineCommentEvent extends Event { + public final PatchLineComment c; + private final Change change; + private final PatchSet ps; + private final PatchListCache cache; + + PatchLineCommentEvent(PatchLineComment c, Change change, PatchSet ps, + PatchListCache cache) { + super(PatchLineCommentsUtil.getCommentPsId(c), c.getAuthor(), + c.getWrittenOn(), change.getCreatedOn(), c.getTag()); + this.c = c; + this.change = change; + this.ps = ps; + this.cache = cache; + } + + @Override + boolean uniquePerUpdate() { + return false; + } + + @Override + void apply(ChangeUpdate update) throws OrmException { + checkUpdate(update); + if (c.getRevId() == null) { + setCommentRevId(c, cache, change, ps); + } + update.putComment(c); + } + + void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException { + if (c.getRevId() == null) { + setCommentRevId(c, cache, change, ps); + } + draftUpdate.putComment(c); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java new file mode 100644 index 0000000..c3fb267 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
@@ -0,0 +1,87 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gerrit.server.notedb.PatchSetState; +import com.google.gwtorm.server.OrmException; + +import org.eclipse.jgit.errors.InvalidObjectIdException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevWalk; + +import java.io.IOException; +import java.util.List; + +class PatchSetEvent extends Event { + private final Change change; + private final PatchSet ps; + private final RevWalk rw; + boolean createChange; + + PatchSetEvent(Change change, PatchSet ps, RevWalk rw) { + super(ps.getId(), ps.getUploader(), ps.getCreatedOn(), + change.getCreatedOn(), null); + this.change = change; + this.ps = ps; + this.rw = rw; + } + + @Override + boolean uniquePerUpdate() { + return true; + } + + @Override + void apply(ChangeUpdate update) throws IOException, OrmException { + checkUpdate(update); + if (createChange) { + ChangeRebuilderImpl.createChange(update, change); + } else { + update.setSubject(change.getSubject()); + update.setSubjectForCommit("Create patch set " + ps.getPatchSetId()); + } + setRevision(update, ps); + List<String> groups = ps.getGroups(); + if (!groups.isEmpty()) { + update.setGroups(ps.getGroups()); + } + if (ps.isDraft()) { + update.setPatchSetState(PatchSetState.DRAFT); + } + } + + private void setRevision(ChangeUpdate update, PatchSet ps) + throws IOException { + String rev = ps.getRevision().get(); + String cert = ps.getPushCertificate(); + ObjectId id; + try { + id = ObjectId.fromString(rev); + } catch (InvalidObjectIdException e) { + update.setRevisionForMissingCommit(rev, cert); + return; + } + try { + update.setCommit(rw, id, cert); + } catch (MissingObjectException e) { + update.setRevisionForMissingCommit(rev, cert); + return; + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java new file mode 100644 index 0000000..ef9c5c6 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java
@@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.common.collect.Table; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gerrit.server.notedb.ReviewerStateInternal; +import com.google.gwtorm.server.OrmException; + +import java.io.IOException; +import java.sql.Timestamp; + +class ReviewerEvent extends Event { + private Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer; + + ReviewerEvent( + Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer, + Timestamp changeCreatedOn) { + super( + // Reviewers aren't generally associated with a particular patch set + // (although as an implementation detail they were in ReviewDb). Just + // use the latest patch set at the time of the event. + null, + reviewer.getColumnKey(), reviewer.getValue(), changeCreatedOn, null); + this.reviewer = reviewer; + } + + @Override + boolean uniquePerUpdate() { + return false; + } + + @Override + void apply(ChangeUpdate update) throws IOException, OrmException { + checkUpdate(update); + update.putReviewer(reviewer.getColumnKey(), reviewer.getRowKey()); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java new file mode 100644 index 0000000..29e0868 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java
@@ -0,0 +1,90 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.ChangeMessage; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gwtorm.server.OrmException; + +import java.sql.Timestamp; +import java.util.Map; +import java.util.regex.Pattern; + +class StatusChangeEvent extends Event { + private static final ImmutableMap<Change.Status, Pattern> PATTERNS = + ImmutableMap.of( + Change.Status.ABANDONED, Pattern.compile("^Abandoned(\n.*)*$"), + Change.Status.MERGED, Pattern.compile( + "^Change has been successfully" + + " (merged|cherry-picked|rebased|pushed).*$"), + Change.Status.NEW, Pattern.compile("^Restored(\n.*)*$")); + + static Optional<StatusChangeEvent> parseFromMessage(ChangeMessage message, + Change change, Change noteDbChange) { + String msg = message.getMessage(); + if (msg == null) { + return Optional.absent(); + } + for (Map.Entry<Change.Status, Pattern> e : PATTERNS.entrySet()) { + if (e.getValue().matcher(msg).matches()) { + return Optional.of(new StatusChangeEvent( + message, change, noteDbChange, e.getKey())); + } + } + return Optional.absent(); + } + + private final Change change; + private final Change noteDbChange; + private final Change.Status status; + + private StatusChangeEvent(ChangeMessage message, Change change, + Change noteDbChange, Change.Status status) { + this(message.getPatchSetId(), message.getAuthor(), + message.getWrittenOn(), change, noteDbChange, message.getTag(), + status); + } + + private StatusChangeEvent(PatchSet.Id psId, Account.Id author, + Timestamp when, Change change, Change noteDbChange, + String tag, Change.Status status) { + super(psId, author, when, change.getCreatedOn(), tag); + this.change = change; + this.noteDbChange = noteDbChange; + this.status = status; + } + + @Override + boolean uniquePerUpdate() { + return true; + } + + @SuppressWarnings("deprecation") + @Override + void apply(ChangeUpdate update) throws OrmException { + checkUpdate(update); + update.fixStatus(status); + noteDbChange.setStatus(status); + if (status == Change.Status.MERGED) { + update.setSubmissionId(change.getSubmissionId()); + noteDbChange.setSubmissionId(change.getSubmissionId()); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/ComparisonType.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/ComparisonType.java new file mode 100644 index 0000000..abbb680 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/ComparisonType.java
@@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.patch; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32; +import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class ComparisonType { + + /** 1-based parent */ + private final Integer parentNum; + + private final boolean autoMerge; + + public static ComparisonType againstOtherPatchSet() { + return new ComparisonType(null, false); + } + + public static ComparisonType againstParent(int parentNum) { + return new ComparisonType(parentNum, false); + } + + public static ComparisonType againstAutoMerge() { + return new ComparisonType(null, true); + } + + private ComparisonType(Integer parentNum, boolean autoMerge) { + this.parentNum = parentNum; + this.autoMerge = autoMerge; + } + + public boolean isAgainstParentOrAutoMerge() { + return isAgainstParent() || isAgainstAutoMerge(); + } + + public boolean isAgainstParent() { + return parentNum != null; + } + + public boolean isAgainstAutoMerge() { + return autoMerge; + } + + public int getParentNum() { + checkNotNull(parentNum); + return parentNum; + } + + void writeTo(OutputStream out) throws IOException { + writeVarInt32(out, parentNum != null ? parentNum : 0); + writeVarInt32(out, autoMerge ? 1 : 0); + } + + static ComparisonType readFrom(InputStream in) throws IOException { + int p = readVarInt32(in); + Integer parentNum = p > 0 ? p : null; + boolean autoMerge = readVarInt32(in) != 0; + return new ComparisonType(parentNum, autoMerge); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/MergeListBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/MergeListBuilder.java new file mode 100644 index 0000000..8f54e48 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/MergeListBuilder.java
@@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.patch; + +import com.google.common.collect.ImmutableList; + +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class MergeListBuilder { + public static List<RevCommit> build(RevWalk rw, RevCommit merge, + int uninterestingParent) throws IOException { + rw.reset(); + rw.parseBody(merge); + if (merge.getParentCount() < 2) { + return ImmutableList.of(); + } + + for (int parent = 0; parent < merge.getParentCount(); parent++) { + RevCommit parentCommit = merge.getParent(parent); + rw.parseBody(parentCommit); + if (parent == uninterestingParent - 1) { + rw.markUninteresting(parentCommit); + } else { + rw.markStart(parentCommit); + } + } + + List<RevCommit> result = new ArrayList<>(); + RevCommit c; + while ((c = rw.next()) != null) { + result.add(c); + } + return result; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java index e570b3a..d2a6d2b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
@@ -14,6 +14,8 @@ package com.google.gerrit.server.patch; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.gerrit.common.errors.NoSuchEntityException; import com.google.gerrit.reviewdb.client.Patch; @@ -42,9 +44,8 @@ private Text a; private Text b; - public PatchFile(final Repository repo, final PatchList patchList, - final String fileName) throws MissingObjectException, - IncorrectObjectTypeException, IOException { + public PatchFile(Repository repo, PatchList patchList, String fileName) + throws MissingObjectException, IncorrectObjectTypeException, IOException { this.repo = repo; this.entry = patchList.get(fileName); @@ -53,7 +54,7 @@ final RevCommit bCommit = rw.parseCommit(patchList.getNewId()); if (Patch.COMMIT_MSG.equals(fileName)) { - if (patchList.isAgainstParent()) { + if (patchList.getComparisonType().isAgainstParentOrAutoMerge()) { a = Text.EMPTY; } else { // For the initial commit, we have an empty tree on Side A @@ -66,7 +67,16 @@ aTree = null; bTree = null; + } else if (Patch.MERGE_LIST.equals(fileName)) { + // For the initial commit, we have an empty tree on Side A + RevObject object = rw.parseAny(patchList.getOldId()); + a = object instanceof RevCommit + ? Text.forMergeList(patchList.getComparisonType(), reader, object) + : Text.EMPTY; + b = Text.forMergeList(patchList.getComparisonType(), reader, bCommit); + aTree = null; + bTree = null; } else { if (patchList.getOldId() != null) { aTree = rw.parseTree(patchList.getOldId()); @@ -151,7 +161,7 @@ return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB)); } else if (tw.getFileMode(0).getObjectType() == Constants.OBJ_COMMIT) { String str = "Subproject commit " + ObjectId.toString(tw.getObjectId(0)); - return new Text(str.getBytes()); + return new Text(str.getBytes(UTF_8)); } else { return Text.EMPTY; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java index 2a4afb3..2cfd007 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -58,16 +58,19 @@ @Nullable private transient ObjectId oldId; private transient ObjectId newId; - private transient boolean againstParent; + private transient boolean isMerge; + private transient ComparisonType comparisonType; private transient int insertions; private transient int deletions; private transient PatchListEntry[] patches; - public PatchList(@Nullable final AnyObjectId oldId, final AnyObjectId newId, - final boolean againstParent, final PatchListEntry[] patches) { + public PatchList(@Nullable AnyObjectId oldId, AnyObjectId newId, + boolean isMerge, ComparisonType comparisonType, + PatchListEntry[] patches) { this.oldId = oldId != null ? oldId.copy() : null; this.newId = newId.copy(); - this.againstParent = againstParent; + this.isMerge = isMerge; + this.comparisonType = comparisonType; // We assume index 0 contains the magic commit message entry. if (patches.length > 1) { @@ -97,9 +100,9 @@ return Collections.unmodifiableList(Arrays.asList(patches)); } - /** @return true if {@link #getOldId} is {@link #getNewId}'s ancestor. */ - public boolean isAgainstParent() { - return againstParent; + /** @return the comparison type */ + public ComparisonType getComparisonType() { + return comparisonType; } /** @return total number of new lines added. */ @@ -144,9 +147,12 @@ if (Patch.COMMIT_MSG.equals(fileName)) { return 0; } + if (isMerge && Patch.MERGE_LIST.equals(fileName)) { + return 1; + } int high = patches.length; - int low = 1; + int low = isMerge ? 2 : 1; while (low < high) { final int mid = (low + high) >>> 1; final int cmp = patches[mid].getNewName().compareTo(fileName); @@ -166,7 +172,8 @@ try (DeflaterOutputStream out = new DeflaterOutputStream(buf)) { writeCanBeNull(out, oldId); writeNotNull(out, newId); - writeVarInt32(out, againstParent ? 1 : 0); + writeVarInt32(out, isMerge ? 1 : 0); + comparisonType.writeTo(out); writeVarInt32(out, insertions); writeVarInt32(out, deletions); writeVarInt32(out, patches.length); @@ -182,7 +189,8 @@ try (InflaterInputStream in = new InflaterInputStream(buf)) { oldId = readCanBeNull(in); newId = readNotNull(in); - againstParent = readVarInt32(in) != 0; + isMerge = readVarInt32(in) != 0; + comparisonType = ComparisonType.readFrom(in); insertions = readVarInt32(in); deletions = readVarInt32(in); final int cnt = readVarInt32(in);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java index 43e3dce..22f7bf3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -35,7 +35,7 @@ import java.util.Objects; public class PatchListKey implements Serializable { - public static final long serialVersionUID = 22L; + public static final long serialVersionUID = 24L; public static final BiMap<Whitespace, Character> WHITESPACE_TYPES = ImmutableBiMap.of( Whitespace.IGNORE_NONE, 'N', @@ -138,6 +138,10 @@ n.append(".."); n.append(newId.name()); n.append(" "); + if (parentNum != null) { + n.append(parentNum); + n.append(" "); + } n.append(whitespace.name()); n.append("]"); return n.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java index 2fa43bb..9616fc8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -17,11 +17,10 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toSet; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; -import com.google.common.base.Function; import com.google.common.base.Throwables; -import com.google.common.collect.FluentIterable; import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace; import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.Project; @@ -70,6 +69,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; public class PatchListLoader implements Callable<PatchList> { static final Logger log = LoggerFactory.getLogger(PatchListLoader.class); @@ -155,14 +155,19 @@ if (a == null) { // TODO(sop) Remove this case. - // This is a merge commit, compared to its ancestor. + // This is an octopus merge commit which should be compared against the + // auto-merge. However since we don't support computing the auto-merge + // for octopus merge commits, we fall back to diffing against the first + // parent, even though this wasn't what was requested. // - PatchListEntry[] entries = new PatchListEntry[1]; + ComparisonType comparisonType = ComparisonType.againstParent(1); + PatchListEntry[] entries = new PatchListEntry[2]; entries[0] = newCommitMessage(cmp, reader, null, b); - return new PatchList(a, b, true, entries); + entries[1] = newMergeList(cmp, reader, null, b, comparisonType); + return new PatchList(a, b, true, comparisonType, entries); } - boolean againstParent = isAgainstParent(a, b); + ComparisonType comparisonType = getComparisonType(a, b); RevCommit aCommit = a instanceof RevCommit ? (RevCommit) a : null; RevTree aTree = rw.parseTree(a); @@ -179,22 +184,23 @@ key.getNewId(), key.getWhitespace()); PatchListKey oldKey = PatchListKey.againstDefaultBase( key.getOldId(), key.getWhitespace()); - paths = FluentIterable - .from(patchListCache.get(newKey, project).getPatches()) - .append(patchListCache.get(oldKey, project).getPatches()) - .transform(new Function<PatchListEntry, String>() { - @Override - public String apply(PatchListEntry entry) { - return entry.getNewName(); - } - }) - .toSet(); + paths = Stream.concat( + patchListCache.get(newKey, project).getPatches().stream(), + patchListCache.get(oldKey, project).getPatches().stream()) + .map(PatchListEntry::getNewName) + .collect(toSet()); } int cnt = diffEntries.size(); List<PatchListEntry> entries = new ArrayList<>(); entries.add(newCommitMessage(cmp, reader, - againstParent ? null : aCommit, b)); + comparisonType.isAgainstParentOrAutoMerge() ? null : aCommit, b)); + boolean isMerge = b.getParentCount() > 1; + if (isMerge) { + entries.add(newMergeList(cmp, reader, + comparisonType.isAgainstParentOrAutoMerge() ? null : aCommit, b, + comparisonType)); + } for (int i = 0; i < cnt; i++) { DiffEntry e = diffEntries.get(i); if (paths == null || paths.contains(e.getNewPath()) @@ -208,19 +214,23 @@ entries.add(newEntry(aTree, fh, newSize, newSize - oldSize)); } } - return new PatchList(a, b, againstParent, + return new PatchList(a, b, isMerge, comparisonType, entries.toArray(new PatchListEntry[entries.size()])); } } - private boolean isAgainstParent(RevObject a, RevCommit b) { + private ComparisonType getComparisonType(RevObject a, RevCommit b) { for (int i = 0; i < b.getParentCount(); i++) { if (b.getParent(i).equals(a)) { - return true; + return ComparisonType.againstParent(i + 1); } } - return false; + if (key.getOldId() == null && b.getParentCount() > 0) { + return ComparisonType.againstAutoMerge(); + } + + return ComparisonType.againstOtherPatchSet(); } private static long getFileSize(ObjectReader reader, @@ -282,32 +292,30 @@ return diffFormatter.toFileHeader(diffEntry); } - private PatchListEntry newCommitMessage(final RawTextComparator cmp, - final ObjectReader reader, - final RevCommit aCommit, final RevCommit bCommit) throws IOException { - StringBuilder hdr = new StringBuilder(); - - hdr.append("diff --git"); - if (aCommit != null) { - hdr.append(" a/").append(Patch.COMMIT_MSG); - } else { - hdr.append(" ").append(FileHeader.DEV_NULL); - } - hdr.append(" b/").append(Patch.COMMIT_MSG); - hdr.append("\n"); - - if (aCommit != null) { - hdr.append("--- a/").append(Patch.COMMIT_MSG).append("\n"); - } else { - hdr.append("--- ").append(FileHeader.DEV_NULL).append("\n"); - } - hdr.append("+++ b/").append(Patch.COMMIT_MSG).append("\n"); - - Text aText = - aCommit != null ? Text.forCommit(reader, aCommit) : Text.EMPTY; + private PatchListEntry newCommitMessage(RawTextComparator cmp, + ObjectReader reader, RevCommit aCommit, RevCommit bCommit) + throws IOException { + Text aText = aCommit != null + ? Text.forCommit(reader, aCommit) + : Text.EMPTY; Text bText = Text.forCommit(reader, bCommit); + return createPatchListEntry(cmp, aCommit, aText, bText, Patch.COMMIT_MSG); + } - byte[] rawHdr = hdr.toString().getBytes(UTF_8); + private PatchListEntry newMergeList(RawTextComparator cmp, + ObjectReader reader, RevCommit aCommit, RevCommit bCommit, + ComparisonType comparisonType) throws IOException { + Text aText = aCommit != null + ? Text.forMergeList(comparisonType, reader, aCommit) + : Text.EMPTY; + Text bText = + Text.forMergeList(comparisonType, reader, bCommit); + return createPatchListEntry(cmp, aCommit, aText, bText, Patch.MERGE_LIST); + } + + private static PatchListEntry createPatchListEntry(RawTextComparator cmp, + RevCommit aCommit, Text aText, Text bText, String fileName) { + byte[] rawHdr = getRawHeader(aCommit != null, fileName); byte[] aContent = aText.getContent(); byte[] bContent = bText.getContent(); long size = bContent.length; @@ -319,6 +327,26 @@ return new PatchListEntry(fh, edits, size, sizeDelta); } + private static byte[] getRawHeader(boolean hasA, String fileName) { + StringBuilder hdr = new StringBuilder(); + hdr.append("diff --git"); + if (hasA) { + hdr.append(" a/").append(fileName); + } else { + hdr.append(" ").append(FileHeader.DEV_NULL); + } + hdr.append(" b/").append(fileName); + hdr.append("\n"); + + if (hasA) { + hdr.append("--- a/").append(fileName).append("\n"); + } else { + hdr.append("--- ").append(FileHeader.DEV_NULL).append("\n"); + } + hdr.append("+++ b/").append(fileName).append("\n"); + return hdr.toString().getBytes(UTF_8); + } + private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader, long size, long sizeDelta) { if (aTree == null // want combined diff
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java index 2ccc9f1..fab66cb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java
@@ -21,6 +21,10 @@ super(message); } + public PatchListNotAvailableException(String message, Throwable cause) { + super(message, cause); + } + public PatchListNotAvailableException(Throwable cause) { super(cause); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java index e09d26f..7eee6a3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -66,7 +66,7 @@ private ObjectReader reader; private Change change; private DiffPreferencesInfo diffPrefs; - private boolean againstParent; + private ComparisonType comparisonType; private ObjectId aId; private ObjectId bId; @@ -79,7 +79,8 @@ private int context; @Inject - PatchScriptBuilder(final FileTypeRegistry ftr, final PatchListCache plc) { + PatchScriptBuilder(FileTypeRegistry ftr, + PatchListCache plc) { a = new Side(); b = new Side(); registry = ftr; @@ -106,8 +107,8 @@ } } - void setTrees(final boolean ap, final ObjectId a, final ObjectId b) { - againstParent = ap; + void setTrees(final ComparisonType ct, final ObjectId a, final ObjectId b) { + comparisonType = ct; aId = a; bId = b; } @@ -435,7 +436,8 @@ try { final boolean reuse; if (Patch.COMMIT_MSG.equals(path)) { - if (againstParent && (aId == within || within.equals(aId))) { + if (comparisonType.isAgainstParentOrAutoMerge() + && (aId == within || within.equals(aId))) { id = ObjectId.zeroId(); src = Text.EMPTY; srcContent = Text.NO_BYTES; @@ -453,7 +455,26 @@ } } reuse = false; - + } else if (Patch.MERGE_LIST.equals(path)) { + if (comparisonType.isAgainstParentOrAutoMerge() + && (aId == within || within.equals(aId))) { + id = ObjectId.zeroId(); + src = Text.EMPTY; + srcContent = Text.NO_BYTES; + mode = FileMode.MISSING; + displayMethod = DisplayMethod.NONE; + } else { + id = within; + src = Text.forMergeList(comparisonType, reader, within); + srcContent = src.getContent(); + if (src == Text.EMPTY) { + mode = FileMode.MISSING; + displayMethod = DisplayMethod.NONE; + } else { + mode = FileMode.REGULAR_FILE; + } + } + reuse = false; } else { final TreeWalk tw = find(within);
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 a7d2523..91f8cf8 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
@@ -15,7 +15,6 @@ package com.google.gerrit.server.patch; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.gerrit.server.util.GitUtil.getParent; import com.google.common.base.Optional; import com.google.gerrit.common.Nullable; @@ -214,8 +213,6 @@ bId = toObjectId(psEntityB); if (parentNum < 0) { aId = psEntityA != null ? toObjectId(psEntityA) : null; - } else { - aId = getParent(git, bId, parentNum); } try { @@ -247,7 +244,10 @@ } private PatchListKey keyFor(final Whitespace whitespace) { - return new PatchListKey(aId, bId, whitespace); + if (parentNum < 0) { + return new PatchListKey(aId, bId, whitespace); + } + return PatchListKey.againstParentNum(parentNum + 1, bId, whitespace); } private PatchList listFor(final PatchListKey key) @@ -260,7 +260,7 @@ b.setRepository(git, project); b.setChange(change); b.setDiffPrefs(diffPrefs); - b.setTrees(list.isAgainstParent(), list.getOldId(), list.getNewId()); + b.setTrees(list.getComparisonType(), list.getOldId(), list.getNewId()); return b; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java index 7982479..a84dd92 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
@@ -87,6 +87,36 @@ } } + public static Text forMergeList(ComparisonType comparisonType, + ObjectReader reader, AnyObjectId commitId) throws IOException { + try (RevWalk rw = new RevWalk(reader)) { + RevCommit c = rw.parseCommit(commitId); + StringBuilder b = new StringBuilder(); + switch (c.getParentCount()) { + case 0: + break; + case 1: { + break; + } + default: + int uniterestingParent = comparisonType.isAgainstParent() + ? comparisonType.getParentNum() + : 1; + + b.append("Merge List:\n\n"); + for (RevCommit commit : MergeListBuilder.build(rw, c, + uniterestingParent)) { + b.append("* "); + b.append(reader.abbreviate(commit, 8).name()); + b.append(" "); + b.append(commit.getShortMessage()); + b.append("\n"); + } + } + return new Text(b.toString().getBytes(UTF_8)); + } + } + private static void appendPersonIdent(StringBuilder b, String field, PersonIdent person) { if (person != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java index 1f612a3..be9bbad 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -17,7 +17,6 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.Iterables.transform; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.base.Strings; @@ -59,15 +58,6 @@ public class JarScanner implements PluginContentScanner { private static final int SKIP_ALL = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; - private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA = - new Function<ClassData, ExtensionMetaData>() { - @Override - public ExtensionMetaData apply(ClassData classData) { - return new ExtensionMetaData(classData.className, - classData.annotationValue); - } - }; - private final JarFile jarFile; public JarScanner(Path src) throws IOException { @@ -128,8 +118,11 @@ Collection<ClassData> values = firstNonNull(discoverdData, Collections.<ClassData> emptySet()); - result.put(annotoation, - transform(values, CLASS_DATA_TO_EXTENSION_META_DATA)); + result.put( + annotoation, + transform( + values, + cd -> new ExtensionMetaData(cd.className, cd.annotationValue))); } return result.build(); @@ -307,15 +300,12 @@ public Enumeration<PluginEntry> entries() { return Collections.enumeration(Lists.transform( Collections.list(jarFile.entries()), - new Function<JarEntry, PluginEntry>() { - @Override - public PluginEntry apply(JarEntry jarEntry) { - try { - return resourceOf(jarEntry); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot convert jar entry " - + jarEntry + " to a resource", e); - } + jarEntry -> { + try { + return resourceOf(jarEntry); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot convert jar entry " + + jarEntry + " to a resource", e); } })); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java index cf38310..e89eb7d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
@@ -14,11 +14,10 @@ package com.google.gerrit.server.plugins; -import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.collect.Iterables; +import static java.util.stream.Collectors.joining; import java.nio.file.Path; +import java.util.stream.StreamSupport; class MultipleProvidersForPluginException extends IllegalArgumentException { private static final long serialVersionUID = 1L; @@ -32,14 +31,8 @@ private static String providersListToString( Iterable<ServerPluginProvider> providersHandlers) { - Iterable<String> providerNames = - Iterables.transform(providersHandlers, - new Function<ServerPluginProvider, String>() { - @Override - public String apply(ServerPluginProvider provider) { - return provider.getProviderPluginName(); - } - }); - return Joiner.on(", ").join(providerNames); + return StreamSupport.stream(providersHandlers.spliterator(), false) + .map(ServerPluginProvider::getProviderPluginName) + .collect(joining(", ")); } }
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 e170510..5667003 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
@@ -17,7 +17,6 @@ import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; -import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; @@ -720,12 +719,9 @@ private static Iterable<Path> filterDisabledPlugins( Collection<Path> paths) { - return Iterables.filter(paths, new Predicate<Path>() { - @Override - public boolean apply(Path p) { - return !p.getFileName().toString().endsWith(".disabled"); - } - }); + return Iterables.filter( + paths, + p -> !p.getFileName().toString().endsWith(".disabled")); } public String getGerritPluginName(Path srcPath) {
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 f0c2b78..ce97a83 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
@@ -14,7 +14,6 @@ package com.google.gerrit.server.project; -import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.gerrit.common.errors.PermissionDeniedException; import com.google.gerrit.extensions.restapi.AuthException; @@ -91,14 +90,7 @@ if (commits == null || commits.isEmpty()) { return null; } - - return Lists.transform(commits, - new Function<ObjectId, String>() { - @Override - public String apply(ObjectId id) { - return id.getName(); - } - }); + return Lists.transform(commits, ObjectId::getName); } public static class BanResultInfo {
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 9086b6a..d18ee66 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
@@ -269,7 +269,7 @@ /** Can this user rebase this change? */ public boolean canRebase(ReviewDb db) throws OrmException { - return (isOwner() || getRefControl().canSubmit() + return (isOwner() || getRefControl().canSubmit(isOwner()) || getRefControl().canRebase()) && !isPatchSetLocked(db); } @@ -352,6 +352,16 @@ return false; } + /** Is this user assigned to this change? */ + public boolean isAssignee() { + Account.Id currentAssignee = notes.getAssignee(); + if (currentAssignee != null && getUser().isIdentifiedUser()) { + Account.Id id = getUser().getAccountId(); + return id.equals(currentAssignee); + } + return false; + } + /** Is this user a reviewer for the change? */ public boolean isReviewer(ReviewDb db) throws OrmException { return isReviewer(db, null); @@ -414,6 +424,13 @@ return getRefControl().canForceEditTopicName(); } + public boolean canEditAssignee() { + return isOwner() + || getProjectControl().isOwner() + || getRefControl().canEditAssignee() + || isAssignee(); + } + /** Can this user edit the hashtag name? */ public boolean canEditHashtags() { return isOwner() // owner (aka creator) of the change can edit hashtags @@ -424,7 +441,7 @@ } public boolean canSubmit() { - return getRefControl().canSubmit(); + return getRefControl().canSubmit(isOwner()); } public boolean canSubmitAs() {
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 446fa72..9e090c4 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
@@ -116,7 +116,7 @@ if (isSigned) { throw new MethodNotAllowedException( "Cannot create signed tag \"" + ref + "\""); - } else if (isAnnotated && !refControl.canPerform(Permission.PUSH_TAG)) { + } else if (isAnnotated && !refControl.canPerform(Permission.CREATE_TAG)) { throw new AuthException("Cannot create annotated tag \"" + ref + "\""); } else if (!refControl.canPerform(Permission.CREATE)) { throw new AuthException("Cannot create tag \"" + ref + "\"");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java index 47942be..82ea155 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
@@ -14,16 +14,39 @@ package com.google.gerrit.server.project; +import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestResource; import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.TypeLiteral; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; + +import java.io.IOException; public class FileResource implements RestResource { public static final TypeLiteral<RestView<FileResource>> FILE_KIND = new TypeLiteral<RestView<FileResource>>() {}; + public static FileResource create(GitRepositoryManager repoManager, + ProjectControl project, ObjectId rev, String path) + throws ResourceNotFoundException, IOException { + try (Repository repo = + repoManager.openRepository(project.getProject().getNameKey()); + RevWalk rw = new RevWalk(repo)) { + RevTree tree = rw.parseTree(rev); + if (TreeWalk.forPath(repo, path, tree) != null) { + return new FileResource(project, rev, path); + } + } + throw new ResourceNotFoundException(IdString.fromDecoded(path)); + } + private final ProjectControl project; private final ObjectId rev; private final String path;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java index d0460d5..dcb8747 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
@@ -19,19 +19,25 @@ import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.jgit.lib.ObjectId; +import java.io.IOException; + @Singleton public class FilesCollection implements ChildCollection<BranchResource, FileResource> { private final DynamicMap<RestView<FileResource>> views; + private final GitRepositoryManager repoManager; @Inject - FilesCollection(DynamicMap<RestView<FileResource>> views) { + FilesCollection(DynamicMap<RestView<FileResource>> views, + GitRepositoryManager repoManager) { this.views = views; + this.repoManager = repoManager; } @Override @@ -40,11 +46,10 @@ } @Override - public FileResource parse(BranchResource parent, IdString id) { - return new FileResource( - parent.getControl(), - ObjectId.fromString(parent.getRevision()), - id.get()); + public FileResource parse(BranchResource parent, IdString id) + throws ResourceNotFoundException, IOException { + return FileResource.create(repoManager, parent.getControl(), + ObjectId.fromString(parent.getRevision()), id.get()); } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java index 8e0aab8..0f44a48 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
@@ -19,17 +19,24 @@ import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.reviewdb.client.Patch; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; import com.google.inject.Singleton; +import java.io.IOException; + @Singleton public class FilesInCommitCollection implements ChildCollection<CommitResource, FileResource> { private final DynamicMap<RestView<FileResource>> views; + private final GitRepositoryManager repoManager; @Inject - FilesInCommitCollection(DynamicMap<RestView<FileResource>> views) { + FilesInCommitCollection(DynamicMap<RestView<FileResource>> views, + GitRepositoryManager repoManager) { this.views = views; + this.repoManager = repoManager; } @Override @@ -39,8 +46,13 @@ @Override public FileResource parse(CommitResource parent, IdString id) - throws ResourceNotFoundException { - return new FileResource(parent.getProject(), parent.getCommit(), id.get()); + throws ResourceNotFoundException, IOException { + if (Patch.isMagic(id.get())) { + return new FileResource(parent.getProject(), parent.getCommit(), + id.get()); + } + return FileResource.create(repoManager, parent.getProject(), + parent.getCommit(), id.get()); } @Override
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 b957ba1..8718a9b 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
@@ -14,7 +14,6 @@ package com.google.gerrit.server.project; -import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.gerrit.extensions.common.GitPerson; import com.google.gerrit.extensions.restapi.AuthException; @@ -89,8 +88,8 @@ limit > 0 ? r.getReverseEntries(limit) : r.getReverseEntries(); } else { entries = limit > 0 - ? new ArrayList<ReflogEntry>(limit) - : new ArrayList<ReflogEntry>(); + ? new ArrayList<>(limit) + : new ArrayList<>(); for (ReflogEntry e : r.getReverseEntries()) { Timestamp timestamp = new Timestamp(e.getWho().getWhen().getTime()); if ((from == null || from.before(timestamp)) && @@ -102,12 +101,7 @@ } } } - return Lists.transform(entries, new Function<ReflogEntry, ReflogEntryInfo>() { - @Override - public ReflogEntryInfo apply(ReflogEntry e) { - return new ReflogEntryInfo(e); - } - }); + return Lists.transform(entries, ReflogEntryInfo::new); } }
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 bf17a37..1ea0c62 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
@@ -16,7 +16,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -445,13 +444,8 @@ } else if (matchSubstring != null) { checkMatchOptions(matchPrefix == null && matchRegex == null); return Iterables.filter(projectCache.all(), - new Predicate<Project.NameKey>() { - @Override - public boolean apply(Project.NameKey in) { - return in.get().toLowerCase(Locale.US) - .contains(matchSubstring.toLowerCase(Locale.US)); - } - }); + p -> p.get().toLowerCase(Locale.US) + .contains(matchSubstring.toLowerCase(Locale.US))); } else if (matchRegex != null) { checkMatchOptions(matchPrefix == null && matchSubstring == null); RegexListSearcher<Project.NameKey> searcher;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java index a862ac2..ce1bab4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -18,8 +18,10 @@ import static com.google.gerrit.server.project.RefPattern.isRE; import com.google.auto.value.AutoValue; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.Permission; @@ -116,6 +118,8 @@ HashMap<String, List<PermissionRule>> permissions = new HashMap<>(); HashMap<String, List<PermissionRule>> overridden = new HashMap<>(); Map<PermissionRule, ProjectRef> ruleProps = Maps.newIdentityHashMap(); + Multimap<Project.NameKey, String> exclusivePermissionsByProject = + ArrayListMultimap.create(); for (AccessSection section : sections) { Project.NameKey project = sectionToProject.get(section); for (Permission permission : section.getPermissions()) { @@ -126,7 +130,8 @@ SeenRule s = SeenRule.create(section, permission, rule); boolean addRule; if (rule.isBlock()) { - addRule = true; + addRule = !exclusivePermissionsByProject.containsEntry(project, + permission.getName()); } else { addRule = seen.add(s) && !rule.isDeny() && !exclusivePermissionExists; } @@ -150,6 +155,7 @@ } if (permission.getExclusiveGroup()) { + exclusivePermissionsByProject.put(project, permission.getName()); exclusiveGroupPermissions.add(permission.getName()); } }
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 22e5d69..d9cc59c 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
@@ -307,8 +307,9 @@ /** Is this user a project owner? Ownership does not imply {@link #isVisible()} */ public boolean isOwner() { - return isDeclaredOwner() - || user.getCapabilities().canAdministrateServer(); + return (isDeclaredOwner() + && !controlForRef("refs/*").isBlocked(Permission.OWNER)) + || user.getCapabilities().canAdministrateServer(); } private boolean isDeclaredOwner() { @@ -327,8 +328,8 @@ /** @return true if the user can upload to at least one reference */ public Capable canPushToAtLeastOneRef() { - if (! canPerformOnAnyRef(Permission.PUSH) && - ! canPerformOnAnyRef(Permission.PUSH_TAG)) { + if (!canPerformOnAnyRef(Permission.PUSH) && + !canPerformOnAnyRef(Permission.CREATE_TAG)) { String pName = state.getProject().getName(); return new Capable("Upload denied for project '" + pName + "'"); }
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 68d236e..f4ef129 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
@@ -17,7 +17,7 @@ import static com.google.gerrit.common.data.PermissionRule.Action.ALLOW; import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -72,6 +72,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** Cached information on a project. */ public class ProjectState { @@ -365,8 +366,8 @@ * from the immediate parent of this project and progresses up the * hierarchy to All-Projects. */ - public Iterable<ProjectState> parents() { - return Iterables.skip(tree(), 1); + public FluentIterable<ProjectState> parents() { + return FluentIterable.from(tree()).skip(1); } public boolean isAllProjects() { @@ -378,75 +379,35 @@ } public boolean isUseContributorAgreements() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getUseContributorAgreements(); - } - }); + return getInheritableBoolean(Project::getUseContributorAgreements); } public boolean isUseContentMerge() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getUseContentMerge(); - } - }); + return getInheritableBoolean(Project::getUseContentMerge); } public boolean isUseSignedOffBy() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getUseSignedOffBy(); - } - }); + return getInheritableBoolean(Project::getUseSignedOffBy); } public boolean isRequireChangeID() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getRequireChangeID(); - } - }); + return getInheritableBoolean(Project::getRequireChangeID); } public boolean isCreateNewChangeForAllNotInTarget() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getCreateNewChangeForAllNotInTarget(); - } - }); + return getInheritableBoolean(Project::getCreateNewChangeForAllNotInTarget); } public boolean isEnableSignedPush() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getEnableSignedPush(); - } - }); + return getInheritableBoolean(Project::getEnableSignedPush); } public boolean isRequireSignedPush() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getRequireSignedPush(); - } - }); + return getInheritableBoolean(Project::getRequireSignedPush); } public boolean isRejectImplicitMerges() { - return getInheritableBoolean(new Function<Project, InheritableBoolean>() { - @Override - public InheritableBoolean apply(Project input) { - return input.getRejectImplicitMerges(); - } - }); + return getInheritableBoolean(Project::getRejectImplicitMerges); } public LabelTypes getLabelTypes() { @@ -551,7 +512,8 @@ return Files.exists(p) ? new String(Files.readAllBytes(p), UTF_8) : null; } - private boolean getInheritableBoolean(Function<Project, InheritableBoolean> func) { + private boolean getInheritableBoolean( + Function<Project, InheritableBoolean> func) { for (ProjectState s : tree()) { switch (func.apply(s.getProject())) { case TRUE:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java index e06fb86..52bbdf3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.project; +import com.google.gerrit.extensions.api.projects.BranchInfo; import com.google.gerrit.extensions.api.projects.BranchInput; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestModifyView; @@ -23,7 +24,7 @@ public class PutBranch implements RestModifyView<BranchResource, BranchInput> { @Override - public Object apply(BranchResource rsrc, BranchInput input) + public BranchInfo apply(BranchResource rsrc, BranchInput input) throws ResourceConflictException { throw new ResourceConflictException("Branch \"" + rsrc.getBranchInfo().ref + "\" already exists");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutTag.java index a87882e..1be4b0e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutTag.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutTag.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.project; +import com.google.gerrit.extensions.api.projects.TagInfo; import com.google.gerrit.extensions.api.projects.TagInput; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestModifyView; @@ -21,7 +22,7 @@ public class PutTag implements RestModifyView<TagResource, TagInput> { @Override - public Object apply(TagResource resource, TagInput input) + public TagInfo apply(TagResource resource, TagInput input) throws ResourceConflictException { throw new ResourceConflictException("Tag \"" + resource.getTagInfo().ref + "\" already exists");
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 ad41522..3314309 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
@@ -171,7 +171,7 @@ } /** @return true if this user can submit patch sets to this ref */ - public boolean canSubmit() { + public boolean canSubmit(boolean isChangeOwner) { if (RefNames.REFS_CONFIG.equals(refName)) { // Always allow project owners to submit configuration changes. // Submitting configuration changes modifies the access control @@ -180,7 +180,7 @@ // granting of powers beyond submitting to the configuration. return projectControl.isOwner(); } - return canPerform(Permission.SUBMIT) + return canPerform(Permission.SUBMIT, isChangeOwner) && canWrite(); } @@ -213,7 +213,27 @@ /** @return true if the user can rewind (force push) the reference. */ public boolean canForceUpdate() { - return (canPushWithForce() || canDelete()) && canWrite(); + if (!canWrite()) { + return false; + } + + if (canPushWithForce()) { + return true; + } + + switch (getUser().getAccessPath()) { + case GIT: + return false; + + case JSON_RPC: + case REST_API: + case SSH_COMMAND: + case UNKNOWN: + case WEB_BROWSER: + default: + return getUser().getCapabilities().canAdministrateServer() + || (isOwner() && !isForceBlocked(Permission.PUSH)); + } } public boolean canWrite() { @@ -251,43 +271,13 @@ if (!canWrite()) { return false; } - boolean owner; - boolean admin; - switch (getUser().getAccessPath()) { - case REST_API: - case JSON_RPC: - case UNKNOWN: - owner = isOwner(); - admin = getUser().getCapabilities().canAdministrateServer(); - break; - - case GIT: - case SSH_COMMAND: - case WEB_BROWSER: - default: - owner = false; - admin = false; - } if (object instanceof RevCommit) { - if (admin || (owner && !isBlocked(Permission.CREATE))) { - // Admin or project owner; bypass visibility check. - return true; - } else if (!canPerform(Permission.CREATE)) { + if (!canPerform(Permission.CREATE)) { // No create permissions. return false; - } else if (canUpdate()) { - // If the user has push permissions, they can create the ref regardless - // of whether they are pushing any new objects along with the create. - return true; - } else if (isMergedIntoBranchOrTag(db, repo, (RevCommit) object)) { - // If the user has no push permissions, check whether the object is - // merged into a branch or tag readable by this user. If so, they are - // not effectively "pushing" more objects, so they can create the ref - // even if they don't have push permission. - return true; } - return false; + return canCreateCommit(db, repo, (RevCommit) object); } else if (object instanceof RevTag) { final RevTag tag = (RevTag) object; try (RevWalk rw = new RevWalk(repo)) { @@ -307,7 +297,18 @@ } else { valid = false; } - if (!valid && !owner && !canForgeCommitter()) { + if (!valid && !canForgeCommitter()) { + return false; + } + } + + RevObject tagObject = tag.getObject(); + if (tagObject instanceof RevCommit) { + if (!canCreateCommit(db, repo, (RevCommit) tagObject)) { + return false; + } + } else { + if (!canCreate(db, repo, tagObject)) { return false; } } @@ -316,14 +317,30 @@ // than if it doesn't have a PGP signature. // if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) { - return owner || canPerform(Permission.PUSH_SIGNED_TAG); + return canPerform(Permission.CREATE_SIGNED_TAG); } - return owner || canPerform(Permission.PUSH_TAG); + return canPerform(Permission.CREATE_TAG); } else { return false; } } + private boolean canCreateCommit(ReviewDb db, Repository repo, + RevCommit commit) { + if (canUpdate()) { + // If the user has push permissions, they can create the ref regardless + // of whether they are pushing any new objects along with the create. + return true; + } else if (isMergedIntoBranchOrTag(db, repo, commit)) { + // If the user has no push permissions, check whether the object is + // merged into a branch or tag readable by this user. If so, they are + // not effectively "pushing" more objects, so they can create the ref + // even if they don't have push permission. + return true; + } + return false; + } + private boolean isMergedIntoBranchOrTag(ReviewDb db, Repository repo, RevCommit commit) { try (RevWalk rw = new RevWalk(repo)) { @@ -359,7 +376,7 @@ switch (getUser().getAccessPath()) { case GIT: - return canPushWithForce(); + return canPushWithForce() || canPerform(Permission.DELETE); case JSON_RPC: case REST_API: @@ -369,7 +386,8 @@ default: return getUser().getCapabilities().canAdministrateServer() || (isOwner() && !isForceBlocked(Permission.PUSH)) - || canPushWithForce(); + || canPushWithForce() + || canPerform(Permission.DELETE); } } @@ -429,6 +447,10 @@ return canPerform(Permission.EDIT_HASHTAGS); } + public boolean canEditAssignee() { + return canPerform(Permission.EDIT_ASSIGNEE); + } + /** @return true if this user can force edit topic names. */ public boolean canForceEditTopicName() { return canForcePerform(Permission.EDIT_TOPIC_NAME); @@ -531,16 +553,21 @@ /** True if the user has this permission. Works only for non labels. */ boolean canPerform(String permissionName) { - return doCanPerform(permissionName, false); + return canPerform(permissionName, false); + } + + boolean canPerform(String permissionName, boolean isChangeOwner) { + return doCanPerform(permissionName, isChangeOwner, false); } /** True if the user is blocked from using this permission. */ public boolean isBlocked(String permissionName) { - return !doCanPerform(permissionName, true); + return !doCanPerform(permissionName, false, true); } - private boolean doCanPerform(String permissionName, boolean blockOnly) { - List<PermissionRule> access = access(permissionName); + private boolean doCanPerform(String permissionName, boolean isChangeOwner, + boolean blockOnly) { + List<PermissionRule> access = access(permissionName, isChangeOwner); List<PermissionRule> overridden = relevant.getOverridden(permissionName); Set<ProjectRef> allows = new HashSet<>(); Set<ProjectRef> blocks = new HashSet<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java index cda548a..594763e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java
@@ -20,7 +20,9 @@ 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.Response; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.server.project.DashboardsCollection.DashboardInfo; import com.google.gerrit.server.project.SetDashboard.Input; import com.google.inject.Inject; import com.google.inject.Provider; @@ -44,7 +46,7 @@ } @Override - public Object apply(DashboardResource resource, Input input) + public Response<DashboardInfo> apply(DashboardResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException, MethodNotAllowedException, ResourceNotFoundException, IOException { if (resource.isProjectDefault()) {
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 01aacfb..cc215d2 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
@@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.MoreObjects; -import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.gerrit.extensions.restapi.AuthException; @@ -124,13 +123,10 @@ + " not found"); } - if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() { - @Override - public boolean apply(ProjectState input) { - return input.getProject().getNameKey() - .equals(ctl.getProject().getNameKey()); - } - }).isPresent()) { + if (Iterables.tryFind(parent.tree(), p -> { + return p.getProject().getNameKey() + .equals(ctl.getProject().getNameKey()); + }).isPresent()) { throw new ResourceConflictException("cycle exists between " + ctl.getProject().getName() + " and " + parent.getProject().getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java index 5d0f4f1..b0c521b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -121,8 +121,9 @@ } /** - * @param fast if true, infer label information from rules rather than reading - * from project config. + * @param fast if true assume reviewers are permitted to use label values + * currently stored on the change. Fast mode bypasses some reviewer + * permission checks. * @return this */ public SubmitRuleEvaluator setFastEvalLabels(boolean fast) { @@ -201,7 +202,7 @@ initPatchSet(); } catch (OrmException e) { return ruleError("Error looking up patch set " - + control.getChange().currentPatchSetId()); + + control.getChange().currentPatchSetId(), e); } if (patchSet.isDraft()) { return cannotSubmitDraft(); @@ -372,7 +373,7 @@ initPatchSet(); } catch (OrmException e) { return typeError("Error looking up patch set " - + control.getChange().currentPatchSetId()); + + control.getChange().currentPatchSetId(), e); } try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java index 168be5d..9f0bf89 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
@@ -16,7 +16,6 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -157,12 +156,7 @@ private Iterable<T> buffer(ResultSet<T> scanner) { return FluentIterable.from(Iterables.partition(scanner, 50)) - .transformAndConcat(new Function<List<T>, List<T>>() { - @Override - public List<T> apply(List<T> buffer) { - return transformBuffer(buffer); - } - }); + .transformAndConcat(this::transformBuffer); } protected List<T> transformBuffer(List<T> buffer) throws OrmRuntimeException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java index 0288cb2..40fb3b6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -14,7 +14,6 @@ package com.google.gerrit.server.query.account; -import com.google.common.base.Function; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.common.primitives.Ints; @@ -124,13 +123,9 @@ public Predicate<AccountState> defaultQuery(String query) { return Predicate.and( - Lists.transform(Splitter.on(' ').omitEmptyStrings().splitToList(query), - new Function<String, Predicate<AccountState>>() { - @Override - public Predicate<AccountState> apply(String s) { - return defaultField(s); - } - })); + Lists.transform( + Splitter.on(' ').omitEmptyStrings().splitToList(query), + this::defaultField)); } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java index a260d02..5560b86 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
@@ -24,6 +24,7 @@ import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; @@ -43,6 +44,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.AnonymousUser; import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.ChangeMessagesUtil; import com.google.gerrit.server.CurrentUser; @@ -307,6 +309,7 @@ return cd; } + private boolean lazyLoad = true; private final ReviewDb db; private final GitRepositoryManager repoManager; private final ChangeControl.GenericFactory changeControlFactory; @@ -548,6 +551,11 @@ this.project = null; } + public ChangeData setLazyLoad(boolean load) { + lazyLoad = load; + return this; + } + public ReviewDb db() { return db; } @@ -568,10 +576,7 @@ public List<String> currentFilePaths() throws OrmException { PatchSet ps = currentPatchSet(); - if (ps == null) { - return null; - } - return filePaths(currentPatchSet); + return ps != null ? filePaths(ps) : null; } public List<String> filePaths(PatchSet ps) throws OrmException { @@ -586,13 +591,15 @@ Optional<PatchList> p = getPatchList(c, ps); if (!p.isPresent()) { List<String> emptyFileList = Collections.emptyList(); - files.put(ps.getPatchSetId(), emptyFileList); + if (lazyLoad) { + files.put(ps.getPatchSetId(), emptyFileList); + } return emptyFileList; } r = new ArrayList<>(p.get().getPatches().size()); for (PatchListEntry e : p.get().getPatches()) { - if (Patch.COMMIT_MSG.equals(e.getNewName())) { + if (Patch.isMagic(e.getNewName())) { continue; } switch (e.getChangeType()) { @@ -624,6 +631,9 @@ } Optional<PatchList> r = patchLists.get(psId); if (r == null) { + if (!lazyLoad) { + return Optional.absent(); + } try { r = Optional.of(patchListCache.get(c, ps)); } catch (PatchListNotAvailableException e) { @@ -653,6 +663,9 @@ public Optional<ChangedLines> changedLines() throws OrmException { if (changedLines == null) { + if (!lazyLoad) { + return Optional.absent(); + } changedLines = computeChangedLines(); } return changedLines; @@ -703,10 +716,7 @@ public ChangeControl changeControl(CurrentUser user) throws OrmException { if (changeControl != null) { CurrentUser oldUser = user; - // TODO(dborowitz): This is a hack; general CurrentUser equality would be - // better. - if (user.isIdentifiedUser() && oldUser.isIdentifiedUser() - && user.getAccountId().equals(oldUser.getAccountId())) { + if (sameUser(user, oldUser)) { return changeControl; } throw new IllegalStateException( @@ -725,13 +735,26 @@ return changeControl; } + private static boolean sameUser(CurrentUser a, CurrentUser b) { + // TODO(dborowitz): This is a hack; general CurrentUser equality would be + // better. + if (a.isInternalUser() && b.isInternalUser()) { + return true; + } else if (a instanceof AnonymousUser && b instanceof AnonymousUser) { + return true; + } else if (a.isIdentifiedUser() && b.isIdentifiedUser()) { + return a.getAccountId().equals(b.getAccountId()); + } + return false; + } + void cacheVisibleTo(ChangeControl ctl) { visibleTo = ctl.getUser(); changeControl = ctl; } public Change change() throws OrmException { - if (change == null) { + if (change == null && lazyLoad) { reloadChange(); } return change; @@ -751,11 +774,15 @@ if (change == null) { throw new OrmException("Unable to load change " + legacyId); } + setPatchSets(null); return change; } public ChangeNotes notes() throws OrmException { if (notes == null) { + if (!lazyLoad) { + throw new OrmException("ChangeNotes not available, lazyLoad = false"); + } notes = notesFactory.create(db, project(), legacyId); } return notes; @@ -780,12 +807,23 @@ public List<PatchSetApproval> currentApprovals() throws OrmException { if (currentApprovals == null) { + if (!lazyLoad) { + return Collections.emptyList(); + } Change c = change(); if (c == null) { currentApprovals = Collections.emptyList(); } else { - currentApprovals = ImmutableList.copyOf(approvalsUtil.byPatchSet( - db, changeControl(), c.currentPatchSetId())); + try { + currentApprovals = ImmutableList.copyOf(approvalsUtil.byPatchSet( + db, changeControl(), c.currentPatchSetId())); + } catch (OrmException e) { + if (e.getCause() instanceof NoSuchChangeException) { + currentApprovals = Collections.emptyList(); + } else { + throw e; + } + } } } return currentApprovals; @@ -866,14 +904,11 @@ * @throws OrmException an error occurred reading the database. */ public Collection<PatchSet> visiblePatchSets() throws OrmException { - Predicate<PatchSet> predicate = new Predicate<PatchSet>() { - @Override - public boolean apply(PatchSet input) { - try { - return changeControl().isPatchVisible(input, db); - } catch (OrmException e) { - return false; - } + Predicate<PatchSet> predicate = ps -> { + try { + return changeControl().isPatchVisible(ps, db); + } catch (OrmException e) { + return false; } }; return FluentIterable.from(patchSets()).filter(predicate).toList(); @@ -908,6 +943,9 @@ public ListMultimap<PatchSet.Id, PatchSetApproval> approvals() throws OrmException { if (allApprovals == null) { + if (!lazyLoad) { + return ImmutableListMultimap.of(); + } allApprovals = approvalsUtil.byChange(db, notes()); } return allApprovals; @@ -929,6 +967,9 @@ public ReviewerSet reviewers() throws OrmException { if (reviewers == null) { + if (!lazyLoad) { + return ReviewerSet.empty(); + } reviewers = approvalsUtil.getReviewers(notes(), approvals().values()); } return reviewers; @@ -944,6 +985,9 @@ public List<ReviewerStatusUpdate> reviewerUpdates() throws OrmException { if (reviewerUpdates == null) { + if (!lazyLoad) { + return Collections.emptyList(); + } reviewerUpdates = approvalsUtil.getReviewerUpdates(notes()); } return reviewerUpdates; @@ -960,6 +1004,9 @@ public Collection<PatchLineComment> publishedComments() throws OrmException { if (publishedComments == null) { + if (!lazyLoad) { + return Collections.emptyList(); + } publishedComments = plcUtil.publishedByChange(db, notes()); } return publishedComments; @@ -968,6 +1015,9 @@ public List<ChangeMessage> messages() throws OrmException { if (messages == null) { + if (!lazyLoad) { + return Collections.emptyList(); + } messages = cmUtil.byChange(db, notes()); } return messages; @@ -1001,10 +1051,21 @@ if (c.getStatus() == Change.Status.MERGED) { mergeable = true; } else { - PatchSet ps = currentPatchSet(); - if (ps == null || !changeControl().isPatchVisible(ps, db)) { + if (!lazyLoad) { return null; } + PatchSet ps = currentPatchSet(); + try { + if (ps == null || !changeControl().isPatchVisible(ps, db)) { + return null; + } + } catch (OrmException e) { + if (e.getCause() instanceof NoSuchChangeException) { + return null; + } + throw e; + } + try (Repository repo = repoManager.openRepository(project())) { Ref ref = repo.getRefDatabase().exactRef(c.getDest().get()); SubmitTypeRecord str = submitTypeRecord(); @@ -1029,6 +1090,9 @@ public Set<Account.Id> editsByUser() throws OrmException { if (editsByUser == null) { + if (!lazyLoad) { + return Collections.emptySet(); + } Change c = change(); if (c == null) { return Collections.emptySet(); @@ -1051,6 +1115,9 @@ public Set<Account.Id> draftsByUser() throws OrmException { if (draftsByUser == null) { + if (!lazyLoad) { + return Collections.emptySet(); + } Change c = change(); if (c == null) { return Collections.emptySet(); @@ -1065,6 +1132,9 @@ public Set<Account.Id> reviewedBy() throws OrmException { if (reviewedBy == null) { + if (!lazyLoad) { + return Collections.emptySet(); + } Change c = change(); if (c == null) { return Collections.emptySet(); @@ -1094,6 +1164,9 @@ public Set<String> hashtags() throws OrmException { if (hashtags == null) { + if (!lazyLoad) { + return Collections.emptySet(); + } hashtags = notes().getHashtags(); } return hashtags; @@ -1106,6 +1179,9 @@ @Deprecated public Set<Account.Id> starredBy() throws OrmException { if (starredByUser == null) { + if (!lazyLoad) { + return Collections.emptySet(); + } starredByUser = checkNotNull(starredChangesUtil).byChange( legacyId, StarredChangesUtil.DEFAULT_LABEL); } @@ -1119,6 +1195,9 @@ public ImmutableMultimap<Account.Id, String> stars() throws OrmException { if (stars == null) { + if (!lazyLoad) { + return ImmutableMultimap.of(); + } stars = checkNotNull(starredChangesUtil).byChange(legacyId); } return stars;
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 d7c7730..f697d15 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
@@ -16,9 +16,9 @@ import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN; import static com.google.gerrit.server.query.change.ChangeData.asChanges; +import static java.util.stream.Collectors.toSet; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -26,7 +26,6 @@ import com.google.common.primitives.Ints; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.errors.NotSignedInException; -import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; @@ -632,14 +631,9 @@ // expand a group predicate into multiple user predicates if (group != null) { Set<Account.Id> allMembers = - new HashSet<>(Lists.transform( - args.listMembers.get().setRecursive(true).apply(group), - new Function<AccountInfo, Account.Id>() { - @Override - public Account.Id apply(AccountInfo accountInfo) { - return new Account.Id(accountInfo._accountId); - } - })); + args.listMembers.get().setRecursive(true).apply(group).stream() + .map(a -> new Account.Id(a._accountId)) + .collect(toSet()); int maxLimit = args.indexConfig.maxLimit(); if (allMembers.size() > maxLimit) { // limit the number of query terms otherwise Gerrit will barf
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java index 45a00c6..d2f6876 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java
@@ -16,7 +16,6 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments; import com.google.gwtorm.server.ListResultSet; import com.google.gwtorm.server.OrmException; @@ -50,9 +49,9 @@ @Override public ResultSet<ChangeData> read() throws OrmException { Set<Change.Id> ids = new HashSet<>(); - for (PatchLineComment sc : - args.plcUtil.draftByAuthor(args.db.get(), accountId)) { - ids.add(sc.getKey().getParentKey().getParentKey().getParentKey()); + for (Change.Id changeId : args.plcUtil + .changesWithDraftsByAuthor(args.db.get(), accountId)) { + ids.add(changeId); } List<ChangeData> r = new ArrayList<>(ids.size());
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 6aa33352..a1f0dc8 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
@@ -22,7 +22,6 @@ import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -161,7 +160,7 @@ } public Iterable<ChangeData> byCommitsOnBranchNotMerged(Repository repo, - ReviewDb db, Branch.NameKey branch, List<String> hashes) + ReviewDb db, Branch.NameKey branch, Collection<String> hashes) throws OrmException, IOException { return byCommitsOnBranchNotMerged(repo, db, branch, hashes, // Account for all commit predicates plus ref, project, status. @@ -170,7 +169,7 @@ @VisibleForTesting Iterable<ChangeData> byCommitsOnBranchNotMerged(Repository repo, ReviewDb db, - Branch.NameKey branch, List<String> hashes, int indexLimit) + Branch.NameKey branch, Collection<String> hashes, int indexLimit) throws OrmException, IOException { if (hashes.size() > indexLimit) { return byCommitsOnBranchNotMergedFromDatabase(repo, db, branch, hashes); @@ -180,7 +179,7 @@ private Iterable<ChangeData> byCommitsOnBranchNotMergedFromDatabase( Repository repo, final ReviewDb db, final Branch.NameKey branch, - List<String> hashes) throws OrmException, IOException { + Collection<String> hashes) throws OrmException, IOException { Set<Change.Id> changeIds = Sets.newHashSetWithExpectedSize(hashes.size()); String lastPrefix = null; for (Ref ref : @@ -199,24 +198,18 @@ } } - return Lists.transform(notesFactory.create(db, branch.getParentKey(), - changeIds, new com.google.common.base.Predicate<ChangeNotes>() { - @Override - public boolean apply(ChangeNotes notes) { - Change c = notes.getChange(); + List<ChangeNotes> notes = notesFactory.create( + db, branch.getParentKey(), changeIds, + cn -> { + Change c = cn.getChange(); return c.getDest().equals(branch) && c.getStatus() != Change.Status.MERGED; - } - }), new Function<ChangeNotes, ChangeData>() { - @Override - public ChangeData apply(ChangeNotes notes) { - return changeDataFactory.create(db, notes); - } }); + return Lists.transform(notes, n -> changeDataFactory.create(db, n)); } private Iterable<ChangeData> byCommitsOnBranchNotMergedFromIndex( - Branch.NameKey branch, List<String> hashes) throws OrmException { + Branch.NameKey branch, Collection<String> hashes) throws OrmException { return query(and( ref(branch), project(branch.getParentKey()), @@ -224,7 +217,7 @@ or(commits(hashes)))); } - private static List<Predicate<ChangeData>> commits(List<String> hashes) { + private static List<Predicate<ChangeData>> commits(Collection<String> hashes) { List<Predicate<ChangeData>> commits = new ArrayList<>(hashes.size()); for (String s : hashes) { commits.add(commit(s));
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 69a392b..ad32edd 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
@@ -14,6 +14,8 @@ package com.google.gerrit.server.query.change; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.restapi.AuthException; @@ -122,6 +124,7 @@ int cnt = queries.size(); List<QueryResult<ChangeData>> results = imp.query(qb.parse(queries)); List<List<ChangeInfo>> res = json.create(options) + .lazyLoad(containsAnyOf(options, ChangeJson.REQUIRE_LAZY_LOAD)) .formatQueryResults(results); for (int n = 0; n < cnt; n++) { List<ChangeInfo> info = res.get(n); @@ -131,4 +134,10 @@ } return res; } + + private static boolean containsAnyOf( + EnumSet<ListChangesOption> set, + ImmutableSet<ListChangesOption> toFind) { + return !Sets.intersection(toFind, set).isEmpty(); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java index 7c7417a..1bc878e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -165,8 +165,9 @@ grant(config, heads, Permission.FORGE_COMMITTER, admin, owners); grant(config, heads, Permission.EDIT_TOPIC_NAME, true, admin, owners); - grant(config, tags, Permission.PUSH_TAG, admin, owners); - grant(config, tags, Permission.PUSH_SIGNED_TAG, admin, owners); + grant(config, tags, Permission.CREATE, admin, owners); + grant(config, tags, Permission.CREATE_TAG, admin, owners); + grant(config, tags, Permission.CREATE_SIGNED_TAG, admin, owners); grant(config, magic, Permission.PUSH, registered); grant(config, magic, Permission.PUSH_MERGE, registered);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java index 9dee9f5..a2046b5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java
@@ -18,6 +18,8 @@ import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.notedb.ChangeBundleReader; +import com.google.gerrit.server.notedb.GwtormChangeBundleReader; import com.google.gwtorm.jdbc.Database; import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Key; @@ -37,5 +39,6 @@ .to(database) .in(SINGLETON); bind(database).toProvider(ReviewDbDatabaseProvider.class); + bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java new file mode 100644 index 0000000..2d9714f --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
@@ -0,0 +1,106 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import static com.google.gerrit.server.git.ProjectConfig.ACCESS; +import static java.util.stream.Collectors.toList; + +import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.reviewdb.client.RefNames; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.server.git.VersionedMetaData; +import com.google.gwtorm.server.OrmException; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.PersonIdent; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class ProjectConfigSchemaUpdate extends VersionedMetaData { + + private final MetaDataUpdate update; + private Config config; + private boolean updated; + + public static ProjectConfigSchemaUpdate read(MetaDataUpdate update) + throws IOException, ConfigInvalidException { + ProjectConfigSchemaUpdate r = new ProjectConfigSchemaUpdate(update); + r.load(update); + return r; + } + + private ProjectConfigSchemaUpdate(MetaDataUpdate update) { + this.update = update; + } + + @Override + protected String getRefName() { + return RefNames.REFS_CONFIG; + } + + @Override + protected void onLoad() throws IOException, ConfigInvalidException { + config = readConfig(ProjectConfig.PROJECT_CONFIG); + } + + public void removeForceFromPermission(String name) { + for (String subsection : config.getSubsections(ACCESS)) { + Set<String> names = config.getNames(ACCESS, subsection); + if (names.contains(name)) { + List<String> values = + Arrays.stream(config.getStringList(ACCESS, subsection, name)) + .map(r -> { + PermissionRule rule = PermissionRule.fromString(r, false); + if (rule.getForce()) { + rule.setForce(false); + updated = true; + } + return rule.asString(false); + }) + .collect(toList()); + config.setStringList(ACCESS, subsection, name, values); + } + } + } + + @Override + protected boolean onSave(CommitBuilder commit) + throws IOException, ConfigInvalidException { + saveConfig(ProjectConfig.PROJECT_CONFIG, config); + return true; + } + + public void save(PersonIdent personIdent, String commitMessage) + throws OrmException { + if (!updated) { + return; + } + + update.getCommitBuilder().setAuthor(personIdent); + update.getCommitBuilder().setCommitter(personIdent); + update.setMessage(commitMessage); + try { + commit(update); + } catch (IOException e) { + throw new OrmException(e); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java index 7217fd0..5d1e579 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -33,7 +33,7 @@ /** A version of the database schema. */ public abstract class SchemaVersion { /** The current schema version. */ - public static final Class<Schema_129> C = Schema_129.class; + public static final Class<Schema_131> C = Schema_131.class; public static int getBinaryVersion() { return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_119.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_119.java index 9fdec25..cd42e75 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_119.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_119.java
@@ -136,6 +136,8 @@ p.reviewCategoryStrategy = toReviewCategoryStrategy(rs.getString(14)); p.muteCommonPathPrefixes = toBoolean(rs.getString(15)); + p.defaultBaseForMerges = + GeneralPreferencesInfo.defaults().defaultBaseForMerges; imports.put(accountId, p); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java index 16f0bcf..895c905 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java
@@ -14,7 +14,8 @@ package com.google.gerrit.server.schema; -import com.google.common.base.Function; +import static java.util.Comparator.comparing; + import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; @@ -124,13 +125,7 @@ private Collection<AccountSshKey> fixInvalidSequenceNumbers( Collection<AccountSshKey> keys) { - Ordering<AccountSshKey> o = - Ordering.natural().onResultOf(new Function<AccountSshKey, Integer>() { - @Override - public Integer apply(AccountSshKey sshKey) { - return sshKey.getKey().get(); - } - }); + Ordering<AccountSshKey> o = Ordering.from(comparing(k -> k.getKey().get())); List<AccountSshKey> fixedKeys = new ArrayList<>(keys); AccountSshKey minKey = o.min(keys); while (minKey.getKey().get() <= 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java new file mode 100644 index 0000000..d7fcc3b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java
@@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; + +import java.io.IOException; + +public class Schema_130 extends SchemaVersion { + private static final String COMMIT_MSG = + "Remove force option from 'Push Annotated Tag' permission\n" + + "\n" + + "The force option on 'Push Annotated Tag' had no effect and is no longer\n" + + "supported."; + + private final GitRepositoryManager repoManager; + private final PersonIdent serverUser; + + @Inject + Schema_130(Provider<Schema_129> prior, + GitRepositoryManager repoManager, + @GerritPersonIdent PersonIdent serverUser) { + super(prior); + this.repoManager = repoManager; + this.serverUser = serverUser; + } + + @Override + protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException { + for (Project.NameKey projectName : repoManager.list()) { + try (Repository git = repoManager.openRepository(projectName); + MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, + projectName, git)) { + ProjectConfigSchemaUpdate cfg = ProjectConfigSchemaUpdate.read(md); + cfg.removeForceFromPermission("pushTag"); + cfg.save(serverUser, COMMIT_MSG); + } catch (ConfigInvalidException | IOException ex) { + throw new OrmException(ex); + } + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_131.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_131.java new file mode 100644 index 0000000..1be4c3e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_131.java
@@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; + +import java.io.IOException; + +public class Schema_131 extends SchemaVersion { + private static final String COMMIT_MSG = + "Rename 'Push Annotated/Signed Tag' permission to 'Create Annotated/Signed Tag'"; + + private final GitRepositoryManager repoManager; + private final PersonIdent serverUser; + + @Inject + Schema_131(Provider<Schema_130> prior, + GitRepositoryManager repoManager, + @GerritPersonIdent PersonIdent serverUser) { + super(prior); + this.repoManager = repoManager; + this.serverUser = serverUser; + } + + @Override + protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException { + for (Project.NameKey projectName : repoManager.list()) { + try (Repository git = repoManager.openRepository(projectName); + MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, + projectName, git)) { + ProjectConfig config = ProjectConfig.read(md); + if (config.hasLegacyPermissions()) { + md.getCommitBuilder().setAuthor(serverUser); + md.getCommitBuilder().setCommitter(serverUser); + md.setMessage(COMMIT_MSG); + config.commit(md); + } + } catch (ConfigInvalidException | IOException ex) { + throw new OrmException(ex); + } + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/GitUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/GitUtil.java deleted file mode 100644 index 2d1e1fa..0000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/util/GitUtil.java +++ /dev/null
@@ -1,51 +0,0 @@ -// Copyright (C) 2016 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.server.util; - -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; - -import java.io.IOException; - -public class GitUtil { - - /** - * @param git - * @param commitId - * @param parentNum - * @return the {@code paretNo} parent of given commit or {@code null} - * when {@code parentNo} exceed number of {@code commitId} parents. - * @throws IncorrectObjectTypeException - * the supplied id is not a commit or an annotated tag. - * @throws IOException - * a pack file or loose object could not be read. - */ - public static RevCommit getParent(Repository git, - ObjectId commitId, int parentNum) throws IOException { - try (RevWalk walk = new RevWalk(git)) { - RevCommit commit = walk.parseCommit(commitId); - if (commit.getParentCount() > parentNum) { - return commit.getParent(parentNum); - } - } - return null; - } - - private GitUtil() { - } -}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java index fab0b34..030383a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
@@ -58,6 +58,16 @@ Short.parseShort(text.substring(e + 1), text.length())); } + public static StringBuilder appendTo(StringBuilder sb, String label, + short value) { + if (value == (short) 0) { + return sb.append('-').append(label); + } else if (value < 0) { + return sb.append(label).append(value); + } + return sb.append(label).append('+').append(value); + } + public static LabelVote create(String label, short value) { return new AutoValue_LabelVote(LabelType.checkNameInternal(label), value); } @@ -70,13 +80,9 @@ public abstract short value(); public String format() { - if (value() == (short) 0) { - return '-' + label(); - } else if (value() < 0) { - return label() + value(); - } else { - return label() + '+' + value(); - } + // Max short string length is "-32768".length() == 6. + return appendTo(new StringBuilder(label().length() + 6), label(), value()) + .toString(); } public String formatWithEquals() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java index 0a99a8a..bbc97df 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
@@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -94,12 +93,7 @@ return Iterables.filter( list.subList(begin, end), - new Predicate<T>() { - @Override - public boolean apply(T in) { - return pattern.run(RegexListSearcher.this.apply(in)); - } - }); + x -> pattern.run(apply(x))); } public boolean hasMatch(List<T> list) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java index b2899c1..bfec979 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
@@ -14,6 +14,7 @@ package com.google.gerrit.server.validators; +import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.annotations.ExtensionPoint; import com.google.gerrit.server.mail.Address; import com.google.gerrit.server.mail.EmailHeader; @@ -32,6 +33,8 @@ class Args { // in arguments public String messageClass; + public String textBody; + @Nullable public String htmlBody; // in/out arguments public Address smtpFromAddress;
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java index 1dbdb68..a855868 100644 --- a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java +++ b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
@@ -14,8 +14,10 @@ package gerrit; +import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.rules.StoredValues; import com.google.gerrit.server.patch.PatchList; +import com.google.gerrit.server.patch.PatchListEntry; import com.googlecode.prolog_cafe.exceptions.PrologException; import com.googlecode.prolog_cafe.lang.IntegerTerm; @@ -24,6 +26,8 @@ import com.googlecode.prolog_cafe.lang.Prolog; import com.googlecode.prolog_cafe.lang.Term; +import java.util.List; + /** * Exports basic commit statistics. * @@ -48,7 +52,11 @@ Term a3 = arg3.dereference(); PatchList pl = StoredValues.PATCH_LIST.get(engine); - if (!a1.unify(new IntegerTerm(pl.getPatches().size() - 1),engine.trail)) { //Account for /COMMIT_MSG. + // Account for magic files + if (!a1.unify( + new IntegerTerm( + pl.getPatches().size() - countMagicFiles(pl.getPatches())), + engine.trail)) { return engine.fail(); } if (!a2.unify(new IntegerTerm(pl.getInsertions()),engine.trail)) { @@ -59,4 +67,14 @@ } return cont; } + + private int countMagicFiles(List<PatchListEntry> entries) { + int count = 0; + for (PatchListEntry e : entries) { + if (Patch.isMagic(e.getNewName())) { + count++; + } + } + return count; + } }
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 new file mode 100644 index 0000000..50c5fc3 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
@@ -0,0 +1,39 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * .Abandoned template will determine the contents of the email related to a + * change being abandoned. + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .Abandoned autoescape="strict" kind="text"} + {$fromName} has abandoned this change. + {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {if $coverLetter} + {\n} + {\n} + {$coverLetter} + {\n} + {/if} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm deleted file mode 100644 index accd3b8..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm +++ /dev/null
@@ -1,46 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The Abandoned.vm template will determine the contents of the email related -## to a change being abandoned. It is a ChangeEmail: see ChangeSubject.vm and -## ChangeFooter.vm. -## -$fromName has abandoned this change.#** -*##if($email.changeUrl) ( $email.changeUrl )#end - - -Change subject: $change.subject -...................................................................... - - -#if ($coverLetter) -$coverLetter - -#end
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 new file mode 100644 index 0000000..0f16890 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
@@ -0,0 +1,44 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .AbandonedHtml autoescape="strict" kind="html"} + <p> + {$fromName} has abandoned this change. + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + {if $coverLetter} + <pre>{$coverLetter}</pre> + {/if} +{/template}
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 new file mode 100644 index 0000000..aa2b27d --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
@@ -0,0 +1,71 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .AddKey template will determine the contents of the email related to + * adding a new SSH or GPG key to an account. + * @param email + */ +{template .AddKey autoescape="strict" kind="text"} + One or more new {$email.keyType} keys have been added to Gerrit Code Review at + {sp}{$email.gerritHost}: + + {\n} + {\n} + + {if $email.sshKey} + {$email.sshKey} + {elseif $email.gpgKeys} + {$email.gpgKeys} + {/if} + + {\n} + {\n} + + If this is not expected, please contact your Gerrit Administrators + immediately. + + {\n} + {\n} + + You can also manage your {$email.keyType} keys by visiting + {\n} + {if $email.sshKey} + {$email.gerritUrl}#/settings/ssh-keys + {elseif $email.gpgKeys} + {$email.gerritUrl}#/settings/gpg-keys + {/if} + {\n} + {if $email.userNameEmail} + (while signed in as {$email.userNameEmail}) + {else} + (while signed in as {$email.email}) + {/if} + + {\n} + {\n} + + If clicking the link above does not work, copy and paste the URL in a new + browser window instead. + + {\n} + {\n} + + This is a send-only email address. Replies to this message will not be read + or answered. +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm deleted file mode 100644 index c60ce8b..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.vm +++ /dev/null
@@ -1,61 +0,0 @@ -## Copyright (C) 2015 The Android Open Source Project -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The AddKey.vm template will determine the contents of the email -## related to adding a new SSH or GPG key to an account. -## -One or more new ${email.keyType} keys have been added to Gerrit Code Review at ${email.gerritHost}: - -#if($email.sshKey) -$email.sshKey -#elseif($email.gpgKeys) -$email.gpgKeys -#end - -If this is not expected, please contact your Gerrit Administrators -immediately. - -You can also manage your ${email.keyType} keys by visiting -#if($email.sshKey) -$email.gerritUrl#/settings/ssh-keys -#elseif($email.gpgKeys) -$email.gerritUrl#/settings/gpg-keys -#end -#if($email.userNameEmail) -(while signed in as $email.userNameEmail) -#else -(while signed in as $email.email) -#end - -If clicking the link above does not work, copy and paste the URL in a -new browser window instead. - -This is a send-only email address. Replies to this message will not -be read or answered.
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 new file mode 100644 index 0000000..017fd6d --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
@@ -0,0 +1,66 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param email + */ +{template .AddKeyHtml autoescape="strict" kind="html"} + <p> + One or more new {$email.keyType} keys have been added to Gerrit Code Review + at {$email.gerritHost}: + </p> + + {let $keyStyle kind="css"} + background: #f0f0f0; + border: 1px solid #ccc; + color: #555; + padding: 12px; + width: 400px; + {/let} + + {if $email.sshKey} + <pre style="{$keyStyle}">{$email.sshKey}</pre> + {elseif $email.gpgKeys} + <pre style="{$keyStyle}">{$email.gpgKeys}</pre> + {/if} + + <p> + If this is not expected, please contact your Gerrit Administrators + immediately. + </p> + + <p> + You can also manage your {$email.keyType} keys by following{sp} + {if $email.sshKey} + <a href="{$email.gerritUrl}#/settings/ssh-keys">this link</a> + {elseif $email.gpgKeys} + <a href="{$email.gerritUrl}#/settings/gpg-keys">this link</a> + {/if} + {sp} + {if $email.userNameEmail} + (while signed in as {$email.userNameEmail}) + {else} + (while signed in as {$email.email}) + {/if}. + </p> + + <p> + This is a send-only email address. Replies to this message will not be read + or answered. + </p> +{/template}
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 new file mode 100644 index 0000000..9906dd8d --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
@@ -0,0 +1,52 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .ChangeFooter template will determine the contents of the footer text + * that will be appended to ALL emails related to changes. + * @param branch + * @param change + * @param changeId + * @param email + * @param messageClass + * @param patchSet + * @param projectName + */ +{template .ChangeFooter autoescape="strict" kind="text"} + --{sp} + {\n} + + {if $email.changeUrl} + To view, visit {$email.changeUrl}{\n} + {/if} + + {if $email.settingsUrl} + To unsubscribe, visit {$email.settingsUrl}{\n} + {/if} + + {if $email.changeUrl or $email.settingsUrl} + {\n} + {/if} + + Gerrit-MessageType: {$messageClass}{\n} + Gerrit-Change-Id: {$changeId}{\n} + Gerrit-PatchSet: {$patchSet.patchSetId}{\n} + Gerrit-Project: {$projectName}{\n} + Gerrit-Branch: {$branch.shortName}{\n} + Gerrit-Owner: {$change.ownerEmail}{\n} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm deleted file mode 100644 index f1d3e90..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm +++ /dev/null
@@ -1,52 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The ChangeFooter.vm template will determine the contents of the footer -## text that will be appended to ALL emails related to changes. -## -#set ($SPACE = " ") ---$SPACE -#if ($email.changeUrl) -To view, visit $email.changeUrl -#set ($notblank = 1) -#end -#if ($email.settingsUrl) -To unsubscribe, visit $email.settingsUrl -#set ($notblank = 1) -#end -#if ($notblank) - -#end -Gerrit-MessageType: $messageClass -Gerrit-Change-Id: $changeId -Gerrit-PatchSet: $patchSet.patchSetId -Gerrit-Project: $projectName -Gerrit-Branch: $branch.shortName -Gerrit-Owner: $email.getNameEmailFor($change.owner)
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 new file mode 100644 index 0000000..6d04ac3 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
@@ -0,0 +1,49 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param branch + * @param change + * @param changeId + * @param email + * @param messageClass + * @param patchSet + * @param projectName + */ +{template .ChangeFooterHtml autoescape="strict" kind="html"} + {if $email.changeUrl or $email.settingsUrl} + <p> + {if $email.changeUrl} + To view, visit <a href="{$email.changeUrl}">this change</a>. + {/if} + {if $email.changeUrl and $email.settingsUrl}{sp}{/if} + {if $email.settingsUrl} + To unsubscribe, visit <a href="{$email.settingsUrl}">settings</a>. + {/if} + </p> + {/if} + + <p style="color: #555;"> + Gerrit-MessageType: {$messageClass}<br/> + Gerrit-Change-Id: {$changeId}<br/> + Gerrit-PatchSet: {$patchSet.patchSetId}<br/> + Gerrit-Project: {$projectName}<br/> + Gerrit-Branch: {$branch.shortName}<br/> + Gerrit-Owner: {$change.ownerEmail} + </p> +{/template}
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 new file mode 100644 index 0000000..98de6e7 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
@@ -0,0 +1,28 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .ChangeSubject template will determine the contents of the email subject + * line for ALL emails related to changes. + * @param branch + * @param change + * @param shortProjectName + */ +{template .ChangeSubject autoescape="strict" kind="text"} + Change in {$shortProjectName}[{$branch.shortName}]: {$change.shortSubject} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm deleted file mode 100644 index 4fd9a23..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm +++ /dev/null
@@ -1,42 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The ChangeSubject.vm template will determine the contents of the email -## subject line for ALL emails related to changes. -## -## Optionally $change.originalSubject can be used for the first subject -## in a change. This allows subject based email clients such as GMail -## to thread comments together even if subsequent patch sets change the -## first line of the commit message. -## -#macro(ellipsis $length $str) -#if($str.length() > $length)#set($length = $length - 3)${str.substring(0,$length)}...#else$str#end -#end -Change in ${projectName.replaceAll('/.*/', '...')}[$branch.shortName]: #ellipsis(63, $change.subject)
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 new file mode 100644 index 0000000..781d8a0 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
@@ -0,0 +1,46 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .Comment template will determine the contents of the email related to a + * user submitting comments on changes. + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .Comment autoescape="strict" kind="text"} + {if $coverLetter or $email.hasInlineComments} + {$fromName} has posted comments on this change. + {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {if $coverLetter} + {\n} + {\n} + {$coverLetter} + {/if} + {if $email.hasInlineComments} + {\n} + {\n} + {$email.inlineComments} + {/if} + {/if} + {\n} +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm deleted file mode 100644 index a442311..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm +++ /dev/null
@@ -1,55 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The Comment.vm template will determine the contents of the email related to -## a user submitting comments on changes. It is a ChangeEmail: see -## ChangeSubject.vm, ChangeFooter.vm and CommentFooter.vm. -## -#if ($email.coverLetter || $email.hasInlineComments()) -$fromName has posted comments on this change.#** -*##if($email.changeUrl) ( $email.changeUrl )#end - - -Change subject: $change.subject -...................................................................... - - -#if ($email.coverLetter) -$email.coverLetter - -#end -## -## It is possible to increase the span of the quoted lines by using the line -## count parameter when calling $email.getInlineComments as a function. -## -## Example: #if($email.hasInlineComments())$email.getInlineComments(5)#end -## -#if($email.hasInlineComments())$email.inlineComments#end -#end
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 new file mode 100644 index 0000000..3fcad6b --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
@@ -0,0 +1,31 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .CommentFooter template will determine the contents of the footer text + * that will be appended to emails related to a user submitting comments on + * changes. + * @param email + */ +{template .CommentFooter autoescape="strict" kind="text"} + {if $email.hasInlineComments} + Gerrit-HasComments: Yes + {else} + Gerrit-HasComments: No + {/if} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.vm deleted file mode 100644 index e0832e6..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.vm +++ /dev/null
@@ -1,40 +0,0 @@ -## Copyright (C) 2012 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The CommentFooter.vm template will determine the contents of the footer -## text that will be appended to emails related to a user submitting comments -## on changes. -## -## See ChangeSubject.vm and ChangeFooter.vm. -#if($email.hasInlineComments()) -Gerrit-HasComments: Yes -#else -Gerrit-HasComments: No -#end
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 new file mode 100644 index 0000000..888ee4b --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
@@ -0,0 +1,44 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .DeleteReviewer template will determine the contents of the email related + * to removal of a reviewer (and the reviewer's votes) from reviews. + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .DeleteReviewer autoescape="strict" kind="text"} + {$fromName} has removed{sp} + {foreach $reviewerName in $email.reviewerNames} + {if not isFirst($reviewerName)},{sp}{/if} + {$reviewerName} + {/foreach}{sp} + from this change.{sp} + {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {if $coverLetter} + {\n} + {\n} + {$coverLetter} + {\n} + {/if} +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm deleted file mode 100644 index 635b716..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm +++ /dev/null
@@ -1,47 +0,0 @@ -## Copyright (C) 2016 The Android Open Source Project -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The DeleteReviewer.vm template will determine the contents of the email -## related to removal of a reviewer (and the reviewer's votes) from reviews. -## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm. -## -$fromName has removed $email.joinStrings($email.reviewerNames, ', ') from #** -*#this change.#** -*##if($email.changeUrl) ( $email.changeUrl )#end - - -Change subject: $change.subject -...................................................................... - - -#if ($email.coverLetter) -$email.coverLetter - -#end
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 new file mode 100644 index 0000000..2985ef8 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
@@ -0,0 +1,49 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .DeleteReviewerHtml autoescape="strict" kind="html"} + <p> + {$fromName} has removed{sp} + {foreach $reviewerName in $email.reviewerNames} + {if not isFirst($reviewerName)},{sp}{/if} + {$reviewerName} + {/foreach}{sp} + from this change. + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + {if $coverLetter} + <pre>{$coverLetter}</pre> + {/if} +{/template}
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 new file mode 100644 index 0000000..b249ded --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
@@ -0,0 +1,37 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .DeleteVote template will determine the contents of the email related + * to removing votes on changes. + * @param change + * @param coverLetter + * @param fromName + */ +{template .DeleteVote autoescape="strict" kind="text"} + {$fromName} has removed a vote on this change.{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {if $coverLetter} + {\n} + {\n} + {$coverLetter} + {\n} + {/if} +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.vm deleted file mode 100644 index 294063e..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.vm +++ /dev/null
@@ -1,44 +0,0 @@ -## Copyright (C) 2016 The Android Open Source Project -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The DeleteVote.vm template will determine the contents of the email related -## to removing votes on changes. It is a ChangeEmail: see ChangeSubject.vm -## and ChangeFooter.vm. -## -$fromName has removed a vote on this change. - -Change subject: $change.subject -...................................................................... - - -#if ($coverLetter) -$coverLetter - -#end
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 new file mode 100644 index 0000000..8aa430a --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
@@ -0,0 +1,44 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .DeleteVoteHtml autoescape="strict" kind="html"} + <p> + {$fromName} removed a vote on this change. + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + {if $coverLetter} + <pre>{$coverLetter}</pre> + {/if} +{/template}
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 new file mode 100644 index 0000000..6467e95 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
@@ -0,0 +1,25 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .Footer template will determine the contents of the footer text + * appended to the end of all outgoing emails after the ChangeFooter and + * CommentFooter. + */ +{template .Footer} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.vm deleted file mode 100644 index 28f29fd..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.vm +++ /dev/null
@@ -1,33 +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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The Footer.vm template will determine the contents of the footer text -## appended to the end of all outgoing emails after the ChangeFooter and -## CommentFooter.
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 new file mode 100644 index 0000000..9befa51 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
@@ -0,0 +1,20 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +{namespace com.google.gerrit.server.mail.template} + +{template .FooterHtml autoescape="strict" kind="html"} +{/template}
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 new file mode 100644 index 0000000..fdc3fee --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
@@ -0,0 +1,20 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +{namespace com.google.gerrit.server.mail.template} + +{template .HeaderHtml autoescape="strict" kind="html"} +{/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 new file mode 100644 index 0000000..d483264 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
@@ -0,0 +1,42 @@ + +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .Merged template will determine the contents of the email related to + * a change successfully merged to the head. + * @param change + * @param email + * @param fromName + */ +{template .Merged autoescape="strict" kind="text"} + {$fromName} has submitted this change and it was merged. + {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {\n} + {$email.changeDetail} + {$email.approvals} + {if $email.includeDiff} + {\n} + {\n} + {$email.unifiedDiff} + {\n} + {/if} +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm deleted file mode 100644 index 3e49e92..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm +++ /dev/null
@@ -1,47 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The Merged.vm template will determine the contents of the email related to -## a change successfully merged to the head. It is a ChangeEmail: see -## ChangeSubject.vm and ChangeFooter.vm. -## -$fromName has submitted this change and it was merged.#** -*##if($email.changeUrl) ( $email.changeUrl )#end - - -Change subject: $change.subject -...................................................................... - - -$email.changeDetail$email.approvals - -#if($email.includeDiff) -$email.UnifiedDiff -#end
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 new file mode 100644 index 0000000..296f625 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
@@ -0,0 +1,81 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .NewChange template will determine the contents of the email related to a + * user submitting a new change for review. + * @param change + * @param email + * @param fromName + * @param patchSet + * @param projectName + */ +{template .NewChange autoescape="strict" kind="text"} + {if $email.reviewerNames} + Hello{sp} + {foreach $reviewerName in $email.reviewerNames} + {if not isFirst($reviewerName)},{sp}{/if} + {$reviewerName} + {/foreach}, + + {\n} + {\n} + + I'd like you to do a code review. + + {if $email.changeUrl} + {sp}Please visit + + {\n} + {\n} + + {sp}{sp}{sp}{sp}{$email.changeUrl} + + {\n} + {\n} + + to review the following change. + {/if} + {else} + {$fromName} has uploaded a new change for review. + {if $email.changeUrl} ( {$email.changeUrl}{/if} + {/if}{\n} + + {\n} + {\n} + + Change subject: {$change.subject}{\n} + ......................................................................{\n} + + {\n} + + {$email.changeDetail}{\n} + + {if $email.sshHost} + {\n} + {sp}{sp}git pull ssh:{print '//'}{$email.sshHost}/{$projectName} + {sp}{$patchSet.refName} + {\n} + {/if} + + {if $email.includeDiff} + {\n} + {$email.unifiedDiff} + {\n} + {/if} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm deleted file mode 100644 index 8b66e81..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm +++ /dev/null
@@ -1,60 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The NewChange.vm template will determine the contents of the email related -## to a user submitting a new change for review. It is a ChangeEmail: see -## ChangeSubject.vm and ChangeFooter.vm. -## -#if($email.reviewerNames) -Hello $email.joinStrings($email.reviewerNames, ', '), - -I'd like you to do a code review.#if($email.changeUrl) Please visit - - $email.changeUrl - -to review the following change. -#end -#else -$fromName has uploaded a new change for review.#** -*##if($email.changeUrl) ( $email.changeUrl )#end -#end - - -Change subject: $change.subject -...................................................................... - -$email.changeDetail -#if($email.sshHost) - git pull ssh://$email.sshHost/$projectName $patchSet.refName -#end -#if($email.includeDiff) - -$email.UnifiedDiff -#end
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 new file mode 100644 index 0000000..fda960d --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
@@ -0,0 +1,78 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param email + * @param fromName + * @param patchSet + * @param projectName + */ +{template .NewChangeHtml autoescape="strict" kind="html"} + {if $email.reviewerNames} + <p> + Hello{sp} + {foreach $reviewerName in $email.reviewerNames} + {if not isFirst($reviewerName)},{sp}{/if} + {$reviewerName} + {/foreach}, + </p> + + <p> + I'd like you to do a code review. + </p> + + {if $email.changeUrl} + <p> + Please visit <a href="{$email.changeUrl}">this change</a> to review. + </p> + {/if} + {else} + <p> + {$fromName} has uploaded a new change for review. + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + <pre> + {$email.changeDetail} + </pre> + + {if $email.sshHost} + <pre> + git pull ssh:{print '//'}{$email.sshHost}/{$projectName} + {sp}{$patchSet.refName} + </pre> + {/if} + + {if $email.includeDiff} + <pre> + {$email.unifiedDiff} + </pre> + {/if} +{/template}
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 new file mode 100644 index 0000000..2b30ae6 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
@@ -0,0 +1,54 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .RegisterNewEmail template will determine the contents of the email + * related to registering new email accounts. + * @param email + */ +{template .RegisterNewEmail autoescape="strict" kind="text"} + Welcome to Gerrit Code Review at {$email.gerritHost}.{\n} + + {\n} + + To add a verified email address to your user account, please{\n} + click on the following link + {if $email.userNameEmail} + {sp}while signed in as {$email.userNameEmail} + {/if}:{\n} + + {\n} + + {$email.gerritUrl}#/VE/{$email.emailRegistrationToken}{\n} + + {\n} + + If you have received this mail in error, you do not need to take any{\n} + action to cancel the account. The address will not be activated, and{\n} + you will not receive any further emails.{\n} + + {\n} + + If clicking the link above does not work, copy and paste the URL in a{\n} + new browser window instead.{\n} + + {\n} + + This is a send-only email address. Replies to this message will not{\n} + be read or answered.{\n} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm deleted file mode 100644 index 7e095fb..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm +++ /dev/null
@@ -1,49 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The RegisterNewEmail.vm template will determine the contents of the email -## related to registering new email accounts. -## -Welcome to Gerrit Code Review at ${email.gerritHost}. - -To add a verified email address to your user account, please -click on the following link#if($email.userNameEmail) while signed in as $email.userNameEmail#end: - -$email.gerritUrl#/VE/$email.emailRegistrationToken - -If you have received this mail in error, you do not need to take any -action to cancel the account. The address will not be activated, and -you will not receive any further emails. - -If clicking the link above does not work, copy and paste the URL in a -new browser window instead. - -This is a send-only email address. Replies to this message will not -be read or answered.
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 new file mode 100644 index 0000000..2236725 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
@@ -0,0 +1,59 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .ReplacePatchSet template will determine the contents of the email + * related to a user submitting a new patchset for a change. + * @param change + * @param email + * @param fromName + * @param patchSet + * @param projectName + */ +{template .ReplacePatchSet autoescape="strict" kind="text"} + {if $email.reviewerNames} + Hello{sp} + {foreach $reviewerName in $email.reviewerNames} + {$reviewerName},{sp} + {/foreach}{\n} + {\n} + I'd like you to reexamine a change. + {if $email.changeUrl} + {sp}Please visit + {\n} + {\n} + {sp}{sp}{sp}{sp}{$email.changeUrl} + {\n} + {\n} + to look at the new patch set (#{$patchSet.patchSetId}). + {/if} + {else} + {$fromName} has uploaded a new patch set (#{$patchSet.patchSetId}). + {if $email.changeUrl} ( {$email.changeUrl}{/if} + {/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {\n} + {$email.changeDetail}{\n} + {if $email.sshHost} + {sp}{sp}git pull ssh:{print '//'}{$email.sshHost}/{$projectName}{sp} + {$patchSet.refName} + {\n} + {/if} +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm deleted file mode 100644 index e45bf30..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm +++ /dev/null
@@ -1,56 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The ReplacePatchSet.vm template will determine the contents of the email -## related to a user submitting a new patchset for a change. It is a -## ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm. -## -#if($email.reviewerNames) -Hello $email.joinStrings($email.reviewerNames, ', '), - -I'd like you to reexamine a change.#if($email.changeUrl) Please visit - - $email.changeUrl - -to look at the new patch set (#$patchSet.patchSetId). -#end -#else -$fromName has uploaded a new patch set (#$patchSet.patchSetId).#** -*##if($email.changeUrl) ( $email.changeUrl )#end - -#end - -Change subject: $change.subject -...................................................................... - -$email.changeDetail -#if($email.sshHost) - git pull ssh://$email.sshHost/$projectName $patchSet.refName -#end
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 new file mode 100644 index 0000000..9df9a71 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
@@ -0,0 +1,72 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param email + * @param fromName + * @param patchSet + * @param projectName + */ +{template .ReplacePatchSetHtml autoescape="strict" kind="html"} + {if $email.reviewerNames} + <p> + Hello{sp} + {foreach $reviewerName in $email.reviewerNames} + {$reviewerName},{sp} + {/foreach} + </p> + + <p> + I'd like you to reexamine a change. + </p> + + {if $email.changeUrl} + <p> + Please visit <a href="{$email.changeUrl}">this change</a> to look at{sp} + the new patch set (#{$patchSet.patchSetId}). + </p> + {/if} + {else} + <p> + {$fromName} has uploaded a new patch set (#{$patchSet.patchSetId}). + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + <pre> + {$email.changeDetail} + </pre> + + {if $email.sshHost} + <pre> + git pull ssh:{print '//'}{$email.sshHost}/{$projectName}{sp} + {$patchSet.refName} + </pre> + {/if} +{/template}
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 new file mode 100644 index 0000000..14ae0f3 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
@@ -0,0 +1,39 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .Restored template will determine the contents of the email related to a + * change being restored. + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .Restored autoescape="strict" kind="text"} + {$fromName} has restored this change. + {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {if $coverLetter} + {\n} + {\n} + {$coverLetter} + {\n} + {/if} +{/template} \ No newline at end of file
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.vm deleted file mode 100644 index 31e1c69..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.vm +++ /dev/null
@@ -1,46 +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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The Restored.vm template will determine the contents of the email related -## to a change being restored. It is a ChangeEmail: see ChangeSubject.vm and -## ChangeFooter.vm. -## -$fromName has restored this change.#** -*##if($email.changeUrl) ( $email.changeUrl )#end - - -Change subject: $change.subject -...................................................................... - - -#if ($coverLetter) -$coverLetter - -#end
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 new file mode 100644 index 0000000..8a7dcc2 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
@@ -0,0 +1,44 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .RestoredHtml autoescape="strict" kind="html"} + <p> + {$fromName} has restored this change. + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + {if $coverLetter} + <pre>{$coverLetter}</pre> + {/if} +{/template}
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 new file mode 100644 index 0000000..7f74df9 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
@@ -0,0 +1,39 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * The .Reverted template will determine the contents of the email related + * to a change being reverted. + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .Reverted autoescape="strict" kind="text"} + {$fromName} has reverted this change. + {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n} + {\n} + Change subject: {$change.subject}{\n} + ......................................................................{\n} + {if $coverLetter} + {\n} + {\n} + {$coverLetter} + {\n} + {/if} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm deleted file mode 100644 index 1e9e251..0000000 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.vm +++ /dev/null
@@ -1,46 +0,0 @@ -## Copyright (C) 2010 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. -## -## -## Template Type: -## ------------- -## This is a velocity mail template, see: http://velocity.apache.org and the -## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates. -## -## Template File Names and extensions: -## ---------------------------------- -## Gerrit will use templates ending in ".vm" but will ignore templates ending -## in ".vm.example". If a .vm template does not exist, the default internal -## gerrit template which is the same as the .vm.example will be used. If you -## want to override the default template, copy the .vm.example file to a .vm -## file and edit it appropriately. -## -## This Template: -## -------------- -## The Reverted.vm template will determine the contents of the email related -## to a change being reverted. It is a ChangeEmail: see ChangeSubject.vm and -## ChangeFooter.vm. -## -$fromName has reverted this change.#** -*##if($email.changeUrl) ( $email.changeUrl )#end - - -Change subject: $change.subject -...................................................................... - - -#if ($coverLetter) -$coverLetter - -#end
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 new file mode 100644 index 0000000..03d4dea --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
@@ -0,0 +1,44 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * @param change + * @param coverLetter + * @param email + * @param fromName + */ +{template .RevertedHtml autoescape="strict" kind="html"} + <p> + {$fromName} has reverted this change. + </p> + + {if $email.changeUrl} + <p> + {call .ViewChangeButton data="all" /} + </p> + {/if} + + <p> + Change subject: {$change.subject} + </p> + <hr/> + + {if $coverLetter} + <pre>{$coverLetter}</pre> + {/if} +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy new file mode 100644 index 0000000..defde55 --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy
@@ -0,0 +1,25 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{namespace com.google.gerrit.server.mail.template} + +/** + * Private template to generate "View Change" buttons. + * @param email + */ +{template .ViewChangeButton private="true" autoescape="strict" kind="html"} + <a href="{$email.changeUrl}">View Change</a> +{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties index d51547c..5a937b6 100644 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -97,7 +97,7 @@ in = text/x-properties ini = text/x-properties intr = text/x-dylan -jade = text/x-jade +jade = text/x-pug java = text/x-java jl = text/x-julia jruby = text/x-ruby @@ -163,6 +163,7 @@ ps1 = application/x-powershell psd1 = application/x-powershell psm1 = application/x-powershell +pug = text/x-pug py = text/x-python pyw = text/x-python pyx = text/x-cython
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh new file mode 100644 index 0000000..256be9c --- /dev/null +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
@@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# A sceleton script to demonstrate how to use the preview_submit REST API call. +# +# + +if test -z $server +then + echo "The variable 'server' needs to point to your Gerrit instance" + exit 1 +fi + +if test -z $changeId +then + echo "The variable 'changeId' must contain a valid change Id" + exit 1 +fi + +if test -z $gerrituser +then + echo "The variable 'gerrituser' must contain a user/password" + exit 1 +fi + +curl --digest -u $gerrituser -w '%{http_code}' -o preview \ + $server/a/changes/$changeId/revisions/current/preview_submit?format=zip >http_code +if ! grep 200 http_code >/dev/null +then + # error out: + echo "Error previewing submit $changeId due to:" + cat preview + echo +else + # valid zip file, extract and obtain a bundle for each project + mkdir tmp-bundles + unzip preview -d tmp-bundles + for project in $(cd tmp-bundles && find -type f) + do + # Projects may contain slashes, so create the required + # directory structure + mkdir -p $(dirname $project) + # $project is in the format of "./path/name/project.git" + # remove the leading ./ + proj=${project:-./} + git clone $server/$proj $proj + + # First some nice output: + echo "Verify that the bundle is good:" + GIT_WORK_TREE=$proj GIT_DIR=$proj/.git \ + git bundle verify tmp-bundles/$proj + echo "Checking that the bundle only contains one branch..." + if test \ + "$(GIT_WORK_TREE=$proj GIT_DIR=$proj/.git \ + git bundle list-heads tmp-bundles/$proj |wc -l)" != 1 + then + echo "Submitting $changeId would affect the project" + echo "$proj" + echo "on multiple branches:" + git bundle list-heads + echo "This script does not demonstrate this use case." + exit 1 + fi + # find the target branch: + branch=$(GIT_WORK_TREE=$proj GIT_DIR=$proj/.git \ + git bundle list-heads tmp-bundles/$proj | awk '{print $2}') + echo "found branch $branch" + echo "fetch the bundle into the repository" + GIT_WORK_TREE=$proj GIT_DIR=$proj/.git \ + git fetch tmp-bundles/$proj $branch + echo "and checkout the state" + git -C $proj checkout FETCH_HEAD + done + echo "Now run a test for all of: $(cd tmp-bundles && find -type f)" +fi
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java index bf36738..222fb14 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
@@ -145,6 +145,7 @@ RepositoryConfig.OWNER_GROUP_NAME, ownerGroups); } + @SuppressWarnings("cast") @Test public void testBasePathWhenNotConfigured() { assertThat((Object)repoCfg.getBasePath(new NameKey("someProject"))).isNull(); @@ -158,6 +159,7 @@ .isEqualTo(basePath); } + @SuppressWarnings("cast") @Test public void testBasePathForSpecificFilter() { String basePath = "/someAbsolutePath/someDirectory";
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java index 11f1d54..87dd3d9 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -35,8 +35,10 @@ import org.junit.Before; import org.junit.Test; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Set; public class FromAddressGeneratorProviderTest { @@ -60,6 +62,10 @@ config.setString("sendemail", null, "from", newFrom); } + private void setDomains(List<String> domains) { + config.setStringList("sendemail", null, "allowedDomain", domains); + } + @Test public void testDefaultIsMIXED() { assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.PatternGen.class); @@ -118,7 +124,7 @@ replay(accountCache); final Address r = create().from(user); assertThat(r).isNotNull(); - assertThat(r.name).isEqualTo(name); + assertThat(r.name).isEqualTo(name + " (Code Review)"); assertThat(r.email).isEqualTo(ident.getEmailAddress()); verify(accountCache); } @@ -135,6 +141,88 @@ } @Test + public void testUSERAllowDomain() { + setFrom("USER"); + setDomains(Arrays.asList("*.example.com")); + final String name = "A U. Thor"; + final String email = "a.u.thor@test.example.com"; + final Account.Id user = user(name, email); + + replay(accountCache); + final Address r = create().from(user); + assertThat(r).isNotNull(); + assertThat(r.name).isEqualTo(name); + assertThat(r.email).isEqualTo(email); + verify(accountCache); + } + + @Test + public void testUSERNoAllowDomain() { + setFrom("USER"); + setDomains(Arrays.asList("example.com")); + final String name = "A U. Thor"; + final String email = "a.u.thor@test.com"; + final Account.Id user = user(name, email); + + replay(accountCache); + final Address r = create().from(user); + assertThat(r).isNotNull(); + assertThat(r.name).isEqualTo(name + " (Code Review)"); + assertThat(r.email).isEqualTo(ident.getEmailAddress()); + verify(accountCache); + } + + @Test + public void testUSERAllowDomainTwice() { + setFrom("USER"); + setDomains(Arrays.asList("example.com")); + setDomains(Arrays.asList("test.com")); + final String name = "A U. Thor"; + final String email = "a.u.thor@test.com"; + final Account.Id user = user(name, email); + + replay(accountCache); + final Address r = create().from(user); + assertThat(r).isNotNull(); + assertThat(r.name).isEqualTo(name); + assertThat(r.email).isEqualTo(email); + verify(accountCache); + } + + @Test + public void testUSERAllowDomainTwiceReverse() { + setFrom("USER"); + setDomains(Arrays.asList("test.com")); + setDomains(Arrays.asList("example.com")); + final String name = "A U. Thor"; + final String email = "a.u.thor@test.com"; + final Account.Id user = user(name, email); + + replay(accountCache); + final Address r = create().from(user); + assertThat(r).isNotNull(); + assertThat(r.name).isEqualTo(name + " (Code Review)"); + assertThat(r.email).isEqualTo(ident.getEmailAddress()); + verify(accountCache); + } + + @Test + public void testUSERAllowTwoDomains() { + setFrom("USER"); + setDomains(Arrays.asList("example.com", "test.com")); + final String name = "A U. Thor"; + final String email = "a.u.thor@test.com"; + final Account.Id user = user(name, email); + + replay(accountCache); + final Address r = create().from(user); + assertThat(r).isNotNull(); + assertThat(r.name).isEqualTo(name); + assertThat(r.email).isEqualTo(email); + verify(accountCache); + } + + @Test public void testSelectSERVER() { setFrom("SERVER"); assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.ServerGen.class);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java index fabb53d..0fe4941 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -54,6 +54,7 @@ import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.FakeAccountCache; import com.google.gerrit.testutil.GerritBaseTests; import com.google.gerrit.testutil.InMemoryRepositoryManager; @@ -76,12 +77,31 @@ import org.junit.After; import org.junit.Before; import org.junit.Ignore; +import org.junit.runner.RunWith; import java.sql.Timestamp; import java.util.TimeZone; @Ignore +@RunWith(ConfigSuite.class) public abstract class AbstractChangeNotesTest extends GerritBaseTests { + @ConfigSuite.Default + public static Config changeNotesLegacy() { + Config cfg = new Config(); + cfg.setBoolean("notedb", null, "writeJson", false); + return cfg; + } + + @ConfigSuite.Config + public static Config changeNotesJson() { + Config cfg = new Config(); + cfg.setBoolean("notedb", null, "writeJson", true); + return cfg; + } + + @ConfigSuite.Parameter + public Config testConfig; + private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles"); @@ -110,12 +130,9 @@ protected AllUsersName allUsers; @Inject - protected ChangeNoteUtil noteUtil; - - @Inject protected AbstractChangeNotes.Args args; - private Injector injector; + protected Injector injector; private String systemTimeZone; @Before @@ -143,9 +160,8 @@ injector = Guice.createInjector(new FactoryModule() { @Override public void configure() { - Config cfg = new Config(); install(new GitModule()); - install(NoteDbModule.forTest(cfg)); + install(NoteDbModule.forTest(testConfig)); bind(AllUsersName.class).toProvider(AllUsersNameProvider.class); bind(String.class).annotatedWith(GerritServerId.class) .toInstance("gerrit"); @@ -155,7 +171,7 @@ bind(CapabilityControl.Factory.class) .toProvider(Providers.<CapabilityControl.Factory> of(null)); bind(Config.class).annotatedWith(GerritServerConfig.class) - .toInstance(cfg); + .toInstance(testConfig); bind(String.class).annotatedWith(AnonymousCowardName.class) .toProvider(AnonymousCowardNameProvider.class); bind(String.class).annotatedWith(CanonicalWebUrl.class)
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 c093b75..97bf864 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
@@ -39,6 +39,7 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.server.ReviewerSet; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl; import com.google.gerrit.testutil.TestChanges; import com.google.gerrit.testutil.TestTimeUtil; import com.google.gwtorm.client.KeyUtil; @@ -1259,7 +1260,9 @@ } private static List<PatchSet> latest(Change c) { - return ImmutableList.of(new PatchSet(c.currentPatchSetId())); + PatchSet ps = new PatchSet(c.currentPatchSetId()); + ps.setCreatedOn(c.getLastUpdatedOn()); + return ImmutableList.of(ps); } private static List<PatchSetApproval> approvals(PatchSetApproval... ents) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java index ab37ec9..a893ea8 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -449,6 +449,7 @@ } private RevCommit writeCommit(String body) throws Exception { + ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class); return writeCommit(body, noteUtil.newIdent( changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent, "Anonymous Coward")); @@ -496,6 +497,7 @@ private ChangeNotesParser newParser(ObjectId tip) throws Exception { walk.reset(); + ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class); return new ChangeNotesParser( newChange().getId(), tip, walk, noteUtil, args.metrics); }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java index 0173b05..7a1dbf7 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -24,7 +24,6 @@ import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.junit.Assert.fail; -import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -34,7 +33,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; -import com.google.common.collect.Ordering; import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.reviewdb.client.Account; @@ -47,8 +45,10 @@ import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.RevId; +import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.ReviewerSet; +import com.google.gerrit.server.config.GerritServerId; import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk; import com.google.gerrit.server.util.RequestId; import com.google.gerrit.testutil.TestChanges; @@ -76,6 +76,12 @@ @Inject private DraftCommentNotes.Factory draftNotesFactory; + @Inject + private ChangeNoteUtil noteUtil; + + @Inject + private @GerritServerId String serverId; + @Test public void tagChangeMessage() throws Exception { String tag = "jenkins"; @@ -322,7 +328,10 @@ update.commit(); notes = newNotes(c); - assertThat(notes.getApprovals()).isEmpty(); + assertThat(notes.getApprovals()).containsExactlyEntriesIn( + ImmutableMultimap.of( + psa.getPatchSetId(), + new PatchSetApproval(psa.getKey(), (short) 0, update.getWhen()))); } @Test @@ -344,7 +353,10 @@ update.commit(); notes = newNotes(c); - assertThat(notes.getApprovals()).isEmpty(); + assertThat(notes.getApprovals()).containsExactlyEntriesIn( + ImmutableMultimap.of( + psa.getPatchSetId(), + new PatchSetApproval(psa.getKey(), (short) 0, update.getWhen()))); // Add back approval on same label. update = newUpdate(c, otherUser); @@ -368,13 +380,9 @@ update.commit(); ChangeNotes notes = newNotes(c); - List<PatchSetApproval> approvals = Ordering.natural().onResultOf( - new Function<PatchSetApproval, Integer>() { - @Override - public Integer apply(PatchSetApproval in) { - return in.getAccountId().get(); - } - }).sortedCopy(notes.getApprovals().get(c.currentPatchSetId())); + List<PatchSetApproval> approvals = ReviewDbUtil.intKeyOrdering() + .onResultOf(PatchSetApproval::getAccountId) + .sortedCopy(notes.getApprovals().get(c.currentPatchSetId())); assertThat(approvals).hasSize(2); assertThat(approvals.get(0).getAccountId()) @@ -553,6 +561,47 @@ } @Test + public void assigneeCommit() throws Exception { + Change c = newChange(); + ChangeUpdate update = newUpdate(c, changeOwner); + update.setAssignee(otherUserId); + ObjectId result = update.commit(); + assertThat(result).isNotNull(); + try (RevWalk rw = new RevWalk(repo)) { + RevCommit commit = rw.parseCommit(update.getResult()); + rw.parseBody(commit); + String strIdent = + otherUser.getName() + + " <" + + otherUserId + + "@" + + serverId + + ">"; + assertThat(commit.getFullMessage()) + .contains("Assignee: " + strIdent); + } + } + + @Test + public void assigneeChangeNotes() throws Exception { + Change c = newChange(); + ChangeUpdate update = newUpdate(c, changeOwner); + update.setAssignee(otherUserId); + update.commit(); + + ChangeNotes notes = newNotes(c); + assertThat(notes.getAssignee()).isEqualTo(otherUserId); + + update = newUpdate(c, changeOwner); + update.setAssignee(changeOwner.getAccountId()); + update.commit(); + + notes = newNotes(c); + assertThat(notes.getAssignee()).isEqualTo(changeOwner.getAccountId()); + + } + + @Test public void hashtagCommit() throws Exception { Change c = newChange(); ChangeUpdate update = newUpdate(c, changeOwner); @@ -960,7 +1009,10 @@ update.commit(); ChangeNotes notes = newNotes(c); - assertThat(readNote(notes, commit)).isEqualTo(pushCert); + String note = readNote(notes, commit); + if (!testJson()) { + assertThat(note).isEqualTo(pushCert); + } Map<PatchSet.Id, PatchSet> patchSets = notes.getPatchSets(); assertThat(patchSets.get(psId1).getPushCertificate()).isNull(); assertThat(patchSets.get(psId2).getPushCertificate()).isEqualTo(pushCert); @@ -976,23 +1028,27 @@ update.commit(); notes = newNotes(c); - assertThat(readNote(notes, commit)).isEqualTo( - pushCert - + "Revision: " + commit.name() + "\n" - + "Patch-set: 2\n" - + "File: a.txt\n" - + "\n" - + "1:2-3:4\n" - + ChangeNoteUtil.formatTime(serverIdent, ts) + "\n" - + "Author: Change Owner <1@gerrit>\n" - + "UUID: uuid1\n" - + "Bytes: 7\n" - + "Comment\n" - + "\n"); + patchSets = notes.getPatchSets(); assertThat(patchSets.get(psId1).getPushCertificate()).isNull(); assertThat(patchSets.get(psId2).getPushCertificate()).isEqualTo(pushCert); assertThat(notes.getComments()).isNotEmpty(); + + if (!testJson()) { + assertThat(readNote(notes, commit)).isEqualTo( + pushCert + + "Revision: " + commit.name() + "\n" + + "Patch-set: 2\n" + + "File: a.txt\n" + + "\n" + + "1:2-3:4\n" + + ChangeNoteUtil.formatTime(serverIdent, ts) + "\n" + + "Author: Change Owner <1@gerrit>\n" + + "UUID: uuid1\n" + + "Bytes: 7\n" + + "Comment\n" + + "\n"); + } } @Test @@ -1397,34 +1453,37 @@ walk.getObjectReader().open( note.getData(), Constants.OBJ_BLOB).getBytes(); String noteString = new String(bytes, UTF_8); - assertThat(noteString).isEqualTo( - "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" - + "Patch-set: 1\n" - + "File: file1\n" - + "\n" - + "1:1-2:1\n" - + ChangeNoteUtil.formatTime(serverIdent, time1) + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid1\n" - + "Bytes: 9\n" - + "comment 1\n" - + "\n" - + "2:1-3:1\n" - + ChangeNoteUtil.formatTime(serverIdent, time2) + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid2\n" - + "Bytes: 9\n" - + "comment 2\n" - + "\n" - + "File: file2\n" - + "\n" - + "3:0-4:1\n" - + ChangeNoteUtil.formatTime(serverIdent, time3) + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid3\n" - + "Bytes: 9\n" - + "comment 3\n" - + "\n"); + + if (!testJson()) { + assertThat(noteString).isEqualTo( + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" + + "Patch-set: 1\n" + + "File: file1\n" + + "\n" + + "1:1-2:1\n" + + ChangeNoteUtil.formatTime(serverIdent, time1) + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid1\n" + + "Bytes: 9\n" + + "comment 1\n" + + "\n" + + "2:1-3:1\n" + + ChangeNoteUtil.formatTime(serverIdent, time2) + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid2\n" + + "Bytes: 9\n" + + "comment 2\n" + + "\n" + + "File: file2\n" + + "\n" + + "3:0-4:1\n" + + ChangeNoteUtil.formatTime(serverIdent, time3) + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid3\n" + + "Bytes: 9\n" + + "comment 3\n" + + "\n"); + } } } @@ -1468,25 +1527,28 @@ walk.getObjectReader().open( note.getData(), Constants.OBJ_BLOB).getBytes(); String noteString = new String(bytes, UTF_8); - assertThat(noteString).isEqualTo( - "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" - + "Base-for-patch-set: 1\n" - + "File: file1\n" - + "\n" - + "1:1-2:1\n" - + ChangeNoteUtil.formatTime(serverIdent, time1) + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid1\n" - + "Bytes: 9\n" - + "comment 1\n" - + "\n" - + "2:1-3:1\n" - + ChangeNoteUtil.formatTime(serverIdent, time2) + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid2\n" - + "Bytes: 9\n" - + "comment 2\n" - + "\n"); + + if (!testJson()) { + assertThat(noteString).isEqualTo( + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" + + "Base-for-patch-set: 1\n" + + "File: file1\n" + + "\n" + + "1:1-2:1\n" + + ChangeNoteUtil.formatTime(serverIdent, time1) + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid1\n" + + "Bytes: 9\n" + + "comment 1\n" + + "\n" + + "2:1-3:1\n" + + ChangeNoteUtil.formatTime(serverIdent, time2) + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid2\n" + + "Bytes: 9\n" + + "comment 2\n" + + "\n"); + } } } @@ -1537,37 +1599,39 @@ note.getData(), Constants.OBJ_BLOB).getBytes(); String noteString = new String(bytes, UTF_8); String timeStr = ChangeNoteUtil.formatTime(serverIdent, time); - assertThat(noteString).isEqualTo( - "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" - + "Base-for-patch-set: 1\n" - + "File: file1\n" - + "\n" - + "1:1-2:1\n" - + timeStr + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid1\n" - + "Bytes: 9\n" - + "comment 1\n" - + "\n" - + "2:1-3:1\n" - + timeStr + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid2\n" - + "Bytes: 9\n" - + "comment 2\n" - + "\n" - + "Base-for-patch-set: 2\n" - + "File: file1\n" - + "\n" - + "1:1-2:1\n" - + timeStr + "\n" - + "Author: Other Account <2@gerrit>\n" - + "UUID: uuid3\n" - + "Bytes: 9\n" - + "comment 3\n" - + "\n"); - } + if (!testJson()) { + assertThat(noteString).isEqualTo( + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" + + "Base-for-patch-set: 1\n" + + "File: file1\n" + + "\n" + + "1:1-2:1\n" + + timeStr + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid1\n" + + "Bytes: 9\n" + + "comment 1\n" + + "\n" + + "2:1-3:1\n" + + timeStr + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid2\n" + + "Bytes: 9\n" + + "comment 2\n" + + "\n" + + "Base-for-patch-set: 2\n" + + "File: file1\n" + + "\n" + + "1:1-2:1\n" + + timeStr + "\n" + + "Author: Other Account <2@gerrit>\n" + + "UUID: uuid3\n" + + "Bytes: 9\n" + + "comment 3\n" + + "\n"); + } + } assertThat(notes.getComments()).isEqualTo( ImmutableMultimap.of( revId, comment1, @@ -1609,20 +1673,22 @@ note.getData(), Constants.OBJ_BLOB).getBytes(); String noteString = new String(bytes, UTF_8); String timeStr = ChangeNoteUtil.formatTime(serverIdent, time); - assertThat(noteString).isEqualTo( - "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" - + "Patch-set: 1\n" - + "File: file1\n" - + "\n" - + "1:1-2:1\n" - + timeStr + "\n" - + "Author: Weird\u0002User <3@gerrit>\n" - + "UUID: uuid\n" - + "Bytes: 7\n" - + "comment\n" - + "\n"); - } + if (!testJson()) { + assertThat(noteString).isEqualTo( + "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n" + + "Patch-set: 1\n" + + "File: file1\n" + + "\n" + + "1:1-2:1\n" + + timeStr + "\n" + + "Author: Weird\u0002User <3@gerrit>\n" + + "UUID: uuid\n" + + "Bytes: 7\n" + + "comment\n" + + "\n"); + } + } assertThat(notes.getComments()) .isEqualTo(ImmutableMultimap.of(comment.getRevId(), comment)); } @@ -2294,6 +2360,10 @@ assertThat(comments.get(1).getMessage()).isEqualTo("comment 2"); } + private boolean testJson() { + return noteUtil.getWriteJson(); + } + private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception { ObjectId dataId = notes.revisionNoteMap.noteMap.getNote(noteId).getData(); return new String(
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java index bf5abba..5a8c12b 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.util.RequestId; +import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.TestChanges; import org.eclipse.jgit.lib.ObjectId; @@ -30,10 +31,12 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.Date; import java.util.TimeZone; +@RunWith(ConfigSuite.class) public class CommitMessageOutputTest extends AbstractChangeNotesTest { @Test public void approvalsCommitFormatSimple() throws Exception {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java new file mode 100644 index 0000000..969adf0 --- /dev/null +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
@@ -0,0 +1,177 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.notedb.rebuild; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.collect.Lists; +import com.google.gerrit.common.TimeUtil; +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.server.notedb.ChangeUpdate; +import com.google.gerrit.testutil.TestTimeUtil; + +import org.junit.Before; +import org.junit.Test; + +import java.sql.Timestamp; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class EventSorterTest { + private class TestEvent extends Event { + protected TestEvent(Timestamp when) { + super( + new PatchSet.Id(new Change.Id(1), 1), + new Account.Id(1000), + when, changeCreatedOn, null); + } + + @Override + boolean uniquePerUpdate() { + return false; + } + + @Override + void apply(ChangeUpdate update) { + throw new UnsupportedOperationException(); + } + } + + private Timestamp changeCreatedOn; + + @Before + public void setUp() { + TestTimeUtil.resetWithClockStep(10, TimeUnit.SECONDS); + changeCreatedOn = TimeUtil.nowTs(); + } + + @Test + public void naturalSort() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + + List<Event> events = events(e2, e1, e3); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e1, e2, e3).inOrder(); + } + + @Test + public void topoSortNoChange() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + e2.addDep(e1); + + List<Event> events = events(e2, e1, e3); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e1, e2, e3).inOrder(); + } + + @Test + public void topoSortOneDep() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + e1.addDep(e2); + + List<Event> events = events(e2, e3, e1); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e2, e1, e3).inOrder(); + } + + @Test + public void topoSortChainOfDeps() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + Event e4 = new TestEvent(TimeUtil.nowTs()); + e1.addDep(e2); + e2.addDep(e3); + e3.addDep(e4); + + List<Event> events = events(e1, e2, e3, e4); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e4, e3, e2, e1).inOrder(); + } + + @Test + public void topoSortMultipleDeps() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + Event e4 = new TestEvent(TimeUtil.nowTs()); + e1.addDep(e2); + e1.addDep(e4); + e2.addDep(e3); + + // Processing 3 pops 2, processing 4 pops 1. + List<Event> events = events(e2, e3, e1, e4); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e3, e2, e4, e1).inOrder(); + } + + @Test + public void topoSortMultipleDepsPreservesNaturalOrder() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + Event e4 = new TestEvent(TimeUtil.nowTs()); + e1.addDep(e4); + e2.addDep(e4); + e3.addDep(e4); + + // Processing 4 pops 1, 2, 3 in natural order. + List<Event> events = events(e4, e3, e2, e1); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e4, e1, e2, e3).inOrder(); + } + + @Test + public void topoSortCycle() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + + // Implementation is not really defined, but infinite looping would be bad. + // According to current implementation details, 2 pops 1, 1 pops 2 which was + // already seen. + List<Event> events = events(e2, e1); + new EventSorter(events).sort(); + assertThat(events).containsExactly(e1, e2).inOrder(); + } + + @Test + public void topoSortDepNotInInputList() { + Event e1 = new TestEvent(TimeUtil.nowTs()); + Event e2 = new TestEvent(TimeUtil.nowTs()); + Event e3 = new TestEvent(TimeUtil.nowTs()); + e1.addDep(e3); + + List<Event> events = events(e2, e1); + try { + new EventSorter(events).sort(); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + private List<Event> events(Event... es) { + return Lists.newArrayList(es); + } +}
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 d4d77bd..596ed59 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
@@ -137,13 +137,13 @@ } private void assertCanSubmit(String ref, ProjectControl u) { - assertThat(u.controlForRef(ref).canSubmit()) + assertThat(u.controlForRef(ref).canSubmit(false)) .named("can submit " + ref) .isTrue(); } private void assertCannotSubmit(String ref, ProjectControl u) { - assertThat(u.controlForRef(ref).canSubmit()) + assertThat(u.controlForRef(ref).canSubmit(false)) .named("can submit " + ref) .isFalse(); } @@ -685,6 +685,43 @@ } @Test + public void testUnblockMoreSpecificRefInLocal_Fails() { + block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*"); + allow(local, PUSH, DEVS, "refs/heads/master"); + + ProjectControl u = user(local, DEVS); + assertCannotUpdate("refs/heads/master", u); + } + + @Test + public void testUnblockMoreSpecificRefWithExclusiveFlag() { + block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*"); + allow(local, PUSH, DEVS, "refs/heads/master", true); + + ProjectControl u = user(local, DEVS); + assertCanUpdate("refs/heads/master", u); + } + + @Test + public void testUnblockMoreSpecificRefInLocalWithExclusiveFlag_Fails() { + block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*"); + allow(local, PUSH, DEVS, "refs/heads/master", true); + + ProjectControl u = user(local, DEVS); + assertCannotUpdate("refs/heads/master", u); + } + + @Test + public void testUnblockOtherPermissionWithMoreSpecificRefAndExclusiveFlag_Fails() { + block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*"); + allow(local, PUSH, DEVS, "refs/heads/master"); + allow(local, SUBMIT, DEVS, "refs/heads/master", true); + + ProjectControl u = user(local, DEVS); + assertCannotUpdate("refs/heads/master", u); + } + + @Test public void testUnblockLargerScope_Fails() { block(local, PUSH, ANONYMOUS_USERS, "refs/heads/master"); allow(local, PUSH, DEVS, "refs/heads/*"); @@ -825,6 +862,14 @@ } @Test + public void testBlockOwner() { + block(parent, OWNER, ANONYMOUS_USERS, "refs/*"); + allow(local, OWNER, DEVS, "refs/*"); + + assertThat(user(local, DEVS).isOwner()).isFalse(); + } + + @Test public void testValidateRefPatternsOK() throws Exception { RefPattern.validate("refs/*"); RefPattern.validate("^refs/heads/*");
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 772c778..9cbdae2 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
@@ -97,6 +97,13 @@ } public static PermissionRule allow(ProjectConfig project, + String permissionName, AccountGroup.UUID group, String ref, + boolean exclusive) { + return grant(project, permissionName, newRule(project, group), ref, + exclusive); + } + + public static PermissionRule allow(ProjectConfig project, String capabilityName, AccountGroup.UUID group) { PermissionRule rule = newRule(project, group); project.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true) @@ -163,9 +170,18 @@ private static PermissionRule grant(ProjectConfig project, String permissionName, PermissionRule rule, String ref) { - project.getAccessSection(ref, true) // - .getPermission(permissionName, true) // - .add(rule); + return grant(project, permissionName, rule, ref, false); + } + + private static PermissionRule grant(ProjectConfig project, + String permissionName, PermissionRule rule, String ref, + boolean exclusive) { + Permission permission = project.getAccessSection(ref, true) + .getPermission(permissionName, true); + if (exclusive) { + permission.setExclusiveGroup(exclusive); + } + permission.add(rule); return rule; }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java index f7b3b11..b65d49d 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -17,7 +17,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.gerrit.extensions.api.GerritApi; @@ -476,22 +475,10 @@ } protected static Iterable<Integer> ids(AccountInfo... accounts) { - return FluentIterable.from(Arrays.asList(accounts)).transform( - new Function<AccountInfo, Integer>() { - @Override - public Integer apply(AccountInfo in) { - return in._accountId; - } - }); + return FluentIterable.of(accounts).transform(a -> a._accountId); } protected static Iterable<Integer> ids(Iterable<AccountInfo> accounts) { - return FluentIterable.from(accounts).transform( - new Function<AccountInfo, Integer>() { - @Override - public Integer apply(AccountInfo in) { - return in._accountId; - } - }); + return FluentIterable.from(accounts).transform(a -> a._accountId); } }
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 0c658bf..dae2abb 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
@@ -23,7 +23,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.fail; -import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -37,6 +36,7 @@ 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; +import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.StarsInput; import com.google.gerrit.extensions.api.groups.GroupInput; @@ -1436,13 +1436,8 @@ for (int i = 1; i <= 11; i++) { Iterable<ChangeData> cds = internalChangeQuery.byCommitsOnBranchNotMerged( repo.getRepository(), db, dest, shas, i); - Iterable<Integer> ids = FluentIterable.from(cds).transform( - new Function<ChangeData, Integer>() { - @Override - public Integer apply(ChangeData in) { - return in.getId().get(); - } - }); + Iterable<Integer> ids = FluentIterable.from(cds) + .transform(in -> in.getId().get()); String name = "limit " + i; assertThat(ids).named(name).hasSize(n); assertThat(ids).named(name) @@ -1583,7 +1578,7 @@ PatchSetInserter inserter = patchSetFactory.create( ctl, new PatchSet.Id(c.getId(), n), commit) - .setSendMail(false) + .setNotify(NotifyHandling.NONE) .setFireRevisionCreated(false) .setValidatePolicy(CommitValidators.Policy.NONE); try (BatchUpdate bu = updateFactory.create( @@ -1639,24 +1634,22 @@ StringBuilder b = new StringBuilder(); b.append("query '").append(query.getQuery()) .append("' with expected changes "); - b.append(format(Iterables.transform(Arrays.asList(expectedChanges), - new Function<Change, Integer>() { - @Override - public Integer apply(Change change) { - return change.getChangeId(); - } - }))); + b.append(format( + Arrays.stream(expectedChanges).map(Change::getChangeId).iterator())); b.append(" and result "); b.append(format(actualIds)); return b.toString(); } private String format(Iterable<Integer> changeIds) throws RestApiException { + return format(changeIds.iterator()); + } + + private String format(Iterator<Integer> changeIds) throws RestApiException { StringBuilder b = new StringBuilder(); b.append("["); - Iterator<Integer> it = changeIds.iterator(); - while (it.hasNext()) { - int id = it.next(); + while (changeIds.hasNext()) { + int id = changeIds.next(); ChangeInfo c = gApi.changes().id(id).get(); b.append("{").append(id).append(" (").append(c.changeId) .append("), ").append("dest=").append( @@ -1665,7 +1658,7 @@ .append("status=").append(c.status).append(", ") .append("lastUpdated=").append(c.updated.getTime()) .append("}"); - if (it.hasNext()) { + if (changeIds.hasNext()) { b.append(", "); } } @@ -1674,23 +1667,13 @@ } protected static Iterable<Integer> ids(Change... changes) { - return FluentIterable.from(Arrays.asList(changes)).transform( - new Function<Change, Integer>() { - @Override - public Integer apply(Change in) { - return in.getId().get(); - } - }); + return FluentIterable.from(Arrays.asList(changes)) + .transform(in -> in.getId().get()); } protected static Iterable<Integer> ids(Iterable<ChangeInfo> changes) { - return FluentIterable.from(changes).transform( - new Function<ChangeInfo, Integer>() { - @Override - public Integer apply(ChangeInfo in) { - return in._number; - } - }); + return FluentIterable.from(changes) + .transform(in -> in._number); } protected static long lastUpdatedMs(Change c) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java index f2d563e..b8888e2 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
@@ -14,9 +14,9 @@ package com.google.gerrit.testutil; +import static java.util.stream.Collectors.toList; + import com.google.auto.value.AutoValue; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gerrit.common.errors.EmailException; @@ -111,17 +111,14 @@ } } - public ImmutableList<Message> getMessages(String changeId, String type) { + public List<Message> getMessages(String changeId, String type) { final String idFooter = "\nGerrit-Change-Id: " + changeId + "\n"; final String typeFooter = "\nGerrit-MessageType: " + type + "\n"; - return FluentIterable.from(getMessages()) - .filter(new Predicate<Message>() { - @Override - public boolean apply(Message in) { - return in.body().contains(idFooter) - && in.body().contains(typeFooter); - } - }).toList(); + return getMessages() + .stream() + .filter(in -> in.body().contains(idFooter) + && in.body().contains(typeFooter)) + .collect(toList()); } private void waitForEmails() {
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 9e5b776..84fd9d7 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
@@ -18,11 +18,11 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.gpg.GpgModule; import com.google.gerrit.metrics.DisabledMetricMaker; import com.google.gerrit.metrics.MetricMaker; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdentProvider; @@ -36,6 +36,7 @@ import com.google.gerrit.server.config.CanonicalWebUrlModule; import com.google.gerrit.server.config.CanonicalWebUrlProvider; import com.google.gerrit.server.config.GerritGlobalModule; +import com.google.gerrit.server.config.GerritOptions; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerId; import com.google.gerrit.server.config.SitePath; @@ -50,6 +51,8 @@ import com.google.gerrit.server.index.IndexModule.IndexType; import com.google.gerrit.server.index.change.ChangeSchemaDefinitions; import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier; +import com.google.gerrit.server.notedb.ChangeBundleReader; +import com.google.gerrit.server.notedb.GwtormChangeBundleReader; import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.patch.DiffExecutor; import com.google.gerrit.server.schema.DataSourceType; @@ -148,6 +151,8 @@ // TODO(dborowitz): Use jimfs. bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get(".")); bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg); + bind(GerritOptions.class) + .toInstance(new GerritOptions(cfg, false, false, false)); bind(PersonIdent.class) .annotatedWith(GerritPersonIdent.class) .toProvider(GerritPersonIdentProvider.class); @@ -175,6 +180,7 @@ .to(InMemoryH2Type.class); bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}) .to(InMemoryDatabase.class); + bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class); bind(SecureStore.class).to(DefaultSecureStore.class);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java index 61bfe78..6dba19a 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java
@@ -15,9 +15,10 @@ package com.google.gerrit.testutil; import static com.google.common.truth.Truth.assertThat; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; import com.google.common.base.Joiner; -import com.google.common.collect.Iterables; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; @@ -26,8 +27,12 @@ import com.google.gerrit.server.PatchLineCommentsUtil; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeBundle; +import com.google.gerrit.server.notedb.ChangeBundleReader; import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.notedb.ChangeRebuilder; +import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder; +import com.google.gwtorm.client.IntKey; +import com.google.gwtorm.server.OrmException; +import com.google.gwtorm.server.OrmRuntimeException; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -40,6 +45,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; @Singleton public class NoteDbChecker { @@ -48,6 +54,7 @@ private final Provider<ReviewDb> dbProvider; private final GitRepositoryManager repoManager; private final TestNotesMigration notesMigration; + private final ChangeBundleReader bundleReader; private final ChangeNotes.Factory notesFactory; private final ChangeRebuilder changeRebuilder; private final PatchLineCommentsUtil plcUtil; @@ -56,11 +63,13 @@ NoteDbChecker(Provider<ReviewDb> dbProvider, GitRepositoryManager repoManager, TestNotesMigration notesMigration, + ChangeBundleReader bundleReader, ChangeNotes.Factory notesFactory, ChangeRebuilder changeRebuilder, PatchLineCommentsUtil plcUtil) { this.dbProvider = dbProvider; this.repoManager = repoManager; + this.bundleReader = bundleReader; this.notesMigration = notesMigration; this.notesFactory = notesFactory; this.changeRebuilder = changeRebuilder; @@ -69,16 +78,14 @@ public void rebuildAndCheckAllChanges() throws Exception { rebuildAndCheckChanges( - Iterables.transform( - getUnwrappedDb().changes().all(), - ReviewDbUtil.changeIdFunction())); + getUnwrappedDb().changes().all().toList().stream().map(Change::getId)); } public void rebuildAndCheckChanges(Change.Id... changeIds) throws Exception { - rebuildAndCheckChanges(Arrays.asList(changeIds)); + rebuildAndCheckChanges(Arrays.stream(changeIds)); } - public void rebuildAndCheckChanges(Iterable<Change.Id> changeIds) + private void rebuildAndCheckChanges(Stream<Change.Id> changeIds) throws Exception { ReviewDb db = getUnwrappedDb(); @@ -107,11 +114,7 @@ } public void checkChanges(Change.Id... changeIds) throws Exception { - checkChanges(Arrays.asList(changeIds)); - } - - public void checkChanges(Iterable<Change.Id> changeIds) throws Exception { - checkActual(readExpected(changeIds), new ArrayList<String>()); + checkActual(readExpected(Arrays.stream(changeIds)), new ArrayList<>()); } public void assertNoChangeRef(Project.NameKey project, Change.Id changeId) @@ -121,24 +124,26 @@ } } - private List<ChangeBundle> readExpected(Iterable<Change.Id> changeIds) + private List<ChangeBundle> readExpected(Stream<Change.Id> changeIds) throws Exception { - ReviewDb db = getUnwrappedDb(); boolean old = notesMigration.readChanges(); try { notesMigration.setReadChanges(false); - List<Change.Id> sortedIds = - ReviewDbUtil.intKeyOrdering().sortedCopy(changeIds); - List<ChangeBundle> expected = new ArrayList<>(sortedIds.size()); - for (Change.Id id : sortedIds) { - expected.add(ChangeBundle.fromReviewDb(db, id)); - } - return expected; + return changeIds.sorted(comparing(IntKey::get)) + .map(this::readBundleUnchecked).collect(toList()); } finally { notesMigration.setReadChanges(old); } } + private ChangeBundle readBundleUnchecked(Change.Id id) { + try { + return bundleReader.fromReviewDb(getUnwrappedDb(), id); + } catch (OrmException e) { + throw new OrmRuntimeException(e); + } + } + private void checkActual(List<ChangeBundle> allExpected, List<String> msgs) throws Exception { ReviewDb db = getUnwrappedDb();
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK index 54b83e2..fcb844f 100644 --- a/gerrit-sshd/BUCK +++ b/gerrit-sshd/BUCK
@@ -56,5 +56,4 @@ '//lib:truth', '//lib/mina:sshd', ], - source_under_test = [':sshd'], )
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 25fb7a7..3e31fab 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
@@ -385,14 +385,6 @@ } } - public void checkExclusivity(final Object arg1, final String arg1name, - final Object arg2, final String arg2name) throws UnloggedFailure { - if (arg1 != null && arg2 != null) { - throw new UnloggedFailure(String.format( - "%s and %s options are mutually exclusive.", arg1name, arg2name)); - } - } - private final class TaskThunk implements CancelableRunnable, ProjectRunnable { private final CommandRunnable thunk; private final String taskName;
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 eb0d7b2..bd5e9f3 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,9 +14,6 @@ package com.google.gerrit.sshd.commands; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.common.ProjectInfo; @@ -200,15 +197,11 @@ return childProjects; } - private Set<Project.NameKey> getAllParents(final Project.NameKey projectName) { + private Set<Project.NameKey> getAllParents(Project.NameKey projectName) { ProjectState ps = projectCache.get(projectName); - return ImmutableSet.copyOf(Iterables.transform( - ps != null ? ps.parents() : Collections.<ProjectState> emptySet(), - new Function<ProjectState, Project.NameKey> () { - @Override - public Project.NameKey apply(ProjectState in) { - return in.getProject().getNameKey(); - } - })); + if (ps == null) { + return Collections.emptySet(); + } + return ps.parents().transform(s -> s.getProject().getNameKey()).toSet(); } }
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 f78b4df..e15a792 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
@@ -16,7 +16,6 @@ import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.gerrit.extensions.restapi.RestApiException; @@ -56,14 +55,8 @@ @Override protected void run() throws Failure { try { - BanCommit.Input input = - BanCommit.Input.fromCommits(Lists.transform(commitsToBan, - new Function<ObjectId, String>() { - @Override - public String apply(ObjectId oid) { - return oid.getName(); - } - })); + BanCommit.Input input = BanCommit.Input.fromCommits( + Lists.transform(commitsToBan, ObjectId::getName)); input.reason = reason; BanResultInfo r = banCommit.apply(new ProjectResource(projectControl), input);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java index d3ff06f..4ecf284 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -16,7 +16,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.extensions.annotations.RequiresCapability; @@ -74,12 +73,7 @@ input.name = fullName; input.sshKey = readSshKey(); input.httpPassword = httpPassword; - input.groups = Lists.transform(groups, new Function<AccountGroup.Id, String>() { - @Override - public String apply(AccountGroup.Id id) { - return id.toString(); - } - }); + input.groups = Lists.transform(groups, AccountGroup.Id::toString); try { createAccountFactory.create(username).apply(TopLevelResource.INSTANCE, input); } catch (RestApiException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java index 22f9683..f9fd1a9 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -14,8 +14,8 @@ package com.google.gerrit.sshd.commands; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; +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.api.groups.GroupInput; @@ -123,30 +123,15 @@ private void addMembers(GroupResource rsrc) throws RestApiException, OrmException, IOException { - AddMembers.Input input = - AddMembers.Input.fromMembers(FluentIterable - .from(initialMembers) - .transform(new Function<Account.Id, String>() { - @Override - public String apply(Account.Id id) { - return String.valueOf(id.get()); - } - }) - .toList()); + AddMembers.Input input = AddMembers.Input.fromMembers( + initialMembers.stream().map(Object::toString).collect(toList())); addMembers.apply(rsrc, input); } private void addIncludedGroups(GroupResource rsrc) throws RestApiException, OrmException { - AddIncludedGroups.Input input = - AddIncludedGroups.Input.fromGroups(FluentIterable.from(initialGroups) - .transform(new Function<AccountGroup.UUID, String>() { - @Override - public String apply(AccountGroup.UUID id) { - return id.get(); - } - }).toList()); - + AddIncludedGroups.Input input = AddIncludedGroups.Input.fromGroups( + initialGroups.stream().map(AccountGroup.UUID::get).collect(toList())); addIncludedGroups.apply(rsrc, input); } }
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 db4f313..3ef3309 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
@@ -15,7 +15,6 @@ package com.google.gerrit.sshd.commands; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.gerrit.common.data.GlobalCapability; @@ -140,13 +139,7 @@ ProjectInput input = new ProjectInput(); input.name = projectName; if (ownerIds != null) { - input.owners = Lists.transform(ownerIds, - new Function<AccountGroup.UUID, String>() { - @Override - public String apply(AccountGroup.UUID uuid) { - return uuid.get(); - } - }); + input.owners = Lists.transform(ownerIds, AccountGroup.UUID::get); } if (newParent != null) { input.parent = newParent.getProject().getName();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java index a7e01c5..f654f6a 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -14,11 +14,10 @@ package com.google.gerrit.sshd.commands; -import com.google.common.base.Function; -import com.google.common.base.Joiner; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + import com.google.common.base.MoreObjects; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.reviewdb.client.Account; @@ -110,55 +109,37 @@ private void reportMembersAction(String action, GroupResource group, List<Account.Id> accountIdList) throws UnsupportedEncodingException, IOException { - out.write(String.format( - "Members %s group %s: %s\n", - action, - group.getName(), - Joiner.on(", ").join( - Iterables.transform(accountIdList, - new Function<Account.Id, String>() { - @Override - public String apply(Account.Id accountId) { - return MoreObjects.firstNonNull(accountCache.get(accountId) - .getAccount().getPreferredEmail(), "n/a"); - } - }))).getBytes(ENC)); + String names = accountIdList.stream() + .map(accountId -> + MoreObjects.firstNonNull( + accountCache.get(accountId).getAccount().getPreferredEmail(), + "n/a")) + .collect(joining(", ")); + out.write( + String.format( + "Members %s group %s: %s\n", action, group.getName(), names) + .getBytes(ENC)); } private void reportGroupsAction(String action, GroupResource group, List<AccountGroup.UUID> groupUuidList) throws UnsupportedEncodingException, IOException { - out.write(String.format( - "Groups %s group %s: %s\n", - action, - group.getName(), - Joiner.on(", ").join( - Iterables.transform(groupUuidList, - new Function<AccountGroup.UUID, String>() { - @Override - public String apply(AccountGroup.UUID uuid) { - return groupCache.get(uuid).getName(); - } - }))).getBytes(ENC)); + String names = groupUuidList.stream() + .map(uuid -> groupCache.get(uuid).getName()) + .collect(joining(", ")); + out.write( + String.format( + "Groups %s group %s: %s\n", action, group.getName(), names) + .getBytes(ENC)); } private AddIncludedGroups.Input fromGroups(List<AccountGroup.UUID> accounts) { - return AddIncludedGroups.Input.fromGroups(Lists.newArrayList(Iterables - .transform(accounts, new Function<AccountGroup.UUID, String>() { - @Override - public String apply(AccountGroup.UUID uuid) { - return uuid.toString(); - } - }))); + return AddIncludedGroups.Input.fromGroups( + accounts.stream().map(Object::toString).collect(toList())); } private AddMembers.Input fromMembers(List<Account.Id> accounts) { - return AddMembers.Input.fromMembers(Lists.newArrayList(Iterables.transform( - accounts, new Function<Account.Id, String>() { - @Override - public String apply(Account.Id id) { - return id.toString(); - } - }))); + return AddMembers.Input.fromMembers( + accounts.stream().map(Object::toString).collect(toList())); } }
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 ac64803..bffb114 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
@@ -15,6 +15,7 @@ package com.google.gerrit.sshd.commands; import com.google.gerrit.extensions.api.changes.AddReviewerInput; +import com.google.gerrit.extensions.api.changes.DeleteReviewerInput; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; @@ -111,7 +112,7 @@ ReviewerResource rsrc = reviewerFactory.create(changeRsrc, reviewer); String error = null; try { - deleteReviewer.apply(rsrc, new DeleteReviewer.Input()); + deleteReviewer.apply(rsrc, new DeleteReviewerInput()); } catch (ResourceNotFoundException e) { error = String.format("could not remove %s: not found", reviewer); } catch (Exception e) {
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 0edba4f..99ced68 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,8 +18,8 @@ import com.google.common.collect.ImmutableMap; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.change.AllowedFormats; import com.google.gerrit.server.change.ArchiveFormat; -import com.google.gerrit.server.change.GetArchive; import com.google.gerrit.sshd.AbstractGitCommand; import com.google.inject.Inject; @@ -101,7 +101,7 @@ } @Inject - private GetArchive.AllowedFormats allowedFormats; + private AllowedFormats allowedFormats; @Inject private ReviewDb db; private Options options = new Options();
diff --git a/gerrit-util-http/BUCK b/gerrit-util-http/BUCK index cfab096..79ef836 100644 --- a/gerrit-util-http/BUCK +++ b/gerrit-util-http/BUCK
@@ -34,7 +34,6 @@ '//lib:truth', '//lib/easymock:easymock', ], - source_under_test = [':http'], # TODO(sop) Remove after Buck supports Eclipse visibility = ['//tools/eclipse:classpath'], )
diff --git a/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java b/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java index 3991b95..34f2672 100644 --- a/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java +++ b/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Function; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedListMultimap; @@ -143,18 +142,12 @@ return Iterables.getFirst(parameters.get(name), null); } - private static final Function<Collection<String>, String[]> STRING_COLLECTION_TO_ARRAY = - new Function<Collection<String>, String[]>() { - @Override - public String[] apply(Collection<String> values) { - return values.toArray(new String[0]); - } - }; - @Override public Map<String, String[]> getParameterMap() { return Collections.unmodifiableMap( - Maps.transformValues(parameters.asMap(), STRING_COLLECTION_TO_ARRAY)); + Maps.transformValues( + parameters.asMap(), + vs -> vs.toArray(new String[0]))); } @Override @@ -164,7 +157,7 @@ @Override public String[] getParameterValues(String name) { - return STRING_COLLECTION_TO_ARRAY.apply(parameters.get(name)); + return parameters.get(name).toArray(new String[0]); } public void setQueryString(String qs) {
diff --git a/gerrit-war/BUILD b/gerrit-war/BUILD new file mode 100644 index 0000000..86c838f --- /dev/null +++ b/gerrit-war/BUILD
@@ -0,0 +1,70 @@ +load('//tools/bzl:genrule2.bzl', 'genrule2') + +java_library( + name = 'init', + srcs = glob(['src/main/java/**/*.java']), + deps = [ + '//gerrit-cache-h2:cache-h2', + '//gerrit-extension-api:api', + '//gerrit-gpg:gpg', + '//gerrit-httpd:httpd', + '//gerrit-lucene:lucene', + '//gerrit-oauth:oauth', + '//gerrit-openid:openid', + '//gerrit-pgm:http', + '//gerrit-pgm:init', + '//gerrit-pgm:init-api', + '//gerrit-pgm:util', + '//gerrit-reviewdb:server', + '//gerrit-server:server', + '//gerrit-server/src/main/prolog:common', + '//gerrit-sshd:sshd', + '//lib:guava', + '//lib:gwtorm', + '//lib:servlet-api-3_1', + '//lib/guice:guice', + '//lib/guice:guice-servlet', + '//lib/jgit/org.eclipse.jgit:jgit', + '//lib/log:api', + ], + visibility = ['//visibility:public'], +) + +genrule2( + name = 'webapp_assets', + cmd = 'cd gerrit-war/src/main/webapp; zip -qr $$ROOT/$@ .', + srcs = glob(['src/main/webapp/**/*']), + out = 'webapp_assets.zip', + visibility = ['//visibility:public'], +) + +java_import( + name = 'log4j-config', + jars = [':log4j-config__jar'], + visibility = ['//visibility:public'], +) + +genrule2( + name = 'log4j-config__jar', + cmd = 'cd gerrit-war/src/main/resources && zip -9Dqr $$ROOT/$@ .', + srcs = ['src/main/resources/log4j.properties'], + out = 'log4j-config.jar', +) + +java_import( + name = 'version', + jars = [':gen_version'], + visibility = ['//visibility:public'], +) + +genrule2( + name = 'gen_version', + cmd = ' && '.join([ + 'cd $$TMP', + 'mkdir -p com/google/gerrit/common', + 'cat $$ROOT/$(location //:version) >com/google/gerrit/common/Version', + 'zip -9Dqr $$ROOT/$@ .', + ]), + tools = ['//:version'], + out = 'gen_version.jar', +)
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml index 1495cd6..acc9b86 100644 --- a/gerrit-war/pom.xml +++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-war</artifactId> - <version>2.13</version> + <version>2.14-SNAPSHOT</version> <packaging>war</packaging> <name>Gerrit Code Review - WAR</name> <description>Gerrit WAR</description>
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 71eed63..fc0beae 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
@@ -19,6 +19,7 @@ import com.google.common.base.Splitter; import com.google.gerrit.common.EventBroker; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.gpg.GpgModule; import com.google.gerrit.httpd.auth.oauth.OAuthModule; import com.google.gerrit.httpd.auth.openid.OpenIdModule; @@ -29,7 +30,6 @@ import com.google.gerrit.lucene.LuceneIndexModule; import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; import com.google.gerrit.pgm.util.LogFileCompressor; -import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.account.InternalAccountDirectory; import com.google.gerrit.server.cache.h2.DefaultCacheFactory; import com.google.gerrit.server.change.ChangeCleanupRunner; @@ -38,6 +38,7 @@ import com.google.gerrit.server.config.CanonicalWebUrlModule; import com.google.gerrit.server.config.DownloadConfig; import com.google.gerrit.server.config.GerritGlobalModule; +import com.google.gerrit.server.config.GerritOptions; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfigModule; import com.google.gerrit.server.config.RestCacheAdminModule;
diff --git a/lib/BUCK b/lib/BUCK index df7c9b3..14ae9df 100644 --- a/lib/BUCK +++ b/lib/BUCK
@@ -17,6 +17,7 @@ define_license(name = 'fetch') define_license(name = 'h2') define_license(name = 'highlightjs') +define_license(name = 'icu4j') define_license(name = 'jgit') define_license(name = 'jsch') define_license(name = 'MPL1.1') @@ -38,9 +39,9 @@ maven_jar( name = 'gwtorm_client', - id = 'com.google.gerrit:gwtorm:1.15', - bin_sha1 = '26a2459f543ed78977535f92e379dc0d6cdde8bb', - src_sha1 = '9524088d6e46e299b12791cb1a63c4ba6a478b96', + id = 'com.google.gerrit:gwtorm:1.16', + bin_sha1 = '3e41b6d7bb352fa0539ce23b9bce97cf8c26c3bf', + src_sha1 = 'f45b7bacc79a0e5a7f6cf799a2dba23cc5bca19b', license = 'Apache2.0', ) @@ -53,9 +54,9 @@ maven_jar( name = 'gwtjsonrpc', - id = 'com.google.gerrit:gwtjsonrpc:1.9', - bin_sha1 = '458f55e92584fbd9ab91a89fa1c37654922a0f2b', - src_sha1 = 'ba539361c80a26f0d30a2f56068f6d83f44062d8', + id = 'com.google.gerrit:gwtjsonrpc:1.11', + bin_sha1 = '0990e7eec9eec3a15661edcf9232acbac4aeacec', + src_sha1 = 'a682afc46284fb58197a173cb5818770a1e7834a', license = 'Apache2.0', ) @@ -90,7 +91,11 @@ # Whitelist lib targets that have jsr305 as a dependency. Generally speaking # Gerrit core should not depend on these annotations, and instead use # equivalent annotations in com.google.gerrit.common. - visibility = ['//lib:guava-retrying'], + visibility = [ + '//gerrit-plugin-api:lib', + '//lib:guava-retrying', + '//lib:soy', + ], ) maven_jar( @@ -217,8 +222,8 @@ maven_jar( name = 'jimfs', - id = 'com.google.jimfs:jimfs:1.0', - sha1 = 'edd65a2b792755f58f11134e76485a928aab4c97', + id = 'com.google.jimfs:jimfs:1.1', + sha1 = '8fbd0579dc68aba6186935cc1bee21d2f3e7ec1c', license = 'DO_NOT_DISTRIBUTE', deps = [':guava'], ) @@ -273,3 +278,34 @@ license = 'Apache2.0', repository = GERRIT, ) + +# Keep this version of Soy synchronized with the version used in Gitiles. +maven_jar( + name = 'soy', + id = 'com.google.template:soy:2016-08-09', + sha1 = '43d33651e95480d515fe26c10a662faafe3ad1e4', + license = 'Apache2.0', + deps = [ + ':args4j', + ':guava', + ':gson', + ':icu4j', + ':jsr305', + ':protobuf', + '//lib/guice:guice', + '//lib/guice:guice-assistedinject', + '//lib/guice:multibindings', + '//lib/guice:javax-inject', + '//lib/ow2:ow2-asm', + '//lib/ow2:ow2-asm-analysis', + '//lib/ow2:ow2-asm-commons', + '//lib/ow2:ow2-asm-util', + ], +) + +maven_jar( + name = 'icu4j', + id = 'com.ibm.icu:icu4j:57.1', + sha1 = '198ea005f41219f038f4291f0b0e9f3259730e92', + license = 'icu4j', +)
diff --git a/lib/BUILD b/lib/BUILD index e89e63c..44c293d 100644 --- a/lib/BUILD +++ b/lib/BUILD
@@ -151,6 +151,7 @@ visibility = ['//visibility:public'], ) + java_library( name = 'h2', exports = ['@h2//jar'], @@ -202,3 +203,37 @@ exports = ['@derby//jar'], visibility = ['//visibility:public'], ) + +java_library( + name = 'soy', + exports = ['@soy//jar'], + runtime_deps = [ + ':args4j', + ':guava', + ':gson', + ':icu4j', + ':jsr305', + ':protobuf', + '//lib/guice:guice', + '//lib/guice:guice-assistedinject', + '//lib/guice:multibindings', + '//lib/guice:javax-inject', + '//lib/ow2:ow2-asm', + '//lib/ow2:ow2-asm-analysis', + '//lib/ow2:ow2-asm-commons', + '//lib/ow2:ow2-asm-util', + ], + visibility = ['//visibility:public'], +) + +java_library( + name = 'icu4j', + exports = [ '@icu4j//jar' ], + visibility = ['//visibility:public'], +) + +java_library( + name = 'postgresql', + exports = ['@postgresql//jar'], + visibility = ['//visibility:public'], +)
diff --git a/lib/LICENSE-icu4j b/lib/LICENSE-icu4j new file mode 100644 index 0000000..90be7cd --- /dev/null +++ b/lib/LICENSE-icu4j
@@ -0,0 +1,385 @@ +COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +--------------------- + +Third-Party Software Licenses + +This section contains third-party software notices and/or additional +terms for licensed third-party software components included within ICU +libraries. + +1. ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) + + # The Google Chrome software developed by Google is licensed under + # the BSD license. Other software included in this distribution is + # provided under other licenses, as set forth below. + # + # The BSD License + # http://opensource.org/licenses/bsd-license.php + # Copyright (C) 2006-2008, Google Inc. + # + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # Redistributions in binary form must reproduce the above + # copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided with + # the distribution. + # Neither the name of Google Inc. nor the names of its + # contributors may be used to endorse or promote products derived from + # this software without specific prior written permission. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + # + # The word list in cjdict.txt are generated by combining three word lists + # listed below with further processing for compound word breaking. The + # frequency is generated with an iterative training against Google web + # corpora. + # + # * Libtabe (Chinese) + # - https://sourceforge.net/project/?group_id=1519 + # - Its license terms and conditions are shown below. + # + # * IPADIC (Japanese) + # - http://chasen.aist-nara.ac.jp/chasen/distribution.html + # - Its license terms and conditions are shown below. + # + # ---------COPYING.libtabe ---- BEGIN-------------------- + # + # /* + # * Copyrighy (c) 1999 TaBE Project. + # * Copyright (c) 1999 Pai-Hsiang Hsiao. + # * All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the TaBE Project nor the names of its + # * contributors may be used to endorse or promote products derived + # * from this software without specific prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # /* + # * Copyright (c) 1999 Computer Systems and Communication Lab, + # * Institute of Information Science, Academia + # * Sinica. All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the Computer Systems and Communication Lab + # * nor the names of its contributors may be used to endorse or + # * promote products derived from this software without specific + # * prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, + # University of Illinois + # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 + # + # ---------------COPYING.libtabe-----END-------------------------------- + # + # + # ---------------COPYING.ipadic-----BEGIN------------------------------- + # + # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science + # and Technology. All Rights Reserved. + # + # Use, reproduction, and distribution of this software is permitted. + # Any copy of this software, whether in its original form or modified, + # must include both the above copyright notice and the following + # paragraphs. + # + # Nara Institute of Science and Technology (NAIST), + # the copyright holders, disclaims all warranties with regard to this + # software, including all implied warranties of merchantability and + # fitness, in no event shall NAIST be liable for + # any special, indirect or consequential damages or any damages + # whatsoever resulting from loss of use, data or profits, whether in an + # action of contract, negligence or other tortuous action, arising out + # of or in connection with the use or performance of this software. + # + # A large portion of the dictionary entries + # originate from ICOT Free Software. The following conditions for ICOT + # Free Software applies to the current dictionary as well. + # + # Each User may also freely distribute the Program, whether in its + # original form or modified, to any third party or parties, PROVIDED + # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear + # on, or be attached to, the Program, which is distributed substantially + # in the same form as set out herein and that such intended + # distribution, if actually made, will neither violate or otherwise + # contravene any of the laws and regulations of the countries having + # jurisdiction over the User or the intended distribution itself. + # + # NO WARRANTY + # + # The program was produced on an experimental basis in the course of the + # research and development conducted during the project and is provided + # to users as so produced on an experimental basis. Accordingly, the + # program is provided without any warranty whatsoever, whether express, + # implied, statutory or otherwise. The term "warranty" used herein + # includes, but is not limited to, any warranty of the quality, + # performance, merchantability and fitness for a particular purpose of + # the program and the nonexistence of any infringement or violation of + # any right of any third party. + # + # Each user of the program will agree and understand, and be deemed to + # have agreed and understood, that there is no warranty whatsoever for + # the program and, accordingly, the entire risk arising from or + # otherwise connected with the program is assumed by the user. + # + # Therefore, neither ICOT, the copyright holder, or any other + # organization that participated in or was otherwise related to the + # development of the program and their respective officials, directors, + # officers and other employees shall be held liable for any and all + # damages, including, without limitation, general, special, incidental + # and consequential damages, arising out of or otherwise in connection + # with the use or inability to use the program or any product, material + # or result produced or otherwise obtained by using the program, + # regardless of whether they have been advised of, or otherwise had + # knowledge of, the possibility of such damages at any time during the + # project or thereafter. Each user will be deemed to have agreed to the + # foregoing by his or her commencement of use of the program. The term + # "use" as used herein includes, but is not limited to, the use, + # modification, copying and distribution of the program and the + # production of secondary products from the program. + # + # In the case where the program, whether in its original form or + # modified, was distributed or delivered to or received by a user from + # any person, organization or entity other than ICOT, unless it makes or + # grants independently of ICOT any specific warranty to the user in + # writing, such person, organization or entity, will also be exempted + # from and not be held liable to the user for any such damages as noted + # above as far as the program is concerned. + # + # ---------------COPYING.ipadic-----END---------------------------------- + +3. Lao Word Break Dictionary Data (laodict.txt) + + # Copyright (c) 2013 International Business Machines Corporation + # and others. All Rights Reserved. + # + # Project: http://code.google.com/p/lao-dictionary/ + # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt + # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt + # (copied below) + # + # This file is derived from the above dictionary, with slight + # modifications. + # ---------------------------------------------------------------------- + # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, + # are permitted provided that the following conditions are met: + # + # + # Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. Redistributions in + # binary form must reproduce the above copyright notice, this list of + # conditions and the following disclaimer in the documentation and/or + # other materials provided with the distribution. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # OF THE POSSIBILITY OF SUCH DAMAGE. + # -------------------------------------------------------------------------- + +4. Burmese Word Break Dictionary Data (burmesedict.txt) + + # Copyright (c) 2014 International Business Machines Corporation + # and others. All Rights Reserved. + # + # This list is part of a project hosted at: + # github.com/kanyawtech/myanmar-karen-word-lists + # + # -------------------------------------------------------------------------- + # Copyright (c) 2013, LeRoy Benjamin Sharon + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: Redistributions of source code must retain the above + # copyright notice, this list of conditions and the following + # disclaimer. Redistributions in binary form must reproduce the + # above copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided + # with the distribution. + # + # Neither the name Myanmar Karen Word Lists, nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + # -------------------------------------------------------------------------- + +5. Time Zone Database + + ICU uses the public domain data and code derived from Time Zone +Database for its time zone support. The ownership of the TZ database +is explained in BCP 175: Procedure for Maintaining the Time Zone +Database section 7. + + # 7. Database Ownership + # + # The TZ database itself is not an IETF Contribution or an IETF + # document. Rather it is a pre-existing and regularly updated work + # that is in the public domain, and is intended to remain in the + # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do + # not apply to the TZ Database or contributions that individuals make + # to it. Should any claims be made and substantiated against the TZ + # Database, the organization that is providing the IANA + # Considerations defined in this RFC, under the memorandum of + # understanding with the IETF, currently ICANN, may act in accordance + # with all competent court orders. No ownership claims will be made + # by ICANN or the IETF Trust on the database or the code. Any person + # making a contribution to the database or code waives all rights to + # future claims in that contribution or in the TZ Database.
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK index a0e0e9a..56145ea 100644 --- a/lib/codemirror/BUCK +++ b/lib/codemirror/BUCK
@@ -1,14 +1,14 @@ include_defs('//lib/maven.defs') include_defs('//lib/codemirror/cm.defs') -VERSION = '5.17.0' +VERSION = '5.18.2' TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION TOP_MINIFIED = 'META-INF/resources/webjars/codemirror-minified/%s' % VERSION maven_jar( name = 'codemirror-minified', id = 'org.webjars.npm:codemirror-minified:' + VERSION, - sha1 = '05ad901fc9be67eb7ba8997d896488093deb898e', + sha1 = '6755af157a7eaf2401468906bef67bbacc3c97f6', attach_source = False, license = 'codemirror-minified', visibility = [], @@ -17,7 +17,7 @@ maven_jar( name = 'codemirror-original', id = 'org.webjars.npm:codemirror:' + VERSION, - sha1 = 'c025b8d9aca1061e26d1fa482bea0ecea1412e85', + sha1 = '18c721ae88eed27cddb458c42f5d221fa3d9713e', attach_source = False, license = 'codemirror-original', visibility = [],
diff --git a/lib/codemirror/BUILD b/lib/codemirror/BUILD new file mode 100644 index 0000000..0a62d41 --- /dev/null +++ b/lib/codemirror/BUILD
@@ -0,0 +1,2 @@ +load('//lib/codemirror:cm.bzl', 'pkg_cm') +pkg_cm()
diff --git a/lib/codemirror/cm.bzl b/lib/codemirror/cm.bzl new file mode 100644 index 0000000..b4e55fe --- /dev/null +++ b/lib/codemirror/cm.bzl
@@ -0,0 +1,355 @@ +load('//tools/bzl:genrule2.bzl', 'genrule2') + +CM_CSS = [ + 'lib/codemirror.css', + 'addon/dialog/dialog.css', + 'addon/merge/merge.css', + 'addon/scroll/simplescrollbars.css', + 'addon/search/matchesonscrollbar.css', + 'addon/lint/lint.css', +] + +CM_JS = [ + 'lib/codemirror.js', + 'mode/meta.js', + 'keymap/emacs.js', + 'keymap/sublime.js', + 'keymap/vim.js', +] + +CM_ADDONS = [ + 'dialog/dialog.js', + 'edit/closebrackets.js', + 'edit/matchbrackets.js', + 'edit/trailingspace.js', + 'scroll/annotatescrollbar.js', + 'scroll/simplescrollbars.js', + 'search/jump-to-line.js', + 'search/matchesonscrollbar.js', + 'search/searchcursor.js', + 'search/search.js', + 'selection/mark-selection.js', + 'mode/multiplex.js', + 'mode/overlay.js', + 'mode/simple.js', + 'lint/lint.js', +] + +# Available themes must be enumerated here, +# in gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Theme.java, +# in gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java +CM_THEMES = [ + '3024-day', + '3024-night', + 'abcdef', + 'ambiance', + 'base16-dark', + 'base16-light', + 'bespin', + 'blackboard', + 'cobalt', + 'colorforth', + 'dracula', + 'eclipse', + 'elegant', + 'erlang-dark', + 'hopscotch', + 'icecoder', + 'isotope', + 'lesser-dark', + 'liquibyte', + 'material', + 'mbo', + 'mdn-like', + 'midnight', + 'monokai', + 'neat', + 'neo', + 'night', + 'paraiso-dark', + 'paraiso-light', + 'pastel-on-dark', + 'railscasts', + 'rubyblue', + 'seti', + 'solarized', + 'the-matrix', + 'tomorrow-night-bright', + 'tomorrow-night-eighties', + 'ttcn', + 'twilight', + 'vibrant-ink', + 'xq-dark', + 'xq-light', + 'yeti', + 'zenburn', +] + +# Available modes must be enumerated here, +# in gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java, +# gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java, +# and in CodeMirror's own mode/meta.js script. +CM_MODES = [ + 'apl', + 'asciiarmor', + 'asn.1', + 'asterisk', + 'brainfuck', + 'clike', + 'clojure', + 'cmake', + 'cobol', + 'coffeescript', + 'commonlisp', + 'crystal', + 'css', + 'cypher', + 'd', + 'dart', + 'diff', + 'django', + 'dockerfile', + 'dtd', + 'dylan', + 'ebnf', + 'ecl', + 'eiffel', + 'elm', + 'erlang', + 'factor', + 'fcl', + 'forth', + 'fortran', + 'gas', + 'gfm', + 'gherkin', + 'go', + 'groovy', + 'haml', + 'handlebars', + 'haskell-literate', + 'haskell', + 'haxe', + 'htmlembedded', + 'htmlmixed', + 'http', + 'idl', + 'javascript', + 'jinja2', + 'jsx', + 'julia', + 'livescript', + 'lua', + 'markdown', + 'mathematica', + 'mbox', + 'mirc', + 'mllike', + 'modelica', + 'mscgen', + 'mumps', + 'nginx', + 'nsis', + 'ntriples', + 'octave', + 'oz', + 'pascal', + 'pegjs', + 'perl', + 'php', + 'pig', + 'powershell', + 'properties', + 'protobuf', + 'pug', + 'puppet', + 'python', + 'q', + 'r', + 'rpm', + 'rst', + 'ruby', + 'rust', + 'sas', + 'sass', + 'scheme', + 'shell', + 'sieve', + 'slim', + 'smalltalk', + 'smarty', + 'solr', + 'soy', + 'sparql', + 'spreadsheet', + 'sql', + 'stex', + 'stylus', + 'swift', + 'tcl', + 'textile', + 'tiddlywiki', + 'tiki', + 'toml', + 'tornado', + 'troff', + 'ttcn-cfg', + 'ttcn', + 'turtle', + 'twig', + 'vb', + 'vbscript', + 'velocity', + 'verilog', + 'vhdl', + 'vue', + 'webidl', + 'xml', + 'xquery', + 'yacas', + 'yaml-frontmatter', + 'yaml', + 'z80', +] + +VERSION = '5.18.2' +TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION +TOP_MINIFIED = 'META-INF/resources/webjars/codemirror-minified/%s' % VERSION + +DIFF_MATCH_PATCH_VERSION = '20121119-1' +DIFF_MATCH_PATCH_TOP = ('META-INF/resources/webjars/google-diff-match-patch/%s' + % DIFF_MATCH_PATCH_VERSION) + +def pkg_cm(): + for archive, suffix, top in [ + ('@codemirror_original//jar', '', TOP), + ('@codemirror_minified//jar', '_r', TOP_MINIFIED) + ]: + # Main JavaScript and addons + genrule2( + name = 'cm' + suffix, + cmd = ' && '.join([ + "echo '/** @license' >$@", + 'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top), + "echo '*/' >>$@", + ] + + ['unzip -p $(location %s) %s/%s >>$@' % (archive, top, n) for n in CM_JS] + + ['unzip -p $(location %s) %s/addon/%s >>$@' % (archive, top, n) + for n in CM_ADDONS] + ), + tools = [ + '@codemirror_original//jar', + '@codemirror_minified//jar', + ], + out = 'cm%s.js' % suffix, + ) + + # Main CSS + genrule2( + name = 'css' + suffix, + cmd = ' && '.join([ + "echo '/** @license' >$@", + 'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top), + "echo '*/' >>$@", + ] + + ['unzip -p $(location %s) %s/%s >>$@' % (archive, top, n) + for n in CM_CSS] + ), + tools = [ + '@codemirror_original//jar', + '@codemirror_minified//jar', + ], + out = 'cm%s.css' % suffix, + ) + + # Modes + for n in CM_MODES: + genrule2( + name = 'mode_%s%s' % (n, suffix), + cmd = ' && '.join([ + "echo '/** @license' >$@", + 'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top), + "echo '*/' >>$@", + 'unzip -p $(location %s) %s/mode/%s/%s.js >>$@' % (archive, top, n, n), + ] + ), + tools = [ + '@codemirror_original//jar', + '@codemirror_minified//jar', + ], + out = 'mode_%s%s.js' % (n, suffix), + ) + + # Themes + for n in CM_THEMES: + genrule2( + name = 'theme_%s%s' % (n, suffix), + cmd = ' && '.join([ + "echo '/** @license' >$@", + 'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top), + "echo '*/' >>$@", + 'unzip -p $(location %s) %s/theme/%s.css >>$@' % (archive, top, n) + ] + ), + tools = [ + '@codemirror_original//jar', + '@codemirror_minified//jar', + ], + out = 'theme_%s%s.css' % (n, suffix), + ) + + # Merge Addon bundled with diff-match-patch + genrule2( + name = 'addon_merge%s' % suffix, + cmd = ' && '.join([ + "echo '/** @license' >$@", + 'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top), + "echo '*/\n' >>$@", + "echo '// The google-diff-match-patch library is from https://google-diff-match-patch.googlecode.com/svn-history/r106/trunk/javascript/diff_match_patch.js\n' >> $@", + "echo '/** @license' >>$@", + "echo 'LICENSE-Apache2.0' >>$@", + "echo '*/' >>$@", + 'unzip -p $(location @diff_match_patch//jar) %s/diff_match_patch.js >>$@' % DIFF_MATCH_PATCH_TOP, + "echo ';' >> $@", + 'unzip -p $(location %s) %s/addon/merge/merge.js >>$@' % (archive, top) + ] + ), + tools = [ + '@diff_match_patch//jar', + '@codemirror_original//jar', + '@codemirror_minified//jar', + ], + out = 'addon_merge%s.js' % suffix, + ) + + # Jar packaging + genrule2( + name = 'jar' + suffix, + cmd = ' && '.join([ + 'cd $$TMP', + 'mkdir -p net/codemirror/{addon,lib,mode,theme}', + 'cp $$ROOT/$(location :css%s) net/codemirror/lib/cm.css' % suffix, + 'cp $$ROOT/$(location :cm%s) net/codemirror/lib/cm.js' % suffix] + + ['cp $$ROOT/$(location :mode_%s%s) net/codemirror/mode/%s.js' % (n, suffix, n) + for n in CM_MODES] + + ['cp $$ROOT/$(location :theme_%s%s) net/codemirror/theme/%s.css' % (n, suffix, n) + for n in CM_THEMES] + + ['cp $$ROOT/$(location :addon_merge%s) net/codemirror/addon/merge_bundled.js' % suffix] + + ['zip -qr $$ROOT/$@ net/codemirror/{addon,lib,mode,theme}']), + tools = [ + ':addon_merge%s' % suffix, + ':cm%s' % suffix, + ':css%s' % suffix, + ] + [ + ':mode_%s%s' % (n, suffix) for n in CM_MODES + ] + [ + ':theme_%s%s' % (n, suffix) for n in CM_THEMES + ], + out = 'codemirror%s.jar' % suffix, + ) + + native.java_import( + name = 'codemirror' + suffix, + jars = [':jar%s' % suffix], + visibility = ['//visibility:public'], + )
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs index baf2ce5..a1be90f 100644 --- a/lib/codemirror/cm.defs +++ b/lib/codemirror/cm.defs
@@ -132,7 +132,6 @@ 'htmlmixed', 'http', 'idl', - 'jade', 'javascript', 'jinja2', 'jsx', @@ -160,6 +159,7 @@ 'powershell', 'properties', 'protobuf', + 'pug', 'puppet', 'python', 'q',
diff --git a/lib/guice/BUCK b/lib/guice/BUCK index 867b521..8022ac8 100644 --- a/lib/guice/BUCK +++ b/lib/guice/BUCK
@@ -12,6 +12,7 @@ exported_deps = [ ':guice_library', ':javax-inject', + ':multibindings', ], visibility = ['PUBLIC'], ) @@ -63,3 +64,16 @@ license = 'Apache2.0', visibility = ['PUBLIC'], ) + +maven_jar( + name = 'multibindings', + id = 'com.google.inject.extensions:guice-multibindings:' + VERSION, + sha1 = '3b27257997ac51b0f8d19676f1ea170427e86d51', + exclude_java_sources = True, + exclude = EXCLUDE + [ + 'META-INF/maven/com.google.guava/guava/pom.properties', + 'META-INF/maven/com.google.guava/guava/pom.xml', + ], + license = 'Apache2.0', + visibility = ['PUBLIC'] +)
diff --git a/lib/guice/BUILD b/lib/guice/BUILD index acade50..5850af2 100644 --- a/lib/guice/BUILD +++ b/lib/guice/BUILD
@@ -3,6 +3,7 @@ exports = [ ':guice_library', ':javax-inject', + ':multibindings', ], visibility = ['//visibility:public'], ) @@ -36,4 +37,11 @@ java_library( name = 'javax-inject', exports = ['@javax_inject//jar'], + visibility = ['//visibility:public'], +) + +java_library( + name = 'multibindings', + exports = [ '@multibindings//jar' ], + visibility = ['//visibility:public'], )
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK index 44a9341..41000d3 100644 --- a/lib/gwt/BUCK +++ b/lib/gwt/BUCK
@@ -1,11 +1,11 @@ include_defs('//lib/maven.defs') -VERSION = '2.7.0' +VERSION = '2.8.0-rc2' maven_jar( name = 'user', id = 'com.google.gwt:gwt-user:' + VERSION, - sha1 = 'bdc7af42581745d3d79c2efe0b514f432b998a5b', + sha1 = 'ad99b09a626c20cce2bdacf3726a51b2cd16b99e', license = 'Apache2.0', attach_source = False, ) @@ -13,10 +13,9 @@ maven_jar( name = 'dev', id = 'com.google.gwt:gwt-dev:' + VERSION, - sha1 = 'c2c3dd5baf648a0bb199047a818be5e560f48982', + sha1 = 'd70a6feb4661c07488090cb81303415e9110b15a', license = 'Apache2.0', attach_source = False, - exclude = ['org/eclipse/jetty/*'], ) maven_jar( @@ -28,3 +27,47 @@ visibility = ['PUBLIC'], ) +maven_jar( + name = 'jsinterop-annotations', + id = 'com.google.jsinterop:jsinterop-annotations:1.0.0', + bin_sha1 = '23c3a3c060ffe4817e67673cc8294e154b0a4a95', + src_sha1 = '5d7c478efbfccc191430d7c118d7bd2635e43750', + license = 'Apache2.0', + visibility = ['PUBLIC'], +) + +maven_jar( + name = 'ant', + id = 'ant:ant:1.6.5', + bin_sha1 = '7d18faf23df1a5c3a43613952e0e8a182664564b', + src_sha1 = '9e0a847494563f35f9b02846a1c1eb4aa2ee5a9a', + license = 'Apache2.0', + visibility = ['PUBLIC'], +) + +maven_jar( + name = 'colt', + id = 'colt:colt:1.2.0', + attach_source = False, + bin_sha1 = '0abc984f3adc760684d49e0f11ddf167ba516d4f', + license = 'DO_NOT_DISTRIBUTE', + visibility = ['PUBLIC'], +) + +maven_jar( + name = 'tapestry', + id = 'tapestry:tapestry:4.0.2', + attach_source = False, + bin_sha1 = 'e855a807425d522e958cbce8697f21e9d679b1f7', + license = 'Apache2.0', + visibility = ['PUBLIC'], +) + +maven_jar( + name = 'w3c-css-sac', + id = 'org.w3c.css:sac:1.3', + attach_source = False, + bin_sha1 = 'cdb2dcb4e22b83d6b32b93095f644c3462739e82', + license = 'DO_NOT_DISTRIBUTE', + visibility = ['PUBLIC'], +)
diff --git a/lib/gwt/BUILD b/lib/gwt/BUILD index 2168bb4..bf04c95 100644 --- a/lib/gwt/BUILD +++ b/lib/gwt/BUILD
@@ -7,3 +7,9 @@ 'dev', 'user', ]] + +java_library( + name = 'javax-validation_src', + exports = ['@javax_validation_src//jar'], + visibility = ['//visibility:public'], +)
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK index cc22b80..e24cfe5 100644 --- a/lib/jetty/BUCK +++ b/lib/jetty/BUCK
@@ -1,12 +1,12 @@ include_defs('//lib/maven.defs') -VERSION = '9.2.14.v20151106' +VERSION = '9.3.11.v20160721' EXCLUDE = ['about.html'] maven_jar( name = 'servlet', id = 'org.eclipse.jetty:jetty-servlet:' + VERSION, - sha1 = '3a2cd4d8351a38c5d60e0eee010fee11d87483ef', + sha1 = 'd550147b85c73ea81084a4ac7915ba7f609021c5', license = 'Apache2.0', deps = [':security'], exclude = EXCLUDE, @@ -15,7 +15,7 @@ maven_jar( name = 'security', id = 'org.eclipse.jetty:jetty-security:' + VERSION, - sha1 = '2d36974323fcb31e54745c1527b996990835db67', + sha1 = '1cbefc5d1196b9e1ca6f4cc36738998a6ebde8bf', license = 'Apache2.0', deps = [':server'], exclude = EXCLUDE, @@ -25,7 +25,7 @@ maven_jar( name = 'servlets', id = 'org.eclipse.jetty:jetty-servlets:' + VERSION, - sha1 = 'a75c78a0ee544073457ca5ee9db20fdc6ed55225', + sha1 = 'a9f7a43977151a463aa21a9b0e882aa3d25452ef', license = 'Apache2.0', exclude = EXCLUDE, visibility = [ @@ -37,7 +37,7 @@ maven_jar( name = 'server', id = 'org.eclipse.jetty:jetty-server:' + VERSION, - sha1 = '70b22c1353e884accf6300093362b25993dac0f5', + sha1 = 'd932e0dc1e9bd4839ae446754615163d60271a66', license = 'Apache2.0', exported_deps = [ ':continuation', @@ -49,7 +49,7 @@ maven_jar( name = 'jmx', id = 'org.eclipse.jetty:jetty-jmx:' + VERSION, - sha1 = '617edc5e966b4149737811ef8b289cd94b831bab', + sha1 = '21a658d2f5eb87c23eef4911966625ea95f66d32', license = 'Apache2.0', exported_deps = [ ':continuation', @@ -61,7 +61,7 @@ maven_jar( name = 'continuation', id = 'org.eclipse.jetty:jetty-continuation:' + VERSION, - sha1 = '8909d62fd7e28351e2da30de6fb4105539b949c0', + sha1 = '92a91c0dcc5f5d779a1c9f94038332be3f46c9df', license = 'Apache2.0', exclude = EXCLUDE, ) @@ -69,7 +69,7 @@ maven_jar( name = 'http', id = 'org.eclipse.jetty:jetty-http:' + VERSION, - sha1 = '699ad1f2fa6fb0717e1b308a8c9e1b8c69d81ef6', + sha1 = 'dcfb95e5b886a981bb76467b911c5b706117f9cf', license = 'Apache2.0', exported_deps = [':io'], exclude = EXCLUDE, @@ -78,7 +78,7 @@ maven_jar( name = 'io', id = 'org.eclipse.jetty:jetty-io:' + VERSION, - sha1 = 'dfa4137371a3f08769820138ca1a2184dacda267', + sha1 = 'db5f4f481159894a4b670072a34917b5414d0c98', license = 'Apache2.0', exported_deps = [':util'], exclude = EXCLUDE, @@ -88,7 +88,7 @@ maven_jar( name = 'util', id = 'org.eclipse.jetty:jetty-util:' + VERSION, - sha1 = '0057e00b912ae0c35859ac81594a996007706a0b', + sha1 = '1812ffd5a04698051180d582c146ca807760c808', license = 'Apache2.0', exclude = EXCLUDE, visibility = [],
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK index c4a9872..64dda1d 100644 --- a/lib/lucene/BUCK +++ b/lib/lucene/BUCK
@@ -1,6 +1,6 @@ include_defs('//lib/maven.defs') -VERSION = '5.5.0' +VERSION = '5.5.3' # core and backward-codecs both provide # META-INF/services/org.apache.lucene.codecs.Codec, so they must be merged. @@ -16,7 +16,7 @@ maven_jar( name = 'lucene-core', id = 'org.apache.lucene:lucene-core:' + VERSION, - sha1 = 'a74fd869bb5ad7fe6b4cd29df9543a34aea81164', + sha1 = '20540c6347259f35a0d264605b22ce2a13917066', license = 'Apache2.0', exclude = [ 'META-INF/LICENSE.txt', @@ -28,7 +28,7 @@ maven_jar( name = 'lucene-analyzers-common', id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION, - sha1 = '1e0e8243a4410be20c34683034fafa7bb52e55cc', + sha1 = 'cf734ab72813af33dc1544ce61abc5c17b9d35e9', license = 'Apache2.0', deps = [':lucene-core-and-backward-codecs'], exclude = [ @@ -40,7 +40,7 @@ maven_jar( name = 'backward-codecs_jar', id = 'org.apache.lucene:lucene-backward-codecs:' + VERSION, - sha1 = '68480974b2f54f519763632a7c1c5d51cbff3805', + sha1 = 'a167789e52a9dc6d93bf3b588f79fdc9d7559c15', license = 'Apache2.0', deps = [':lucene-core'], exclude = [ @@ -53,7 +53,7 @@ maven_jar( name = 'lucene-misc', id = 'org.apache.lucene:lucene-misc:' + VERSION, - sha1 = '504d855a1a38190622fdf990b2298c067e7d60ca', + sha1 = 'e356975c46447f06c71842632d0af9ec1baecfce', license = 'Apache2.0', deps = [':lucene-core-and-backward-codecs'], exclude = [ @@ -65,7 +65,7 @@ maven_jar( name = 'lucene-queryparser', id = 'org.apache.lucene:lucene-queryparser:' + VERSION, - sha1 = '0fddc49725b562fd48dff0cff004336ad2a090a4', + sha1 = 'e2452203d2c44cac5ac42b34e5dcc0a44bf29a53', license = 'Apache2.0', deps = [':lucene-core-and-backward-codecs'], exclude = [
diff --git a/lib/ow2/BUCK b/lib/ow2/BUCK index fabcb25..653bd2b 100644 --- a/lib/ow2/BUCK +++ b/lib/ow2/BUCK
@@ -1,25 +1,25 @@ include_defs('//lib/maven.defs') -VERSION = '5.0.3' +VERSION = '5.1' maven_jar( name = 'ow2-asm', id = 'org.ow2.asm:asm:' + VERSION, - sha1 = 'dcc2193db20e19e1feca8b1240dbbc4e190824fa', + sha1 = '5ef31c4fe953b1fd00b8a88fa1d6820e8785bb45', license = 'ow2', ) maven_jar( name = 'ow2-asm-analysis', id = 'org.ow2.asm:asm-analysis:' + VERSION, - sha1 = 'c7126aded0e8e13fed5f913559a0dd7b770a10f3', + sha1 = '6d1bf8989fc7901f868bee3863c44f21aa63d110', license = 'ow2', ) maven_jar( name = 'ow2-asm-commons', id = 'org.ow2.asm:asm-commons:' + VERSION, - sha1 = 'a7111830132c7f87d08fe48cb0ca07630f8cb91c', + sha1 = '25d8a575034dd9cfcb375a39b5334f0ba9c8474e', deps = [':ow2-asm-tree'], license = 'ow2', ) @@ -27,14 +27,13 @@ maven_jar( name = 'ow2-asm-tree', id = 'org.ow2.asm:asm-tree:' + VERSION, - sha1 = '287749b48ba7162fb67c93a026d690b29f410bed', + sha1 = '87b38c12a0ea645791ead9d3e74ae5268d1d6c34', license = 'ow2', ) maven_jar( name = 'ow2-asm-util', id = 'org.ow2.asm:asm-util:' + VERSION, - sha1 = '1512e5571325854b05fb1efce1db75fcced54389', + sha1 = 'b60e33a6bd0d71831e0c249816d01e6c1dd90a47', license = 'ow2', ) -
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin index fc39c55..23572e6 160000 --- a/plugins/cookbook-plugin +++ b/plugins/cookbook-plugin
@@ -1 +1 @@ -Subproject commit fc39c552cffb94d15797d02e272fdc543c35b6bd +Subproject commit 23572e6c83531fceea69c1356a5e1b6753ecf0e3
diff --git a/plugins/download-commands b/plugins/download-commands index 7b41f3a..3fb4fb6 160000 --- a/plugins/download-commands +++ b/plugins/download-commands
@@ -1 +1 @@ -Subproject commit 7b41f3a413b46140b050ae5324cbbcdd467d2b3a +Subproject commit 3fb4fb63317b6004761d1fea98a8f4d288d95409
diff --git a/plugins/hooks b/plugins/hooks index 3acc14d..c1705a7 160000 --- a/plugins/hooks +++ b/plugins/hooks
@@ -1 +1 @@ -Subproject commit 3acc14d10d26678eae6489038fe0d4dad644a9b4 +Subproject commit c1705a739f117b9123e1d63aebf07d043afb0867
diff --git a/plugins/replication b/plugins/replication index c5123d6..9411b6d 160000 --- a/plugins/replication +++ b/plugins/replication
@@ -1 +1 @@ -Subproject commit c5123d6a5604cc740d6f42485235c0d3ec141c4e +Subproject commit 9411b6d9d37fbbd9a6bb98307bcb8f4f47c58f37
diff --git a/plugins/reviewnotes b/plugins/reviewnotes index 3f3d572..85069a4 160000 --- a/plugins/reviewnotes +++ b/plugins/reviewnotes
@@ -1 +1 @@ -Subproject commit 3f3d572e9618f268b19cc54856deee4c96180e4c +Subproject commit 85069a4bd88b9a18c9df89c86229daf2a07cf345
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js index 90b2e1d..a43f564 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js +++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -108,11 +108,15 @@ }, _computeProjectURL: function(project) { - return '/q/status:open+project:' + project; + // @see Issue 4255. + return '/q/status:open+project:' + + encodeURIComponent(encodeURIComponent(project)); }, _computeProjectBranchURL: function(project, branch) { - return '/q/status:open+project:' + project + '+branch:' + branch; + // @see Issue 4255. + return this._computeProjectURL(project) + + '+branch:' + encodeURIComponent(encodeURIComponent(branch)); }, }); })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html index b7c0853..d2e6028 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html +++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -120,12 +120,12 @@ assert.equal(element._computeLabelValue( {labels: {Verified: {rejected: true}}}, 'Verified'), '✕'); - assert.equal(element._computeProjectURL('combustible-stuff'), - '/q/status:open+project:combustible-stuff'); + assert.equal(element._computeProjectURL('combustible/stuff'), + '/q/status:open+project:combustible%252Fstuff'); assert.equal(element._computeProjectBranchURL( - 'combustible-stuff', 'lemons'), - '/q/status:open+project:combustible-stuff+branch:lemons'); + 'combustible-stuff', 'le/mons'), + '/q/status:open+project:combustible-stuff+branch:le%252Fmons'); element.change = {_number: 42}; assert.equal(element.changeURL, '/c/42/');
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html index b741784..09d5b84 100644 --- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html +++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -32,19 +32,14 @@ <template> <style> :host { - display: block; + display: inline-block; + font-family: var(--font-family); } section { - margin-top: 1em; - } - .groupLabel { - color: #666; - margin-bottom: .15em; - text-align: center; + display: inline-block; } gr-button { - display: block; - margin-bottom: .5em; + margin-left: .5em; } gr-button:before { content: attr(data-label); @@ -53,6 +48,15 @@ content: attr(data-loading-label); } @media screen and (max-width: 50em) { + :host, + section, + gr-button { + display: block; + } + gr-button { + margin-bottom: .5em; + margin-left: 0; + } .confirmDialog { width: 90vw; } @@ -60,7 +64,6 @@ </style> <div> <section hidden$="[[!_actionCount(actions.*, _additionalActions.*)]]"> - <div class="groupLabel">Change</div> <template is="dom-repeat" items="[[_changeActionValues]]" as="action"> <gr-button title$="[[action.title]]" primary$="[[action.__primary]]" @@ -73,7 +76,6 @@ </template> </section> <section hidden$="[[!_actionCount(_revisionActions.*, _additionalActions.*)]]"> - <div class="groupLabel">Revision</div> <template is="dom-repeat" items="[[_revisionActionValues]]" as="action"> <gr-button title$="[[action.title]]" primary$="[[action.__primary]]" @@ -94,13 +96,12 @@ hidden></gr-confirm-rebase-dialog> <gr-confirm-cherrypick-dialog id="confirmCherrypick" class="confirmDialog" - commit-info="[[commitInfo]]" + message="[[commitMessage]]" on-confirm="_handleCherrypickConfirm" on-cancel="_handleConfirmDialogCancel" hidden></gr-confirm-cherrypick-dialog> <gr-confirm-revert-dialog id="confirmRevertDialog" class="confirmDialog" - commit-info="[[commitInfo]]" on-confirm="_handleRevertDialogConfirm" on-cancel="_handleConfirmDialogCancel" hidden></gr-confirm-revert-dialog>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js index 3445f4e..0676595 100644 --- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js +++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -75,7 +75,10 @@ }, changeNum: String, patchNum: String, - commitInfo: Object, + commitMessage: { + type: String, + value: '', + }, _loading: { type: Boolean, @@ -208,13 +211,13 @@ _computeRevisionActionValues: function(actionsChangeRecord, primariesChangeRecord, additionalActionsChangeRecord) { return this._getActionValues(actionsChangeRecord, primariesChangeRecord, - additionalActionsChangeRecord, 'revision'); + additionalActionsChangeRecord, ActionType.REVISION); }, _computeChangeActionValues: function(actionsChangeRecord, primariesChangeRecord, additionalActionsChangeRecord) { return this._getActionValues(actionsChangeRecord, primariesChangeRecord, - additionalActionsChangeRecord, 'change'); + additionalActionsChangeRecord, ActionType.CHANGE); }, _getActionValues: function(actionsChangeRecord, primariesChangeRecord, @@ -231,6 +234,15 @@ actions[a].__key = a; actions[a].__type = type; actions[a].__primary = primaryActionKeys.indexOf(a) !== -1; + if (actions[a].label === 'Delete') { + // This label is common within change and revision actions. Make it + // more explicit to the user. + if (type === ActionType.CHANGE) { + actions[a].label += ' Change'; + } else if (type === ActionType.REVISION) { + actions[a].label += ' Revision'; + } + } // Triggers a re-render by ensuring object inequality. // TODO(andybons): Polyfill for Object.assign. result.push(Object.assign({}, actions[a])); @@ -274,7 +286,7 @@ if (type === ActionType.REVISION) { this._handleRevisionAction(key); } else if (key === ChangeActions.REVERT) { - this.$.confirmRevertDialog.populateRevertMessage(); + this.$.confirmRevertDialog.populateRevertMessage(this.commitMessage); this.$.confirmRevertDialog.message = this._modifyRevertMsg(); this._showActionDialog(this.$.confirmRevertDialog); } else if (key === ChangeActions.ABANDON) { @@ -394,7 +406,11 @@ _showActionDialog: function(dialog) { dialog.hidden = false; - this.$.overlay.open(); + this.$.overlay.open().then(function() { + if (dialog.resetFocus) { + dialog.resetFocus(); + } + }); }, _handleResponse: function(action, response) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html index 80aaf3b..9b04c8a 100644 --- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html +++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -38,6 +38,12 @@ stub('gr-rest-api-interface', { getChangeRevisionActions: function() { return Promise.resolve({ + '/': { + method: 'DELETE', + label: 'Delete', + title: 'Delete draft revision 2', + enabled: true + }, cherrypick: { method: 'POST', label: 'Cherry Pick', @@ -52,9 +58,9 @@ submit: { method: 'POST', label: 'Submit', - title: 'Submit patch set 1 into master', + title: 'Submit patch set 2 into master', enabled: true - } + }, }); }, send: function(method, url, payload) { @@ -79,18 +85,45 @@ element = fixture('basic'); element.changeNum = '42'; element.patchNum = '2'; + element.actions = { + '/': { + method: 'DELETE', + label: 'Delete', + title: 'Delete draft change 42', + enabled: true + }, + }; return element.reload(); }); - test('submit, rebase, and cherry-pick buttons show', function(done) { + test('buttons show', function(done) { flush(function() { var buttonEls = Polymer.dom(element.root).querySelectorAll('gr-button'); - assert.equal(buttonEls.length, 3); + assert.equal(buttonEls.length, 5); assert.isFalse(element.hidden); done(); }); }); + test('delete buttons have explicit labels', function(done) { + flush(function() { + var buttonEls = + Polymer.dom(element.root).querySelectorAll('[data-action-key="/"]'); + assert.equal(buttonEls.length, 2); + assert.notEqual(buttonEls[0].getAttribute('data-label'), + buttonEls[1].getAttribute['data-label']); + assert.isTrue( + buttonEls[0].getAttribute('data-label') === 'Delete Revision' || + buttonEls[0].getAttribute('data-label') === 'Delete Change' + ); + assert.isTrue( + buttonEls[1].getAttribute('data-label') === 'Delete Revision' || + buttonEls[1].getAttribute('data-label') === 'Delete Change' + ); + done(); + }); + }); + test('submit change', function(done) { flush(function() { var submitButton = element.$$('gr-button[data-action-key="submit"]');
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html index 8b51312..84b2391 100644 --- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html +++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -37,6 +37,9 @@ color: #666; font-weight: bold; } + gr-editable-label { + max-width: 9em; + } .labelValueContainer:not(:first-of-type) { margin-top: .25em; } @@ -128,18 +131,6 @@ <span class="value">[[change.branch]]</span> </section> <section> - <span class="title">Commit</span> - <span class="value"> - <template is="dom-if" if="[[_showWebLink]]"> - <a target="_blank" - href$="[[_webLink]]">[[_computeShortHash(commitInfo)]]</a> - </template> - <template is="dom-if" if="[[!_showWebLink]]"> - [[_computeShortHash(commitInfo)]] - </template> - </span> - </section> - <section> <span class="title">Topic</span> <span class="value"> <gr-editable-label
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js index af19703..20c117e 100644 --- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js +++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -27,17 +27,8 @@ properties: { change: Object, - commitInfo: Object, mutable: Boolean, serverConfig: Object, - _showWebLink: { - type: Boolean, - computed: '_computeShowWebLink(change, commitInfo, serverConfig)', - }, - _webLink: { - type: String, - computed: '_computeWebLink(change, commitInfo, serverConfig)', - }, _topicReadOnly: { type: Boolean, computed: '_computeTopicReadOnly(mutable, change)', @@ -52,38 +43,6 @@ Gerrit.RESTClientBehavior, ], - _computeShowWebLink: function(change, commitInfo, serverConfig) { - var webLink = commitInfo.web_links && commitInfo.web_links.length; - var gitWeb = serverConfig.gitweb && serverConfig.gitweb.url && - serverConfig.gitweb.type && serverConfig.gitweb.type.revision; - return webLink || gitWeb; - }, - - _computeWebLink: function(change, commitInfo, serverConfig) { - if (!this._computeShowWebLink(change, commitInfo, serverConfig)) { - return; - } - - if (serverConfig.gitweb && serverConfig.gitweb.url && - serverConfig.gitweb.type && serverConfig.gitweb.type.revision) { - return serverConfig.gitweb.url + - serverConfig.gitweb.type.revision - .replace('${project}', change.project) - .replace('${commit}', commitInfo.commit); - } - - var webLink = commitInfo.web_links[0].url; - if (!/^https?\:\/\//.test(webLink)) { - webLink = '../../' + webLink; - } - - return webLink; - }, - - _computeShortHash: function(commitInfo) { - return commitInfo.commit.slice(0, 7); - }, - _computeHideStrategy: function(change) { return !this.changeIsOpen(change.status); },
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html index 01f0649..a2d4946 100644 --- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html +++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -20,11 +20,9 @@ <script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script> <script src="../../../bower_components/web-component-tester/browser.js"></script> -<script src="../../../bower_components/page/page.js"></script> <link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html"> <link rel="import" href="gr-change-metadata.html"> -<script src="../../../scripts/util.js"></script> <test-fixture id="basic"> <template> @@ -68,79 +66,6 @@ assert.isTrue(element.$$('.strategy').hasAttribute('hidden')); }); - test('no web link when unavailable', function() { - element.commitInfo = {}; - element.serverConfig = {}; - element.change = {labels: []}; - - assert.isNotOk(element._computeShowWebLink(element.change, - element.commitInfo, element.serverConfig)); - }); - - test('use web link when available', function() { - element.commitInfo = {web_links: [{url: 'link-url'}]}; - element.serverConfig = {}; - - assert.isOk(element._computeShowWebLink(element.change, - element.commitInfo, element.serverConfig)); - assert.equal(element._computeWebLink(element.change, element.commitInfo, - element.serverConfig), '../../link-url'); - }); - - test('does not relativize web links that begin with scheme', function() { - element.commitInfo = {web_links: [{url: 'https://link-url'}]}; - element.serverConfig = {}; - - assert.isOk(element._computeShowWebLink(element.change, - element.commitInfo, element.serverConfig)); - assert.equal(element._computeWebLink(element.change, element.commitInfo, - element.serverConfig), 'https://link-url'); - }); - - test('use gitweb when available', function() { - element.commitInfo = {commit: 'commit-sha'}; - element.serverConfig = {gitweb: { - url: 'url-base/', - type: {revision: 'xx ${project} xx ${commit} xx'}, - }}; - element.change = { - project: 'project-name', - labels: [], - current_revision: element.commitInfo.commit - }; - - assert.isOk(element._computeShowWebLink(element.change, - element.commitInfo, element.serverConfig)); - - assert.equal(element._computeWebLink(element.change, element.commitInfo, - element.serverConfig), 'url-base/xx project-name xx commit-sha xx'); - }); - - test('prefer gitweb when both are available', function() { - element.commitInfo = { - commit: 'commit-sha', - web_links: [{url: 'link-url'}] - }; - element.serverConfig = {gitweb: { - url: 'url-base/', - type: {revision: 'xx ${project} xx ${commit} xx'}, - }}; - element.change = { - project: 'project-name', - labels: [], - current_revision: element.commitInfo.commit - }; - - assert.isOk(element._computeShowWebLink(element.change, - element.commitInfo, element.serverConfig)); - - var link = element._computeWebLink(element.change, element.commitInfo, - element.serverConfig); - - assert.equal(link, 'url-base/xx project-name xx commit-sha xx'); - assert.notEqual(link, '../../link-url'); - }); - test('show CC section when NoteDb enabled', function() { function hasCc() { return element._showReviewersByState;
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 e3f7fd2..37c9f62 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
@@ -29,6 +29,7 @@ <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-messages-list/gr-messages-list.html"> @@ -45,18 +46,16 @@ color: #666; padding: 1em var(--default-horizontal-margin); } - .headerContainer { - height: 4.1em; - margin-bottom: .5em; - } .header { align-items: center; background-color: var(--view-background-color); - border-bottom: 1px solid #ddd; display: flex; - padding: 1em var(--default-horizontal-margin); + padding: .65em var(--default-horizontal-margin); z-index: 99; /* Less than gr-overlay's backdrop */ } + .header .download { + margin-right: 1em; + } .header.pinned { border-bottom-color: transparent; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); @@ -69,24 +68,11 @@ flex: 1; font-size: 1.2em; font-weight: bold; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } gr-change-star { margin-right: .25em; vertical-align: -.425em; } - .download, - .patchSelectLabel { - margin-left: 1em; - } - .header select { - margin-left: .5em; - } - .header .reply { - margin-left: var(--default-horizontal-margin); - } gr-reply-dialog { width: 50em; } @@ -94,45 +80,46 @@ color: #999; text-transform: capitalize; } - section { - margin: 10px 0; - padding: 10px var(--default-horizontal-margin); - } /* Strong specificity here is needed due to https://github.com/Polymer/polymer/issues/2531 */ .container section.changeInfo { - border-bottom: 1px solid #ddd; display: flex; - margin-top: 0; - padding-top: 0; + padding: 0 var(--default-horizontal-margin); } .changeInfo-column:not(:last-of-type) { margin-right: 1em; padding-right: 1em; } .changeMetadata { - border-right: 1px solid #ddd; - font-size: .9em; - } - gr-change-actions { - margin-top: 1em; + font-size: .95em; } .commitMessage { font-family: var(--monospace-font-family); flex: 0 0 72ch; margin-right: 2em; margin-bottom: 1em; - overflow-x: hidden; - } - .commitMessage h4 { - font-family: var(--font-family); - font-weight: bold; - margin-bottom: .25em; } .commitMessage gr-linked-text { - --linked-text-white-space: pre; overflow: auto; } + .editCommitMessage { + margin-top: 1em; + } + .commitActions { + border-bottom: 1px solid #ddd; + display: flex; + justify-content: space-between; + margin-bottom: .5em; + padding-bottom: .5em; + } + .reply { + margin-right: .5em; + } + .mainChangeInfo { + display: flex; + flex: 1; + flex-direction: column; + } .commitAndRelated { align-content: flex-start; display: flex; @@ -144,14 +131,30 @@ font-size: .9em; overflow: hidden; } + .patchInfo { + border: 1px solid #ddd; + margin: 1em var(--default-horizontal-margin); + } + .patchInfo--oldPatchSet .patchInfo-header { + background-color: #fff9c4; + } + .patchInfo--oldPatchSet .latestPatchContainer { + display: initial; + } + .patchInfo-header, gr-file-list { - margin-bottom: 1em; - padding: 0 var(--default-horizontal-margin); + padding: .5em calc(var(--default-horizontal-margin) / 2); + } + .patchInfo-header { + background-color: #f6f6f6; + border-bottom: 1px solid #ebebeb; + display: flex; + justify-content: space-between; + } + .latestPatchContainer { + display: none; } @media screen and (max-width: 50em) { - .headerContainer { - height: 5.15em; - } .header { align-items: flex-start; flex-direction: column; @@ -163,30 +166,17 @@ .header-title { font-size: 1.1em; } - .header-actions { - align-items: center; - display: flex; - justify-content: space-between; - margin-top: .5em; - } gr-reply-dialog { min-width: initial; width: 90vw; } - .download { + .downloadContainer { display: none; } - .patchSelectLabel { - margin-left: 0; - margin-right: .5em; - } - .header select { - margin-left: 0; - margin-right: .5em; - } - .header .reply { - margin-left: 0; - margin-right: .5em; + .reply { + display: block; + margin-right: 0; + margin-bottom: .5em; } .changeInfo-column:not(:last-of-type) { margin-right: 0; @@ -207,6 +197,9 @@ margin-top: .25em; max-width: none; } + .commitActions { + flex-direction: column; + } .commitMessage { flex: initial; margin-right: 0; @@ -215,85 +208,98 @@ </style> <div class="container loading" hidden$="{{!_loading}}">Loading...</div> <div class="container" hidden$="{{_loading}}"> - <div class="headerContainer"> - <div class="header"> - <span class="header-title"> - <gr-change-star change="{{_change}}" hidden$="[[!_loggedIn]]"></gr-change-star> - <a href$="[[_computeChangePermalink(_change._number)]]">[[_change._number]]</a><span>:</span> - <span>[[_change.subject]]</span> - <span class="changeStatus">[[_computeChangeStatus(_change, _patchRange.patchNum)]]</span> - </span> - <span class="header-actions"> - <gr-button hidden - class="reply" - primary$="[[_computeReplyButtonHighlighted(_diffDrafts.*)]]" - hidden$="[[!_loggedIn]]" - on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button> - <gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button> - <span> - <label class="patchSelectLabel" for="patchSetSelect">Patch set</label> - <select id="patchSetSelect" on-change="_handlePatchChange"> - <template is="dom-repeat" items="{{_allPatchSets}}" as="patchNumber"> - <option value$="[[patchNumber]]" selected$="[[_computePatchIndexIsSelected(index, _patchRange.patchNum)]]"> - <span>[[patchNumber]]</span> - / - <span>[[_computeLatestPatchNum(_allPatchSets)]]</span> - </option> - </template> - </select> - </span> - </span> - </div> + <div class="header"> + <span class="header-title"> + <gr-change-star change="{{_change}}" hidden$="[[!_loggedIn]]"></gr-change-star> + <a href$="[[_computeChangePermalink(_change._number)]]">[[_change._number]]</a><span>:</span> + <span>[[_change.subject]]</span> + <span class="changeStatus">[[_computeChangeStatus(_change, _patchRange.patchNum)]]</span> + </span> </div> <section class="changeInfo"> <div class="changeInfo-column changeMetadata"> <gr-change-metadata change="{{_change}}" - commit-info="[[_commitInfo]]" server-config="[[serverConfig]]" mutable="[[_loggedIn]]" on-show-reply-dialog="_handleShowReplyDialog"> </gr-change-metadata> - <gr-change-actions id="actions" - change="[[_change]]" - actions="[[_change.actions]]" - change-num="[[_changeNum]]" - patch-num="[[_patchRange.patchNum]]" - commit-info="[[_commitInfo]]" - on-reload-change="_handleReloadChange"></gr-change-actions> </div> - <div class="changeInfo-column commitAndRelated"> - <div class="commitMessage"> - <h4> - Commit message + <div class="changeInfo-column mainChangeInfo"> + <div class="commitActions" hidden$="[[!_loggedIn]]""> + <gr-button + class="reply" + secondary + on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button> + <gr-change-actions id="actions" + change="[[_change]]" + actions="[[_change.actions]]" + change-num="[[_changeNum]]" + patch-num="[[_computeLatestPatchNum(_allPatchSets)]]" + commit-message="[[_latestCommitMessage]]" + on-reload-change="_handleReloadChange"></gr-change-actions> + </div> + <div class="commitAndRelated"> + <div class="commitMessage"> + <gr-editable-content id="commitMessageEditor" + editing="[[_editingCommitMessage]]" + content="{{_latestCommitMessage}}"> + <gr-linked-text pre + content="[[_latestCommitMessage]]" + config="[[_projectConfig.commentlinks]]"></gr-linked-text> + </gr-editable-content> <gr-button link + class="editCommitMessage" on-tap="_handleEditCommitMessage" hidden$="[[_hideEditCommitMessage]]">Edit</gr-button> - </h4> - <gr-editable-content id="commitMessageEditor" - editing="[[_editingCommitMessage]]" - content="{{_commitInfo.message}}"> - <gr-linked-text pre - content="[[_commitInfo.message]]" - config="[[_projectConfig.commentlinks]]"></gr-linked-text> - </gr-editable-content> - </div> - <div class="relatedChanges"> - <gr-related-changes-list id="relatedChanges" - change="[[_change]]" - patch-num="[[_patchRange.patchNum]]"></gr-related-changes-list> + </div> + <div class="relatedChanges"> + <gr-related-changes-list id="relatedChanges" + change="[[_change]]" + patch-num="[[_computeLatestPatchNum(_allPatchSets)]]"></gr-related-changes-list> + </div> </div> </div> </section> - <gr-file-list id="fileList" - change="[[_change]]" - change-num="[[_changeNum]]" - patch-range="[[_patchRange]]" - comments="[[_comments]]" - drafts="[[_diffDrafts]]" - revisions="[[_change.revisions]]" - projectConfig="[[_projectConfig]]" - selected-index="{{viewState.selectedFileIndex}}"></gr-file-list> + <section class$="patchInfo [[_computePatchInfoClass(_patchRange.patchNum, _allPatchSets)]]"> + <div class="patchInfo-header"> + <div> + <label class="patchSelectLabel" for="patchSetSelect">Patch set</label> + <select id="patchSetSelect" on-change="_handlePatchChange"> + <template is="dom-repeat" items="[[_allPatchSets]]" as="patchNumber"> + <option value$="[[patchNumber]]" selected$="[[_computePatchIndexIsSelected(index, _patchRange.patchNum)]]"> + <span>[[patchNumber]]</span> + / + <span>[[_computeLatestPatchNum(_allPatchSets)]]</span> + </option> + </template> + </select> + <span class="downloadContainer"> + / + <gr-button link + class="download" + on-tap="_handleDownloadTap">Download</gr-button> + </span> + <span class="latestPatchContainer"> + / + <a href$="/c/[[_change._number]]">Go to latest patch set</a> + </span> + </div> + <gr-commit-info + change="[[_change]]" + server-config="[[serverConfig]]" + commit-info="[[_commitInfo]]"></gr-commit-info> + </div> + <gr-file-list id="fileList" + change="[[_change]]" + change-num="[[_changeNum]]" + patch-range="[[_patchRange]]" + comments="[[_comments]]" + drafts="[[_diffDrafts]]" + revisions="[[_change.revisions]]" + projectConfig="[[_projectConfig]]" + selected-index="{{viewState.selectedFileIndex}}"></gr-file-list> + </section> <gr-messages-list id="messageList" change-num="[[_changeNum]]" messages="[[_change.messages]]" @@ -312,12 +318,12 @@ on-close="_handleDownloadDialogClose"></gr-download-dialog> </gr-overlay> <gr-overlay id="replyOverlay" + no-cancel-on-outside-click on-iron-overlay-opened="_handleReplyOverlayOpen" with-backdrop> <gr-reply-dialog id="replyDialog" change="[[_change]]" - patch-num="[[_patchRange.patchNum]]" - revisions="[[_change.revisions]]" + patch-num="[[_computeLatestPatchNum(_allPatchSets)]]" labels="[[_change.labels]]" permitted-labels="[[_change.permitted_labels]]" diff-drafts="[[_diffDrafts]]"
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 14ac4d1..b5e1ef0 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
@@ -66,7 +66,11 @@ _hideEditCommitMessage: { type: Boolean, computed: '_computeHideEditCommitMessage(_loggedIn, ' + - '_editingCommitMessage, _change.*, _patchRange.patchNum)', + '_editingCommitMessage, _change)', + }, + _latestCommitMessage: { + type: String, + value: '', }, _patchRange: Object, _allPatchSets: { @@ -78,14 +82,16 @@ value: false, }, _loading: Boolean, - _headerContainerEl: Object, - _headerEl: Object, _projectConfig: Object, _replyButtonLabel: { type: String, value: 'Reply', computed: '_computeReplyButtonLabel(_diffDrafts.*)', }, + _initialLoadComplete: { + type: Boolean, + value: false, + }, }, behaviors: [ @@ -98,10 +104,6 @@ '_paramsAndChangeChanged(params, _change)', ], - ready: function() { - this._headerEl = this.$$('.header'); - }, - attached: function() { this._getLoggedIn().then(function(loggedIn) { this._loggedIn = loggedIn; @@ -114,34 +116,6 @@ this._handleCommitMessageSave.bind(this)); this.addEventListener('editable-content-cancel', this._handleCommitMessageCancel.bind(this)); - this.listen(window, 'scroll', '_handleBodyScroll'); - }, - - detached: function() { - this.unlisten(window, 'scroll', '_handleBodyScroll'); - }, - - _handleBodyScroll: function(e) { - var containerEl = this._headerContainerEl || - this.$$('.headerContainer'); - - // Calculate where the header is relative to the window. - var top = containerEl.offsetTop; - for (var offsetParent = containerEl.offsetParent; - offsetParent; - offsetParent = offsetParent.offsetParent) { - top += offsetParent.offsetTop; - } - // The element may not be displayed yet, in which case do nothing. - if (top == 0) { return; } - - this._headerEl.classList.toggle('pinned', window.scrollY >= top); - }, - - _resetHeaderEl: function() { - var el = this._headerEl || this.$$('.header'); - this._headerEl = el; - el.classList.remove('pinned'); }, _handleEditCommitMessage: function(e) { @@ -157,7 +131,7 @@ this.$.commitMessageEditor.disabled = false; if (!resp.ok) { return; } - this.set('_commitInfo.message', message); + this._latestCommitMessage = message; this._editingCommitMessage = false; this._reloadWindow(); }.bind(this)).catch(function(err) { @@ -182,16 +156,8 @@ }.bind(this)); }, - _computeHideEditCommitMessage: function(loggedIn, editing, changeRecord, - patchNum) { - if (!changeRecord || !loggedIn || editing) { return true; } - - patchNum = parseInt(patchNum, 10); - if (isNaN(patchNum)) { return true; } - - var change = changeRecord.base; - if (!change.current_revision) { return true; } - if (change.revisions[change.current_revision]._number !== patchNum) { + _computeHideEditCommitMessage: function(loggedIn, editing, change) { + if (!loggedIn || editing || change.status === this.ChangeStatus.MERGED) { return true; } @@ -267,19 +233,7 @@ }, _handlePatchChange: function(e) { - var patchNum = e.target.value; - var currentPatchNum; - if (this._change.current_revision) { - currentPatchNum = - this._change.revisions[this._change.current_revision]._number; - } else { - currentPatchNum = this._computeLatestPatchNum(this._allPatchSets); - } - if (patchNum == currentPatchNum) { - page.show(this.changePath(this._changeNum)); - return; - } - page.show(this.changePath(this._changeNum) + '/' + patchNum); + this._changePatchNum(parseInt(e.target.value, 10)); }, _handleReplyTap: function(e) { @@ -330,32 +284,66 @@ }, _paramsChanged: function(value) { - if (value.view !== this.tagName.toLowerCase()) { return; } + if (value.view !== this.tagName.toLowerCase()) { + this._initialLoadComplete = false; + return; + } - this._changeNum = value.changeNum; - this._patchRange = { + var patchChanged = this._patchRange && + (this._patchRange.patchNum !== value.patchNum || + this._patchRange.basePatchNum !== value.basePatchNum); + + if (this._changeNum !== value.changeNum) { + this._initialLoadComplete = false; + } + + var patchRange = { patchNum: value.patchNum, basePatchNum: value.basePatchNum || 'PARENT', }; + if (this._initialLoadComplete && patchChanged) { + if (patchRange.patchNum == null) { + patchRange.patchNum = this._computeLatestPatchNum(this._allPatchSets); + } + this._patchRange = patchRange; + this._reloadPatchNumDependentResources().then(function() { + this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, { + change: this._change, + patchNum: patchRange.patchNum, + }); + }.bind(this)); + return; + } + + this._changeNum = value.changeNum; + this._patchRange = patchRange; + this._reload().then(function() { - this.$.messageList.topMargin = this._headerEl.offsetHeight; - this.$.fileList.topMargin = this._headerEl.offsetHeight; - - // Allow the message list to render before scrolling. - this.async(function() { - this._maybeScrollToMessage(); - }.bind(this), 1); - - this._maybeShowReplyDialog(); - - this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, { - change: this._change, - patchNum: this._patchRange.patchNum, - }); + this._performPostLoadTasks(); }.bind(this)); }, + _performPostLoadTasks: function() { + // Allow the message list and related changes to render before scrolling. + // Related changes are loaded here (after everything else) because they + // take the longest and are secondary information. Because the element may + // alter the total height of the page, the call to potentially scroll to + // a linked message is performed after related changes is fully loaded. + this.$.relatedChanges.reload().then(function() { + this.async(function() { this._maybeScrollToMessage(); }, 1); + }.bind(this)); + + this._maybeShowReplyDialog(); + + this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, { + change: this._change, + patchNum: this._patchRange.patchNum, + }); + + this._initialLoadComplete = true; + }, + _paramsAndChangeChanged: function(value) { // If the change number or patch range is different, then reset the // selected file index. @@ -389,6 +377,12 @@ _resetFileListViewState: function() { this.set('viewState.selectedFileIndex', 0); + if (!!this.viewState.changeNum && + this.viewState.changeNum !== this._changeNum) { + // Reset the diff mode to null when navigating from one change to + // another, so that the user's preference is restored. + this.set('viewState.diffMode', null); + } this.set('viewState.changeNum', this._changeNum); this.set('viewState.patchRange', this._patchRange); }, @@ -405,6 +399,25 @@ this.fire('title-change', {title: title}); }, + /** + * Change active patch to the provided patch num. + * @param {int} patchNum the patchn number to be viewed. + */ + _changePatchNum: function(patchNum) { + var currentPatchNum; + if (this._change.current_revision) { + currentPatchNum = + this._change.revisions[this._change.current_revision]._number; + } else { + currentPatchNum = this._computeLatestPatchNum(this._allPatchSets); + } + if (patchNum === currentPatchNum) { + page.show(this.changePath(this._changeNum)); + return; + } + page.show(this.changePath(this._changeNum) + '/' + patchNum); + }, + _computeChangePermalink: function(changeNum) { return '/' + changeNum; }, @@ -426,6 +439,14 @@ return allPatchSets[allPatchSets.length - 1]; }, + _computePatchInfoClass: function(patchNum, allPatchSets) { + if (parseInt(patchNum, 10) === + this._computeLatestPatchNum(allPatchSets)) { + return ''; + } + return 'patchInfo--oldPatchSet'; + }, + _computeAllPatchSets: function(change) { var patchNums = []; for (var rev in change.revisions) { @@ -477,11 +498,6 @@ return result; }, - _computeReplyButtonHighlighted: function(changeRecord) { - var drafts = (changeRecord && changeRecord.base) || {}; - return Object.keys(drafts).length > 0; - }, - _computeReplyButtonLabel: function(changeRecord) { var drafts = (changeRecord && changeRecord.base) || {}; var draftCount = Object.keys(drafts).reduce(function(count, file) { @@ -495,9 +511,17 @@ return label; }, + _switchToMostRecentPatchNum: function() { + this._getChangeDetail().then(function() { + var patchNum = this._allPatchSets[this._allPatchSets.length - 1]; + if (patchNum !== this._patchRange.patchNum) { + this._changePatchNum(patchNum); + } + }.bind(this)); + }, + _handleKey: function(e) { if (this.shouldSupressKeyboardShortcut(e)) { return; } - switch (e.keyCode) { case 65: // 'a' if (this._loggedIn && !e.shiftKey) { @@ -505,6 +529,16 @@ this._openReplyDialog(); } break; + case 68: // 'd' + e.preventDefault(); + this.$.downloadOverlay.open(); + break; + case 82: // 'r' + if (e.shiftKey) { + e.preventDefault(); + this._switchToMostRecentPatchNum(); + } + break; case 85: // 'u' e.preventDefault(); page.show('/'); @@ -572,6 +606,14 @@ }.bind(this)); }, + _getLatestCommitMessage: function() { + return this.$.restAPI.getChangeCommitInfo(this._changeNum, + this._computeLatestPatchNum(this._allPatchSets)).then( + function(commitInfo) { + this._latestCommitMessage = commitInfo.message; + }.bind(this)); + }, + _getCommitInfo: function() { return this.$.restAPI.getChangeCommitInfo( this._changeNum, this._patchRange.patchNum).then( @@ -603,33 +645,46 @@ }.bind(this)); this._getComments(); - var reloadPatchNumDependentResources = function() { - return Promise.all([ - this._getCommitInfo(), - this.$.actions.reload(), - this.$.fileList.reload(), - ]); - }.bind(this); - var reloadDetailDependentResources = function() { - if (!this._change) { return Promise.resolve(); } - - return Promise.all([ - this.$.relatedChanges.reload(), - this._getProjectConfig(), - ]); - }.bind(this); - - this._resetHeaderEl(); - if (this._patchRange.patchNum) { - return reloadPatchNumDependentResources().then(function() { + return this._reloadPatchNumDependentResources().then(function() { return detailCompletes; - }).then(reloadDetailDependentResources); + }).then(function() { + return this._reloadDetailDependentResources(); + }.bind(this)); } else { // The patch number is reliant on the change detail request. - return detailCompletes.then(reloadPatchNumDependentResources).then( - reloadDetailDependentResources); + return detailCompletes.then(function() { + this._reloadPatchNumDependentResources(); + }.bind(this)).then(function() { + this._reloadDetailDependentResources(); + }.bind(this)); } }, + + /** + * Kicks off requests for resources that rely on the change detail + * (`this._change`) being loaded. + */ + _reloadDetailDependentResources: function() { + if (!this._change) { return Promise.resolve(); } + + return this._getProjectConfig().then(function() { + return Promise.all([ + this._getLatestCommitMessage(), + this.$.actions.reload(), + ]); + }.bind(this)); + }, + + /** + * Kicks off requests for resources that rely on the patch range + * (`this._patchRange`) being defined. + */ + _reloadPatchNumDependentResources: function() { + return Promise.all([ + this._getCommitInfo(), + this.$.fileList.reload(), + ]); + }, }); })();
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 c9a687b..04e0bc01 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
@@ -43,44 +43,96 @@ element = fixture('basic'); }); - test('keyboard shortcuts', function() { - var showStub = sinon.stub(page, 'show'); + suite('keyboard shortcuts', function() { + test('U should navigate to /', function() { + var showStub = sinon.stub(page, 'show'); + MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U' + assert(showStub.lastCall.calledWithExactly('/')); + showStub.restore(); + }); - MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U' - assert(showStub.lastCall.calledWithExactly('/'), - 'Should navigate to /'); - showStub.restore(); + test('A should toggle overlay', function() { + MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A' + var overlayEl = element.$.replyOverlay; + assert.isFalse(overlayEl.opened); + element._loggedIn = true; - MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A' - var overlayEl = element.$.replyOverlay; - assert.isFalse(overlayEl.opened); - element._loggedIn = true; + MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A' + assert.isFalse(overlayEl.opened); - MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A' - assert.isFalse(overlayEl.opened); + MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A' + assert.isTrue(overlayEl.opened); + overlayEl.close(); + assert.isFalse(overlayEl.opened); + }); - MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A' - assert.isTrue(overlayEl.opened); - overlayEl.close(); - assert.isFalse(overlayEl.opened); + test('shift + R should fetch and navigate to the latest patch set', + function(done) { + element._changeNum = '42'; + element._patchRange = { + basePatchNum: 'PARENT', + patchNum: 1, + }; + element._change = { + change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca', + revisions: { + rev1: {_number: 1}, + }, + current_revision: 'rev1', + status: 'NEW', + labels: {}, + actions: {}, + }; + + sinon.stub(element.$.restAPI, '_getChangeDetail', function() { + // Mock change obj. + return Promise.resolve({ + change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca', + revisions: { + rev1: {_number: 1}, + rev13: {_number: 13}, + }, + current_revision: 'rev1', + status: 'NEW', + labels: {}, + actions: {}, + }); + }); + + var showStub = sinon.stub(page, 'show', function(arg) { + assert.equal(arg, '/c/42/13'); + showStub.restore(); + element.$.restAPI._getChangeDetail.restore(); + done(); + }); + + // 'shift + R' + MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift'); + }); + + test('d should open download overlay', function() { + var stub = sinon.stub(element.$.downloadOverlay, 'open'); + MockInteractions.pressAndReleaseKeyOn(element, 68); // 'd' + assert.isTrue(stub.called); + stub.restore(); + }); }); - test('reply button is highlighted when there are drafts', function() { + test('reply button has updated count when there are drafts', function() { var replyButton = element.$$('gr-button.reply'); assert.ok(replyButton); - assert.isFalse(replyButton.hasAttribute('primary')); + assert.equal(replyButton.textContent, 'Reply'); element._diffDrafts = null; - assert.isFalse(replyButton.hasAttribute('primary')); + assert.equal(replyButton.textContent, 'Reply'); element._diffDrafts = {}; - assert.isFalse(replyButton.hasAttribute('primary')); + assert.equal(replyButton.textContent, 'Reply'); element._diffDrafts = { 'file1.txt': [{}], 'file2.txt': [{}, {}], }; - assert.isTrue(replyButton.hasAttribute('primary')); assert.equal(replyButton.textContent, 'Reply (3)'); }); @@ -120,6 +172,36 @@ assert.deepEqual(element._diffDrafts, {}); }); + test('change num change', function() { + element._changeNum = null; + element._patchRange = { + basePatchNum: 'PARENT', + patchNum: 2, + }; + element._change = { + change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca', + labels: {}, + }; + element.viewState.changeNum = null; + element.viewState.diffMode = 'UNIFIED'; + flushAsynchronousOperations(); + assert.equal(element.viewState.diffMode, 'UNIFIED'); + + element._changeNum = '1'; + element.params = {changeNum: '1'}; + element._change.newProp = '1'; + flushAsynchronousOperations(); + assert.equal(element.viewState.diffMode, 'UNIFIED'); + assert.equal(element.viewState.changeNum, '1'); + + element._changeNum = '2'; + element.params = {changeNum: '2'}; + element._change.newProp = '2'; + flushAsynchronousOperations(); + assert.isNull(element.viewState.diffMode); + assert.equal(element.viewState.changeNum, '2'); + }); + test('patch num change', function(done) { element._changeNum = '42'; element._patchRange = { @@ -138,24 +220,27 @@ status: 'NEW', labels: {}, }; + element.viewState.diffMode = 'UNIFIED'; flushAsynchronousOperations(); - var selectEl = element.$$('.header select'); + + var selectEl = element.$$('.patchInfo-header select'); assert.ok(selectEl); - var optionEls = - Polymer.dom(element.root).querySelectorAll('.header option'); + var optionEls = Polymer.dom(element.root).querySelectorAll( + '.patchInfo-header option'); assert.equal(optionEls.length, 4); - assert.isFalse( - element.$$('.header option[value="1"]').hasAttribute('selected')); - assert.isTrue( - element.$$('.header option[value="2"]').hasAttribute('selected')); - assert.isFalse( - element.$$('.header option[value="3"]').hasAttribute('selected')); + assert.isFalse(element.$$('.patchInfo-header option[value="1"]') + .hasAttribute('selected')); + assert.isTrue(element.$$('.patchInfo-header option[value="2"]') + .hasAttribute('selected')); + assert.isFalse(element.$$('.patchInfo-header option[value="3"]') + .hasAttribute('selected')); assert.equal(optionEls[3].value, 13); var showStub = sinon.stub(page, 'show'); var numEvents = 0; selectEl.addEventListener('change', function(e) { + assert.equal(element.viewState.diffMode, 'UNIFIED'); numEvents++; if (numEvents == 1) { assert(showStub.lastCall.calledWithExactly('/c/42/1'), @@ -191,17 +276,17 @@ labels: {}, }; flushAsynchronousOperations(); - var selectEl = element.$$('.header select'); + var selectEl = element.$$('.patchInfo-header select'); assert.ok(selectEl); - var optionEls = - Polymer.dom(element.root).querySelectorAll('.header option'); + var optionEls = Polymer.dom(element.root).querySelectorAll( + '.patchInfo-header option'); assert.equal(optionEls.length, 4); - assert.isFalse( - element.$$('.header option[value="1"]').hasAttribute('selected')); - assert.isTrue( - element.$$('.header option[value="2"]').hasAttribute('selected')); - assert.isFalse( - element.$$('.header option[value="3"]').hasAttribute('selected')); + assert.isFalse(element.$$('.patchInfo-header option[value="1"]') + .hasAttribute('selected')); + assert.isTrue(element.$$('.patchInfo-header option[value="2"]') + .hasAttribute('selected')); + assert.isFalse(element.$$('.patchInfo-header option[value="3"]') + .hasAttribute('selected')); assert.equal(optionEls[3].value, 13); var showStub = sinon.stub(page, 'show'); @@ -225,6 +310,31 @@ element.fire('change', {}, {node: selectEl}); }); + test('don’t reload entire page when patchRange changes', function() { + var reloadStub = sinon.stub(element, '_reload', + function() { return Promise.resolve(); }); + var reloadPatchDependentStub = sinon.stub(element, + '_reloadPatchNumDependentResources', + function() { return Promise.resolve(); }); + + var value = { + view: 'gr-change-view', + patchNum: '1', + }; + element._paramsChanged(value); + assert.isTrue(reloadStub.calledOnce); + element._initialLoadComplete = true; + + value.basePatchNum = '1'; + value.patchNum = '2'; + element._paramsChanged(value); + assert.isFalse(reloadStub.calledTwice); + assert.isTrue(reloadPatchDependentStub.calledOnce); + + reloadStub.restore(); + reloadPatchDependentStub.restore(); + }); + test('change status new', function() { element._changeNum = '1'; element._patchRange = { @@ -287,33 +397,26 @@ }); test('show commit message edit button', function() { - var changeRecord = { - base: { - revisions: { - rev1: {_number: 1}, - rev2: {_number: 2}, - }, - current_revision: 'rev2', - }, + var _change = { + status: element.ChangeStatus.MERGED, }; - assert.isTrue(element._computeHideEditCommitMessage( - false, false, changeRecord, '2')); - assert.isTrue(element._computeHideEditCommitMessage( - true, true, changeRecord, '2')); - assert.isTrue(element._computeHideEditCommitMessage( - true, false, changeRecord, '1')); - assert.isFalse(element._computeHideEditCommitMessage( - true, false, changeRecord, '2')); + assert.isTrue(element._computeHideEditCommitMessage(false, false, {})); + assert.isTrue(element._computeHideEditCommitMessage(true, true, {})); + assert.isTrue(element._computeHideEditCommitMessage(false, true, {})); + assert.isFalse(element._computeHideEditCommitMessage(true, false, {})); + assert.isTrue(element._computeHideEditCommitMessage(true, false, + _change)); }); - test('topic is coalesced to null', function() { + test('topic is coalesced to null', function(done) { sinon.stub(element, '_changeChanged'); - sinon.stub(element.$.restAPI, 'getChangeDetail', function(num) { + sinon.stub(element.$.restAPI, 'getChangeDetail', function() { return Promise.resolve({id: '123456789', labels: {}}); }); element._getChangeDetail().then(function() { assert.isNull(element._change.topic); + done(); }); }); @@ -331,5 +434,14 @@ assert(openSpy.lastCall.calledWithExactly(FocusTarget.CCS), '_openReplyDialog should have been passed CCS'); }); + + test('class is applied to file list on old patch set', function() { + var allPatcheSets = [1, 2, 4]; + assert.equal(element._computePatchInfoClass('1', allPatcheSets), + 'patchInfo--oldPatchSet'); + assert.equal(element._computePatchInfoClass('2', allPatcheSets), + 'patchInfo--oldPatchSet'); + assert.equal(element._computePatchInfoClass('4', allPatcheSets), ''); + }); }); </script>
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html new file mode 100644 index 0000000..5cd65fa --- /dev/null +++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
@@ -0,0 +1,35 @@ +<!-- +Copyright (C) 2016 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<link rel="import" href="../../../bower_components/polymer/polymer.html"> + +<dom-module id="gr-commit-info"> + <template> + <style> + :host { + display: inline-block; + } + </style> + <template is="dom-if" if="[[_showWebLink]]"> + <a target="_blank" + href$="[[_webLink]]">[[_computeShortHash(commitInfo)]]</a> + </template> + <template is="dom-if" if="[[!_showWebLink]]"> + [[_computeShortHash(commitInfo)]] + </template> + </template> + <script src="gr-commit-info.js"></script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js new file mode 100644 index 0000000..5aa8601 --- /dev/null +++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
@@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +(function() { + 'use strict'; + + Polymer({ + is: 'gr-commit-info', + + properties: { + change: Object, + commitInfo: Object, + serverConfig: Object, + _showWebLink: { + type: Boolean, + computed: '_computeShowWebLink(change, commitInfo, serverConfig)', + }, + _webLink: { + type: String, + computed: '_computeWebLink(change, commitInfo, serverConfig)', + }, + }, + + _isWebLink: function(link) { + // This is a whitelist of web link types that provide direct links to + // the commit in the url property. + return link.name === 'gitiles' || link.name === 'gitweb'; + }, + + _computeShowWebLink: function(change, commitInfo, serverConfig) { + if (serverConfig.gitweb && serverConfig.gitweb.url && + serverConfig.gitweb.type && serverConfig.gitweb.type.revision) { + return true; + } + + if (!commitInfo.web_links) { + return false; + } + + for (var i = 0; i < commitInfo.web_links.length; i++) { + if (this._isWebLink(commitInfo.web_links[i])) { + return true; + } + } + + return false; + }, + + _computeWebLink: function(change, commitInfo, serverConfig) { + if (!this._computeShowWebLink(change, commitInfo, serverConfig)) { + return; + } + + if (serverConfig.gitweb && serverConfig.gitweb.url && + serverConfig.gitweb.type && serverConfig.gitweb.type.revision) { + return serverConfig.gitweb.url + + serverConfig.gitweb.type.revision + .replace('${project}', change.project) + .replace('${commit}', commitInfo.commit); + } + + var webLink = null; + for (var i = 0; i < commitInfo.web_links.length; i++) { + if (this._isWebLink(commitInfo.web_links[i])) { + webLink = commitInfo.web_links[i].url; + break; + } + } + + if (!webLink) { + return; + } + + if (!/^https?\:\/\//.test(webLink)) { + webLink = '../../' + webLink; + } + + return webLink; + }, + + _computeShortHash: function(commitInfo) { + if (!commitInfo || !commitInfo.commit) { + return; + } + return commitInfo.commit.slice(0, 7); + }, + }); +})();
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html new file mode 100644 index 0000000..36b1628 --- /dev/null +++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
@@ -0,0 +1,145 @@ +<!DOCTYPE html> +<!-- +Copyright (C) 2015 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-commit-info</title> + +<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script> +<script src="../../../bower_components/web-component-tester/browser.js"></script> + +<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html"> +<link rel="import" href="gr-commit-info.html"> + +<test-fixture id="basic"> + <template> + <gr-commit-info></gr-commit-info> + </template> +</test-fixture> + +<script> + suite('gr-commit-info tests', function() { + var element; + + setup(function() { + element = fixture('basic'); + }); + + test('no web link when unavailable', function() { + element.commitInfo = {}; + element.serverConfig = {}; + element.change = {labels: []}; + + assert.isNotOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + }); + + test('use web link when available', function() { + element.commitInfo = {web_links: [{name: 'gitweb', url: 'link-url'}]}; + element.serverConfig = {}; + + assert.isOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + assert.equal(element._computeWebLink(element.change, element.commitInfo, + element.serverConfig), '../../link-url'); + }); + + test('does not relativize web links that begin with scheme', function() { + element.commitInfo = { + web_links: [{name: 'gitweb', url: 'https://link-url'}] + }; + element.serverConfig = {}; + + assert.isOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + assert.equal(element._computeWebLink(element.change, element.commitInfo, + element.serverConfig), 'https://link-url'); + }); + + test('use gitweb when available', function() { + element.commitInfo = {commit: 'commit-sha'}; + element.serverConfig = {gitweb: { + url: 'url-base/', + type: {revision: 'xx ${project} xx ${commit} xx'}, + }}; + element.change = { + project: 'project-name', + labels: [], + current_revision: element.commitInfo.commit + }; + + assert.isOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + + assert.equal(element._computeWebLink(element.change, element.commitInfo, + element.serverConfig), 'url-base/xx project-name xx commit-sha xx'); + }); + + test('prefer gitweb when both are available', function() { + element.commitInfo = { + commit: 'commit-sha', + web_links: [{url: 'link-url'}] + }; + element.serverConfig = {gitweb: { + url: 'url-base/', + type: {revision: 'xx ${project} xx ${commit} xx'}, + }}; + element.change = { + project: 'project-name', + labels: [], + current_revision: element.commitInfo.commit + }; + + assert.isOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + + var link = element._computeWebLink(element.change, element.commitInfo, + element.serverConfig); + + assert.equal(link, 'url-base/xx project-name xx commit-sha xx'); + assert.notEqual(link, '../../link-url'); + }); + + test('ignore web links that are neither gitweb nor gitiles', function() { + element.commitInfo = { + commit: 'commit-sha', + web_links: [ + { + name: 'ignore', + url: 'ignore', + }, + { + name: 'gitiles', + url: 'https://link-url', + } + ], + }; + element.serverConfig = {}; + + assert.isOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + assert.equal(element._computeWebLink(element.change, element.commitInfo, + element.serverConfig), 'https://link-url'); + + // Remove gitiles link. + element.commitInfo.web_links.splice(1, 1); + assert.isNotOk(element._computeShowWebLink(element.change, + element.commitInfo, element.serverConfig)); + assert.isNotOk(element._computeWebLink(element.change, element.commitInfo, + element.serverConfig)); + }); + }); +</script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js index 0ce1cbb..e47f14f 100644 --- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js +++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
@@ -33,6 +33,10 @@ message: String, }, + resetFocus: function() { + this.$.messageInput.textarea.focus(); + }, + _handleConfirmTap: function(e) { e.preventDefault(); this.fire('confirm', {reason: this.message}, {bubbles: false});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js index 97342d1..f27e4e2 100644 --- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js +++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
@@ -32,16 +32,6 @@ properties: { branch: String, message: String, - commitInfo: { - type: Object, - readOnly: true, - observer: '_commitInfoChanged', - }, - }, - - _commitInfoChanged: function(commitInfo) { - // Pre-populate cherry-pick message for editing from commit info. - this.message = commitInfo.message; }, _handleConfirmTap: function(e) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js index b4baa26..2eb6646 100644 --- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js +++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
@@ -30,26 +30,22 @@ */ properties: { - branch: String, message: String, - commitInfo: Object, }, - populateRevertMessage: function() { + populateRevertMessage: function(message) { // Figure out what the revert title should be. - var originalTitle = this.commitInfo.message.split('\n')[0]; - var revertTitle = 'Revert of ' + originalTitle; - if (originalTitle.startsWith('Revert of ')) { - revertTitle = 'Reland of ' + - originalTitle.substring('Revert of '.length); - } else if (originalTitle.startsWith('Reland of ')) { - revertTitle = 'Revert of ' + - originalTitle.substring('Reland of '.length); - } + var originalTitle = message.split('\n')[0]; + var revertTitle = 'Revert "' + originalTitle + '"'; + // Figure out what the revert commit message should be. + var commitRegex = /\n{1,2}\nChange-Id: (\w+)\n/gm; + var match = commitRegex.exec(message); + var revertCommitText = 'This reverts commit ' + match[1] + '.'; // Add '> ' in front of the original commit text. - var originalCommitText = this.commitInfo.message.replace(/^/gm, '> '); + var originalCommitText = message.replace(/^/gm, '> '); this.message = revertTitle + '\n\n' + + revertCommitText + '\n\n' + 'Reason for revert: <INSERT REASONING HERE>\n\n' + 'Original issue\'s description:\n' + originalCommitText; },
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html index 1d53eef..521aeef 100644 --- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html +++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -40,25 +40,39 @@ test('single line', function() { assert.isNotOk(element.message); - element.commitInfo = {message: 'one line commit'}; - assert.isNotOk(element.message); - element.populateRevertMessage(); - var expected = 'Revert of one line commit\n\n' + + element.populateRevertMessage('one line commit\n\nChange-Id: abcdefg\n'); + var expected = 'Revert "one line commit"\n\n' + + 'This reverts commit abcdefg.\n\n' + 'Reason for revert: <INSERT REASONING HERE>\n\n' + 'Original issue\'s description:\n' + - '> one line commit'; + '> one line commit\n> \n' + + '> Change-Id: abcdefg\n> '; assert.equal(element.message, expected); }); test('multi line', function() { assert.isNotOk(element.message); - element.commitInfo = {message: 'many lines\ncommit\n\nmessage\n'}; - assert.isNotOk(element.message); - element.populateRevertMessage(); - var expected = 'Revert of many lines\n\n' + + element.populateRevertMessage( + 'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n'); + var expected = 'Revert "many lines"\n\n' + + 'This reverts commit abcdefg.\n\n' + 'Reason for revert: <INSERT REASONING HERE>\n\n' + 'Original issue\'s description:\n' + - '> many lines\n> commit\n> \n> message\n> '; + '> many lines\n> commit\n> \n> message\n> \n' + + '> Change-Id: abcdefg\n> '; + assert.equal(element.message, expected); + }); + + test('revert a revert', function () { + assert.isNotOk(element.message); + element.populateRevertMessage( + 'Revert "one line commit"\n\nChange-Id: abcdefg\n'); + var expected = 'Revert "Revert "one line commit""\n\n' + + 'This reverts commit abcdefg.\n\n' + + 'Reason for revert: <INSERT REASONING HERE>\n\n' + + 'Original issue\'s description:\n' + + '> Revert "one line commit"\n> \n' + + '> Change-Id: abcdefg\n> '; assert.equal(element.message, expected); }); });
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 ef5ceed..d1cb719 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
@@ -100,6 +100,20 @@ color: #C62828; font-weight: bold; } + .show-hide { + margin-left: .4em; + } + input.show-hide { + display: none; + } + label.show-hide { + color: #00f; + cursor: pointer; + display: block; + font-size: .8em; + min-width: 2em; + margin-top: .1em; + } gr-diff { box-shadow: 0 1px 3px rgba(0, 0, 0, .3); display: block; @@ -142,11 +156,12 @@ </label> </div> </header> - <template is="dom-repeat" items="[[_files]]" as="file"> + <template is="dom-repeat" items="[[_files]]" as="file" initial-count="25" target-framerate="30"> <div class="row" selected$="[[_computeFileSelected(index, selectedIndex)]]"> <div class="reviewed" hidden$="[[!_loggedIn]]" hidden> <input type="checkbox" checked$="[[_computeReviewed(file, _reviewed)]]" - data-path$="[[file.__path]]" on-change="_handleReviewedChange"> + data-path$="[[file.__path]]" on-change="_handleReviewedChange" + class="reviewed"> </div> <div class$="[[_computeClass('status', file.__path)]]"> [[_computeFileStatus(file.status)]] @@ -168,8 +183,17 @@ <span class="added">+[[file.lines_inserted]]</span> <span class="removed">-[[file.lines_deleted]]</span> </div> + <div class="show-hide"> + <label class="show-hide"> + <input type="checkbox" class="show-hide" + checked$="[[!file.__expanded]]" data-path$="[[file.__path]]" + on-change="_handleHiddenChange"> + [[_computeShowHideText(file.__expanded)]] + </label> + </div> </div> - <gr-diff hidden + <gr-diff + hidden$="[[_computeHiddenState(file.__expanded)]]" project="[[change.project]]" commit="[[change.current_revision]]" change-num="[[changeNum]]" @@ -181,9 +205,7 @@ </template> <gr-rest-api-interface id="restAPI"></gr-rest-api-interface> <gr-storage id="storage"></gr-storage> - <gr-diff-cursor - id="cursor" - fold-offset-top="[[topMargin]]"></gr-diff-cursor> + <gr-diff-cursor id="cursor"></gr-diff-cursor> </template> <script src="gr-file-list.js"></script> </dom-module>
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 225d8b3..aff635c 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
@@ -27,7 +27,6 @@ drafts: Object, revisions: Object, projectConfig: Object, - topMargin: Number, selectedIndex: { type: Number, notify: true, @@ -121,10 +120,16 @@ return parseInt(patchNum, 10) === parseInt(basePatchNum, 10); }, + _handleHiddenChange: function(e) { + var model = e.model; + model.set('file.__expanded', !model.file.__expanded); + }, + _handlePatchChange: function(e) { - this.set('patchRange.basePatchNum', Polymer.dom(e).rootTarget.value); + var patchRange = Object.assign({}, this.patchRange); + patchRange.basePatchNum = Polymer.dom(e).rootTarget.value; page.show('/c/' + encodeURIComponent(this.changeNum) + '/' + - encodeURIComponent(this._patchRangeStr(this.patchRange))); + encodeURIComponent(this._patchRangeStr(patchRange))); }, _forEachDiff: function(fn) { @@ -136,10 +141,9 @@ _expandAllDiffs: function(e) { this._showInlineDiffs = true; - this._forEachDiff(function(diff) { - diff.hidden = false; - diff.reload(); - }); + for (var index in this._files) { + this.set(['_files', index, '__expanded'], true); + } if (e && e.target) { e.target.blur(); } @@ -147,9 +151,9 @@ _collapseAllDiffs: function(e) { this._showInlineDiffs = false; - this._forEachDiff(function(diff) { - diff.hidden = true; - }); + for (var index in this._files) { + this.set(['_files', index, '__expanded'], false); + } this.$.cursor.handleDiffUpdate(); if (e && e.target) { e.target.blur(); @@ -212,12 +216,17 @@ _getFiles: function() { return this.$.restAPI.getChangeFilesAsSpeciallySortedArray( - this.changeNum, this.patchRange); + this.changeNum, this.patchRange).then(function(files) { + // Append UI-specific properties. + return files.map(function(file) { + file.__expanded = false; + return file; + }); + }); }, _handleKey: function(e) { if (this.shouldSupressKeyboardShortcut(e)) { return; } - switch (e.keyCode) { case 37: // left if (e.shiftKey && this._showInlineDiffs) { @@ -232,9 +241,14 @@ } break; case 73: // 'i' - if (!e.shiftKey) { return; } - e.preventDefault(); - this._toggleInlineDiffs(); + if (e.shiftKey) { + e.preventDefault(); + this._toggleInlineDiffs(); + } else if (this.selectedIndex !== undefined) { + e.preventDefault(); + var expanded = this._files[this.selectedIndex].__expanded; + this.set(['_files', this.selectedIndex, '__expanded'], !expanded); + } break; case 40: // down case 74: // 'j' @@ -352,7 +366,7 @@ } // Don't scroll if it's already in view. - if (top > window.pageYOffset + this.topMargin && + if (top > window.pageYOffset && top < window.pageYOffset + window.innerHeight - el.clientHeight) { return; } @@ -369,6 +383,11 @@ }, _computeDiffURL: function(changeNum, patchRange, path) { + // @see Issue 4255 regarding double-encoding. + path = encodeURIComponent(encodeURIComponent(path)); + // @see Issue 4577 regarding more readable URLs. + path = path.replace(/%252F/g, '/'); + path = path.replace(/%2520/g, '+'); return '/c/' + encodeURIComponent(changeNum) + '/' + @@ -395,6 +414,14 @@ return classes.join(' '); }, + _computeShowHideText: function(expanded) { + return expanded ? 'â–¼' : 'â—€'; + }, + + _computeHiddenState: function(expanded) { + return !expanded; + }, + _filesChanged: function() { this.async(function() { var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
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 f61566a..76aceef 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
@@ -39,6 +39,7 @@ setup(function() { stub('gr-rest-api-interface', { getLoggedIn: function() { return Promise.resolve(true); }, + getPreferences: function() { return Promise.resolve({}); }, }); element = fixture('basic'); }); @@ -60,16 +61,19 @@ lines_inserted: 9, lines_deleted: 0, __path: '/COMMIT_MSG', + __expanded: false, }); assert.deepEqual(files[1], { lines_inserted: 0, lines_deleted: 0, __path: 'about.txt', + __expanded: false, }); assert.deepEqual(files[2], { lines_inserted: 0, lines_deleted: 123, __path: 'tags.html', + __expanded: false, }); getChangeFilesStub.restore(); @@ -77,62 +81,77 @@ }); }); - test('toggle left diff via shortcut', function() { - var toggleLeftDiffStub = sinon.stub(); - sinon.stub(element, 'diffs', {get: function() { - return [{toggleLeftDiff: toggleLeftDiffStub}]; - }}); - MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A' - assert.isTrue(toggleLeftDiffStub.calledOnce); - }); + suite('keyboard shortcuts', function() { + setup(function() { + element._files = [ + {__path: '/COMMIT_MSG', __expanded: false}, + {__path: 'file_added_in_rev2.txt', __expanded: false}, + {__path: 'myfile.txt', __expanded: false}, + ]; + element.changeNum = '42'; + element.patchRange = { + basePatchNum: 'PARENT', + patchNum: '2', + }; + element.selectedIndex = 0; + }); - test('keyboard shortcuts', function() { - var toggleInlineDiffsStub = sinon.stub(element, '_toggleInlineDiffs'); - MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift'); // 'I' - assert.isTrue(toggleInlineDiffsStub.calledOnce); - toggleInlineDiffsStub.restore(); + test('toggle left diff via shortcut', function() { + var toggleLeftDiffStub = sinon.stub(); + sinon.stub(element, 'diffs', {get: function() { + return [{toggleLeftDiff: toggleLeftDiffStub}]; + }}); + MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A' + assert.isTrue(toggleLeftDiffStub.calledOnce); + }); - element._files = [ - {__path: '/COMMIT_MSG'}, - {__path: 'file_added_in_rev2.txt'}, - {__path: 'myfile.txt'}, - ]; - element.changeNum = '42'; - element.patchRange = { - basePatchNum: 'PARENT', - patchNum: '2', - }; - element.selectedIndex = 0; + test('keyboard shortcuts', function() { + var toggleInlineDiffsStub = sinon.stub(element, '_toggleInlineDiffs'); + MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift'); // 'I' + assert.isTrue(toggleInlineDiffsStub.calledOnce); + toggleInlineDiffsStub.restore(); - flushAsynchronousOperations(); - var elementItems = Polymer.dom(element.root).querySelectorAll( - '.row:not(.header)'); - assert.equal(elementItems.length, 3); - assert.isTrue(elementItems[0].hasAttribute('selected')); - assert.isFalse(elementItems[1].hasAttribute('selected')); - assert.isFalse(elementItems[2].hasAttribute('selected')); - MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J' - assert.equal(element.selectedIndex, 1); - MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J' + flushAsynchronousOperations(); + var elementItems = Polymer.dom(element.root).querySelectorAll( + '.row:not(.header)'); + assert.equal(elementItems.length, 3); + assert.isTrue(elementItems[0].hasAttribute('selected')); + assert.isFalse(elementItems[1].hasAttribute('selected')); + assert.isFalse(elementItems[2].hasAttribute('selected')); + MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J' + assert.equal(element.selectedIndex, 1); + MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J' - var showStub = sinon.stub(page, 'show'); - assert.equal(element.selectedIndex, 2); - MockInteractions.pressAndReleaseKeyOn(element, 13); // 'ENTER' - assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'), - 'Should navigate to /c/42/2/myfile.txt'); + var showStub = sinon.stub(page, 'show'); + assert.equal(element.selectedIndex, 2); + MockInteractions.pressAndReleaseKeyOn(element, 13); // 'ENTER' + assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'), + 'Should navigate to /c/42/2/myfile.txt'); - MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' - assert.equal(element.selectedIndex, 1); - MockInteractions.pressAndReleaseKeyOn(element, 79); // 'O' - assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'), - 'Should navigate to /c/42/2/file_added_in_rev2.txt'); + MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' + assert.equal(element.selectedIndex, 1); + MockInteractions.pressAndReleaseKeyOn(element, 79); // 'O' + assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'), + 'Should navigate to /c/42/2/file_added_in_rev2.txt'); - MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' - MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' - MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' - assert.equal(element.selectedIndex, 0); + MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' + MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' + MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' + assert.equal(element.selectedIndex, 0); - showStub.restore(); + showStub.restore(); + }); + + test('i key shows/hides selected inline diff', function() { + element.selectedIndex = 0; + MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I' + assert.isTrue(element._files[0].__expanded); + MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I' + assert.isFalse(element._files[0].__expanded); + element.selectedIndex = 1; + MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I' + assert.isTrue(element._files[1].__expanded); + }); }); test('comment filtering', function() { @@ -187,9 +206,9 @@ test('file review status', function() { element._files = [ - {__path: '/COMMIT_MSG'}, - {__path: 'file_added_in_rev2.txt'}, - {__path: 'myfile.txt'}, + {__path: '/COMMIT_MSG', __expanded: false}, + {__path: 'file_added_in_rev2.txt', __expanded: false}, + {__path: 'myfile.txt', __expanded: false}, ]; element._reviewed = ['/COMMIT_MSG', 'myfile.txt']; element.changeNum = '42'; @@ -202,9 +221,12 @@ flushAsynchronousOperations(); var fileRows = Polymer.dom(element.root).querySelectorAll('.row:not(.header)'); - var commitMsg = fileRows[0].querySelector('input[type="checkbox"]'); - var fileAdded = fileRows[1].querySelector('input[type="checkbox"]'); - var myFile = fileRows[2].querySelector('input[type="checkbox"]'); + var commitMsg = fileRows[0].querySelector( + 'input.reviewed[type="checkbox"]'); + var fileAdded = fileRows[1].querySelector( + 'input.reviewed[type="checkbox"]'); + var myFile = fileRows[2].querySelector( + 'input.reviewed[type="checkbox"]'); assert.isTrue(commitMsg.checked); assert.isFalse(fileAdded.checked); @@ -267,5 +289,46 @@ element.fire('change', {}, {node: selectEl}); }); }); + + test('checkbox shows/hides diff inline', function() { + element._files = [ + {__path: 'myfile.txt', __expanded: false}, + ]; + element.changeNum = '42'; + element.patchRange = { + basePatchNum: 'PARENT', + patchNum: '2', + }; + element.selectedIndex = 0; + flushAsynchronousOperations(); + var fileRows = + Polymer.dom(element.root).querySelectorAll('.row:not(.header)'); + // Prevent diff from making API call. + var diffStub = sinon.stub(element.diffs[0], 'reload'); + var showHideCheck = fileRows[0].querySelector( + 'input.show-hide[type="checkbox"]'); + assert.isTrue(showHideCheck.checked); + MockInteractions.tap(showHideCheck); + assert.isFalse(element.diffs[0].hidden); + diffStub.restore(); + }); + + test('path should be properly escaped', function() { + element._files = [ + {__path: 'foo bar/my+file.txt%'}, + ]; + element.changeNum = '42'; + element.patchRange = { + basePatchNum: 'PARENT', + patchNum: '2', + }; + flushAsynchronousOperations(); + // Slashes should be preserved, and spaces should be translated to `+`. + // @see Issue 4255 regarding double-encoding. + // @see Issue 4577 regarding more readable URLs. + assert.equal( + element.$$('a').getAttribute('href'), + '/c/42/2/foo+bar/my%252Bfile.txt%2525'); + }); }); </script>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html index 66254d0..958e00f 100644 --- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -50,12 +50,15 @@ } .showAvatar.collapsed .contentContainer { margin-left: calc(var(--default-horizontal-margin) + 1.75em); - padding: .75em 2em .75em 0; } .hideAvatar.collapsed .contentContainer, .hideAvatar.expanded .contentContainer { margin-left: 0; - padding: .75em 2em .75em 0; + } + .showAvatar.collapsed .contentContainer, + .hideAvatar.collapsed .contentContainer, + .hideAvatar.expanded .contentContainer { + padding: .75em 5em .75em 0; } .collapsed gr-avatar { top: .5em;
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js index c92ad07..acb95c1 100644 --- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -56,15 +56,22 @@ }, showReplyButton: { type: Boolean, - computed: '_computeShowReplyButton(message)', + computed: '_computeShowReplyButton(message, _loggedIn)', }, projectConfig: Object, + _loggedIn: { + type: Boolean, + value: false, + }, }, ready: function() { this.$.restAPI.getConfig().then(function(config) { this.config = config; }.bind(this)); + this.$.restAPI.getLoggedIn().then(function(loggedIn) { + this._loggedIn = loggedIn; + }.bind(this)); }, _computeAuthor: function(message) { @@ -75,8 +82,8 @@ return !!(author && config && config.plugin && config.plugin.has_avatars); }, - _computeShowReplyButton: function(message) { - return !!message.message; + _computeShowReplyButton: function(message, loggedIn) { + return !!message.message && loggedIn; }, _commentsChanged: function(value) {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html index c90f58a..643d55d 100644 --- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -37,6 +37,7 @@ setup(function() { element = fixture('basic'); + sinon.stub(element.$.restAPI, 'getLoggedIn').returns(true); }); test('reply event', function(done) { @@ -85,5 +86,12 @@ assert.equal(0, content.textContent.trim().indexOf(updatedBy.name)); }); + test('reply button hidden unless logged in', function() { + var message = { + 'message': 'Uploaded patch set 1.', + }; + assert.isFalse(element._computeShowReplyButton(message, false)); + assert.isTrue(element._computeShowReplyButton(message, true)); + }); }); </script>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js index e7a0573..875bf2b 100644 --- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js +++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -29,7 +29,6 @@ }, comments: Object, projectConfig: Object, - topMargin: Number, showReplyButtons: { type: Boolean, value: false, @@ -52,7 +51,7 @@ offsetParent = offsetParent.offsetParent) { top += offsetParent.offsetTop; } - window.scrollTo(0, top - this.topMargin); + window.scrollTo(0, top); this._highlightEl(el); },
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html index 5d2d20d..adde31e 100644 --- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html +++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
@@ -74,59 +74,61 @@ } </style> <div hidden$="[[!_loading]]">Loading...</div> - <section class="relatedChanges" hidden$="[[!_relatedResponse.changes.length]]" hidden> - <h4>Relation chain</h4> - <template - is="dom-repeat" - items="[[_relatedResponse.changes]]" - as="related"> - <div class$="[[_computeChangeContainerClass(change, related)]]"> - <a href$="[[_computeChangeURL(related._change_number, related._revision_number)]]" - class$="[[_computeLinkClass(related)]]"> - [[related.commit.subject]] + <div hidden$="[[_loading]]"> + <section class="relatedChanges" hidden$="[[!_relatedResponse.changes.length]]" hidden> + <h4>Relation chain</h4> + <template + is="dom-repeat" + items="[[_relatedResponse.changes]]" + as="related"> + <div class$="[[_computeChangeContainerClass(change, related)]]"> + <a href$="[[_computeChangeURL(related._change_number, related._revision_number)]]" + class$="[[_computeLinkClass(related)]]"> + [[related.commit.subject]] + </a> + <span class$="[[_computeChangeStatusClass(related)]]"> + ([[_computeChangeStatus(related)]]) + </span> + </div> + </template> + </section> + <section hidden$="[[!_submittedTogether.length]]" hidden> + <h4>Submitted together</h4> + <template is="dom-repeat" items="[[_submittedTogether]]" as="change"> + <a href$="[[_computeChangeURL(change._number)]]" + class$="[[_computeLinkClass(change)]]"> + [[change.project]]: [[change.branch]]: [[change.subject]] </a> - <span class$="[[_computeChangeStatusClass(related)]]"> - ([[_computeChangeStatus(related)]]) - </span> - </div> - </template> - </section> - <section hidden$="[[!_submittedTogether.length]]" hidden> - <h4>Submitted together</h4> - <template is="dom-repeat" items="[[_submittedTogether]]" as="change"> - <a href$="[[_computeChangeURL(change._number)]]" - class$="[[_computeLinkClass(change)]]"> - [[change.project]]: [[change.branch]]: [[change.subject]] - </a> - </template> - </section> - <section hidden$="[[!_sameTopic.length]]" hidden> - <h4>Same topic</h4> - <template is="dom-repeat" items="[[_sameTopic]]" as="change"> - <a href$="[[_computeChangeURL(change._number)]]" - class$="[[_computeLinkClass(change)]]"> - [[change.project]]: [[change.branch]]: [[change.subject]] - </a> - </template> - </section> - <section hidden$="[[!_conflicts.length]]" hidden> - <h4>Merge conflicts</h4> - <template is="dom-repeat" items="[[_conflicts]]" as="change"> - <a href$="[[_computeChangeURL(change._number)]]" - class$="[[_computeLinkClass(change)]]"> - [[change.subject]] - </a> - </template> - </section> - <section hidden$="[[!_cherryPicks.length]]" hidden> - <h4>Cherry picks</h4> - <template is="dom-repeat" items="[[_cherryPicks]]" as="change"> - <a href$="[[_computeChangeURL(change._number)]]" - class$="[[_computeLinkClass(change)]]"> - [[change.subject]] - </a> - </template> - </section> + </template> + </section> + <section hidden$="[[!_sameTopic.length]]" hidden> + <h4>Same topic</h4> + <template is="dom-repeat" items="[[_sameTopic]]" as="change"> + <a href$="[[_computeChangeURL(change._number)]]" + class$="[[_computeLinkClass(change)]]"> + [[change.project]]: [[change.branch]]: [[change.subject]] + </a> + </template> + </section> + <section hidden$="[[!_conflicts.length]]" hidden> + <h4>Merge conflicts</h4> + <template is="dom-repeat" items="[[_conflicts]]" as="change"> + <a href$="[[_computeChangeURL(change._number)]]" + class$="[[_computeLinkClass(change)]]"> + [[change.subject]] + </a> + </template> + </section> + <section hidden$="[[!_cherryPicks.length]]" hidden> + <h4>Cherry picks</h4> + <template is="dom-repeat" items="[[_cherryPicks]]" as="change"> + <a href$="[[_computeChangeURL(change._number)]]" + class$="[[_computeLinkClass(change)]]"> + [[change.subject]] + </a> + </template> + </section> + </div> <gr-rest-api-interface id="restAPI"></gr-rest-api-interface> </template> <script src="gr-related-changes-list.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js index f4ee53a..cac45c6 100644 --- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js +++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -68,7 +68,7 @@ }.bind(this)), ]; - return this._getServerConfig().then(function(config) { + promises.push(this._getServerConfig().then(function(config) { if (this.change.topic && !config.change.submit_whole_topic) { return this._getChangesWithSameTopic().then(function(response) { this._sameTopic = response; @@ -77,7 +77,9 @@ this._sameTopic = []; } return this._sameTopic; - }.bind(this)).then(Promise.all(promises)).then(function() { + }.bind(this))); + + return Promise.all(promises).then(function() { this._loading = false; }.bind(this)); },
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 cec1e90..1432d8c 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
@@ -97,9 +97,6 @@ border: none; width: 100%; } - .labelsNotShown { - color: #666; - } .labelContainer:not(:first-of-type) { margin-top: .5em; } @@ -211,28 +208,20 @@ </iron-autogrow-textarea> </section> <section class="labelsContainer"> - <template is="dom-if" if="[[_computeShowLabels(patchNum, revisions)]]"> - <template is="dom-repeat" - items="[[_computeLabelArray(permittedLabels)]]" as="label"> - <div class="labelContainer"> - <span class="labelName">[[label]]</span> - <iron-selector data-label$="[[label]]" - selected="[[_computeIndexOfLabelValue(labels, permittedLabels, label, _account)]]"> - <template is="dom-repeat" - items="[[_computePermittedLabelValues(permittedLabels, label)]]" - as="value"> - <gr-button has-tooltip data-value$="[[value]]" - title$="[[_computeLabelValueTitle(labels, label, value)]]">[[value]]</gr-button> - </template> - </iron-selector> - </div> - </template> - </template> - <template is="dom-if" if="[[!_computeShowLabels(patchNum, revisions)]]"> - <span class="labelsNotShown"> - Labels are not shown because this is not the most recent patch set. - <a href$="/c/[[change._number]]">Go to the latest patch set.</a> - </span> + <template is="dom-repeat" + items="[[_computeLabelArray(permittedLabels)]]" as="label"> + <div class="labelContainer"> + <span class="labelName">[[label]]</span> + <iron-selector data-label$="[[label]]" + selected="[[_computeIndexOfLabelValue(labels, permittedLabels, label, _account)]]"> + <template is="dom-repeat" + items="[[_computePermittedLabelValues(permittedLabels, label)]]" + as="value"> + <gr-button has-tooltip data-value$="[[value]]" + title$="[[_computeLabelValueTitle(labels, label, value)]]">[[value]]</gr-button> + </template> + </iron-selector> + </div> </template> </section> <section class="draftsContainer" hidden$="[[_computeHideDraftList(diffDrafts)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js index d2b279d..00fa05e 100644 --- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js +++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -48,7 +48,6 @@ properties: { change: Object, patchNum: String, - revisions: Object, disabled: { type: Boolean, value: false, @@ -153,12 +152,21 @@ var selectorEl = this.$$('iron-selector[data-label="' + label + '"]'); - // The selector may not be present if it’s not at the latest patch set. - if (!selectorEl) { continue; } + // The user may have not voted on this label. + if (!selectorEl.selectedItem) { continue; } var selectedVal = selectorEl.selectedItem.getAttribute('data-value'); selectedVal = parseInt(selectedVal, 10); - obj.labels[label] = selectedVal; + + // Only send the selection if the user changed it. + var prevVal = this._getVoteForAccount(this.labels, label, + this._account); + if (prevVal !== null) { + prevVal = parseInt(prevVal, 10); + } + if (selectedVal !== prevVal) { + obj.labels[label] = selectedVal; + } } if (this.draft != null) { obj.message = this.draft; @@ -259,16 +267,6 @@ }.bind(this)); }, - _computeShowLabels: function(patchNum, revisions) { - var num = parseInt(patchNum, 10); - for (var rev in revisions) { - if (revisions[rev]._number > num) { - return false; - } - } - return true; - }, - _computeHideDraftList: function(drafts) { return Object.keys(drafts || {}).length == 0; }, @@ -291,23 +289,22 @@ return Object.keys(labelsObj).sort(); }, - _computeIndexOfLabelValue: function( - labels, permittedLabels, labelName, account) { - var t = labels[labelName]; - if (!t) { return null; } - var labelValue = t.default_value; - - // Is there an existing vote for the current user? If so, use that. + _getVoteForAccount: function(labels, labelName, account) { var votes = labels[labelName]; if (votes.all && votes.all.length > 0) { for (var i = 0; i < votes.all.length; i++) { if (votes.all[i]._account_id == account._account_id) { - labelValue = votes.all[i].value; - break; + return votes.all[i].value; } } } + return null; + }, + _computeIndexOfLabelValue: function( + labels, permittedLabels, labelName, account) { + if (!labels[labelName]) { return null; } + var labelValue = this._getVoteForAccount(labels, labelName, account); var len = permittedLabels[labelName] != null ? permittedLabels[labelName].length : 0; for (var i = 0; i < len; i++) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html index 8fb4e45..01ca076 100644 --- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html +++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -107,18 +107,7 @@ MockInteractions.tap(element.$$('.cancel')); }); - test('show/hide labels', function() { - var revisions = { - rev1: {_number: 1}, - rev2: {_number: 2}, - }; - assert.isFalse(element._computeShowLabels('1', revisions)); - assert.isTrue(element._computeShowLabels('2', revisions)); - }); - test('label picker', function(done) { - var showLabelsStub = sinon.stub(element, '_computeShowLabels', - function() { return true; }); element.revisions = {}; element.patchNum = ''; @@ -156,7 +145,6 @@ 'Element should be enabled when done sending reply.'); assert.equal(element.draft.length, 0); saveReviewStub.restore(); - showLabelsStub.restore(); done(); }); @@ -312,7 +300,10 @@ assert.equal(body, 'first error, second error'); }); }); - element.send().then(done); + + // Async tick is needed because iron-selector content is distributed and + // distributed content requires an observer to be set up. + flush(function() { element.send().then(done); }); }); test('ccs are displayed if NoteDb is enabled', function() { @@ -389,5 +380,28 @@ assert.strictEqual( element._chooseFocusTarget(), element.FocusTarget.BODY); }); + + test('only send labels that have changed', function(done) { + flush(function() { + var saveReviewStub = sinon.stub(element, '_saveReview', + function(review) { + assert.deepEqual(review.labels, {Verified: -1}); + return Promise.resolve({ok: true}); + }); + + element.addEventListener('send', function() { + saveReviewStub.restore(); + done(); + }); + // Without wrapping this test in flush(), the below two calls to + // MockInteractions.tap() cause a race in some situations in shadow DOM. + // The send button can be tapped before the others, causing the test to + // fail. + MockInteractions.tap(element.$$( + 'iron-selector[data-label="Verified"] > ' + + 'gr-button[data-value="-1"]')); + MockInteractions.tap(element.$$('.send')); + }); + }); }); </script>
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html index 7291199..6e88267 100644 --- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html +++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -155,7 +155,11 @@ </tr> <tr> <td><span class="key">a</span></td> - <td>Review and publish comments</td> + <td>Open reply dialog to publish comments and add reviewers</td> + </tr> + <tr> + <td><span class="key">d</span></td> + <td>Open download overlay</td> </tr> <tr> <td></td><td class="header">File list</td> @@ -173,6 +177,10 @@ <td>Show selected file</td> </tr> <tr> + <td><span class="key">i</span></td> + <td>Show/hide selected inline diff</td> + </tr> + <tr> <td></td><td class="header">Diffs</td> </tr> <tr> @@ -297,7 +305,7 @@ </tr> <tr> <td><span class="key">a</span></td> - <td>Review and publish comments</td> + <td>Open reply dialog to publish comments and add reviewers</td> </tr> <tr> <td><span class="key">,</span></td>
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 930c8cf..916726fd 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
@@ -45,34 +45,38 @@ .links { margin-left: 1em; } - .links ul { + .links .menuContainer { display: none; } .links > li { cursor: default; display: inline-block; margin-left: 1em; - padding: .4em 0; + padding: .5em 0; position: relative; } - .links li:hover ul { + .links li:hover .menuContainer, + .links li:active .menuContainer { background-color: #fff; + border-radius: 3px; box-shadow: 0 1px 1px rgba(0, 0, 0, .3); display: block; - left: -.75em; + left: -.5em; + padding: .5em 0; position: absolute; - top: 2em; + top: 2.45em; z-index: 1000; } .links li ul li a:link, .links li ul li a:visited { color: #00e; display: block; - padding: .5em .75em; + padding: .3em 1em; text-decoration: none; white-space: nowrap; } - .links li ul li:hover a { + .links li ul li:hover a, + .links li ul li:active a { background-color: var(--selection-background-color); } .linksTitle { @@ -87,7 +91,8 @@ height: 0; position: absolute; right: 0; - top: calc(50% - .1em); + top: calc(50% - .05em); + transition: border-top-color 200ms; width: 0; } .links li:hover .downArrow { @@ -137,11 +142,13 @@ <span class="linksTitle"> [[linkGroup.title]] <i class="downArrow"></i> </span> - <ul> - <template is="dom-repeat" items="[[linkGroup.links]]" as="link"> - <li><a href$="[[link.url]]">[[link.name]]</a></li> - </template> - </ul> + <div class="menuContainer"> + <ul> + <template is="dom-repeat" items="[[linkGroup.links]]" as="link"> + <li><a href$="[[link.url]]">[[link.name]]</a></li> + </template> + </ul> + </div> </li> </template> </ul>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html new file mode 100644 index 0000000..7653655 --- /dev/null +++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
@@ -0,0 +1,21 @@ +<!-- +Copyright (C) 2016 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<link rel="import" href="../../../bower_components/polymer/polymer.html"> + +<dom-module id="gr-reporting"> + <script src="gr-reporting.js"></script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js new file mode 100644 index 0000000..5236a1c --- /dev/null +++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -0,0 +1,129 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +(function() { + 'use strict'; + + // Latency reporting constants. + var TIMING = { + TYPE: 'timing-report', + CATEGORY: 'UI Latency', + // Reported events - alphabetize below. + APP_STARTED: 'App Started', + PAGE_LOADED: 'Page Loaded', + }; + + // Navigation reporting constants. + var NAVIGATION = { + TYPE: 'nav-report', + CATEGORY: 'Location Changed', + PAGE: 'Page', + }; + + var CHANGE_VIEW_REGEX = /^\/c\/\d+\/?\d*$/; + var DIFF_VIEW_REGEX = /^\/c\/\d+\/\d+\/.+$/; + + Polymer({ + is: 'gr-reporting', + + properties: { + _baselines: { + type: Array, + value: function() { return {}; }, + } + }, + + get performanceTiming() { + return window.performance.timing; + }, + + now: function() { + return Math.round(10 * window.performance.now()) / 10; + }, + + reporter: function(type, category, eventName, eventValue) { + eventValue = eventValue; + var detail = { + type: type, + category: category, + name: eventName, + value: eventValue, + }; + document.dispatchEvent(new CustomEvent(type, {detail: detail})); + console.log(eventName + ': ' + eventValue); + }, + + /** + * User-perceived app start time, should be reported when the app is ready. + */ + appStarted: function() { + var startTime = + new Date().getTime() - this.performanceTiming.navigationStart; + this.reporter( + TIMING.TYPE, TIMING.CATEGORY, TIMING.APP_STARTED, startTime); + }, + + /** + * Page load time, should be reported at any time after navigation. + */ + pageLoaded: function() { + if (this.performanceTiming.loadEventEnd === 0) { + console.error('pageLoaded should be called after window.onload'); + this.async(this.pageLoaded, 100); + } else { + var loadTime = this.performanceTiming.loadEventEnd - + this.performanceTiming.navigationStart; + this.reporter( + TIMING.TYPE, TIMING.CATEGORY, TIMING.PAGE_LOADED, loadTime); + } + }, + + locationChanged: function() { + var page = ''; + var pathname = this._getPathname(); + if (pathname.startsWith('/q/')) { + page = '/q/'; + } else if (pathname.match(CHANGE_VIEW_REGEX)) { // change view + page = '/c/'; + } else if (pathname.match(DIFF_VIEW_REGEX)) { // diff view + page = '/c//COMMIT_MSG'; + } else { + // Ignore other page changes. + return; + } + this.reporter( + NAVIGATION.TYPE, NAVIGATION.CATEGORY, NAVIGATION.PAGE, page); + }, + + _getPathname: function() { + return window.location.pathname; + }, + + /** + * Reset named timer. + */ + time: function(name) { + this._baselines[name] = this.now(); + }, + + /** + * Finish named timer and report it to server. + */ + timeEnd: function(name) { + var baseTime = this._baselines[name] || 0; + var time = this.now() - baseTime; + this.reporter(TIMING.TYPE, TIMING.CATEGORY, name, time); + delete this._baselines[name]; + }, + }); +})();
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html new file mode 100644 index 0000000..b9d07fc --- /dev/null +++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -0,0 +1,132 @@ +<!DOCTYPE html> +<!-- +Copyright (C) 2016 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-reporting</title> + +<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script> +<script src="../../../bower_components/web-component-tester/browser.js"></script> + +<link rel="import" href="gr-reporting.html"> + +<test-fixture id="basic"> + <template> + <gr-reporting></gr-reporting> + </template> +</test-fixture> + +<script> + suite('gr-reporting tests', function() { + var element; + var sandbox; + var clock; + var fakePerformance; + + var NOW_TIME = 100; + + setup(function() { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(NOW_TIME); + element = fixture('basic'); + fakePerformance = { + navigationStart: 1, + loadEventEnd: 2, + }; + sinon.stub(element, 'performanceTiming', + {get: function() {return fakePerformance;}}); + sandbox.stub(element, 'reporter'); + }); + teardown(function() { + sandbox.restore(); + clock.restore(); + }); + + test('appStarted', function() { + element.appStarted(); + assert.isTrue( + element.reporter.calledWithExactly( + 'timing-report', 'UI Latency', 'App Started', + NOW_TIME - fakePerformance.navigationStart + )); + }); + + test('pageLoaded', function() { + element.pageLoaded(); + assert.isTrue( + element.reporter.calledWithExactly( + 'timing-report', 'UI Latency', 'Page Loaded', + fakePerformance.loadEventEnd - fakePerformance.navigationStart) + ); + }); + + test('time and timeEnd', function() { + var nowStub = sinon.stub(element, 'now').returns(0); + element.time('foo'); + nowStub.returns(1); + element.time('bar'); + nowStub.returns(2); + element.timeEnd('bar'); + nowStub.returns(3.123); + element.timeEnd('foo'); + assert.isTrue(element.reporter.calledWithExactly( + 'timing-report', 'UI Latency', 'foo', 3.123 + )); + assert.isTrue(element.reporter.calledWithExactly( + 'timing-report', 'UI Latency', 'bar', 1 + )); + }); + + suite('location changed', function() { + var pathnameStub; + setup(function() { + pathnameStub = sinon.stub(element, '_getPathname'); + }); + + teardown(function() { + pathnameStub.restore(); + }); + + test('search', function() { + pathnameStub.returns('/q/foo'); + element.locationChanged(); + assert.isTrue(element.reporter.calledWithExactly( + 'nav-report', 'Location Changed', 'Page', '/q/')); + }); + + test('change view', function() { + pathnameStub.returns('/c/42/'); + element.locationChanged(); + assert.isTrue(element.reporter.calledWithExactly( + 'nav-report', 'Location Changed', 'Page', '/c/')); + }); + + test('change view', function() { + pathnameStub.returns('/c/41/2'); + element.locationChanged(); + assert.isTrue(element.reporter.calledWithExactly( + 'nav-report', 'Location Changed', 'Page', '/c/')); + }); + + test('diff view', function() { + pathnameStub.returns('/c/41/2/file.txt'); + element.locationChanged(); + assert.isTrue(element.reporter.calledWithExactly( + 'nav-report', 'Location Changed', 'Page', '/c//COMMIT_MSG')); + }); + }); + }); +</script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.html b/polygerrit-ui/app/elements/core/gr-router/gr-router.html index 2971ed2..4ad2a37 100644 --- a/polygerrit-ui/app/elements/core/gr-router/gr-router.html +++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.html
@@ -15,6 +15,7 @@ --> <link rel="import" href="../../../bower_components/polymer/polymer.html"> <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html"> +<link rel="import" href="../gr-reporting/gr-reporting.html"> <script src="../../../bower_components/page/page.js"></script> <script src="gr-router.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js index d11d438..a182f8a 100644 --- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js +++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -17,9 +17,18 @@ // Polymer makes `app` intrinsically defined on the window by virtue of the // custom element having the id "app", but it is made explicit here. var app = document.querySelector('#app'); - var restAPI = document.createElement('gr-rest-api-interface'); + if (!app) { + console.log('No gr-app found (running tests)'); + return; + } window.addEventListener('WebComponentsReady', function() { + var restAPI = document.createElement('gr-rest-api-interface'); + var reporting = document.createElement('gr-reporting'); + + reporting.timeEnd('WebComponentsReady'); + reporting.pageLoaded(); + // Middleware page(function(ctx, next) { document.body.scrollTop = 0; @@ -28,6 +37,7 @@ // is processed. app.async(function() { app.fire('location-change'); + reporting.locationChanged(); }, 1); next(); }); @@ -124,14 +134,29 @@ }; // Don't allow diffing the same patch number against itself. if (params.basePatchNum === params.patchNum) { + // @see Issue 4255 regarding double-encoding. + var path = encodeURIComponent(encodeURIComponent(path)); + // @see Issue 4577 regarding more readable URLs. + path = path.replace(/%252F/g, '/'); + path = path.replace(/%2520/g, '+'); + page.redirect('/c/' + encodeURIComponent(params.changeNum) + '/' + encodeURIComponent(params.patchNum) + '/' + - encodeURIComponent(params.path)); + path); return; } + + // Check if path has an '@' which indicates it was using GWT style line + // numbers. Even if the filename had an '@' in it, it would have already + // been URI encoded. Redirect to hash version of path. + if (ctx.path.indexOf('@') !== -1) { + page.redirect(ctx.path.replace('@', '#')); + return; + } + normalizePatchRangeParams(params); app.params = params; });
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html index fecb376..957b2ce 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
@@ -18,6 +18,8 @@ <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.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-rest-api-interface/gr-rest-api-interface.html"> + <dom-module id="gr-search-bar"> <template> @@ -51,8 +53,10 @@ on-commit="_handleInputCommit" allowNonSuggestedValues multi - borderless></gr-autocomplete> + borderless + tab-complete-without-commit></gr-autocomplete> <gr-button id="searchButton">Search</gr-button> + <gr-rest-api-interface id="restAPI"></gr-rest-api-interface> </form> </template> <script src="gr-search-bar.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js index 8e52f8f..c33f889 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -78,6 +78,10 @@ 'tr', ]; + var MAX_AUTOCOMPLETE_RESULTS = 10; + + var TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+/g; + Polymer({ is: 'gr-search-bar', @@ -117,41 +121,176 @@ this._preventDefaultAndNavigateToInputVal(e); }, + /** + * This function is called in a few different cases: + * - e.target is the search button + * - e.target is the gr-autocomplete widget (#searchInput) + * - e.target is the input element wrapped within #searchInput + * + * @param {!Event} e + */ _preventDefaultAndNavigateToInputVal: function(e) { e.preventDefault(); - Polymer.dom(e).rootTarget.blur(); - // @see Issue 4255. - page.show('/q/' + encodeURIComponent(encodeURIComponent(this._inputVal))); + var target = Polymer.dom(e).rootTarget; + // If the target is the #searchInput or has a sub-input component, that + // is what holds the focus as opposed to the target from the DOM event. + if (target.$.input) { + target.$.input.blur(); + } else { + target.blur(); + } + if (this._inputVal) { + // @see Issue 4255. + page.show('/q/' + + encodeURIComponent(encodeURIComponent(this._inputVal))); + } }, - // TODO(kaspern): Flesh this out better. - _makeSuggestion: function(str) { - return { - name: str, - value: str, - }; + /** + * Fetch from the API the predicted accounts. + * @param {string} predicate - The first part of the search term, e.g. + * 'owner' + * @param {string} expression - The second part of the search term, e.g. + * 'kasp' + * @return {!Promise} This returns a promise that resolves to an array of + * strings. + */ + _fetchAccounts: function(predicate, expression) { + if (expression.length === 0) { return Promise.resolve([]); } + return this.$.restAPI.getSuggestedAccounts( + expression, + MAX_AUTOCOMPLETE_RESULTS) + .then(function(accounts) { + if (!accounts) { return []; } + return accounts.map(function(acct) { + return predicate + ':"' + acct.name + ' <' + acct.email + '>"'; + }); + }); }, - // TODO(kaspern): Expand support for more complicated autocomplete features. + /** + * Fetch from the API the predicted groups. + * @param {string} predicate - The first part of the search term, e.g. + * 'ownerin' + * @param {string} expression - The second part of the search term, e.g. + * 'polyger' + * @return {!Promise} This returns a promise that resolves to an array of + * strings. + */ + _fetchGroups: function(predicate, expression) { + if (expression.length === 0) { return Promise.resolve([]); } + return this.$.restAPI.getSuggestedGroups( + expression, + MAX_AUTOCOMPLETE_RESULTS) + .then(function(groups) { + if (!groups) { return []; } + var keys = Object.keys(groups); + return keys.map(function(key) { return predicate + ':' + key; }); + }); + }, + + /** + * Fetch from the API the predicted projects. + * @param {string} predicate - The first part of the search term, e.g. + * 'project' + * @param {string} expression - The second part of the search term, e.g. + * 'gerr' + * @return {!Promise} This returns a promise that resolves to an array of + * strings. + */ + _fetchProjects: function(predicate, expression) { + return this.$.restAPI.getSuggestedProjects( + expression, + MAX_AUTOCOMPLETE_RESULTS) + .then(function(projects) { + if (!projects) { return []; } + var keys = Object.keys(projects); + return keys.map(function(key) { return predicate + ':' + key; }); + }); + }, + + /** + * Determine what array of possible suggestions should be provided + * to _getSearchSuggestions. + * @param {string} input - The full search term, in lowercase. + * @return {!Promise} This returns a promise that resolves to an array of + * strings. + */ + _fetchSuggestions: function(input) { + // Split the input on colon to get a two part predicate/expression. + var splitInput = input.split(':'); + var predicate = splitInput[0]; + var expression = splitInput[1] || ''; + // Switch on the predicate to determine what to autocomplete. + switch (predicate) { + case 'ownerin': + case 'reviewerin': + // Fetch groups. + return this._fetchGroups(predicate, expression); + + case 'parentproject': + case 'project': + // Fetch projects. + return this._fetchProjects(predicate, expression); + + case 'author': + case 'commentby': + case 'committer': + case 'from': + case 'owner': + case 'reviewedby': + case 'reviewer': + // Fetch accounts. + return this._fetchAccounts(predicate, expression); + + default: + return Promise.resolve(SEARCH_OPERATORS + .filter(function(operator) { + return operator.indexOf(input) !== -1; + })); + } + }, + + /** + * Get the sorted, pruned list of suggestions for the current search query. + * @param {string} input - The complete search query. + * @return {!Promise} This returns a promise that resolves to an array of + * strings. + */ _getSearchSuggestions: function(input) { - return Promise.resolve(SEARCH_OPERATORS).then(function(operators) { - if (!operators) { return []; } - var lowerCaseInput = input - .substring(input.lastIndexOf(' ') + 1) - .toLowerCase(); - return operators - .filter(function(operator) { - // Disallow autocomplete values that exactly match the whole str. - var opContainsInput = operator.indexOf(lowerCaseInput) !== -1; - var inputContainsOp = lowerCaseInput.indexOf(operator) !== -1; - return opContainsInput && !inputContainsOp; - }) - // Prioritize results that start with the input. - .sort(function(operator) { - return operator.indexOf(lowerCaseInput); - }) - .map(this._makeSuggestion); - }.bind(this)); + // Allow spaces within quoted terms. + var tokens = input.match(TOKENIZE_REGEX); + var trimmedInput = tokens[tokens.length - 1].toLowerCase(); + + return this._fetchSuggestions(trimmedInput) + .then(function(operators) { + if (!operators || !operators.length) { return []; } + return operators + // Prioritize results that start with the input. + .sort(function(a, b) { + var aContains = a.toLowerCase().indexOf(trimmedInput); + var bContains = b.toLowerCase().indexOf(trimmedInput); + if (aContains === bContains) { + return a.localeCompare(b); + } + if (aContains === -1) { + return 1; + } + if (bContains === -1) { + return -1; + } + return aContains - bContains; + }) + // Return only the first {MAX_AUTOCOMPLETE_RESULTS} results. + .slice(0, MAX_AUTOCOMPLETE_RESULTS - 1) + // Map to an object to play nice with gr-autocomplete. + .map(function(operator) { + return { + name: operator, + value: operator, + }; + }); + }); }, _handleKey: function(e) {
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html index 0c16774..696efcd 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -58,6 +58,7 @@ assert.notEqual(getActiveElement(), element.$.searchButton); done(); }); + element.value = 'test'; MockInteractions.tap(element.$.searchButton); }); @@ -68,6 +69,7 @@ assert.notEqual(getActiveElement(), element.$.searchButton); done(); }); + element.value = 'test'; MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); }); @@ -79,22 +81,110 @@ showStub.restore(); }); - test('_getSearchSuggestions returns proper set of suggestions', - function(done) { - element._getSearchSuggestions('is:o') - .then(function(suggestions) { - assert.equal(suggestions[0].name, 'is:open'); - assert.equal(suggestions[0].value, 'is:open'); - assert.equal(suggestions[1].name, 'is:owner'); - assert.equal(suggestions[1].value, 'is:owner'); - }) - .then(function() { - element._getSearchSuggestions('asdasdasdasd') - .then(function(suggestions) { - assert.equal(suggestions.length, 0); - done(); - }); + test('input blurred after commit', function() { + var showStub = sinon.stub(page, 'show'); + var blurSpy = sinon.spy(element.$.searchInput.$.input, 'blur'); + element.$.searchInput.text = 'fate/stay'; + MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); + assert.isTrue(blurSpy.called); + showStub.restore(); + blurSpy.restore(); + }); + + test('empty search query does not trigger nav', function() { + var showSpy = sinon.spy(page, 'show'); + element.value = ''; + MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); + assert.isFalse(showSpy.called); + }); + + suite('_getSearchSuggestions', + function() { + setup(function() { + sinon.stub(element.$.restAPI, 'getSuggestedAccounts', function() { + return Promise.resolve([ + { + name: 'fred', + email: 'fred@goog.co', + }, + ]); + }); + sinon.stub(element.$.restAPI, 'getSuggestedGroups', function() { + return Promise.resolve({ + Polygerrit: 0, + gerrit: 0, + gerrittest: 0, }); + }); + sinon.stub(element.$.restAPI, 'getSuggestedProjects', function() { + return Promise.resolve({ + Polygerrit: 0, + }); + }); + }); + + teardown(function() { + element.$.restAPI.getSuggestedAccounts.restore(); + element.$.restAPI.getSuggestedGroups.restore(); + element.$.restAPI.getSuggestedProjects.restore(); + }); + + test('Autocompletes accounts', + function(done) { + return element._getSearchSuggestions('owner:fr') + .then(function(suggestions) { + assert.equal(suggestions[0].value, 'owner:"fred <fred@goog.co>"'); + done(); + }); + }); + + test('Autocompletes groups', + function(done) { + return element._getSearchSuggestions('ownerin:pol') + .then(function(suggestions) { + assert.equal(suggestions[0].value, 'ownerin:Polygerrit'); + done(); + }); + }); + + test('Autocompletes projects', + function(done) { + return element._getSearchSuggestions('project:pol') + .then(function(suggestions) { + assert.equal(suggestions[0].value, 'project:Polygerrit'); + done(); + }); + }); + + test('Autocompletes simple searches', + function(done) { + return element._getSearchSuggestions('is:o') + .then(function(suggestions) { + assert.equal(suggestions[0].name, 'is:open'); + assert.equal(suggestions[0].value, 'is:open'); + assert.equal(suggestions[1].name, 'is:owner'); + assert.equal(suggestions[1].value, 'is:owner'); + done(); + }); + }); + + test('Does not autocomplete with no match', + function(done) { + return element._getSearchSuggestions('asdasdasdasd') + .then(function(suggestions) { + assert.equal(suggestions.length, 0); + done(); + }); + }); + + test('Autocomplete doesnt override exact matches to input', + function(done) { + return element._getSearchSuggestions('ownerin:gerrit') + .then(function(suggestions) { + assert.equal(suggestions[0].value, 'ownerin:gerrit'); + done(); + }); + }); }); }); </script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html index ec19a2d..40a90b7 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -14,10 +14,12 @@ limitations under the License. --> <link rel="import" href="../../../bower_components/polymer/polymer.html"> +<link rel="import" href="../../core/gr-reporting/gr-reporting.html"> <link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html"> <link rel="import" href="../gr-diff-processor/gr-diff-processor.html"> <link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html"> <link rel="import" href="../gr-syntax-layer/gr-syntax-layer.html"> + <dom-module id="gr-diff-builder"> <template> <div class="contentWrapper"> @@ -32,6 +34,7 @@ <gr-diff-processor id="processor" groups="{{_groups}}"></gr-diff-processor> + <gr-reporting id="reporting"></gr-reporting> </template> <script src="../gr-diff/gr-diff-line.js"></script> <script src="../gr-diff/gr-diff-group.js"></script> @@ -74,6 +77,7 @@ _builder: Object, _groups: Array, _layers: Array, + _showTabs: Boolean, }, get diffElement() { @@ -89,6 +93,7 @@ this._layers = [ this.$.syntaxLayer, this._createIntralineLayer(), + this._createTabIndicatorLayer(), this.$.rangeLayer, ]; @@ -99,6 +104,7 @@ render: function(comments, prefs) { this.$.syntaxLayer.enabled = prefs.syntax_highlighting; + this._showTabs = !!prefs.show_tabs; // Stop the processor (if it's running). this.$.processor.cancel(); @@ -111,17 +117,19 @@ this._clearDiffContent(); - console.time(TimingLabel.TOTAL); - console.time(TimingLabel.CONTENT); + var reporting = this.$.reporting; + + reporting.time(TimingLabel.TOTAL); + reporting.time(TimingLabel.CONTENT); return this.$.processor.process(this.diff.content).then(function() { if (this.isImageDiff) { this._builder.renderDiffImages(); } - console.timeEnd(TimingLabel.CONTENT); - console.time(TimingLabel.SYNTAX); + reporting.timeEnd(TimingLabel.CONTENT); + reporting.time(TimingLabel.SYNTAX); this.$.syntaxLayer.process().then(function() { - console.timeEnd(TimingLabel.SYNTAX); - console.timeEnd(TimingLabel.TOTAL); + reporting.timeEnd(TimingLabel.SYNTAX); + reporting.timeEnd(TimingLabel.TOTAL); }); this.fire('render'); }.bind(this)); @@ -325,6 +333,31 @@ }; }, + _createTabIndicatorLayer: function() { + var show = (function() { return this._showTabs; }).bind(this); + return { + addListener: function() {}, + annotate: function(el, line) { + // If visible tabs are disabled, do nothing. + if (!show()) { return; } + + // Find and annotate the locations of tabs. + var split = line.text.split('\t'); + if (!split) { return; } + for (var i = 0, pos = 0; i < split.length - 1; i++) { + // Skip forward by the length of the content + pos += split[i].length; + + GrAnnotation.annotateElement(el, pos, 1, + 'style-scope gr-diff tab-indicator'); + + // Skip forward by one tab character. + pos++; + } + }, + }; + }, + /** * In pages with large diffs, creating the first comment thread can be * slow because nested Polymer elements (particularly
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js index 2090e98..c524f7f 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -163,8 +163,8 @@ }; /** - * Re-renders the DIV.contentText alement for the given side and range of diff - * content. + * Re-renders the DIV.contentText elements for the given side and range of + * diff content. */ GrDiffBuilder.prototype._renderContentByRange = function(start, end, side) { var lines = []; @@ -340,6 +340,9 @@ side, this._comments.meta.projectConfig); threadEl.comments = comments; + if (opt_side) { + threadEl.setAttribute('data-side', opt_side); + } return threadEl; }; @@ -363,11 +366,14 @@ GrDiffBuilder.prototype._createTextEl = function(line, opt_side) { var td = this._createElement('td'); + var text = line.text; if (line.type !== GrDiffLine.Type.BLANK) { td.classList.add('content'); + if (!text) { + text = '\xa0'; + } } td.classList.add(line.type); - var text = line.text; var html = util.escapeHTML(text); html = this._addTabWrappers(html, this._prefs.tab_size); @@ -493,7 +499,7 @@ for (var i = 0; i < split.length - 1; i++) { offset += split[i].length; width = tabSize - (offset % tabSize); - result += split[i] + this._getTabWrapper(width, this._prefs.show_tabs); + result += split[i] + this._getTabWrapper(width); offset += width; } if (split.length) { @@ -503,7 +509,7 @@ return result; }; - GrDiffBuilder.prototype._getTabWrapper = function(tabSize, showTabs) { + GrDiffBuilder.prototype._getTabWrapper = function(tabSize) { // Force this to be a number to prevent arbitrary injection. tabSize = +tabSize; if (isNaN(tabSize)) { @@ -511,9 +517,6 @@ } var str = '<span class="style-scope gr-diff tab '; - if (showTabs) { - str += 'withIndicator'; - } str += '" style="'; // TODO(andybons): CSS tab-size is not supported in IE. str += 'tab-size:' + tabSize + ';';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html index e8b1453..af44629 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -414,6 +414,117 @@ }); }); + suite('tab indicators', function() { + var sandbox; + var element; + var layer; + + setup(function() { + sandbox = sinon.sandbox.create(); + element = fixture('basic'); + element._showTabs = true; + layer = element._createTabIndicatorLayer(); + }); + + teardown(function() { + sandbox.restore(); + }); + + test('does nothing with empty line', function() { + var line = {text: ''}; + var el = document.createElement('div'); + var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement'); + + layer.annotate(el, line); + + assert.isFalse(annotateElementStub.called); + }); + + test('does nothing with no tabs', function() { + var str = 'lorem ipsum no tabs'; + var line = {text: str}; + var el = document.createElement('div'); + el.textContent = str; + var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement'); + + layer.annotate(el, line); + + assert.isFalse(annotateElementStub.called); + }); + + test('annotates tab at beginning', function() { + var str = '\tlorem upsum'; + var line = {text: str}; + var el = document.createElement('div'); + el.textContent = str; + var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement'); + + layer.annotate(el, line); + + assert.equal(annotateElementStub.callCount, 1); + var args = annotateElementStub.getCalls()[0].args; + assert.equal(args[0], el); + assert.equal(args[1], 0, 'offset of tab indicator'); + assert.equal(args[2], 1, 'length of tab indicator'); + assert.include(args[3], 'tab-indicator'); + }); + + test('does not annotate when disabled', function() { + element._showTabs = false; + + var str = '\tlorem upsum'; + var line = {text: str}; + var el = document.createElement('div'); + el.textContent = str; + var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement'); + + layer.annotate(el, line); + + assert.isFalse(annotateElementStub.called); + }); + + test('annotates multiple in beginning', function() { + var str = '\t\tlorem upsum'; + var line = {text: str}; + var el = document.createElement('div'); + el.textContent = str; + var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement'); + + layer.annotate(el, line); + + assert.equal(annotateElementStub.callCount, 2); + + var args = annotateElementStub.getCalls()[0].args; + assert.equal(args[0], el); + assert.equal(args[1], 0, 'offset of tab indicator'); + assert.equal(args[2], 1, 'length of tab indicator'); + assert.include(args[3], 'tab-indicator'); + + args = annotateElementStub.getCalls()[1].args; + assert.equal(args[0], el); + assert.equal(args[1], 1, 'offset of tab indicator'); + assert.equal(args[2], 1, 'length of tab indicator'); + assert.include(args[3], 'tab-indicator'); + }); + + test('annotates intermediate tabs', function() { + var str = 'lorem\tupsum'; + var line = {text: str}; + var el = document.createElement('div'); + el.textContent = str; + var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement'); + + layer.annotate(el, line); + + assert.equal(annotateElementStub.callCount, 1); + var args = annotateElementStub.getCalls()[0].args; + assert.equal(args[0], el); + assert.equal(args[1], 5, 'offset of tab indicator'); + assert.equal(args[2], 1, 'length of tab indicator'); + assert.include(args[3], 'tab-indicator'); + }); + }); + suite('rendering', function() { var content; var outputEl; @@ -437,6 +548,10 @@ ] }, ]; + stub('gr-reporting', { + time: sinon.stub(), + timeEnd: sinon.stub(), + }); element = fixture('basic'); outputEl = element.queryEffectiveChildren('#diffTable'); element.addEventListener('render', function() { @@ -458,6 +573,20 @@ element.render({left: [], right: []}, prefs); }); + test('reporting', function(done) { + var timeStub = element.$.reporting.time; + var timeEndStub = element.$.reporting.timeEnd; + flush(function() { + assert.isTrue(timeStub.calledWithExactly('Diff Total Render')); + assert.isTrue(timeStub.calledWithExactly('Diff Content Render')); + assert.isTrue(timeStub.calledWithExactly('Diff Syntax Render')); + assert.isTrue(timeEndStub.calledWithExactly('Diff Total Render')); + assert.isTrue(timeEndStub.calledWithExactly('Diff Content Render')); + assert.isTrue(timeEndStub.calledWithExactly('Diff Syntax Render')); + done(); + }); + }); + test('renderSection', function() { var section = outputEl.querySelector('stub:nth-of-type(2)'); var prevInnerHTML = section.innerHTML;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js index 1b30bde..07badbf 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -198,8 +198,15 @@ }, _handleTextareaKeydown: function(e) { - if (e.keyCode == 27) { // 'esc' - this._handleCancel(e); + switch (e.keyCode) { + case 27: // 'esc' + this._handleCancel(e); + break; + case 83: // 's' + if (e.ctrlKey) { + this._handleSave(e); + } + break; } },
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html index fcf8b41..bddc3ab 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -211,6 +211,18 @@ MockInteractions.pressAndReleaseKeyOn(element.$.editTextarea, 27); // esc }); + test('ctrl+s saves comment', function(done) { + var stub = sinon.stub(element, 'save', function() { + assert.isTrue(stub.called); + stub.restore(); + done(); + }); + element._messageText = 'is that the horse from horsing around??'; + MockInteractions.pressAndReleaseKeyOn( + element.$.editTextarea.textarea, + 83, 'ctrl'); // 'ctrl + s' + }); + test('draft saving/editing', function(done) { var fireStub = sinon.stub(element, 'fire');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html index 5a41709..491eded 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -23,7 +23,6 @@ id="cursorManager" scroll="keep-visible" cursor-target-class="target-row" - fold-offset-top="[[foldOffsetTop]]" target="{{diffRow}}"></gr-cursor-manager> </template> <script src="gr-diff-cursor.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js index 99a0b5c..dd11f2c 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -54,11 +54,6 @@ }, }, - foldOffsetTop: { - type: Number, - value: 0, - }, - /** * If set, the cursor will attempt to move to the line number (instead of * the first chunk) the next time the diff renders. It is set back to null
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html index 54294a1..814a760 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html
@@ -37,5 +37,6 @@ </div> </template> <script src="gr-annotation.js"></script> + <script src="gr-range-normalizer.js"></script> <script src="gr-diff-highlight.js"></script> </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js index bfe103b..9d7dc2f 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -94,6 +94,15 @@ } }, + _normalizeRange: function(range) { + range = GrRangeNormalizer.normalize(range); + return { + start: this._normalizeSelectionSide(range.startContainer, + range.startOffset), + end: this._normalizeSelectionSide(range.endContainer, range.endOffset), + }; + }, + /** * Convert DOM Range selection to concrete numbers (line, column, side). * Moves range end if it's not inside td.content. @@ -160,13 +169,12 @@ if (range.collapsed) { return; } - var start = - this._normalizeSelectionSide(range.startContainer, range.startOffset); + var normalizedRange = this._normalizeRange(range); + var start = normalizedRange.start; if (!start) { return; } - var end = - this._normalizeSelectionSide(range.endContainer, range.endOffset); + var end = normalizedRange.end; if (!end) { return; }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html index 5f84e4f..2f1ded9 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -487,6 +487,27 @@ assert.equal(getActionSide(), 'left'); }); + test('properly accounts for syntax highlighting', function() { + var content = stubContent(140, 'left'); + var spy = sinon.spy(element, '_normalizeRange'); + emulateSelection( + content.querySelectorAll('hl')[3], 0, + content.querySelectorAll('span')[1], 0); + var spyCall = spy.getCall(0); + var range = window.getSelection().getRangeAt(0); + assert.notDeepEqual(spyCall.returnValue, range); + }); + + test('GrRangeNormalizer._getTextOffset computes text offset', function() { + var content = stubContent(140, 'left'); + var child = content.lastChild.lastChild; + var result = GrRangeNormalizer._getTextOffset(content, child); + assert.equal(result, 73); + content = stubContent(146, 'right'); + child = content.lastChild; + result = GrRangeNormalizer._getTextOffset(content, child); + assert.equal(result, 0); + }); // TODO (viktard): Selection starts in line number. // TODO (viktard): Empty lines in selection start. // TODO (viktard): Empty lines in selection end.
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js new file mode 100644 index 0000000..8685d7d --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
@@ -0,0 +1,106 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the 'License'); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +(function(window) { + 'use strict'; + + // Prevent redefinition. + if (window.GrRangeNormalizer) { return; } + + // Astral code point as per https://mathiasbynens.be/notes/javascript-unicode + var REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/; + + var GrRangeNormalizer = { + /** + * Remap DOM range to whole lines of a diff if necessary. If the start or + * end containers are DOM elements that are singular pieces of syntax + * highlighting, the containers are remapped to the .contentText divs that + * contain the entire line of code. + * + * @param {Object} range - the standard DOM selector range. + * @return {Object} A modified version of the range that correctly accounts + * for syntax highlighting. + */ + normalize: function(range) { + var startContainer = this._getContentTextParent(range.startContainer); + var startOffset = range.startOffset + this._getTextOffset(startContainer, + range.startContainer); + var endContainer = this._getContentTextParent(range.endContainer); + var endOffset = range.endOffset + this._getTextOffset(endContainer, + range.endContainer); + return { + startContainer: startContainer, + startOffset: startOffset, + endContainer: endContainer, + endOffset: endOffset, + }; + }, + + _getContentTextParent: function(target) { + var element = target; + if (element.nodeName === '#text') { + element = element.parentElement; + } + while (!element.classList.contains('contentText')) { + if (element.parentElement === null) { + return target; + } + element = element.parentElement; + } + return element; + }, + + /** + * Gets the character offset of the child within the parent. + * Performs a synchronous in-order traversal from top to bottom of the node + * element, counting the length of the syntax until child is found. + * + * @param {!Element} The root DOM element to be searched through. + * @param {!Element} The child element being searched for. + * @return {number} + */ + _getTextOffset: function(node, child) { + var count = 0; + var stack = [node]; + while (stack.length) { + var n = stack.pop(); + if (n === child) { + break; + } + if (n.childNodes && n.childNodes.length !== 0) { + var arr = []; + for (var i = 0; i < n.childNodes.length; i++) { + arr.push(n.childNodes[i]); + } + arr.reverse(); + stack = stack.concat(arr); + } else { + count += this._getLength(n); + } + } + return count; + }, + + /** + * The DOM API textContent.length calculation is broken when the text + * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode . + * @param {Text} A text node. + * @return {Number} The length of the text. + */ + _getLength: function(node) { + return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length; + }, + }; + + window.GrRangeNormalizer = GrRangeNormalizer; +})(window);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js index 2a1e880..2dd4c91 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -78,6 +78,22 @@ }, _nextStepHandle: Number, + _isScrolling: Boolean, + }, + + attached: function() { + this.listen(window, 'scroll', '_handleWindowScroll'); + }, + + detached: function() { + this.unlisten(window, 'scroll', '_handleWindowScroll'); + }, + + _handleWindowScroll: function() { + this._isScrolling = true; + this.debounce('resetIsScrolling', function() { + this._isScrolling = false; + }, 50); }, /** @@ -100,6 +116,11 @@ var currentBatch = 0; var nextStep = function() { + + if (this._isScrolling) { + this.async(nextStep, 100); + return; + } // If we are done, resolve the promise. if (state.sectionIndex >= content.length) { resolve(this.groups); @@ -201,11 +222,11 @@ /** * Take rows of a shared diff section and produce an array of corresponding * (potentially collapsed) groups. - * @param {Array<String>} rows - * @param {Number} context - * @param {Number} startLineNumLeft - * @param {Number} startLineNumRight - * @param {String} opt_sectionEnd String representing whether this is the + * @param {Array<String>} rows + * @param {Number} context + * @param {Number} startLineNumLeft + * @param {Number} startLineNumRight + * @param {String} opt_sectionEnd String representing whether this is the * first section or the last section or neither. Use the values 'first', * 'last' and null respectively. * @return {Array<GrDiffGroup>} @@ -264,10 +285,10 @@ /** * Take the rows of a delta diff section and produce the corresponding * group. - * @param {Array<String>} rowsAdded - * @param {Array<String>} rowsRemoved - * @param {Number} startLineNumLeft - * @param {Number} startLineNumRight + * @param {Array<String>} rowsAdded + * @param {Array<String>} rowsRemoved + * @param {Number} startLineNumLeft + * @param {Number} startLineNumRight * @return {GrDiffGroup} */ _deltaGroupFromRows: function(rowsAdded, rowsRemoved, startLineNumLeft, @@ -325,7 +346,7 @@ * In order to show comments out of the bounds of the selected context, * treat them as separate chunks within the model so that the content (and * context surrounding it) renders correctly. - * @param {Object} content The diff content object. + * @param {Object} content The diff content object. * @return {Object} A new diff content object with regions split up. */ _splitCommonGroupsWithComments: function(content) { @@ -477,8 +498,8 @@ /** * Given an array and a size, return an array of arrays where no inner array * is larger than that size, preserving the original order. - * @param {!Array<T>} - * @param {number} + * @param {!Array<T>} array + * @param {number} size * @return {!Array<!Array<T>>} * @template T */ @@ -489,7 +510,7 @@ var head = array.slice(0, array.length - size); var tail = array.slice(array.length - size); - return this._breakdown(head, size).concat([tail]) + return this._breakdown(head, size).concat([tail]); }, }); })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html index 9d687ac..4f8c532 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -40,6 +40,15 @@ 'fugit assum per.'; var element; + var sandbox; + + setup(function() { + sandbox = sinon.sandbox.create(); + }); + + teardown(function() { + sandbox.restore(); + }); suite('not logged in', function() { @@ -409,6 +418,23 @@ ]); }); + test('scrolling pauses rendering', function() { + var contentRow = { + ab: [ + '<!DOCTYPE html>', + '<meta charset="utf-8">', + ] + }; + var content = _.times(200, _.constant(contentRow)); + sandbox.stub(element, 'async'); + element._isScrolling = true; + element.process(content); + assert.equal(element.groups.length, 1); + element._isScrolling = false; + element.process(content); + assert.equal(element.groups.length, 33); + }); + suite('gr-diff-processor helpers', function() { var rows; @@ -512,15 +538,6 @@ }); suite('_breakdown*', function() { - var sandbox; - setup(function() { - sandbox = sinon.sandbox.create(); - }); - - teardown(function() { - sandbox.restore(); - }); - test('_breakdownGroup ignores shared groups', function() { sandbox.stub(element, '_breakdown'); var chunk = {ab: ['blah', 'blah', 'blah']};
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html index 09cab0b..6a02a2d 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
@@ -18,17 +18,21 @@ <dom-module id="gr-diff-selection"> <template> <style> - .contentWrapper ::content .content { + .contentWrapper ::content .content, + .contentWrapper ::content .contextControl { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } - :host.selected-right .contentWrapper ::content .right + .content, - :host.selected-left .contentWrapper ::content .left + .content, - :host.selected-right .contentWrapper ::content .unified .right ~ .content, - :host.selected-left .contentWrapper ::content .unified .left ~ .content { + :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .side-by-side .left + .content .contentText, + :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .side-by-side .right + .content .contentText, + :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .unified .left.lineNum ~ .content:not(.both) .contentText, + :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .unified .right.lineNum ~ .content .contentText, + :host-context(.selected-left.selected-comment) .contentWrapper ::content .side-by-side .left + .content .message, + :host-context(.selected-right.selected-comment) .contentWrapper ::content .side-by-side .right + .content .message, + :host-context(.selected-comment) .contentWrapper ::content .unified .message { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; @@ -39,5 +43,6 @@ <content></content> </div> </template> + <script src="../gr-diff-highlight/gr-range-normalizer.js"></script> <script src="gr-diff-selection.js"></script> </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js index 7d0b7ea..99c049b 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -18,7 +18,12 @@ is: 'gr-diff-selection', properties: { + diff: Object, _cachedDiffBuilder: Object, + _linesCache: { + type: Object, + value: function() { return {left: null, right: null}; }, + }, }, listeners: { @@ -56,8 +61,12 @@ }, _handleCopy: function(e) { - if (!e.target.classList.contains('content')) { - return; + var el = e.target; + while (!el.classList.contains('content')) { + if (!el.parentElement) { + return; + } + el = el.parentElement; } var lineEl = this.diffBuilder.getLineElByChild(e.target); if (!lineEl) { @@ -69,27 +78,53 @@ e.preventDefault(); }, - _getSelectedText: function(opt_side) { + _getSelectedText: function(side) { var sel = window.getSelection(); if (sel.rangeCount != 1) { return; // No multi-select support yet. } - var range = sel.getRangeAt(0); - var fragment = range.cloneContents(); - var selector = '.content,td.content:nth-of-type(1)'; - if (opt_side) { - selector = '.' + opt_side + ' + ' + selector; + var range = GrRangeNormalizer.normalize(sel.getRangeAt(0)); + var startLineEl = this.diffBuilder.getLineElByChild(range.startContainer); + var endLineEl = this.diffBuilder.getLineElByChild(range.endContainer); + var startLineNum = parseInt(startLineEl.getAttribute('data-value'), 10); + var endLineNum = parseInt(endLineEl.getAttribute('data-value'), 10); + + return this._getRangeFromDiff(startLineNum, range.startOffset, endLineNum, + range.endOffset, side); + }, + + _getRangeFromDiff: function(startLineNum, startOffset, endLineNum, + endOffset, side) { + var lines = this._getDiffLines(side).slice(startLineNum - 1, endLineNum); + if (lines.length) { + lines[0] = lines[0].substring(startOffset); + lines[lines.length - 1] = lines[lines.length - 1] + .substring(0, endOffset); } - var contentEls = Polymer.dom(fragment).querySelectorAll(selector); - if (contentEls.length === 0) { - return fragment.textContent; + return lines.join('\n'); + }, + + _getDiffLines: function(side) { + if (this._linesCache[side]) { + return this._linesCache[side]; } - var text = ''; - for (var i = 0; i < contentEls.length; i++) { - text += contentEls[i].textContent + '\n'; + var lines = []; + var chunk; + var key = side === 'left' ? 'a' : 'b'; + for (var chunkIndex = 0; + chunkIndex < this.diff.content.length; + chunkIndex++) { + chunk = this.diff.content[chunkIndex]; + if (chunk.ab) { + lines = lines.concat(chunk.ab); + } else if (chunk[key]) { + lines = lines.concat(chunk[key]); + } } - return text; + + this._linesCache[side] = lines; + return lines; }, }); })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html index f99e373..1ac800d 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -27,24 +27,36 @@ <test-fixture id="basic"> <template> <gr-diff-selection> - <table> + <table class="side-by-side"> <tr> - <td class="lineNum left">1</td> - <td class="content">ba ba</td> - <td class="lineNum right">1</td> - <td class="content">some other text</td> + <td class="lineNum left" data-value="1">1</td> + <td class="content"> + <div class="contentText" data-side="left">ba ba</div> + </td> + <td class="lineNum right" data-value="1">1</td> + <td class="content"> + <div class="contentText" data-side="right">some other text</div> + </td> </tr> <tr> - <td class="lineNum left">2</td> - <td class="content">zin</td> - <td class="lineNum right">2</td> - <td class="content">more more more</td> + <td class="lineNum left" data-value="2">2</td> + <td class="content"> + <div class="contentText" data-side="left">zin</div> + </td> + <td class="lineNum right" data-value="2">2</td> + <td class="content"> + <div class="contentText" data-side="right">more more more</div> + </td> </tr> <tr> - <td class="lineNum left">2</td> - <td class="content">ga ga</td> - <td class="lineNum right">3</td> - <td class="other">some other text</td> + <td class="lineNum left" data-value="3">3</td> + <td class="content"> + <div class="contentText" data-side="left">ga ga</div> + </td> + <td class="lineNum right" data-value="3">3</td> + <td class="other"> + <div class="contentText" data-side="right">some other text</div> + </td> </tr> </table> </gr-diff-selection> @@ -73,6 +85,22 @@ getLineElByChild: sinon.stub().returns({}), getSideByLineEl: sinon.stub(), }; + element.diff = { + content: [ + { + a: ['ba ba'], + b: ['some other text'], + }, + { + a: ['zin'], + b: ['more more more'], + }, + { + a: ['ga ga'], + b: ['some other text'], + }, + ], + }; }); test('applies selected-left on left side click', function() { @@ -105,38 +133,48 @@ test('asks for text for right side Elements', function() { element._cachedDiffBuilder.getSideByLineEl.returns('left'); sinon.stub(element, '_getSelectedText'); - emulateCopyOn(element.querySelector('td.content')); + emulateCopyOn(element.querySelector('div.contentText')); assert.deepEqual(['left'], element._getSelectedText.lastCall.args); }); test('reacts to copy for content Elements', function() { sinon.stub(element, '_getSelectedText'); - emulateCopyOn(element.querySelector('td.content')); + emulateCopyOn(element.querySelector('div.contentText')); assert.isTrue(element._getSelectedText.called); }); test('copy event is prevented for content Elements', function() { sinon.stub(element, '_getSelectedText'); - var event = emulateCopyOn(element.querySelector('td.content')); + var event = emulateCopyOn(element.querySelector('div.contentText')); assert.isTrue(event.preventDefault.called); }); test('inserts text into clipboard on copy', function() { sinon.stub(element, '_getSelectedText').returns('the text'); - var event = emulateCopyOn(element.querySelector('td.content')); + var event = emulateCopyOn(element.querySelector('div.contentText')); assert.deepEqual( ['Text', 'the text'], event.clipboardData.setData.lastCall.args); }); test('copies content correctly', function() { + // Fetch the line number. + element._cachedDiffBuilder.getLineElByChild = function(child) { + while (!child.classList.contains('content') && child.parentElement) { + child = child.parentElement; + } + return child.previousElementSibling; + }; + element.classList.add('selected-left'); + element.classList.remove('selected-right'); + var selection = window.getSelection(); var range = document.createRange(); - range.setStart(element.querySelector('td.content').firstChild, 3); + range.setStart(element.querySelector('div.contentText').firstChild, 3); range.setEnd( - element.querySelectorAll('td.content')[4].firstChild, 2); + element.querySelectorAll('div.contentText')[4].firstChild, 2); selection.addRange(range); - assert.equal('ba\nzin\nga\n', element._getSelectedText('left')); + assert.equal(element._getSelectedText('left'), 'ba\nzin\nga'); }); }); </script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html index 2573ad1..76f7aec 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -33,9 +33,18 @@ background-color: var(--view-background-color); display: block; } - h3 { + header, + .subHeader { + align-items: center; + display: flex; + justify-content: space-between; + } + header { padding: .75em var(--default-horizontal-margin); } + .navLink:not([href]) { + color: #999; + } .reviewed { display: inline-block; margin: 0 .25em; @@ -97,10 +106,7 @@ padding: 0 var(--default-horizontal-margin) 1em; color: #666; } - .header { - align-items: center; - display: flex; - justify-content: space-between; + .subHeader { margin: 0 var(--default-horizontal-margin) .75em; } .prefsButton { @@ -125,49 +131,56 @@ } } </style> - <h3> - <a href$="[[_computeChangePath(_changeNum, _patchRange.*, _change.revisions)]]"> - [[_changeNum]]</a><span>:</span> - <span>[[_change.subject]]</span> - <span class="dash">—</span> - <input id="reviewed" - class="reviewed" - type="checkbox" - on-change="_handleReviewedChange" - hidden$="[[!_loggedIn]]" hidden> - <div class="jumpToFileContainer"> - <gr-button link class="dropdown-trigger" id="trigger" on-tap="_showDropdownTapHandler"> - <span>[[_computeFileDisplayName(_path)]]</span> - <span class="downArrow">▼</span> - </gr-button> - <iron-dropdown id="dropdown" vertical-align="top" vertical-offset="25"> - <div class="dropdown-content"> + <header> + <h3> + <a href$="[[_computeChangePath(_changeNum, _patchRange.*, _change.revisions)]]"> + [[_changeNum]]</a><span>:</span> + <span>[[_change.subject]]</span> + <span class="dash">—</span> + <input id="reviewed" + class="reviewed" + type="checkbox" + on-change="_handleReviewedChange" + hidden$="[[!_loggedIn]]" hidden> + <div class="jumpToFileContainer"> + <gr-button link class="dropdown-trigger" id="trigger" on-tap="_showDropdownTapHandler"> + <span>[[_computeFileDisplayName(_path)]]</span> + <span class="downArrow">▼</span> + </gr-button> + <iron-dropdown id="dropdown" vertical-align="top" vertical-offset="25"> + <div class="dropdown-content"> + <template is="dom-repeat" items="[[_fileList]]" as="path"> + <a href$="[[_computeDiffURL(_changeNum, _patchRange.*, path)]]" + selected$="[[_computeFileSelected(path, _path)]]" + data-key-nav$="[[_computeKeyNav(path, _path, _fileList)]]" + on-tap="_handleFileTap">[[_computeFileDisplayName(path)]]</a> + </template> + </div> + </iron-dropdown> + </div> + <div class="mobileJumpToFileContainer"> + <select on-change="_handleMobileSelectChange"> <template is="dom-repeat" items="[[_fileList]]" as="path"> - <a href$="[[_computeDiffURL(_changeNum, _patchRange.*, path)]]" - selected$="[[_computeFileSelected(path, _path)]]" - data-key-nav$="[[_computeKeyNav(path, _path, _fileList)]]" - on-tap="_handleFileTap"> - [[_computeFileDisplayName(path)]] - </a> + <option + value$="[[path]]" + selected$="[[_computeFileSelected(path, _path)]]"> + [[_computeFileDisplayName(path)]] + </option> </template> - </div> - </iron-dropdown> + </select> + </div> + </h3> + <div> + <a class="navLink" + href$="[[_computeNavLinkURL(_path, _fileList, -1, 1)]]">Prev</a> + / + <a class="navLink" + href$="[[_computeNavLinkURL(_path, _fileList, 1, 1)]]">Next</a> </div> - <div class="mobileJumpToFileContainer"> - <select on-change="_handleMobileSelectChange"> - <template is="dom-repeat" items="[[_fileList]]" as="path"> - <option - value$="[[path]]" - selected$="[[_computeFileSelected(path, _path)]]"> - [[_computeFileDisplayName(path)]] - </option> - </template> - </select> - </div> - </h3> + </header> <div class="loading" hidden$="[[!_loading]]">Loading...</div> <div hidden$="[[_loading]]" hidden> - <div class="header"> + <div class="subHeader"> <gr-patch-range-select path="[[_path]]" change-num="[[_changeNum]]" @@ -180,7 +193,8 @@ id="modeSelect" is="gr-select" bind-value="{{changeViewState.diffMode}}" - hidden$="[[_computeModeSelectHidden(_isImageDiff)]]"> + hidden$="[[_computeModeSelectHidden(_isImageDiff)]]" + on-change="_handleDropdownChange"> <option value="SIDE_BY_SIDE">Side By Side</option> <option value="UNIFIED_DIFF">Unified</option> </select>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js index d6a3bc0..40b0ee1 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -105,6 +105,15 @@ } }.bind(this)); + if (this.changeViewState.diffMode === null) { + // Initialize with user's diff mode preference. Default to + // SIDE_BY_SIDE in the meantime. + this.set('changeViewState.diffMode', DiffViewMode.SIDE_BY_SIDE); + this.$.restAPI.getPreferences().then(function(prefs) { + this.set('changeViewState.diffMode', prefs.diff_view); + }.bind(this)); + } + if (this._path) { this.fire('title-change', {title: this._computeFileDisplayName(this._path)}); @@ -113,11 +122,6 @@ this.$.cursor.push('diffs', this.$.diff); }, - detached: function() { - // Reset the diff mode to null so that it reverts to the user preference. - this.changeViewState.diffMode = null; - }, - _getLoggedIn: function() { return this.$.restAPI.getLoggedIn(); }, @@ -207,11 +211,11 @@ break; case 219: // '[' e.preventDefault(); - this._navToFile(this._fileList, -1); + this._navToFile(this._path, this._fileList, -1); break; case 221: // ']' e.preventDefault(); - this._navToFile(this._fileList, 1); + this._navToFile(this._path, this._fileList, 1); break; case 78: // 'n' e.preventDefault(); @@ -256,20 +260,41 @@ } }, - _navToFile: function(fileList, direction) { - if (fileList.length == 0) { return; } + _navToFile: function(path, fileList, direction) { + var url = this._computeNavLinkURL(path, fileList, direction); + if (!url) { return; } - var idx = fileList.indexOf(this._path) + direction; + page.show(this._computeNavLinkURL(path, fileList, direction)); + }, + + /** + * @param {?string} path The path of the current file being shown. + * @param {Array.<string>} fileList The list of files in this change and + * patch range. + * @param {number} direction Either 1 (next file) or -1 (prev file). + * @param {(number|boolean)} opt_noUp Whether to return to the change view + * when advancing the file goes outside the bounds of fileList. + * + * @return {?string} The next URL when proceeding in the specified + * direction. + */ + _computeNavLinkURL: function(path, fileList, direction, opt_noUp) { + if (!path || fileList.length === 0) { return null; } + + var idx = fileList.indexOf(path); + if (idx === -1) { return null; } + + idx += direction; + // Redirect to the change view if opt_noUp isn’t truthy and idx falls + // outside the bounds of [0, fileList.length). if (idx < 0 || idx > fileList.length - 1) { - page.show(this._getChangePath( + if (opt_noUp) { return null; } + return this._getChangePath( this._changeNum, this._patchRange, - this._change && this._change.revisions)); - return; + this._change && this._change.revisions); } - page.show(this._getDiffURL(this._changeNum, - this._patchRange, - fileList[idx])); + return this._getDiffURL(this._changeNum, this._patchRange, fileList[idx]); }, _paramsChanged: function(value) { @@ -484,5 +509,9 @@ this.$.cursor.moveToLineNumber(detail.number, detail.side); history.pushState(null, null, '#' + this.$.cursor.getAddress()); }, + + _handleDropdownChange: function(e) { + e.target.blur(); + }, }); })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html index 0a4d6b6..979ed55 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -32,6 +32,12 @@ </template> </test-fixture> +<test-fixture id="blank"> + <template> + <div></div> + </template> +</text-fixture> + <script> suite('gr-diff-view tests', function() { var element; @@ -338,6 +344,52 @@ assert.equal(linkEls[2].getAttribute('href'), '/c/42/5..10/wheatley.md'); }); + test('prev/next links', function() { + element._changeNum = '42'; + element._patchRange = { + basePatchNum: 'PARENT', + patchNum: '10', + }; + element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; + element._path = 'glados.txt'; + flushAsynchronousOperations(); + var linkEls = Polymer.dom(element.root).querySelectorAll('.navLink'); + assert.equal(linkEls.length, 2); + assert.equal(linkEls[0].getAttribute('href'), '/c/42/10/chell.go'); + assert.equal(linkEls[1].getAttribute('href'), '/c/42/10/wheatley.md'); + element._path = 'wheatley.md'; + flushAsynchronousOperations(); + assert.equal(linkEls[0].getAttribute('href'), '/c/42/10/glados.txt'); + assert.isFalse(linkEls[1].hasAttribute('href')); + element._path = 'chell.go'; + flushAsynchronousOperations(); + assert.isFalse(linkEls[0].hasAttribute('href')); + assert.equal(linkEls[1].getAttribute('href'), '/c/42/10/glados.txt'); + }); + + test('prev/next links with patch range', function() { + element._changeNum = '42'; + element._patchRange = { + basePatchNum: '5', + patchNum: '10', + }; + element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; + element._path = 'glados.txt'; + flushAsynchronousOperations(); + var linkEls = Polymer.dom(element.root).querySelectorAll('.navLink'); + assert.equal(linkEls.length, 2); + assert.equal(linkEls[0].getAttribute('href'), '/c/42/5..10/chell.go'); + assert.equal(linkEls[1].getAttribute('href'), '/c/42/5..10/wheatley.md'); + element._path = 'wheatley.md'; + flushAsynchronousOperations(); + assert.equal(linkEls[0].getAttribute('href'), '/c/42/5..10/glados.txt'); + assert.isFalse(linkEls[1].hasAttribute('href')); + element._path = 'chell.go'; + flushAsynchronousOperations(); + assert.isFalse(linkEls[0].hasAttribute('href')); + assert.equal(linkEls[1].getAttribute('href'), '/c/42/5..10/glados.txt'); + }); + test('file review status', function(done) { element._loggedIn = true; element._changeNum = '42'; @@ -371,7 +423,7 @@ test('diff mode selector correctly toggles the diff', function() { var select = element.$.modeSelect; var diffDisplay = element.$.diff; - + var blurSpy = sinon.spy(select, 'blur'); element._userPrefs = {diff_view: 'SIDE_BY_SIDE'}; // The mode selected in the view state reflects the selected option. @@ -392,6 +444,30 @@ assert.equal(element._getDiffViewMode(), newMode); assert.equal(element._getDiffViewMode(), select.value); assert.equal(element._getDiffViewMode(), diffDisplay.viewMode); + assert(blurSpy.called, 'select should be blurred after selection'); + }); + + test('diff mode selector initializes from preferences', function() { + var resolvePrefs; + var prefsPromise = new Promise(function(resolve) { + resolvePrefs = resolve; + }); + var getPreferencesStub = sinon.stub(element.$.restAPI, 'getPreferences', + function() { return prefsPromise; }); + + // Attach a new gr-diff-view so we can intercept the preferences fetch. + var view = document.createElement('gr-diff-view'); + var select = view.$.modeSelect; + fixture('blank').appendChild(view); + flushAsynchronousOperations(); + + // At this point the diff mode doesn't yet have the user's preference. + assert.equal(select.value, 'SIDE_BY_SIDE'); + + // Receive the overriding preference. + resolvePrefs({diff_view: 'UNIFIED'}); + flushAsynchronousOperations(); + assert.equal(select.value, 'SIDE_BY_SIDE'); }); test('_loadHash', function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html index 46612a0..4672b31 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -91,13 +91,6 @@ vertical-align: top; white-space: pre; } - .contentText:empty:before { - /** - * Insert glyph to prevent empty diff content from collapsing. - * "\200B" is a 'ZERO WIDTH SPACE' (U+200B) - */ - content: "\200B"; - } .contextLineNum:before, .lineNum:before { display: inline-block; @@ -152,17 +145,17 @@ } .tab { display: inline-block; - position: relative; } - .tab.withIndicator { - color: #D68E47; - text-decoration: line-through; + .tab-indicator:before { + color: #C62828; + /* >> character */ + content: '\00BB'; } </style> <style include="gr-theme-default"></style> <div class$="[[_computeContainerClass(_loggedIn, viewMode)]]" on-tap="_handleTap"> - <gr-diff-selection> + <gr-diff-selection diff="[[_diff]]"> <gr-diff-highlight id="highlights" logged-in="[[_loggedIn]]"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js index dbcbb38..7b93086 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -34,6 +34,10 @@ properties: { changeNum: String, + hidden: { + type: Boolean, + observer: '_handleShowDiff', + }, patchRange: Object, path: String, prefs: { @@ -86,6 +90,12 @@ }.bind(this)); }, + _handleShowDiff: function(hidden) { + if (!hidden) { + this.reload(); + } + }, + reload: function() { this._clearDiffContent();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html index c33eadb..2f0b6bd 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -467,6 +467,17 @@ assert.equal(drafts.length, 1); assert.equal(drafts[0].id, id); }); + + test('_handleShowDiff reloads when hidden is made false', + function(done) { + var stub = sinon.stub(element, 'reload', function() { + assert.isTrue(stub.called); + stub.restore(); + done(); + }); + var spy = sinon.spy(element, '_handleShowDiff'); + element.set('hidden', false); + }); }); }); });
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js index 24d36c4..b50043e 100644 --- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js +++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -33,6 +33,7 @@ rangeStr = leftPatch + '..' + rangeStr; } page.show('/c/' + this.changeNum + '/' + rangeStr + '/' + this.path); + e.target.blur(); }, _computeLeftSelected: function(patchNum, patchRange) {
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html index c7e1196..95789bc 100644 --- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html +++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -63,6 +63,7 @@ var showStub = sinon.stub(page, 'show'); var leftSelectEl = element.$.leftPatchSelect; var rightSelectEl = element.$.rightPatchSelect; + var blurSpy = sinon.spy(leftSelectEl, 'blur'); element.changeNum = '42'; element.path = 'path/to/file.txt'; element.availablePatches = ['1', '2', '3']; @@ -77,6 +78,7 @@ 'Should navigate to /c/42/3/path/to/file.txt'); leftSelectEl.value = '1'; element.fire('change', {}, {node: leftSelectEl}); + assert(blurSpy.called, 'Dropdown should be blurred after selection'); } else if (numEvents == 2) { assert(showStub.lastCall.calledWithExactly( '/c/42/1..3/path/to/file.txt'),
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html index c5c9377..9c5d6bf 100644 --- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html +++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html
@@ -14,7 +14,12 @@ limitations under the License. --> <link rel="import" href="../../../bower_components/polymer/polymer.html"> +<link rel="import" href="../gr-syntax-lib-loader/gr-syntax-lib-loader.html"> + <dom-module id="gr-syntax-layer"> + <template> + <gr-syntax-lib-loader id="libLoader"></gr-syntax-lib-loader> + </template> <script src="../gr-diff/gr-diff-line.js"></script> <script src="../gr-diff-highlight/gr-annotation.js"></script> <script src="gr-syntax-layer.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js index 478bcc8..c0ee1fa 100644 --- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js +++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -106,6 +106,7 @@ value: function() { return []; }, }, _processHandle: Number, + _hljs: Object, }, addListener: function(fn) { @@ -278,7 +279,6 @@ _processNextLine: function(state) { var baseLine = undefined; var revisionLine = undefined; - var hljs = this._getHighlightLib(); var section = this.diff.content[state.sectionIndex]; if (section.ab) { @@ -301,15 +301,15 @@ var result; if (this._baseLanguage && baseLine !== undefined) { - result = hljs.highlight(this._baseLanguage, baseLine, true, + result = this._hljs.highlight(this._baseLanguage, baseLine, true, state.baseContext); this.push('_baseRanges', this._rangesFromString(result.value)); state.baseContext = result.top; } if (this._revisionLanguage && revisionLine !== undefined) { - result = hljs.highlight(this._revisionLanguage, revisionLine, true, - state.revisionContext); + result = this._hljs.highlight(this._revisionLanguage, revisionLine, + true, state.revisionContext); this.push('_revisionRanges', this._rangesFromString(result.value)); state.revisionContext = result.top; } @@ -358,45 +358,10 @@ }); }, - _getHighlightLib: function() { - return window.hljs; - }, - - _isHighlightLibLoaded: function() { - return !!this._getHighlightLib(); - }, - - _configureHighlightLib: function() { - this._getHighlightLib().configure( - {classPrefix: 'gr-diff gr-syntax gr-syntax-'}); - }, - - _getLibRoot: function() { - if (this._cachedLibRoot) { return this._cachedLibRoot; } - - return this._cachedLibRoot = document.head - .querySelector('link[rel=import][href$="gr-app.html"]') - .href - .match(/(.+\/)elements\/gr-app\.html/)[1]; - }, - _cachedLibRoot: null, - - /** - * Load and configure the HighlightJS library. If the library is already - * loaded, then do nothing and resolve. - * @return {Promise} - */ _loadHLJS: function() { - if (this._isHighlightLibLoaded()) { return Promise.resolve(); } - return new Promise(function(resolve) { - var script = document.createElement('script'); - script.src = this._getLibRoot() + HLJS_PATH; - script.onload = function() { - this._configureHighlightLib(); - resolve(); - }.bind(this); - Polymer.dom(this.root).appendChild(script); + return this.$.libLoader.get().then(function(hljs) { + this._hljs = hljs; }.bind(this)); - } + }, }); })();
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html index 5106671..178f61b 100644 --- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html +++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -182,8 +182,8 @@ var mockHLJS = getMockHLJS(); var highlightSpy = sinon.spy(mockHLJS, 'highlight'); - sandbox.stub(element, '_getHighlightLib', - function() { return mockHLJS; }); + sandbox.stub(element.$.libLoader, 'get', + function() { return Promise.resolve(mockHLJS); }); var processNextSpy = sandbox.spy(element, '_processNextLine'); var processPromise = element.process();
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.html b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.html new file mode 100644 index 0000000..fedd22a --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.html
@@ -0,0 +1,20 @@ +<!-- +Copyright (C) 2016 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<link rel="import" href="../../../bower_components/polymer/polymer.html"> + +<dom-module id="gr-syntax-lib-loader"> + <script src="gr-syntax-lib-loader.js"></script> +</dom-module>
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 new file mode 100644 index 0000000..520f24d --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
@@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +(function() { + 'use strict'; + + var HLJS_PATH = 'bower_components/highlightjs/highlight.min.js'; + var LIB_ROOT_PATTERN = /(.+\/)elements\/gr-app\.html/; + + Polymer({ + is: 'gr-syntax-lib-loader', + + properties: { + _state: { + type: Object, + + // NOTE: intended singleton. + value: { + loaded: false, + loading: false, + callbacks: [], + }, + } + }, + + get: function() { + return new Promise(function(resolve) { + // If the lib is totally loaded, resolve immediately. + if (this._state.loaded) { + resolve(this._getHighlightLib()); + return; + } + + // If the library is not currently being loaded, then start loading it. + if (!this._state.loading) { + this._state.loading = true; + this._loadHLJS().then(this._onLibLoaded.bind(this)); + } + + this._state.callbacks.push(resolve); + }.bind(this)); + }, + + _onLibLoaded: function() { + var lib = this._getHighlightLib(); + this._state.loaded = true; + this._state.loading = false; + this._state.callbacks.forEach(function(cb) { cb(lib); }); + this._state.callbacks = []; + }, + + _getHighlightLib: function() { + return window.hljs; + }, + + _configureHighlightLib: function() { + this._getHighlightLib().configure( + {classPrefix: 'gr-diff gr-syntax gr-syntax-'}); + }, + + _getLibRoot: function() { + if (this._cachedLibRoot) { return this._cachedLibRoot; } + + return this._cachedLibRoot = document.head + .querySelector('link[rel=import][href$="gr-app.html"]') + .href + .match(LIB_ROOT_PATTERN)[1]; + }, + _cachedLibRoot: null, + + _loadHLJS: function() { + return new Promise(function(resolve) { + var script = document.createElement('script'); + script.src = this._getLibRoot() + HLJS_PATH; + script.onload = function() { + this._configureHighlightLib(); + resolve(); + }.bind(this); + Polymer.dom(document.head).appendChild(script); + }.bind(this)); + }, + }); +})();
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 new file mode 100644 index 0000000..13bea04 --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
@@ -0,0 +1,95 @@ +<!DOCTYPE html> +<!-- +Copyright (C) 2016 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-syntax-lib-loader</title> + +<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script> +<script src="../../../bower_components/web-component-tester/browser.js"></script> + +<link rel="import" href="gr-syntax-lib-loader.html"> + +<test-fixture id="basic"> + <template> + <gr-syntax-lib-loader></gr-syntax-lib-loader> + </template> +</test-fixture> + +<script> + suite('gr-syntax-lib-loader tests', function() { + var element; + var resolveLoad; + var loadStub; + + setup(function() { + element = fixture('basic'); + + loadStub = sinon.stub(element, '_loadHLJS', function() { + return new Promise(function(resolve) { + resolveLoad = resolve; + }); + }); + + // Assert preconditions: + assert.isFalse(element._state.loaded); + assert.isFalse(element._state.loading); + }); + + teardown(function() { + if (window.hljs) { + delete window.hljs; + } + loadStub.restore(); + + // Because the element state is a singleton, clean it up. + element._state.loading = false; + element._state.loaded = false; + element._state.callbacks = []; + }); + + test('only load once', function(done) { + var firstCallHandler = sinon.stub(); + element.get().then(firstCallHandler); + + // It should now be in the loading state. + assert.isTrue(loadStub.called); + assert.isTrue(element._state.loading); + assert.isFalse(element._state.loaded); + assert.isFalse(firstCallHandler.called); + + var secondCallHandler = sinon.stub(); + element.get().then(secondCallHandler); + + // No change in state. + assert.isTrue(element._state.loading); + assert.isFalse(element._state.loaded); + assert.isFalse(firstCallHandler.called); + assert.isFalse(secondCallHandler.called); + + // Now load the library. + resolveLoad(); + flush(function() { + // The state should be loaded and both handlers called. + assert.isFalse(element._state.loading); + assert.isTrue(element._state.loaded); + assert.isTrue(firstCallHandler.called); + assert.isTrue(secondCallHandler.called); + done(); + }); + }); + }); +</script>
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html index c20795b..25dfaff 100644 --- a/polygerrit-ui/app/elements/gr-app.html +++ b/polygerrit-ui/app/elements/gr-app.html
@@ -22,6 +22,7 @@ <link rel="import" href="./core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html"> <link rel="import" href="./core/gr-main-header/gr-main-header.html"> <link rel="import" href="./core/gr-router/gr-router.html"> +<link rel="import" href="./core/gr-reporting/gr-reporting.html"> <link rel="import" href="./change-list/gr-change-list-view/gr-change-list-view.html"> <link rel="import" href="./change-list/gr-dashboard-view/gr-dashboard-view.html"> @@ -136,6 +137,7 @@ </gr-overlay> <gr-error-manager></gr-error-manager> <gr-rest-api-interface id="restAPI"></gr-rest-api-interface> + <gr-reporting id="reporting"></gr-reporting> </template> <script src="gr-app.js"></script> </dom-module>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js index 0833a72..f24ecfd 100644 --- a/polygerrit-ui/app/elements/gr-app.js +++ b/polygerrit-ui/app/elements/gr-app.js
@@ -72,6 +72,7 @@ }, ready: function() { + this.$.reporting.appStarted(); this._viewState = { changeView: { changeNum: null,
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html new file mode 100644 index 0000000..0c23c4f --- /dev/null +++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -0,0 +1,53 @@ +<!DOCTYPE html> +<!-- +Copyright (C) 2016 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-app</title> + +<script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script> +<script src="../bower_components/web-component-tester/browser.js"></script> + +<link rel="import" href="gr-app.html"> + +<test-fixture id="basic"> + <template> + <gr-app id="app"></gr-app> + </template> +</test-fixture> + +<script> + suite('gr-app tests', function() { + var sandbox; + var element; + + setup(function(done) { + sandbox = sinon.sandbox.create(); + stub('gr-reporting', { + appStarted: sandbox.stub(), + }); + element = fixture('basic'); + flush(done); + }); + teardown(function() { + sandbox.restore(); + }); + + test('reporting', function() { + assert.isTrue(element.$.reporting.appStarted.calledOnce); + }); + }); +</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html index 66576a3..09f7381 100644 --- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html +++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
@@ -138,7 +138,7 @@ assert.isFalse(element._canAddProject({id: 'project b'}, 'filter 1')); assert.isFalse(element._canAddProject({id: 'project b'}, 'filter 2')); - // Can add a projec that is in the list using a new filter. + // Can add a project that is in the list using a new filter. assert.isTrue(element._canAddProject({id: 'project b'}, 'filter 3')); }); @@ -181,10 +181,11 @@ test('_handleRemoveProject', function() { assert.equal(element._projectsToRemove, 0); - var button = element.$$('table tbody tr:nth-child(2) gr-button'); MockInteractions.tap(button); + flushAsynchronousOperations(); + var rows = element.$$('table tbody').querySelectorAll('tr'); assert.equal(rows.length, 3);
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html index cda2492..32c7c70 100644 --- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html +++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -55,11 +55,12 @@ bind-value="{{text}}" placeholder="[[placeholder]]" on-keydown="_handleInputKeydown" - on-focus="_updateSuggestions" + on-focus="_onInputFocus" + on-blur="_onInputBlur" autocomplete="off" /> <div id="suggestions" - hidden$="[[_computeSuggestionsHidden(_suggestions)]]"> + hidden$="[[_computeSuggestionsHidden(_suggestions, _focused)]]"> <ul> <template is="dom-repeat" items="[[_suggestions]]"> <li
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js index 0fc6b07..16ba150 100644 --- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js +++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -14,6 +14,8 @@ (function() { 'use strict'; + var TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+/g; + Polymer({ is: 'gr-autocomplete', @@ -76,6 +78,15 @@ value: false, }, + /** + * When true, tab key autocompletes but does not fire the commit event. + * See Issue 4556. + */ + tabCompleteWithoutCommit: { + type: Boolean, + value: false, + }, + value: Object, /** @@ -99,14 +110,11 @@ value: false, }, - }, + _focused: { + type: Boolean, + value: false, + }, - attached: function() { - this.listen(document.body, 'click', '_handleBodyClick'); - }, - - detached: function() { - this.unlisten(document.body, 'click', '_handleBodyClick'); }, get focusStart() { @@ -131,6 +139,15 @@ this._disableSuggestions = false; }, + _onInputFocus: function() { + this._focused = true; + this._updateSuggestions(); + }, + + _onInputBlur: function() { + this._focused = false; + }, + _updateSuggestions: function() { if (!this.text || this._disableSuggestions) { return; } if (this.text.length < this.threshold) { @@ -153,8 +170,8 @@ }.bind(this)); }, - _computeSuggestionsHidden: function(suggestions) { - return !suggestions.length; + _computeSuggestionsHidden: function(suggestions, focused) { + return !(suggestions.length && focused); }, _computeClass: function(borderless) { @@ -181,10 +198,14 @@ this._cancel(); break; case 9: // Tab + if (this._suggestions.length > 0) { + e.preventDefault(); + this._commit(this.tabCompleteWithoutCommit); + } + break; case 13: // Enter e.preventDefault(); this._commit(); - this._suggestions = []; break; } }, @@ -199,34 +220,34 @@ var completed = suggestions[index].value; if (this.multi) { // Append the completed text to the end of the string. - var shortStr = this.text.substring(0, this.text.lastIndexOf(' ') + 1); - this.value = shortStr + completed; + // Allow spaces within quoted terms. + var tokens = this.text.match(TOKENIZE_REGEX); + tokens[tokens.length - 1] = completed; + this.value = tokens.join(' '); } else { this.value = completed; } }, - _handleBodyClick: function(e) { - var eventPath = Polymer.dom(e).path; - for (var i = 0; i < eventPath.length; i++) { - if (eventPath[i] == this) { - return; - } - } - this._suggestions = []; - }, - _handleSuggestionTap: function(e) { this.$.cursor.setCursor(e.target); this._commit(); + this.focus(); }, - _commit: function() { + /** + * Commits the suggestion, optionally firing the commit event. + * + * @param {Boolean} silent Allows for silent committing of an autocomplete + * suggestion in order to handle cases like tab-to-complete without + * firing the commit event. + */ + _commit: function(silent) { // Allow values that are not in suggestion list iff suggestions are empty. if (this._suggestions.length > 0) { this._updateValue(this._suggestions, this._index); } else { - this.value = this.text; + this.value = this.text || ''; } var value = this.value; @@ -242,7 +263,10 @@ } } - this.fire('commit', {value: value}); + this._suggestions = []; + if (!silent) { + this.fire('commit', {value: value}); + } }, }); })();
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html index f8b16b7..abf052c 100644 --- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html +++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -57,6 +57,7 @@ element.text = 'blah'; assert.isTrue(queryStub.called); + element._focused = true; promise.then(function() { assert.isFalse(element.$.suggestions.hasAttribute('hidden')); @@ -69,7 +70,6 @@ } assert.notEqual(element.$.cursor.index, -1); - done(); }); }); @@ -85,6 +85,7 @@ assert.isTrue(element.$.suggestions.hasAttribute('hidden')); + element._focused = true; element.text = 'blah'; promise.then(function() { @@ -94,7 +95,6 @@ element.addEventListener('cancel', cancelHandler); MockInteractions.pressAndReleaseKeyOn(element.$.input, 27); // Esc - assert.isTrue(cancelHandler.called); assert.isTrue(element.$.suggestions.hasAttribute('hidden')); @@ -117,7 +117,7 @@ assert.isTrue(element.$.suggestions.hasAttribute('hidden')); assert.equal(element.$.cursor.index, -1); - + element._focused = true; element.text = 'blah'; promise.then(function() { @@ -241,5 +241,55 @@ done(); }); }); + + test('tab key completes only when suggestions exist', function() { + var commitStub = sinon.stub(element, '_commit'); + element._suggestions = []; + MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab + assert.isFalse(commitStub.called); + element._suggestions = ['tunnel snakes rule!']; + MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab + assert.isTrue(commitStub.called); + commitStub.restore(); + }); + + test('tabCompleteWithoutCommit flag functions', function() { + var commitHandler = sinon.spy(); + element.addEventListener('commit', commitHandler); + element._suggestions = ['tunnel snakes rule!']; + element.tabCompleteWithoutCommit = true; + MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab + assert.isFalse(commitHandler.called); + element.tabCompleteWithoutCommit = false; + element._suggestions = ['tunnel snakes rule!']; + MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab + assert.isTrue(commitHandler.called); + }); + + test('_focused flag properly triggered', function() { + flushAsynchronousOperations(); + assert.isFalse(element._focused); + element.$.input.focus(); + assert.isTrue(element._focused); + element.$.input.blur(); + assert.isFalse(element._focused); + }); + + test('_focused flag shows/hides the suggestions', function() { + var suggestions = ['hello', 'its me']; + assert.isTrue(element._computeSuggestionsHidden(suggestions, false)); + assert.isFalse(element._computeSuggestionsHidden(suggestions, true)); + }); + + test('tap on suggestion commits and refocuses on input', function() { + var focusStub = sinon.stub(element, 'focus'); + element._focused = true; + element._suggestions = [{name: 'first suggestion'}]; + assert.isFalse(element.$.suggestions.hasAttribute('hidden')); + MockInteractions.tap(element.$$('#suggestions li:first-child')); + assert.isTrue(focusStub.called); + assert.isTrue(element.$.suggestions.hasAttribute('hidden')); + focusStub.restore(); + }); }); </script>
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 c815ffd..164bb2d 100644 --- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html +++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -22,7 +22,7 @@ <template strip-whitespace> <style> :host { - background-color: #fff; + background-color: #f5f5f5; border: 1px solid #d1d2d3; border-radius: 2px; box-sizing: border-box; @@ -30,10 +30,10 @@ cursor: pointer; display: inline-block; font-family: var(--font-family); - font-size: 13px; + font-size: 12px; font-weight: bold; outline-width: 0; - padding: .3em .65em; + padding: .4em .85em; position: relative; text-align: center; -moz-user-select: none; @@ -44,10 +44,17 @@ :host([hidden]) { display: none; } + :host([primary]), + :host([secondary]) { + color: #fff; + } :host([primary]) { background-color: #4d90fe; border-color: #3079ed; - color: #fff; + } + :host([secondary]) { + background-color: #d14836; + border-color: transparent; } :host([small]) { font-size: 12px; @@ -74,32 +81,53 @@ :host([loading][disabled]) { cursor: wait; } - :host(:focus), - :host(:hover) { - border-color: #666; + :host(:focus:not([primary]:not[secondary])), + :host(:hover:not([primary]:not[secondary])) { + background-color: #f8f8f8; + border-color: #aaa; } :host(:active) { border-color: #d1d2d3; color: #aaa; } + :host([primary]:focus), + :host([secondary]:focus), + :host([primary]:active), + :host([secondary]:active) { + color: #fff; + } :host([primary]:focus) { - border-color: #fff; box-shadow: 0 0 1px #00f; } :host([primary]:hover) { + background-color: #4d90fe; border-color: #00F; } + :host([primary]:active), + :host([secondary]:active) { + box-shadow: none; + } :host([primary]:active) { border-color: #0c2188; - box-shadow: none; - color: #fff; } - :host([primary][loading]), - :host([primary][disabled]) { + :host([secondary]:focus) { + box-shadow: 0 0 1px #f00; + } + :host([secondary]:hover) { + background-color: #c53727; + border: 1px solid #b0281a; + } + :host([secondary]:active) { + border-color: #941c0c; + } + :host([primary][loading]) { background-color: #7caeff; border-color: transparent; color: #fff; } + :host([primary][disabled]) { + background-color: #888; + } </style> <content></content> </template>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js index 0d3ea3d..7b3bc23 100644 --- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js +++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -63,15 +63,6 @@ type: String, value: ScrollBehavior.NEVER, }, - - /** - * When using the 'keep-visible' scroll behavior, set an offset to the top - * of the window for what is considered above the upper fold. - */ - foldOffsetTop: { - type: Number, - value: 0, - }, }, detached: function() { @@ -214,7 +205,7 @@ } if (this.scroll === ScrollBehavior.KEEP_VISIBLE && - top > window.pageYOffset + this.foldOffsetTop && + top > window.pageYOffset && top < window.pageYOffset + window.innerHeight) { return; } // Scroll the element to the middle of the window. Dividing by a third
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js index 5b12c8f..f380dd9 100644 --- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js +++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
@@ -111,7 +111,12 @@ var date = moment(util.parseDate(dateStr)); if (!date.isValid()) { return ''; } if (relative) { - return date.fromNow(); + var dateFromNow = date.fromNow(); + if (dateFromNow === 'a few seconds ago') { + return 'just now'; + } else { + return dateFromNow; + } } var now = new Date(); var format = TimeFormats.MONTH_DAY_YEAR;
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 76a9c77..4fec27f 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
@@ -18,14 +18,19 @@ <dom-module id="gr-editable-label"> <template> <style> + :host { + display: inline-block; + } + input, + label { + width: 100%; + } input { font: inherit; - max-width: 8em; } label { color: #777; display: inline-block; - max-width: 8em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
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 2f109c9..ab497c1 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
@@ -94,7 +94,6 @@ fetchJSON: function(url, opt_errFn, opt_cancelCondition, opt_params, opt_opts) { opt_opts = opt_opts || {}; - var fetchOptions = { credentials: 'same-origin', headers: opt_opts.headers, @@ -312,6 +311,10 @@ ListChangesOption.LABELS, ListChangesOption.DETAILED_ACCOUNTS ); + // Issue 4524: respect legacy token with max sortkey. + if (opt_offset === 'n,z') { + opt_offset = 0; + } var params = { n: changesPerPage, O: options, @@ -426,7 +429,7 @@ var bLastDotIndex = b.lastIndexOf('.'); var bExt = b.substr(bLastDotIndex + 1); - var bFile = a.substr(0, bLastDotIndex); + var bFile = b.substr(0, bLastDotIndex); // Sort header files above others with the same base name. var headerExts = ['h', 'hxx', 'hpp']; @@ -442,8 +445,7 @@ return 1; } } - - return a.localeCompare(b); + return aFile.localeCompare(bFile) || a.localeCompare(b); }, getChangeRevisionActions: function(changeNum, patchNum) { @@ -467,8 +469,22 @@ }); }, - getSuggestedProjects: function(inputVal, opt_errFn, opt_ctx) { - return this.fetchJSON('/projects/', opt_errFn, opt_ctx, {p: inputVal}); + getSuggestedGroups: function(inputVal, opt_n, opt_errFn, opt_ctx) { + var params = {s: inputVal}; + if (opt_n) { params.n = opt_n; } + return this.fetchJSON('/groups/', opt_errFn, opt_ctx, params); + }, + + getSuggestedProjects: function(inputVal, opt_n, opt_errFn, opt_ctx) { + var params = {p: inputVal}; + if (opt_n) { params.n = opt_n; } + return this.fetchJSON('/projects/', opt_errFn, opt_ctx, params); + }, + + getSuggestedAccounts: function(inputVal, opt_n, opt_errFn, opt_ctx) { + var params = {q: inputVal, suggest: null}; + if (opt_n) { params.n = opt_n; } + return this.fetchJSON('/accounts/', opt_errFn, opt_ctx, params); }, addChangeReviewer: function(changeNum, reviewerID) { @@ -531,7 +547,7 @@ ].join(' '); var params = { O: options, - q: query + q: query, }; return this.fetchJSON('/changes/', null, null, params); },
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 8dda2ce..c340ee3 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
@@ -237,6 +237,32 @@ ['foo/bar.h', 'foo/bar.hxx', 'foo/bar.hpp'].sort( element._specialFilePathCompare), ['foo/bar.h', 'foo/bar.hpp', 'foo/bar.hxx']); + + // Regression test for Issue 4448. + assert.deepEqual([ + 'minidump/minidump_memory_writer.cc', + 'minidump/minidump_memory_writer.h', + 'minidump/minidump_thread_writer.cc', + 'minidump/minidump_thread_writer.h', + ] + .sort(element._specialFilePathCompare), + [ + 'minidump/minidump_memory_writer.h', + 'minidump/minidump_memory_writer.cc', + 'minidump/minidump_thread_writer.h', + 'minidump/minidump_thread_writer.cc', + ]); + + // Regression test for Issue 4545. + assert.deepEqual([ + 'task_test.go', + 'task.go', + ] + .sort(element._specialFilePathCompare), + [ + 'task.go', + 'task_test.go', + ]); }); test('rebase always enabled', function(done) { @@ -298,5 +324,11 @@ }); }); }); + + test('legacy n,z key in change url is replaced', function() { + var stub = sandbox.stub(element, 'fetchJSON'); + element.getChanges(1, null, 'n,z'); + assert.equal(stub.args[0][3].S, 0); + }); }); </script>
diff --git a/polygerrit-ui/app/styles/app-theme.html b/polygerrit-ui/app/styles/app-theme.html index ecf4ac6..faf45d8 100644 --- a/polygerrit-ui/app/styles/app-theme.html +++ b/polygerrit-ui/app/styles/app-theme.html
@@ -20,7 +20,7 @@ --selection-background-color: #ebf5fb; --default-text-color: #000; --view-background-color: #fff; - --default-horizontal-margin: 1.25rem; + --default-horizontal-margin: 1rem; --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; --monospace-font-family: 'Source Code Pro', Menlo, 'Lucida Console', Monaco, monospace;
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html index d3cb316..156c873 100644 --- a/polygerrit-ui/app/test/index.html +++ b/polygerrit-ui/app/test/index.html
@@ -25,15 +25,15 @@ var basePath = '../elements/'; [ - 'change-list/gr-change-list-item/gr-change-list-item_test.html', - 'change-list/gr-change-list/gr-change-list_test.html', 'change/gr-account-entry/gr-account-entry_test.html', 'change/gr-account-list/gr-account-list_test.html', 'change/gr-change-actions/gr-change-actions_test.html', 'change/gr-change-metadata/gr-change-metadata_test.html', 'change/gr-change-view/gr-change-view_test.html', 'change/gr-comment-list/gr-comment-list_test.html', + 'change/gr-commit-info/gr-commit-info_test.html', 'change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html', + 'change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html', 'change/gr-download-dialog/gr-download-dialog_test.html', 'change/gr-file-list/gr-file-list_test.html', 'change/gr-message/gr-message_test.html', @@ -41,26 +41,31 @@ 'change/gr-related-changes-list/gr-related-changes-list_test.html', 'change/gr-reply-dialog/gr-reply-dialog_test.html', 'change/gr-reviewer-list/gr-reviewer-list_test.html', + 'change-list/gr-change-list/gr-change-list_test.html', + 'change-list/gr-change-list-item/gr-change-list-item_test.html', 'core/gr-account-dropdown/gr-account-dropdown_test.html', 'core/gr-error-manager/gr-error-manager_test.html', 'core/gr-main-header/gr-main-header_test.html', + 'core/gr-reporting/gr-reporting_test.html', 'core/gr-search-bar/gr-search-bar_test.html', + 'diff/gr-diff/gr-diff-group_test.html', + 'diff/gr-diff/gr-diff_test.html', 'diff/gr-diff-builder/gr-diff-builder_test.html', - 'diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html', 'diff/gr-diff-comment/gr-diff-comment_test.html', + 'diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html', 'diff/gr-diff-cursor/gr-diff-cursor_test.html', - 'diff/gr-diff-highlight/gr-diff-highlight_test.html', 'diff/gr-diff-highlight/gr-annotation_test.html', + 'diff/gr-diff-highlight/gr-diff-highlight_test.html', 'diff/gr-diff-preferences/gr-diff-preferences_test.html', 'diff/gr-diff-processor/gr-diff-processor_test.html', 'diff/gr-diff-selection/gr-diff-selection_test.html', 'diff/gr-diff-view/gr-diff-view_test.html', - 'diff/gr-diff/gr-diff-group_test.html', - 'diff/gr-diff/gr-diff_test.html', 'diff/gr-patch-range-select/gr-patch-range-select_test.html', 'diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html', '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', + 'gr-app_test.html', 'settings/gr-account-info/gr-account-info_test.html', 'settings/gr-email-editor/gr-email-editor_test.html', 'settings/gr-group-list/gr-group-list_test.html', @@ -69,10 +74,10 @@ 'settings/gr-settings-view/gr-settings-view_test.html', 'settings/gr-ssh-editor/gr-ssh-editor_test.html', 'settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html', - 'shared/gr-autocomplete/gr-autocomplete_test.html', 'shared/gr-account-label/gr-account-label_test.html', 'shared/gr-account-link/gr-account-link_test.html', 'shared/gr-alert/gr-alert_test.html', + 'shared/gr-autocomplete/gr-autocomplete_test.html', 'shared/gr-avatar/gr-avatar_test.html', 'shared/gr-change-star/gr-change-star_test.html', 'shared/gr-confirm-dialog/gr-confirm-dialog_test.html',
diff --git a/tools/GoogleFormat.xml b/tools/GoogleFormat.xml index 8062246..2c65b16 100644 --- a/tools/GoogleFormat.xml +++ b/tools/GoogleFormat.xml
@@ -45,7 +45,7 @@ <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/> -<setting id="org.eclipse.jdt.core.compiler.source" value="1.7"/> +<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/> @@ -156,7 +156,7 @@ <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> -<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.7"/> +<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/> <setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> @@ -227,7 +227,7 @@ <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/> -<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.7"/> +<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/> <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
diff --git a/tools/bzl/gwt.bzl b/tools/bzl/gwt.bzl index 29987ef..c27e28f 100644 --- a/tools/bzl/gwt.bzl +++ b/tools/bzl/gwt.bzl
@@ -19,8 +19,6 @@ def gwt_module(gwt_xml=None, resources=[], srcs=[], **kwargs): if gwt_xml: resources += [gwt_xml] - if srcs: - resources += srcs java_library2( srcs = srcs,
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl new file mode 100644 index 0000000..80771da --- /dev/null +++ b/tools/bzl/javadoc.bzl
@@ -0,0 +1,71 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Javadoc rule. + +def _impl(ctx): + zip_output = ctx.outputs.zip + + transitive_jar_set = set() + source_jars = set() + for l in ctx.attr.libs: + source_jars += l.java.source_jars + transitive_jar_set += l.java.transitive_deps + + transitive_jar_paths = [j.path for j in transitive_jar_set] + dir = ctx.outputs.zip.path + ".dir" + source = ctx.outputs.zip.path + ".source" + cmd = [ + "mkdir %s" % source, + " && ".join(["unzip -qud %s %s" % (source, j.path) for j in source_jars]), + "mkdir %s" % dir, + " ".join([ + ctx.file._javadoc.path, + "-protected", + "-encoding UTF-8", + "-charset UTF-8", + "-notimestamp", + "-quiet", + "-windowtitle '%s'" % ctx.attr.title, + "-link", "http://docs.oracle.com/javase/7/docs/api", + "-sourcepath %s" % source, + "-subpackages ", + ":".join(ctx.attr.pkgs), + " -classpath ", + ":".join(transitive_jar_paths), + "-d %s" % dir]), + "find %s -exec touch -t 198001010000 '{}' ';'" % dir, + "(cd %s && zip -qr ../%s *)" % (dir, ctx.outputs.zip.basename), + ] + ctx.action( + inputs = list(transitive_jar_set) + list(source_jars) + ctx.files._jdk, + outputs = [zip_output], + command = " && ".join(cmd)) + +java_doc = rule( + attrs = { + "libs": attr.label_list(allow_files = False), + "pkgs": attr.string_list(), + "title": attr.string(), + "_javadoc": attr.label( + default = Label("@local_jdk//:bin/javadoc"), + single_file = True, + allow_files = True), + "_jdk": attr.label( + default = Label("@local_jdk//:jdk-default"), + allow_files = True), + }, + implementation = _impl, + outputs = {"zip" : "%{name}.zip"}, +)
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl new file mode 100644 index 0000000..8c3c6cb --- /dev/null +++ b/tools/bzl/pkg_war.bzl
@@ -0,0 +1,139 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# War packaging. + +jar_filetype = FileType(['.jar']) + +LIBS = [ + '//gerrit-war:init', + '//gerrit-war:log4j-config', + '//gerrit-war:version', + '//lib:postgresql', + '//lib/log:impl_log4j', +] + +PGMLIBS = [ + '//gerrit-pgm:pgm' +] + +def _add_context(in_file, output): + input_path = in_file.path + return [ + 'unzip -qd %s %s' % (output, input_path) + ] + +def _add_file(in_file, output): + output_path = output + input_path = in_file.path + short_path = in_file.short_path + n = in_file.basename + + # TODO(davido): Drop this when provided_deps added to java_library + if n.find('-jdk15on-') != -1: + return [] + + if short_path.startswith('gerrit-'): + n = short_path.split('/')[0] + '-' + n + + output_path += n + return [ + 'test -L %s || ln -s $(pwd)/%s %s' % (output_path, input_path, output_path) + ] + +def _make_war(input_dir, output): + return ''.join([ + '(root=$(pwd) && ', + 'cd %s && ' % input_dir, + 'zip -9qr ${root}/%s .)' % (output.path), + ]) + +def _war_impl(ctx): + war = ctx.outputs.war + build_output = war.path + '.build_output' + inputs = [] + + # Create war layout + cmd = [ + 'set -e;rm -rf ' + build_output, + 'mkdir -p ' + build_output, + 'mkdir -p %s/WEB-INF/lib' % build_output, + 'mkdir -p %s/WEB-INF/pgm-lib' % build_output, + ] + + # Add lib + transitive_lib_deps = set() + for l in ctx.attr.libs: + transitive_lib_deps += l.java.transitive_runtime_deps + + for dep in transitive_lib_deps: + cmd += _add_file(dep, build_output + '/WEB-INF/lib/') + inputs.append(dep) + + # Add pgm lib + transitive_pgmlib_deps = set() + for l in ctx.attr.pgmlibs: + transitive_pgmlib_deps += l.java.transitive_runtime_deps + + for dep in transitive_pgmlib_deps: + if dep not in inputs: + cmd += _add_file(dep, build_output + '/WEB-INF/pgm-lib/') + inputs.append(dep) + + # Add context + transitive_context_deps = set() + if ctx.attr.context: + for jar in ctx.attr.context: + if hasattr(jar, 'java'): + transitive_context_deps += jar.java.transitive_runtime_deps + elif hasattr(jar, 'files'): + transitive_context_deps += jar.files + for dep in transitive_context_deps: + cmd += _add_context(dep, build_output) + inputs.append(dep) + + # Add zip war + cmd.append(_make_war(build_output, war)) + + ctx.action( + inputs = inputs, + outputs = [war], + mnemonic = 'WAR', + command = '\n'.join(cmd), + use_default_shell_env = True, + ) + +_pkg_war = rule( + attrs = { + 'context': attr.label_list(allow_files = True), + 'libs': attr.label_list(allow_files = jar_filetype), + 'pgmlibs': attr.label_list(allow_files = False), + }, + implementation = _war_impl, + outputs = {'war' : '%{name}.war'}, +) + +def pkg_war(name, ui = 'ui_optdbg'): + ui_deps = [] + if ui: + ui_deps.append('//gerrit-gwtui:%s' % ui) + _pkg_war( + name = name, + libs = LIBS, + pgmlibs = PGMLIBS, + context = ui_deps + [ + '//gerrit-main:main_bin_deploy.jar', + '//gerrit-war:webapp_assets', + ], + )
diff --git a/tools/default.defs b/tools/default.defs index 191dfe5..fb4c6de 100644 --- a/tools/default.defs +++ b/tools/default.defs
@@ -201,7 +201,7 @@ ':%s__gwt_application' % name + ';cd $TMP' + ';zip -qr $OUT .', - out = '%s-static.zip' % name, + out = '%s-static.jar' % name, ) gwt_binary( name = name + '__gwt_application',
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK index 0bcde9d..dfd271d 100644 --- a/tools/eclipse/BUCK +++ b/tools/eclipse/BUCK
@@ -15,14 +15,21 @@ '//gerrit-reviewdb:client_tests', '//gerrit-server:server', '//gerrit-server:server_tests', + '//lib:jimfs', '//lib/asciidoctor:asciidoc_lib', '//lib/asciidoctor:doc_indexer_lib', '//lib/auto:auto-value', '//lib/bouncycastle:bcprov', '//lib/bouncycastle:bcpg', '//lib/bouncycastle:bcpkix', + '//lib/gwt:ant', + '//lib/gwt:colt', '//lib/gwt:javax-validation', '//lib/gwt:javax-validation_src', + '//lib/gwt:jsinterop-annotations', + '//lib/gwt:jsinterop-annotations_src', + '//lib/gwt:tapestry', + '//lib/gwt:w3c-css-sac', '//lib/jetty:servlets', '//lib/prolog:compiler_lib', '//polygerrit-ui:polygerrit_components',
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py index 46f5680..3d34588 100755 --- a/tools/eclipse/project.py +++ b/tools/eclipse/project.py
@@ -28,7 +28,7 @@ JRE = '/'.join([ 'org.eclipse.jdt.launching.JRE_CONTAINER', 'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType', - 'JavaSE-1.7', + 'JavaSE-1.8', ]) ROOT = path.abspath(__file__)
diff --git a/tools/gwt-constants.defs b/tools/gwt-constants.defs index 8bafddb..b76c04b 100644 --- a/tools/gwt-constants.defs +++ b/tools/gwt-constants.defs
@@ -14,8 +14,14 @@ ] GWT_TRANSITIVE_DEPS = [ + '//lib/gwt:ant', + '//lib/gwt:colt', '//lib/gwt:javax-validation', '//lib/gwt:javax-validation_src', + '//lib/gwt:jsinterop-annotations', + '//lib/gwt:jsinterop-annotations_src', + '//lib/gwt:tapestry', + '//lib/gwt:w3c-css-sac', '//lib/ow2:ow2-asm', '//lib/ow2:ow2-asm-analysis', '//lib/ow2:ow2-asm-commons',
diff --git a/tools/java_doc.defs b/tools/java_doc.defs index 41a8730..583407c6 100644 --- a/tools/java_doc.defs +++ b/tools/java_doc.defs
@@ -2,23 +2,22 @@ name, title, pkgs, - paths, + source_jar, srcs = [], deps = [], visibility = [], - do_it_wrong = False, external_docs = [], ): - if do_it_wrong: - sourcepath = paths - else: - sourcepath = ['$SRCDIR/' + n for n in paths] - external_docs.insert(0, 'http://docs.oracle.com/javase/7/docs/api') + # TODO(davido): Actually we shouldn't need to extract the source + # archive, javadoc should just work with provided archive. + external_docs.insert(0, 'http://docs.oracle.com/javase/8/docs/api') genrule( name = name, cmd = ' '.join([ - 'while ! test -f .buckconfig; do cd ..; done;', + 'mkdir $TMP/sourcepath &&', + 'unzip $(location %s) -d $TMP/sourcepath &&' % source_jar, 'javadoc', + '-Xdoclint:-missing', '-quiet', '-protected', '-encoding UTF-8', @@ -28,8 +27,7 @@ ' '.join(['-link %s' % url for url in external_docs]), '-subpackages ', ':'.join(pkgs), - '-sourcepath ', - ':'.join(sourcepath), + '-sourcepath $TMP/sourcepath', ' -classpath ', ':'.join(['$(classpath %s)' % n for n in deps]), '-d $TMP', @@ -37,4 +35,4 @@ srcs = srcs, out = name + '.jar', visibility = visibility, -) + )
diff --git a/tools/jgit-snapshot-deploy-pom.diff b/tools/jgit-snapshot-deploy-pom.diff new file mode 100644 index 0000000..01f50e4 --- /dev/null +++ b/tools/jgit-snapshot-deploy-pom.diff
@@ -0,0 +1,43 @@ +diff --git a/pom.xml b/pom.xml +index d256bbb..7e523fd 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -226,6 +226,10 @@ + + <pluginRepositories> + <pluginRepository> ++ <id>gerrit-maven</id> ++ <url>https://gerrit-maven.commondatastorage.googleapis.com</url> ++ </pluginRepository> ++ <pluginRepository> + <id>repo.eclipse.org.cbi-releases</id> + <url>https://repo.eclipse.org/content/repositories/cbi-releases/</url> + </pluginRepository> +@@ -236,6 +240,13 @@ + </pluginRepositories> + + <build> ++ <extensions> ++ <extension> ++ <groupId>com.googlesource.gerrit</groupId> ++ <artifactId>gs-maven-wagon</artifactId> ++ <version>3.3</version> ++ </extension> ++ </extensions> + <pluginManagement> + <plugins> + <plugin> +@@ -649,9 +660,10 @@ + + <distributionManagement> + <repository> +- <id>repo.eclipse.org</id> +- <name>JGit Maven Repository - Releases</name> +- <url>https://repo.eclipse.org/content/repositories/jgit-releases/</url> ++ <id>gerrit-maven-repository</id> ++ <name>Gerrit Maven Repository</name> ++ <url>gs://gerrit-maven</url> ++ <uniqueVersion>true</uniqueVersion> + </repository> + <snapshotRepository> + <id>repo.eclipse.org</id>