Merge branch 'stable-2.11'

* stable-2.11:
  Acceptance-tests: Don't assume UTF-8 system wide encoding
  Update version to 2.10.3.1
  Release notes for 2.10.3.1
  Project Owner Guide: Mention importer plugin to rename project
  Update the cookbook plugin revision
  Acceptance tests: Always treat response encoding as UTF-8
  Fix broken formatting in 2.10.3 release notes
  Update 2.11 release notes
  Add anchors to plugin sections in plugin documentation page
  Add description of importer plugin to the plugin documentation page
  Fix rendering issues in Configuration documentation
  Document that submit should for granted on refs/heads/*
  Update version to 2.10.3
  Update 2.10.3 release notes
  Improve the version computation for the release notes
  Check reachability from R_HEADS/R_TAGS/REFS_CONFIG when creating branches
  Update 2.10.3 release notes
  Include submitter in ChangeMessage on submission
  Support hybrid OpenID and OAuth2 authentication
  Move edit ref name methods from ChangeEditUtil to RefNames
  Remove tests related to duplicate event type registration
  Events: Allow same event type to be re-registered
  InlineEdit: Clarify difference between remove and revert operation
  Documentation: clone buck from Github
  ChangeTable: Always add the title tooltip on label column entries
  Enable 'Save' button when 'Display In Review Category' pref is changed
  Add ForcePushIT to acceptance tests
  InlineEdit: Handle enter event in add file dialog box
  Update replication plugin to latest revision
  Release notes for Gerrit 2.10.3
  Fix NPE in GitWebServlet
  Update revision of the replication plugin
  Fix required index version for online reindexing in 2.11 release notes
  Update buck version to same as master
  OAuth: Respect servlet context path in URL for login token
  Invalidate OAuth session after web_sessions cache expiration
  Set version to 2.11
  Update 2.11 release notes
  Prevent wrong content type for CSS files
  Improve rebase usability with the RebaseDialog
  Update 2.11 release notes
  MergeabilityCacheImpl: Only get needed refs
  Explain online reindexing in 2.11 release
  Revert marking merged or abandoned changes in related changes

Change-Id: Id915ef7316b3e721738064bb1cce97020e04296e
This commit is contained in:
David Pursehouse 2015-04-20 10:11:32 +09:00
commit 409ce6a9fd
55 changed files with 1035 additions and 192 deletions

View File

@ -907,7 +907,7 @@ Suggested access rights to grant:
* xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*' * xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*'
* link:config-labels.html#label_Code-Review[`Label: Code-Review`] with range '-2' to '+2' for 'refs/heads/*' * link:config-labels.html#label_Code-Review[`Label: Code-Review`] with range '-2' to '+2' for 'refs/heads/*'
* link:config-labels.html#label_Verified[`Label: Verified`] with range '-1' to '+1' for 'refs/heads/*' * link:config-labels.html#label_Verified[`Label: Verified`] with range '-1' to '+1' for 'refs/heads/*'
* xref:category_submit[`Submit`] * xref:category_submit[`Submit`] on 'refs/heads/*'
If the project is small or the developers are seasoned it might make If the project is small or the developers are seasoned it might make
sense to give them the freedom to push commits directly to a branch. sense to give them the freedom to push commits directly to a branch.

View File

@ -502,15 +502,17 @@ Values should use common unit suffixes to express their setting:
* y, year, years (`1 year` is treated as `365 days`) * y, year, years (`1 year` is treated as `365 days`)
+ +
--
If a unit suffix is not specified, `seconds` is assumed. If 0 is If a unit suffix is not specified, `seconds` is assumed. If 0 is
supplied, the maximum age is infinite and items are never purged supplied, the maximum age is infinite and items are never purged
except when the cache is full. except when the cache is full.
+
Default is `0`, meaning store forever with no expire, except: Default is `0`, meaning store forever with no expire, except:
+
* `"adv_bases"`: default is `10 minutes` * `"adv_bases"`: default is `10 minutes`
* `"ldap_groups"`: default is `1 hour` * `"ldap_groups"`: default is `1 hour`
* `"web_sessions"`: default is `12 hours` * `"web_sessions"`: default is `12 hours`
--
[[cache.name.memoryLimit]]cache.<name>.memoryLimit:: [[cache.name.memoryLimit]]cache.<name>.memoryLimit::
+ +
@ -735,9 +737,11 @@ Values should use common unit suffixes to express their setting:
* h, hr, hour, hours * h, hr, hour, hours
+ +
--
If a unit suffix is not specified, `milliseconds` is assumed. If a unit suffix is not specified, `milliseconds` is assumed.
+
Default is 5 seconds. Default is 5 seconds.
--
[[cache.diff_intraline.timeout]]cache.diff_intraline.timeout:: [[cache.diff_intraline.timeout]]cache.diff_intraline.timeout::
+ +
@ -758,9 +762,11 @@ Values should use common unit suffixes to express their setting:
* h, hr, hour, hours * h, hr, hour, hours
+ +
--
If a unit suffix is not specified, `milliseconds` is assumed. If a unit suffix is not specified, `milliseconds` is assumed.
+
Default is 5 seconds. Default is 5 seconds.
--
[[cache.diff_intraline.enabled]]cache.diff_intraline.enabled:: [[cache.diff_intraline.enabled]]cache.diff_intraline.enabled::
+ +
@ -1345,12 +1351,14 @@ Values should use common unit suffixes to express their setting:
* h, hr, hour, hours * h, hr, hour, hours
+ +
--
If a unit suffix is not specified, `milliseconds` is assumed. If a unit suffix is not specified, `milliseconds` is assumed.
+
Default is `30 seconds`. Default is `30 seconds`.
+
This setting only applies if This setting only applies if
<<database.connectionPool,database.connectionPool>> is true. <<database.connectionPool,database.connectionPool>> is true.
--
[[database.dataSourceInterceptorClass]]database.dataSourceInterceptorClass:: [[database.dataSourceInterceptorClass]]database.dataSourceInterceptorClass::
@ -1947,10 +1955,12 @@ Behaves exactly like proxy-http, but also sets the scheme to assume
'https://' is the proper URL back to the server. 'https://' is the proper URL back to the server.
+ +
--
If multiple values are supplied, the daemon will listen on all If multiple values are supplied, the daemon will listen on all
of them. of them.
+
By default, http://*:8080. By default, http://*:8080.
--
[[httpd.reuseAddress]]httpd.reuseAddress:: [[httpd.reuseAddress]]httpd.reuseAddress::
+ +
@ -2078,11 +2088,13 @@ Values should use common unit suffixes to express their setting:
* y, year, years (`1 year` is treated as `365 days`) * y, year, years (`1 year` is treated as `365 days`)
+ +
--
If a unit suffix is not specified, `minutes` is assumed. If 0 If a unit suffix is not specified, `minutes` is assumed. If 0
is supplied, the maximum age is infinite and connections will not is supplied, the maximum age is infinite and connections will not
abort until the client disconnects. abort until the client disconnects.
+
By default, 5 minutes. By default, 5 minutes.
--
[[httpd.filterClass]]httpd.filterClass:: [[httpd.filterClass]]httpd.filterClass::
+ +
@ -3082,15 +3094,17 @@ default of 29418.
* 'hostname':'port' (for example `review.example.com:29418`) * 'hostname':'port' (for example `review.example.com:29418`)
* 'IPv4':'port' (for example `10.0.0.1:29418`) * 'IPv4':'port' (for example `10.0.0.1:29418`)
* ['IPv6']:'port' (for example `[ff02::1]:29418`) * ['IPv6']:'port' (for example `[ff02::1]:29418`)
* *:'port' (for example `*:29418`) * +*:'port'+ (for example `+*:29418+`)
+ +
--
If multiple values are supplied, the daemon will listen on all If multiple values are supplied, the daemon will listen on all
of them. of them.
+
To disable the internal SSHD, set listenAddress to `off`. To disable the internal SSHD, set listenAddress to `off`.
+
By default, *:29418. By default, *:29418.
--
[[sshd.advertisedAddress]]sshd.advertisedAddress:: [[sshd.advertisedAddress]]sshd.advertisedAddress::
+ +
@ -3099,16 +3113,18 @@ This may differ from sshd.listenAddress if a firewall based port
redirector is being used, making Gerrit appear to answer on port redirector is being used, making Gerrit appear to answer on port
22. The following forms may be used to specify an address. In any 22. The following forms may be used to specify an address. In any
form, `:'port'` may be omitted to use the default SSH port of 22. form, `:'port'` may be omitted to use the default SSH port of 22.
+
* 'hostname':'port' (for example `review.example.com:22`) * 'hostname':'port' (for example `review.example.com:22`)
* 'IPv4':'port' (for example `10.0.0.1:29418`) * 'IPv4':'port' (for example `10.0.0.1:29418`)
* ['IPv6']:'port' (for example `[ff02::1]:29418`) * ['IPv6']:'port' (for example `[ff02::1]:29418`)
+ +
--
If multiple values are supplied, the daemon will advertise all If multiple values are supplied, the daemon will advertise all
of them. of them.
+
By default, sshd.listenAddress. By default, sshd.listenAddress.
--
[[sshd.tcpKeepAlive]]sshd.tcpKeepAlive:: [[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
+ +

View File

@ -39,6 +39,7 @@ installed during the link:pgm-init.html[Gerrit initialization].
The core plugins are developed and maintained by the Gerrit maintainers The core plugins are developed and maintained by the Gerrit maintainers
and the Gerrit community. and the Gerrit community.
[[commit-message-length-validator]]
=== commit-message-length-validator === commit-message-length-validator
This plugin checks the length of a commits commit message subject and This plugin checks the length of a commits commit message subject and
@ -52,6 +53,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[cookbook-plugin]]
=== cookbook-plugin === cookbook-plugin
Sample plugin to demonstrate features of Gerrit's plugin API. Sample plugin to demonstrate features of Gerrit's plugin API.
@ -61,6 +63,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[download-commands]]
=== download-commands === download-commands
This plugin defines commands for downloading changes in different This plugin defines commands for downloading changes in different
@ -73,6 +76,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[replication]]
=== replication === replication
This plugin can automatically push any changes Gerrit Code Review makes This plugin can automatically push any changes Gerrit Code Review makes
@ -87,6 +91,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[reviewnotes]]
=== reviewnotes === reviewnotes
Stores review information for Gerrit changes in the `refs/notes/review` Stores review information for Gerrit changes in the `refs/notes/review`
@ -97,6 +102,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[singleusergroup]]
=== singleusergroup === singleusergroup
This plugin provides a group per user. This is useful to assign access This plugin provides a group per user. This is useful to assign access
@ -121,6 +127,7 @@ list may not be complete. You may discover more plugins on
link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[ link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[
gerrit-review]. gerrit-review].
[[admin-console]]
=== admin-console === admin-console
Plugin to provide administrator-only functionality, intended to Plugin to provide administrator-only functionality, intended to
@ -133,6 +140,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/admin-console/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/admin-console/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[avatars-external]]
=== avatars/external === avatars/external
This plugin allows to use an external url to load the avatar images This plugin allows to use an external url to load the avatar images
@ -145,6 +153,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[avatars-gravatar]]
=== avatars/gravatar === avatars/gravatar
Plugin to display user icons from Gravatar. Plugin to display user icons from Gravatar.
@ -152,6 +161,7 @@ Plugin to display user icons from Gravatar.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/gravatar[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/gravatar[
Project] Project]
[[branch-network]]
=== branch-network === branch-network
This plugin allows the rendering of Git repository branch network in a This plugin allows the rendering of Git repository branch network in a
@ -166,6 +176,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[changemessage]]
=== changemessage === changemessage
This plugin allows to display a static info message on the change screen. This plugin allows to display a static info message on the change screen.
@ -177,6 +188,7 @@ Plugin Documenatation] |
link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[codenvy]]
=== codenvy === codenvy
Plugin to allow to edit code on-line on either an existing branch or an Plugin to allow to edit code on-line on either an existing branch or an
@ -186,6 +198,7 @@ development platform.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codenvy[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codenvy[
Project] Project]
[[delete-project]]
=== delete-project === delete-project
Provides the ability to delete a project. Provides the ability to delete a project.
@ -195,6 +208,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[egit]]
=== egit === egit
This plugin provides extensions for easier usage with EGit. This plugin provides extensions for easier usage with EGit.
@ -208,6 +222,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/egit/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/egit/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[force-draft]]
=== force-draft === force-draft
Provides an ssh command to force a change or patch set to draft status. Provides an ssh command to force a change or patch set to draft status.
@ -217,6 +232,7 @@ delete a change or patch set from the server.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/force-draft[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/force-draft[
Project] Project]
[[gitblit]]
=== gitblit === gitblit
GitBlit code-viewer plugin with SSO and Security Access Control. GitBlit code-viewer plugin with SSO and Security Access Control.
@ -224,6 +240,7 @@ GitBlit code-viewer plugin with SSO and Security Access Control.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitblit[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitblit[
Project] Project]
[[github]]
=== github === github
Plugin to integrate with GitHub: replication, pull-request to Change-Sets Plugin to integrate with GitHub: replication, pull-request to Change-Sets
@ -231,6 +248,7 @@ Plugin to integrate with GitHub: replication, pull-request to Change-Sets
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/github[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/github[
Project] Project]
[[gitiles]]
=== gitiles === gitiles
Plugin running Gitiles alongside a Gerrit server. Plugin running Gitiles alongside a Gerrit server.
@ -238,6 +256,7 @@ Plugin running Gitiles alongside a Gerrit server.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitiles[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitiles[
Project] Project]
[[imagare]]
=== imagare === imagare
The imagare plugin allows Gerrit users to upload and share images. The imagare plugin allows Gerrit users to upload and share images.
@ -249,6 +268,33 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[importer]]
=== importer
The importer plugin allows to import projects from one Gerrit server
into another Gerrit server.
Projects can be imported while both source and target Gerrit server
are online. There is no downtime required.
The git repository and all changes of the project, including approvals
and review comments, are imported. Historic timestamps are preserved.
Project imports can be resumed. This means a project team can continue
to work in the source system while the import to the target system is
done. By resuming the import the project in the target system can be
updated with the missing delta.
The importer plugin can also be used to copy a project within one Gerrit
server, and in combination with the link:#delete-project[delete-project]
plugin it can be used to rename a project.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/importer[
Project] |
link:https://gerrit.googlesource.com/plugins/importer/+doc/master/src/main/resources/Documentation/about.md[
Documentation]
[[its-plugins]]
=== Issue Tracker System Plugins === Issue Tracker System Plugins
Plugins to integrate with issue tracker systems (ITS), that (based Plugins to integrate with issue tracker systems (ITS), that (based
@ -266,6 +312,7 @@ its-base Documentation] |
link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/config.md[
its-base Configuration] its-base Configuration]
[[its-bugzilla]]
==== its-bugzilla ==== its-bugzilla
Plugin to integrate with Bugzilla. Plugin to integrate with Bugzilla.
@ -275,6 +322,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/its-bugzilla/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/its-bugzilla/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[its-jira]]
==== its-jira ==== its-jira
Plugin to integrate with Jira. Plugin to integrate with Jira.
@ -284,6 +332,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/its-jira/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/its-jira/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[its-rtc]]
==== its-rtc ==== its-rtc
Plugin to integrate with IBM Rational Team Concert (RTC). Plugin to integrate with IBM Rational Team Concert (RTC).
@ -293,6 +342,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/its-rtc/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/its-rtc/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[javamelody]]
=== javamelody === javamelody
This plugin allows to monitor the Gerrit server. This plugin allows to monitor the Gerrit server.
@ -307,6 +357,7 @@ Documentation] |
https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/config.md[ https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[menuextender]]
=== menuextender === menuextender
The menuextender plugin allows Gerrit administrators to configure The menuextender plugin allows Gerrit administrators to configure
@ -319,6 +370,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[motd]]
=== motd === motd
This plugin can output messages to clients when pulling/fetching/cloning This plugin can output messages to clients when pulling/fetching/cloning
@ -334,8 +386,8 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[oauth-authentication-provider]]
=== OAuth authentication provider === OAuth authentication provider
This plugin enables Gerrit to use OAuth2 protocol for authentication. This plugin enables Gerrit to use OAuth2 protocol for authentication.
Two different OAuth providers are supported: Two different OAuth providers are supported:
@ -345,6 +397,7 @@ Two different OAuth providers are supported:
https://github.com/davido/gerrit-oauth-provider[Project] | https://github.com/davido/gerrit-oauth-provider[Project] |
https://github.com/davido/gerrit-oauth-provider/wiki/Getting-Started[Configuration] https://github.com/davido/gerrit-oauth-provider/wiki/Getting-Started[Configuration]
[[project-download-commands]]
=== project-download-commands === project-download-commands
This plugin adds support for project specific download commands. This plugin adds support for project specific download commands.
@ -360,6 +413,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[quota]]
=== quota === quota
This plugin allows to enforce quotas in Gerrit. This plugin allows to enforce quotas in Gerrit.
@ -375,6 +429,7 @@ Documentation]
link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[reviewers]]
=== reviewers === reviewers
A plugin that allows adding default reviewers to a change. A plugin that allows adding default reviewers to a change.
@ -386,6 +441,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[reviewers-by-blame]]
=== reviewers-by-blame === reviewers-by-blame
A plugin that allows automatically adding reviewers to a change from A plugin that allows automatically adding reviewers to a change from
@ -401,6 +457,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[groovy-provider]]
=== scripting/groovy-provider === scripting/groovy-provider
This plugin provides a Groovy runtime environment for Gerrit plugins in Groovy. This plugin provides a Groovy runtime environment for Gerrit plugins in Groovy.
@ -410,6 +467,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/scripting/groovy-provider/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/scripting/groovy-provider/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[scala-provider]]
=== scripting/scala-provider === scripting/scala-provider
This plugin provides a Scala runtime environment for Gerrit plugins in Scala. This plugin provides a Scala runtime environment for Gerrit plugins in Scala.
@ -419,6 +477,7 @@ Project] |
link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[ link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[
Documentation] Documentation]
[[server-config]]
=== server-config === server-config
This plugin enables access (download and upload) to the server config This plugin enables access (download and upload) to the server config
@ -430,6 +489,7 @@ get.
link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/server-config[ link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/server-config[
Project] Project]
[[serviceuser]]
=== serviceuser === serviceuser
This plugin allows to create service users in Gerrit. This plugin allows to create service users in Gerrit.
@ -446,6 +506,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[uploadvalidator]]
=== uploadvalidator === uploadvalidator
This plugin allows to configure upload validations per project. This plugin allows to configure upload validations per project.
@ -461,6 +522,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[websession-flatfile]]
=== websession-flatfile === websession-flatfile
This plugin replaces the built-in Gerrit H2 based websession cache with This plugin replaces the built-in Gerrit H2 based websession cache with
@ -475,6 +537,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[wip]]
=== wip === wip
This plugin adds a new button that allows a change owner to set a This plugin adds a new button that allows a change owner to set a
@ -492,6 +555,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/config.md[ link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/config.md[
Configuration] Configuration]
[[x-docs]]
=== x-docs === x-docs
This plugin serves project documentation as HTML pages. This plugin serves project documentation as HTML pages.

View File

@ -12,7 +12,7 @@ OS are supported. Buck requires Python version 2.7 to be installed.
Clone the git and build it: Clone the git and build it:
---- ----
git clone https://gerrit.googlesource.com/buck git clone https://github.com/facebook/buck
cd buck cd buck
git checkout $(cat ../gerrit/.buckversion) git checkout $(cat ../gerrit/.buckversion)
ant ant

View File

@ -765,6 +765,14 @@ As workaround you may
. link:#import-history[import the history of the old project] . link:#import-history[import the history of the old project]
. link:#project-deletion[delete the old project] . link:#project-deletion[delete the old project]
Please note that a drawback of this workaround is that the whole review
history (changes, review comments) is lost.
Alternatively, you can use the
link:https://gerrit.googlesource.com/plugins/importer/[importer] plugin
to copy the project _including the review history_, and then
link:#project-deletion[delete the old project].
GERRIT GERRIT
------ ------
Part of link:index.html[Gerrit Code Review] Part of link:index.html[Gerrit Code Review]

View File

@ -45,7 +45,9 @@ While in edit mode, it is possible to add new files to the change by clicking
the 'Add...' button at the top of the file list. the 'Add...' button at the top of the file list.
Files can be removed from the change, or restored, by clicking the icon to the Files can be removed from the change, or restored, by clicking the icon to the
left of the file name. left of the file name. Reverting a file in the change is also supported and is
achieved in two steps: remove file from the change and restore the file in the
change.
To switch from edit mode back to review mode, click the 'Done Editing' button. To switch from edit mode back to review mode, click the 'Done Editing' button.

View File

@ -0,0 +1,11 @@
Release notes for Gerrit 2.10.3.1
=================================
There are no schema changes from link:ReleaseNotes-2.10.3.html[2.10.3].
Download:
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.1.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.1.war]
The 2.10.3 release packaged wrong version of the core plugins due to a bug
in our buck build scripts. This version fixes this issue.

View File

@ -0,0 +1,124 @@
Release notes for Gerrit 2.10.3
===============================
Download:
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.war]
Important Notes
---------------
*WARNING:* There are no schema changes from
link:ReleaseNotes-2.10.2.html[2.10.2], but Bouncycastle was upgraded to 1.51.
It is therefore important to upgrade the site with the `init` program, rather
than only copying the .war file over the existing one.
*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
libraries should be manually removed from site's `lib` folder to prevent the
startup failure described in
link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
It is recommended to run the `init` program in interactive mode. Warnings will
be suppressed in batch mode.
----
java -jar gerrit.war init -d site_path
----
New Features
------------
* Support hybrid OpenID and OAuth2 authentication
+
OpenID auth scheme is aware of optional OAuth2 plugin-based authentication.
This feature is considered to be experimental and hasn't reached full feature set yet.
Particularly, linking of user identities accross protocol boundaries and even from
one OAuth2 identity to another OAuth2 identity wasn't implemented yet.
Configuration
~~~~~~~~~~~~~
* Allow to configure
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10.3/config-gerrit.html#sshd.rekeyBytesLimit[
SSHD rekey parameters].
SSH
---
* Update SSHD to 0.14.0.
+
This fixes link:https://issues.apache.org/jira/browse/SSHD-348[SSHD-348] which
was causing ssh threads allocated to stream-events clients to get stuck.
+
Also update SSHD Mina to 2.0.8 and Bouncycastle to 1.51.
* link:https://code.google.com/p/gerrit/issues/detail?id=2797[Issue 2797]:
Add support for ECDSA based public key authentication.
Bug Fixes
---------
* Prevent wrong content type for CSS files.
+
The mime-util library contains two content type mappings for .css files:
`application/x-pointplus` and `text/css`. Unfortunately, using the wrong one
will result in most browsers discarding the file as a CSS file. Ensure we only
use the correct type for CSS files.
* link:https://code.google.com/p/gerrit/issues/detail?id=3289[Issue 3289]:
Prevent NullPointerException in Gitweb servlet.
Replication plugin
~~~~~~~~~~~~~~~~~~
* Set connection timeout to 120 seconds for SSH remote operations.
+
The creation of a missing Git, before starting replication, is a blocking
operation. By setting a timeout, we ensure the operation does not get stuck
forever, essentially blocking all future remote git creation operations.
OAuth extension point
~~~~~~~~~~~~~~~~~~~~~
* Respect servlet context path in URL for login token
+
On sites with non empty context path, first redirect was broken and ended up
with 404 Not found.
* Invalidate OAuth session after web_sessions cache expiration
+
After web session cache expiration there is no way to re-sign-in into Gerrit.
Daemon
~~~~~~
* Print proper names for tasks in output of `show-queue` command.
+
Some tasks were not displayed with the proper name.
Web UI
~~~~~~
* link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]:
Remove stripping `#` in login redirect.
SSH
~~~
* Prevent double authentication for the same public key.
Performance
-----------
* Improved performance when creating a new branch on a repository with a large
number of changes.
Upgrades
--------
* Update Bouncycastle to 1.51.
* Update SSHD to 0.14.0.

View File

@ -8,8 +8,9 @@ link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war] https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war]
Gerrit 2.11 includes the bug fixes done with Gerrit 2.11 includes the bug fixes done with
link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1] and link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1],
link:ReleaseNotes-2.10.2.html[Gerrit 2.10.2]. link:ReleaseNotes-2.10.2.html[Gerrit 2.10.2] and
link:ReleaseNotes-2.10.3.html[Gerrit 2.10.3].
These bug fixes are *not* listed in these release notes. These bug fixes are *not* listed in these release notes.
@ -20,9 +21,38 @@ Important Notes
*WARNING:* This release contains schema changes. To upgrade: *WARNING:* This release contains schema changes. To upgrade:
---- ----
java -jar gerrit.war init -d site_path java -jar gerrit.war init -d site_path
----
Gerrit 2.11 requires a secondary index, which can be created offline
by running the `reindex` program:
----
java -jar gerrit.war reindex -d site_path java -jar gerrit.war reindex -d site_path
---- ----
If the site that is upgraded already has a secondary index, the
secondary index can be upgraded online. This is important for large
sites since running the `reindex` program can take a long time and
contributes significantly to the downtime that is required for the
upgrade.
Gerrit 2.11 supports online reindexing only from the index version `11`
which is the index version of Gerrit 2.10. This means if you come from
an older release it makes sense to first upgrade to 2.10 and then do
the upgrade to 2.11 so that you can profit from online reindexing.
In case you are upgrading from 2.10 it is *important* to check *before*
the upgrade to 2.11 that the index version of your Gerrit 2.10 site is
`11`. You can check the index version in
`$site_path/index/gerrit_index.config`. Your Gerrit 2.10 site may run
with an older index version (e.g. if online reindexing to index version
`11` is still running or if online reindexing to version `11` has
failed). In this case you first need to successfully migrate your index
version of your Gerrit 2.10 site to `11` and only then start with the
2.11 upgrade. If you start the 2.11 upgrade when the schema version of
your Gerrit 2.10 site is older than `11`, online reindexing is no longer
possible and you need to reindex offline by using the `reindex` program.
*WARNING:* Upgrading to 2.11.x requires the server be first upgraded to 2.8 (or *WARNING:* Upgrading to 2.11.x requires the server be first upgraded to 2.8 (or
2.9) and then to 2.11.x. If you are upgrading from 2.8.x or later, you may ignore 2.9) and then to 2.11.x. If you are upgrading from 2.8.x or later, you may ignore
this warning and upgrade directly to 2.11.x. this warning and upgrade directly to 2.11.x.
@ -118,8 +148,6 @@ head of the destination branch or the latest patch set of the predecessor change
* Show the parent commit's subject as a tooltip. * Show the parent commit's subject as a tooltip.
* Decorate abandoned changes in the 'Related Changes' list with a dark red dot.
* link:http://code.google.com/p/gerrit/issues/detail?id=2541[Issue 2541], * link:http://code.google.com/p/gerrit/issues/detail?id=2541[Issue 2541],
link:http://code.google.com/p/gerrit/issues/detail?id=2974[Issue 2974]: link:http://code.google.com/p/gerrit/issues/detail?id=2974[Issue 2974]:
Allow the 'Reply' button's Allow the 'Reply' button's
@ -367,10 +395,6 @@ Allow projects to be configured to create a new change for every uploaded commit
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#container.daemonOpt[ link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#container.daemonOpt[
options to pass to the daemon]. options to pass to the daemon].
* Allow to configure
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#sshd.rekeyBytesLimit[
SSHD rekey parameters].
Daemon Daemon
~~~~~~ ~~~~~~
@ -395,9 +419,6 @@ a change message on the created change.
SSH SSH
~~~ ~~~
* link:https://code.google.com/p/gerrit/issues/detail?id=2797[Issue 2797]:
Add support for ECDSA based public key authentication.
* Add new commands * Add new commands
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[ link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[
`logging ls-level`] and `logging ls-level`] and
@ -542,16 +563,6 @@ Fix server error when checking mergeability of a change.
Due to link:https://github.com/google/guice/issues/745[Guice issue 745], cloning Due to link:https://github.com/google/guice/issues/745[Guice issue 745], cloning
of a repository with a space in its name was impossible. of a repository with a space in its name was impossible.
* Print proper names for tasks in output of `show-queue` command.
+
Some tasks were not displayed with the proper name.
SSH
~~~
* Prevent double authentication for the same public key.
Secondary Index / Search Secondary Index / Search
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
@ -600,19 +611,15 @@ documented.
Web UI Web UI
~~~~~~ ~~~~~~
* link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]: Change List
Remove stripping `#` in login redirect. ^^^^^^^^^^^
* link:http://code.google.com/p/gerrit/issues/detail?id=3304[Issue 3304]:
Always show a tooltip on the label column entries.
Change Screen Change Screen
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
* link:http://code.google.com/p/gerrit/issues/detail?id=2894[Issue 2894]:
Link to change screen for merged or abandoned changes in the 'Related Changes'
list.
+
For changes in the 'Related Changes' tab that are closed the link was
bringing the user to GitWeb, and not as expected to the change screen.
* link:http://code.google.com/p/gerrit/issues/detail?id=3147[Issue 3147]: * link:http://code.google.com/p/gerrit/issues/detail?id=3147[Issue 3147]:
Allow to disable muting of common path prefixes in the file list. Allow to disable muting of common path prefixes in the file list.
+ +
@ -843,8 +850,6 @@ Upgrades
* Update ASM to 5.0.3. * Update ASM to 5.0.3.
* Update Bouncycastle to 1.51.
* Update CodeMirror to 4.10.0-6-gd0a2dda. * Update CodeMirror to 4.10.0-6-gd0a2dda.
* Update Guava to 18.0. * Update Guava to 18.0.
@ -866,5 +871,3 @@ Upgrades
* Update Parboiled to 1.1.7. * Update Parboiled to 1.1.7.
* Update Pegdown to 1.4.2. * Update Pegdown to 1.4.2.
* Update SSHD to 0.14.0.

View File

@ -9,6 +9,8 @@ Version 2.11.x
[[2_10]] [[2_10]]
Version 2.10.x Version 2.10.x
-------------- --------------
* link:ReleaseNotes-2.10.3.1.html[2.10.3.1]
* link:ReleaseNotes-2.10.3.html[2.10.3]
* link:ReleaseNotes-2.10.2.html[2.10.2] * link:ReleaseNotes-2.10.2.html[2.10.2]
* link:ReleaseNotes-2.10.1.html[2.10.1] * link:ReleaseNotes-2.10.1.html[2.10.1]
* link:ReleaseNotes-2.10.html[2.10] * link:ReleaseNotes-2.10.html[2.10]

View File

@ -19,6 +19,7 @@ import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.nio.charset.StandardCharsets;
public class RestResponse extends HttpResponse { public class RestResponse extends HttpResponse {
@ -29,7 +30,9 @@ public class RestResponse extends HttpResponse {
@Override @Override
public Reader getReader() throws IllegalStateException, IOException { public Reader getReader() throws IllegalStateException, IOException {
if (reader == null && response.getEntity() != null) { if (reader == null && response.getEntity() != null) {
reader = new InputStreamReader(response.getEntity().getContent()); reader =
new InputStreamReader(response.getEntity().getContent(),
StandardCharsets.UTF_8);
reader.skip(JSON_MAGIC.length); reader.skip(JSON_MAGIC.length);
} }
return reader; return reader;

View File

@ -72,6 +72,7 @@ import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -658,21 +659,21 @@ public class ChangeEditIT extends AbstractDaemonTest {
private String newChange(PersonIdent ident) throws Exception { private String newChange(PersonIdent ident) throws Exception {
PushOneCommit push = PushOneCommit push =
pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME, pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
new String(CONTENT_OLD)); new String(CONTENT_OLD, StandardCharsets.UTF_8));
return push.to("refs/for/master").getChangeId(); return push.to("refs/for/master").getChangeId();
} }
private String amendChange(PersonIdent ident, String changeId) throws Exception { private String amendChange(PersonIdent ident, String changeId) throws Exception {
PushOneCommit push = PushOneCommit push =
pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME2, pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME2,
new String(CONTENT_NEW2), changeId); new String(CONTENT_NEW2, StandardCharsets.UTF_8), changeId);
return push.to("refs/for/master").getChangeId(); return push.to("refs/for/master").getChangeId();
} }
private String newChange2(PersonIdent ident) throws Exception { private String newChange2(PersonIdent ident) throws Exception {
PushOneCommit push = PushOneCommit push =
pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME, pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
new String(CONTENT_OLD)); new String(CONTENT_OLD, StandardCharsets.UTF_8));
return push.rm("refs/for/master").getChangeId(); return push.rm("refs/for/master").getChangeId();
} }

View File

@ -210,6 +210,7 @@ public class MyPreferencesScreen extends SettingsScreen {
e.listenTo(legacycidInChangeTable); e.listenTo(legacycidInChangeTable);
e.listenTo(muteCommonPathPrefixes); e.listenTo(muteCommonPathPrefixes);
e.listenTo(diffView); e.listenTo(diffView);
e.listenTo(reviewCategoryStrategy);
} }
@Override @Override

View File

@ -130,7 +130,10 @@ class Actions extends Composite {
a2b(actions, "cherrypick", cherrypick); a2b(actions, "cherrypick", cherrypick);
a2b(actions, "rebase", rebase); a2b(actions, "rebase", rebase);
if (rebase.isVisible()) {
// it is the rebase button in RebaseDialog that the server wants to disable
rebase.setEnabled(true);
}
RevisionInfo revInfo = changeInfo.revision(revision); RevisionInfo revInfo = changeInfo.revision(revision);
for (String id : filterNonCore(actions)) { for (String id : filterNonCore(actions)) {
add(new ActionButton(changeInfo, revInfo, actions.get(id))); add(new ActionButton(changeInfo, revInfo, actions.get(id)));
@ -176,7 +179,16 @@ class Actions extends Composite {
@UiHandler("rebase") @UiHandler("rebase")
void onRebase(@SuppressWarnings("unused") ClickEvent e) { void onRebase(@SuppressWarnings("unused") ClickEvent e) {
RebaseAction.call(rebase, project, changeInfo.branch(), changeId, revision); boolean enabled = true;
RevisionInfo revInfo = changeInfo.revision(revision);
if (revInfo.has_actions()) {
NativeMap<ActionInfo> actions = revInfo.actions();
if (actions.containsKey("rebase")) {
enabled = actions.get("rebase").enabled();
}
}
RebaseAction.call(rebase, project, changeInfo.branch(), changeId, revision,
enabled);
} }
@UiHandler("submit") @UiHandler("submit")

View File

@ -28,16 +28,18 @@ class AddFileAction {
private final RevisionInfo revision; private final RevisionInfo revision;
private final ChangeScreen.Style style; private final ChangeScreen.Style style;
private final Widget addButton; private final Widget addButton;
private final FileTable files;
private AddFileBox addBox; private AddFileBox addBox;
private PopupPanel popup; private PopupPanel popup;
AddFileAction(Change.Id changeId, RevisionInfo revision, AddFileAction(Change.Id changeId, RevisionInfo revision,
ChangeScreen.Style style, Widget addButton) { ChangeScreen.Style style, Widget addButton, FileTable files) {
this.changeId = changeId; this.changeId = changeId;
this.revision = revision; this.revision = revision;
this.style = style; this.style = style;
this.addButton = addButton; this.addButton = addButton;
this.files = files;
} }
public void onEdit() { public void onEdit() {
@ -46,8 +48,9 @@ class AddFileAction {
return; return;
} }
files.unregisterKeys();
if (addBox == null) { if (addBox == null) {
addBox = new AddFileBox(changeId, revision); addBox = new AddFileBox(changeId, revision, files);
} }
addBox.clearPath(); addBox.clearPath();

View File

@ -41,6 +41,7 @@ class AddFileBox extends Composite {
private final Change.Id changeId; private final Change.Id changeId;
private final RevisionInfo revision; private final RevisionInfo revision;
private final FileTable fileTable;
@UiField Button open; @UiField Button open;
@UiField Button cancel; @UiField Button cancel;
@ -48,9 +49,10 @@ class AddFileBox extends Composite {
@UiField(provided = true) @UiField(provided = true)
RemoteSuggestBox path; RemoteSuggestBox path;
AddFileBox(Change.Id changeId, RevisionInfo revision) { AddFileBox(Change.Id changeId, RevisionInfo revision, FileTable files) {
this.changeId = changeId; this.changeId = changeId;
this.revision = revision; this.revision = revision;
this.fileTable = files;
path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision)); path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision));
path.addSelectionHandler(new SelectionHandler<String>() { path.addSelectionHandler(new SelectionHandler<String>() {
@ -63,6 +65,7 @@ class AddFileBox extends Composite {
@Override @Override
public void onClose(CloseEvent<RemoteSuggestBox> event) { public void onClose(CloseEvent<RemoteSuggestBox> event) {
hide(); hide();
fileTable.registerKeys();
} }
}); });
@ -92,6 +95,7 @@ class AddFileBox extends Composite {
@UiHandler("cancel") @UiHandler("cancel")
void onCancel(@SuppressWarnings("unused") ClickEvent e) { void onCancel(@SuppressWarnings("unused") ClickEvent e) {
hide(); hide();
fileTable.registerKeys();
} }
private void hide() { private void hide() {

View File

@ -435,7 +435,7 @@ public class ChangeScreen extends Screen {
reviewMode.setVisible(!editMode.isVisible()); reviewMode.setVisible(!editMode.isVisible());
addFileAction = new AddFileAction( addFileAction = new AddFileAction(
changeId, info.revision(revision), changeId, info.revision(revision),
style, addFile); style, addFile, files);
deleteFileAction = new DeleteFileAction( deleteFileAction = new DeleteFileAction(
changeId, info.revision(revision), changeId, info.revision(revision),
style, addFile); style, addFile);

View File

@ -19,11 +19,13 @@ import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink; import com.google.gerrit.client.GitwebLink;
import com.google.gerrit.client.WebLinkInfo; import com.google.gerrit.client.WebLinkInfo;
import com.google.gerrit.client.actions.ActionInfo;
import com.google.gerrit.client.account.AccountInfo; import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.changes.ChangeInfo; import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo; import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
import com.google.gerrit.client.changes.ChangeInfo.GitPerson; import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo; import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives; import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.CommentLinkProcessor; import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.InlineHyperlink; import com.google.gerrit.client.ui.InlineHyperlink;
@ -46,6 +48,7 @@ import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.clippy.client.CopyableLabel; import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@ -77,6 +80,7 @@ class CommitBox extends Composite {
@UiField HTML text; @UiField HTML text;
@UiField ScrollPanel scroll; @UiField ScrollPanel scroll;
@UiField Button more; @UiField Button more;
@UiField Element parentNotCurrentText;
private boolean expanded; private boolean expanded;
CommitBox() { CommitBox() {
@ -121,7 +125,19 @@ class CommitBox extends Composite {
if (revInfo.commit().parents().length() > 1) { if (revInfo.commit().parents().length() > 1) {
mergeCommit.setVisible(true); mergeCommit.setVisible(true);
} }
setParents(change.project(), revInfo.commit().parents()); setParents(change.project(), revInfo.commit().parents());
// display the orange ball if parent has moved on (not current)
boolean parentNotCurrent = false;
if (revInfo.has_actions()) {
NativeMap<ActionInfo> actions = revInfo.actions();
if (actions.containsKey("rebase")) {
parentNotCurrent = actions.get("rebase").enabled();
}
}
UIObject.setVisible(parentNotCurrentText, parentNotCurrent);
parentNotCurrentText.setInnerText(parentNotCurrent ? "\u25CF" : "");
} }
private void setWebLinks(ChangeInfo change, String revision, private void setWebLinks(ChangeInfo change, String revision,

View File

@ -68,7 +68,7 @@ limitations under the License.
padding: 0; padding: 0;
width: 560px; width: 560px;
} }
.header th { width: 70px; } .header th { width: 72px; }
.header td { white-space: nowrap; } .header td { white-space: nowrap; }
.date { width: 132px; } .date { width: 132px; }
@ -106,6 +106,16 @@ limitations under the License.
height: 16px !important; height: 16px !important;
vertical-align: bottom; vertical-align: bottom;
} }
.parent {
margin-right: 3px;
float: left;
}
.parentNotCurrent {
color: #FFA62F; <!-- orange -->
font-weight: bold;
}
</ui:style> </ui:style>
<g:HTMLPanel> <g:HTMLPanel>
<g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'> <g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'>
@ -163,7 +173,15 @@ limitations under the License.
</td> </td>
</tr> </tr>
<tr ui:field='firstParent' style='display: none'> <tr ui:field='firstParent' style='display: none'>
<th><ui:msg>Parent(s)</ui:msg></th> <th>
<div class='{style.parent}'>
<ui:msg>Parent(s)</ui:msg>
</div>
<div ui:field='parentNotCurrentText'
title='Not current - rebase possible'
class='{style.parentNotCurrent}'
style='display: none' aria-hidden='true'/>
</th>
<td> <td>
<g:FlowPanel ui:field='parentCommits'/> <g:FlowPanel ui:field='parentCommits'/>
</td> </td>

View File

@ -224,6 +224,14 @@ public class FileTable extends FlowPanel {
} }
} }
void unregisterKeys() {
register = false;
if (table != null) {
table.setRegisterKeys(false);
}
}
void registerKeys() { void registerKeys() {
register = true; register = true;

View File

@ -27,10 +27,10 @@ import com.google.gwt.user.client.ui.PopupPanel;
class RebaseAction { class RebaseAction {
static void call(final Button b, final String project, final String branch, static void call(final Button b, final String project, final String branch,
final Change.Id id, final String revision) { final Change.Id id, final String revision, final boolean enabled) {
b.setEnabled(false); b.setEnabled(false);
new RebaseDialog(project, branch, id) { new RebaseDialog(project, branch, id, enabled) {
@Override @Override
public void onSend() { public void onSend() {
ChangeApi.rebase(id.get(), revision, getBase(), new GerritCallback<ChangeInfo>() { ChangeApi.rebase(id.get(), revision, getBase(), new GerritCallback<ChangeInfo>() {

View File

@ -167,6 +167,7 @@ public interface ChangeConstants extends Constants {
String buttonRebaseChangeSend(); String buttonRebaseChangeSend();
String rebaseConfirmMessage(); String rebaseConfirmMessage();
String rebaseNotPossibleMessage();
String rebasePlaceholderMessage(); String rebasePlaceholderMessage();
String rebaseTitle(); String rebaseTitle();

View File

@ -153,6 +153,7 @@ cherryPickTitle = Code Review - Cherry Pick Change to Another Branch
buttonRebaseChangeSend = Rebase buttonRebaseChangeSend = Rebase
rebaseConfirmMessage = Change parent revision rebaseConfirmMessage = Change parent revision
rebaseNotPossibleMessage = Change is already up to date
rebasePlaceholderMessage = (subject, change number, or leave empty) rebasePlaceholderMessage = (subject, change number, or leave empty)
rebaseTitle = Code Review - Rebase Change rebaseTitle = Code Review - Rebase Change

View File

@ -253,9 +253,6 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
} }
col++; col++;
boolean displayInfo = Gerrit.isSignedIn() && Gerrit.getUserAccount()
.getGeneralPreferences().isShowInfoInReviewCategory();
for (int idx = 0; idx < labelNames.size(); idx++, col++) { for (int idx = 0; idx < labelNames.size(); idx++, col++) {
String name = labelNames.get(idx); String name = labelNames.get(idx);
@ -276,7 +273,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
user = label.rejected().name(); user = label.rejected().name();
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy, info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
label.rejected()); label.rejected());
if (displayInfo && info != null) { if (info != null) {
FlowPanel panel = new FlowPanel(); FlowPanel panel = new FlowPanel();
panel.add(new Image(Gerrit.RESOURCES.redNot())); panel.add(new Image(Gerrit.RESOURCES.redNot()));
panel.add(new InlineLabel(info)); panel.add(new InlineLabel(info));
@ -288,7 +285,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
user = label.approved().name(); user = label.approved().name();
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy, info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
label.approved()); label.approved());
if (displayInfo && info != null) { if (info != null) {
FlowPanel panel = new FlowPanel(); FlowPanel panel = new FlowPanel();
panel.add(new Image(Gerrit.RESOURCES.greenCheck())); panel.add(new Image(Gerrit.RESOURCES.greenCheck()));
panel.add(new InlineLabel(info)); panel.add(new InlineLabel(info));
@ -301,7 +298,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy, info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
label.disliked()); label.disliked());
String vstr = String.valueOf(label._value()); String vstr = String.valueOf(label._value());
if (displayInfo && info != null) { if (info != null) {
vstr = vstr + " " + info; vstr = vstr + " " + info;
} }
fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore()); fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
@ -311,7 +308,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy, info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
label.recommended()); label.recommended());
String vstr = "+" + label._value(); String vstr = "+" + label._value();
if (displayInfo && info != null) { if (info != null) {
vstr = vstr + " " + info; vstr = vstr + " " + info;
} }
fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore()); fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore());
@ -322,8 +319,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
} }
fmt.addStyleName(row, col, Gerrit.RESOURCES.css().singleLine()); fmt.addStyleName(row, col, Gerrit.RESOURCES.css().singleLine());
if ((!displayInfo || reviewCategoryStrategy == ReviewCategoryStrategy.ABBREV) if (user != null) {
&& user != null) {
// Some web browsers ignore the embedded newline; some like it; // Some web browsers ignore the embedded newline; some like it;
// so we include a space before the newline to accommodate both. // so we include a space before the newline to accommodate both.
fmt.getElement(row, col).setTitle(name + " \nby " + user); fmt.getElement(row, col).setTitle(name + " \nby " + user);

View File

@ -347,7 +347,7 @@ public class EditScreen extends Screen {
private void initEditor(HttpResponse<NativeString> file) { private void initEditor(HttpResponse<NativeString> file) {
ModeInfo mode = null; ModeInfo mode = null;
String content = ""; String content = "";
if (file != null) { if (file != null && file.getResult() != null) {
content = file.getResult().asString(); content = file.getResult().asString();
if (prefs.syntaxHighlighting()) { if (prefs.syntaxHighlighting()) {
mode = ModeInfo.findMode(file.getContentType(), path); mode = ModeInfo.findMode(file.getContentType(), path);

View File

@ -36,10 +36,12 @@ public abstract class RebaseDialog extends CommentedActionDialog {
private final SuggestBox base; private final SuggestBox base;
private final CheckBox cb; private final CheckBox cb;
private List<ChangeInfo> changes; private List<ChangeInfo> changes;
private final boolean sendEnabled;
public RebaseDialog(final String project, final String branch, public RebaseDialog(final String project, final String branch,
final Change.Id changeId) { final Change.Id changeId, final boolean sendEnabled) {
super(Util.C.rebaseTitle(), null); super(Util.C.rebaseTitle(), null);
this.sendEnabled = sendEnabled;
sendButton.setText(Util.C.buttonRebaseChangeSend()); sendButton.setText(Util.C.buttonRebaseChangeSend());
// create the suggestion box // create the suggestion box
@ -63,7 +65,6 @@ public abstract class RebaseDialog extends CommentedActionDialog {
done.onSuggestionsReady(request, new Response(suggestions)); done.onSuggestionsReady(request, new Response(suggestions));
} }
}); });
base.setEnabled(false);
base.getElement().setAttribute("placeholder", base.getElement().setAttribute("placeholder",
Util.C.rebasePlaceholderMessage()); Util.C.rebasePlaceholderMessage());
base.setStyleName(Gerrit.RESOURCES.css().rebaseSuggestBox()); base.setStyleName(Gerrit.RESOURCES.css().rebaseSuggestBox());
@ -81,13 +82,11 @@ public abstract class RebaseDialog extends CommentedActionDialog {
@Override @Override
public void onSuccess(ChangeList result) { public void onSuccess(ChangeList result) {
changes = Natives.asList(result); changes = Natives.asList(result);
base.setEnabled(true); updateControls(true);
base.setFocus(true);
} }
}); });
} else { } else {
base.setEnabled(false); updateControls(false);
sendButton.setFocus(true);
} }
} }
}); });
@ -102,7 +101,26 @@ public abstract class RebaseDialog extends CommentedActionDialog {
public void center() { public void center() {
super.center(); super.center();
GlobalKey.dialog(this); GlobalKey.dialog(this);
updateControls(false);
}
private void updateControls(boolean changeParentEnabled) {
if (changeParentEnabled) {
sendButton.setTitle(null);
sendButton.setEnabled(true);
base.setEnabled(true);
base.setFocus(true);
} else {
base.setEnabled(false);
sendButton.setEnabled(sendEnabled);
if (sendEnabled) {
sendButton.setTitle(null);
sendButton.setFocus(true); sendButton.setFocus(true);
} else {
sendButton.setTitle(Util.C.rebaseNotPossibleMessage());
cancelButton.setFocus(true);
}
}
} }
public String getBase() { public String getBase() {

View File

@ -71,7 +71,8 @@ class UrlModule extends ServletModule {
} }
serve("/cat/*").with(CatServlet.class); serve("/cat/*").with(CatServlet.class);
if (authConfig.getAuthType() != AuthType.OAUTH) { if (authConfig.getAuthType() != AuthType.OAUTH &&
authConfig.getAuthType() != AuthType.OPENID) {
serve("/logout").with(HttpLogoutServlet.class); serve("/logout").with(HttpLogoutServlet.class);
serve("/signout").with(HttpLogoutServlet.class); serve("/signout").with(HttpLogoutServlet.class);
} }

View File

@ -363,16 +363,19 @@ class GitWebServlet extends HttpServlet {
} }
final Map<String, String> params = getParameters(req); final Map<String, String> params = getParameters(req);
if (deniedActions.contains(params.get("a"))) { String a = params.get("a");
if (a != null) {
if (deniedActions.contains(a)) {
rsp.sendError(HttpServletResponse.SC_FORBIDDEN); rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return; return;
} }
if (params.get("a").equals(PROJECT_LIST_ACTION)) { if (a.equals(PROJECT_LIST_ACTION)) {
rsp.sendRedirect(req.getContextPath() + "/#" + PageLinks.ADMIN_PROJECTS rsp.sendRedirect(req.getContextPath() + "/#" + PageLinks.ADMIN_PROJECTS
+ "?filter=" + Url.encode(params.get("pf") + "/")); + "?filter=" + Url.encode(params.get("pf") + "/"));
return; return;
} }
}
String name = params.get("p"); String name = params.get("p");
if (name == null) { if (name == null) {

View File

@ -583,6 +583,9 @@ class HttpPluginServlet extends HttpServlet
if ("application/octet-stream".equals(contentType) if ("application/octet-stream".equals(contentType)
&& entry.getName().endsWith(".js")) { && entry.getName().endsWith(".js")) {
contentType = "application/javascript"; contentType = "application/javascript";
} else if ("application/x-pointplus".equals(contentType)
&& entry.getName().endsWith(".css")) {
contentType = "text/css";
} }
} }

View File

@ -22,6 +22,8 @@ import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo; import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.auth.oauth.OAuthVerifier; import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.WebSession; import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountException; import com.google.gerrit.server.account.AccountException;
@ -36,8 +38,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -53,17 +53,20 @@ class OAuthSession {
private final String state; private final String state;
private final DynamicItem<WebSession> webSession; private final DynamicItem<WebSession> webSession;
private final AccountManager accountManager; private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
private OAuthServiceProvider serviceProvider; private OAuthServiceProvider serviceProvider;
private OAuthToken token; private OAuthToken token;
private OAuthUserInfo user; private OAuthUserInfo user;
private String redirectUrl; private String redirectToken;
@Inject @Inject
OAuthSession(DynamicItem<WebSession> webSession, OAuthSession(DynamicItem<WebSession> webSession,
AccountManager accountManager) { AccountManager accountManager,
CanonicalWebUrl urlProvider) {
this.state = generateRandomState(); this.state = generateRandomState();
this.webSession = webSession; this.webSession = webSession;
this.accountManager = accountManager; this.accountManager = accountManager;
this.urlProvider = urlProvider;
} }
boolean isLoggedIn() { boolean isLoggedIn() {
@ -95,7 +98,7 @@ class OAuthSession {
if (isLoggedIn()) { if (isLoggedIn()) {
log.debug("Login-SUCCESS " + this); log.debug("Login-SUCCESS " + this);
authenticateAndRedirect(response); authenticateAndRedirect(request, response);
return true; return true;
} else { } else {
response.sendError(SC_UNAUTHORIZED); response.sendError(SC_UNAUTHORIZED);
@ -103,15 +106,22 @@ class OAuthSession {
} }
} else { } else {
log.debug("Login-PHASE1 " + this); log.debug("Login-PHASE1 " + this);
redirectUrl = request.getRequestURI(); redirectToken = request.getRequestURI();
// We are here in content of filter.
// Due to this Jetty limitation:
// https://bz.apache.org/bugzilla/show_bug.cgi?id=28323
// we cannot use LoginUrlToken.getToken() method,
// because it relies on getPathInfo() and it is always null here.
redirectToken = redirectToken.substring(
request.getContextPath().length());
response.sendRedirect(oauth.getAuthorizationUrl() + response.sendRedirect(oauth.getAuthorizationUrl() +
"&state=" + state); "&state=" + state);
return false; return false;
} }
} }
private void authenticateAndRedirect(HttpServletResponse rsp) private void authenticateAndRedirect(HttpServletRequest req,
throws IOException { HttpServletResponse rsp) throws IOException {
com.google.gerrit.server.account.AuthRequest areq = com.google.gerrit.server.account.AuthRequest areq =
new com.google.gerrit.server.account.AuthRequest(user.getExternalId()); new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
AuthResult arsp; AuthResult arsp;
@ -164,16 +174,17 @@ class OAuthSession {
} }
webSession.get().login(arsp, true); webSession.get().login(arsp, true);
String suffix = redirectUrl.substring( String suffix = redirectToken.substring(
OAuthWebFilter.GERRIT_LOGIN.length() + 1); OAuthWebFilter.GERRIT_LOGIN.length() + 1);
suffix = URLDecoder.decode(suffix, StandardCharsets.UTF_8.name()); StringBuilder rdr = new StringBuilder(urlProvider.get(req));
rsp.sendRedirect(suffix); rdr.append(Url.decode(suffix));
rsp.sendRedirect(rdr.toString());
} }
void logout() { void logout() {
token = null; token = null;
user = null; user = null;
redirectUrl = null; redirectToken = null;
serviceProvider = null; serviceProvider = null;
} }

View File

@ -89,18 +89,22 @@ class OAuthWebFilter implements Filter {
FilterChain chain) throws IOException, ServletException { FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false); HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
OAuthSession oauthSession = oauthSessionProvider.get();
if (currentUserProvider.get().isIdentifiedUser()) { if (currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) { if (httpSession != null) {
httpSession.invalidate(); httpSession.invalidate();
} }
chain.doFilter(request, response); chain.doFilter(request, response);
return; return;
} else {
if (oauthSession.isLoggedIn()) {
oauthSession.logout();
}
} }
HttpServletResponse httpResponse = (HttpServletResponse) response; HttpServletResponse httpResponse = (HttpServletResponse) response;
String provider = httpRequest.getParameter("provider"); String provider = httpRequest.getParameter("provider");
OAuthSession oauthSession = oauthSessionProvider.get();
OAuthServiceProvider service = ssoProvider == null OAuthServiceProvider service = ssoProvider == null
? oauthSession.getServiceProvider() ? oauthSession.getServiceProvider()
: ssoProvider; : ssoProvider;

View File

@ -12,6 +12,7 @@ java_library(
'//gerrit-server:server', '//gerrit-server:server',
'//lib:guava', '//lib:guava',
'//lib:gwtorm', '//lib:gwtorm',
'//lib/commons:codec',
'//lib/guice:guice', '//lib/guice:guice',
'//lib/guice:guice-servlet', '//lib/guice:guice-servlet',
'//lib/jgit:jgit', '//lib/jgit:jgit',

View File

@ -22,11 +22,14 @@ import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.openid.OpenIdUrls; import com.google.gerrit.common.auth.openid.OpenIdUrls;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.HtmlDomUtil; import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.LoginUrlToken; import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.template.SiteHeaderFooter; import com.google.gerrit.httpd.template.SiteHeaderFooter;
import com.google.gerrit.reviewdb.client.AuthType; 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.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
@ -61,10 +64,13 @@ class LoginForm extends HttpServlet {
private final ImmutableSet<String> suggestProviders; private final ImmutableSet<String> suggestProviders;
private final Provider<String> urlProvider; private final Provider<String> urlProvider;
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
private final OpenIdServiceImpl impl; private final OpenIdServiceImpl impl;
private final int maxRedirectUrlLength; private final int maxRedirectUrlLength;
private final String ssoUrl; private final String ssoUrl;
private final SiteHeaderFooter header; private final SiteHeaderFooter header;
private final Provider<CurrentUser> currentUserProvider;
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
@Inject @Inject
LoginForm( LoginForm(
@ -72,13 +78,19 @@ class LoginForm extends HttpServlet {
@GerritServerConfig Config config, @GerritServerConfig Config config,
AuthConfig authConfig, AuthConfig authConfig,
OpenIdServiceImpl impl, OpenIdServiceImpl impl,
SiteHeaderFooter header) { SiteHeaderFooter header,
Provider<OAuthSessionOverOpenID> oauthSessionProvider,
Provider<CurrentUser> currentUserProvider,
DynamicMap<OAuthServiceProvider> oauthServiceProviders) {
this.urlProvider = urlProvider; this.urlProvider = urlProvider;
this.impl = impl; this.impl = impl;
this.header = header; this.header = header;
this.maxRedirectUrlLength = config.getInt( this.maxRedirectUrlLength = config.getInt(
"openid", "maxRedirectUrlLength", "openid", "maxRedirectUrlLength",
10); 10);
this.oauthSessionProvider = oauthSessionProvider;
this.currentUserProvider = currentUserProvider;
this.oauthServiceProviders = oauthServiceProviders;
if (urlProvider == null || Strings.isNullOrEmpty(urlProvider.get())) { if (urlProvider == null || Strings.isNullOrEmpty(urlProvider.get())) {
log.error("gerrit.canonicalWebUrl must be set in gerrit.config"); log.error("gerrit.canonicalWebUrl must be set in gerrit.config");
@ -152,7 +164,23 @@ class LoginForm extends HttpServlet {
mode = SignInMode.SIGN_IN; mode = SignInMode.SIGN_IN;
} }
OAuthServiceProvider oauthProvider = lookupOAuthServiceProvider(id);
if (oauthProvider == null) {
discover(req, res, link, id, remember, token, mode); discover(req, res, link, id, remember, token, mode);
} else {
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
if (!currentUserProvider.get().isIdentifiedUser()
&& oauthSession.isLoggedIn()) {
oauthSession.logout();
}
if ((isGerritLogin(req)
|| oauthSession.isOAuthFinal(req))
&& !oauthSession.isLoggedIn()) {
oauthSession.setServiceProvider(oauthProvider);
oauthSession.login(req, res, oauthProvider);
}
}
} }
private void discover(HttpServletRequest req, HttpServletResponse res, private void discover(HttpServletRequest req, HttpServletResponse res,
@ -267,6 +295,20 @@ class LoginForm extends HttpServlet {
} }
a.setAttribute("href", u.toString()); a.setAttribute("href", u.toString());
} }
// OAuth: Add plugin based providers
Element providers = HtmlDomUtil.find(doc, "providers");
Set<String> plugins = oauthServiceProviders.plugins();
for (String pluginName : plugins) {
Map<String, Provider<OAuthServiceProvider>> m =
oauthServiceProviders.byPlugin(pluginName);
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
: m.entrySet()) {
addProvider(providers, pluginName, e.getKey(),
e.getValue().get().getName());
}
}
sendHtml(res, doc); sendHtml(res, doc);
} }
@ -285,6 +327,38 @@ class LoginForm extends HttpServlet {
} }
} }
private static void addProvider(Element form, String pluginName,
String id, String serviceName) {
Element div = form.getOwnerDocument().createElement("div");
div.setAttribute("id", id);
Element hyperlink = form.getOwnerDocument().createElement("a");
hyperlink.setAttribute("href", String.format("?id=%s_%s",
pluginName, id));
hyperlink.setTextContent(serviceName +
" (" + pluginName + " plugin)");
div.appendChild(hyperlink);
form.appendChild(div);
}
private OAuthServiceProvider lookupOAuthServiceProvider(String providerId) {
if (providerId.startsWith("http://")) {
providerId = providerId.substring("http://".length());
}
Set<String> plugins = oauthServiceProviders.plugins();
for (String pluginName : plugins) {
Map<String, Provider<OAuthServiceProvider>> m =
oauthServiceProviders.byPlugin(pluginName);
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
: m.entrySet()) {
if (providerId.equals(
String.format("%s_%s", pluginName, e.getKey()))) {
return e.getValue().get();
}
}
}
return null;
}
private static String getLastId(HttpServletRequest req) { private static String getLastId(HttpServletRequest req) {
Cookie[] cookies = req.getCookies(); Cookie[] cookies = req.getCookies();
if (cookies != null) { if (cookies != null) {
@ -296,4 +370,9 @@ class LoginForm extends HttpServlet {
} }
return null; return null;
} }
private static boolean isGerritLogin(HttpServletRequest request) {
return request.getRequestURI().indexOf(
OAuthSessionOverOpenID.GERRIT_LOGIN) >= 0;
}
} }

View File

@ -0,0 +1,55 @@
// 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.
package com.google.gerrit.httpd.auth.openid;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.HttpLogoutServlet;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
class OAuthOverOpenIDLogoutServlet extends HttpLogoutServlet {
private static final long serialVersionUID = 1L;
private final Provider<OAuthSessionOverOpenID> oauthSession;
@Inject
OAuthOverOpenIDLogoutServlet(AuthConfig authConfig,
DynamicItem<WebSession> webSession,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
AuditService audit,
Provider<OAuthSessionOverOpenID> oauthSession) {
super(authConfig, webSession, urlProvider, audit);
this.oauthSession = oauthSession;
}
@Override
protected void doLogout(HttpServletRequest req, HttpServletResponse rsp)
throws IOException {
super.doLogout(req, rsp);
oauthSession.get().logout();
}
}

View File

@ -0,0 +1,216 @@
// 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.
package com.google.gerrit.httpd.auth.openid;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.servlet.SessionScoped;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** OAuth protocol implementation */
@SessionScoped
class OAuthSessionOverOpenID {
static final String GERRIT_LOGIN = "/login";
private static final Logger log = LoggerFactory.getLogger(
OAuthSessionOverOpenID.class);
private static final SecureRandom randomState = newRandomGenerator();
private final String state;
private final DynamicItem<WebSession> webSession;
private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
private OAuthServiceProvider serviceProvider;
private OAuthToken token;
private OAuthUserInfo user;
private String redirectToken;
@Inject
OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
AccountManager accountManager,
CanonicalWebUrl urlProvider) {
this.state = generateRandomState();
this.webSession = webSession;
this.accountManager = accountManager;
this.urlProvider = urlProvider;
}
boolean isLoggedIn() {
return token != null && user != null;
}
boolean isOAuthFinal(HttpServletRequest request) {
return Strings.emptyToNull(request.getParameter("code")) != null;
}
boolean login(HttpServletRequest request, HttpServletResponse response,
OAuthServiceProvider oauth) throws IOException {
if (isLoggedIn()) {
return true;
}
log.debug("Login " + this);
if (isOAuthFinal(request)) {
if (!checkState(request)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return false;
}
log.debug("Login-Retrieve-User " + this);
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
user = oauth.getUserInfo(token);
if (isLoggedIn()) {
log.debug("Login-SUCCESS " + this);
authenticateAndRedirect(request, response);
return true;
} else {
response.sendError(SC_UNAUTHORIZED);
return false;
}
} else {
log.debug("Login-PHASE1 " + this);
redirectToken = LoginUrlToken.getToken(request);
response.sendRedirect(oauth.getAuthorizationUrl() +
"&state=" + state);
return false;
}
}
private void authenticateAndRedirect(HttpServletRequest req,
HttpServletResponse rsp) throws IOException {
com.google.gerrit.server.account.AuthRequest areq =
new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
AuthResult arsp = null;
try {
String claimedIdentifier = user.getClaimedIdentity();
Account.Id actualId = accountManager.lookup(user.getExternalId());
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
if (claimedId != null && actualId != null) {
if (claimedId.equals(actualId)) {
// Both link to the same account, that's what we expected.
} else {
// This is (for now) a fatal error. There are two records
// for what might be the same user.
//
log.error("OAuth accounts disagree over user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
+ "\n" + " Delgate ID: " + actualId + " is "
+ user.getExternalId());
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
} else if (claimedId != null && actualId == null) {
// Claimed account already exists: link to it.
//
try {
accountManager.link(claimedId, areq);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
+ " to user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
}
}
areq.setUserName(user.getUserName());
areq.setEmailAddress(user.getEmailAddress());
areq.setDisplayName(user.getDisplayName());
arsp = accountManager.authenticate(areq);
} catch (AccountException e) {
log.error("Unable to authenticate user \"" + user + "\"", e);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
webSession.get().login(arsp, true);
StringBuilder rdr = new StringBuilder(urlProvider.get(req));
rdr.append(Url.decode(redirectToken));
rsp.sendRedirect(rdr.toString());
}
void logout() {
token = null;
user = null;
redirectToken = null;
serviceProvider = null;
}
private boolean checkState(ServletRequest request) {
String s = Strings.nullToEmpty(request.getParameter("state"));
if (!s.equals(state)) {
log.error("Illegal request state '" + s + "' on OAuthProtocol " + this);
return false;
}
return true;
}
private static SecureRandom newRandomGenerator() {
try {
return SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(
"No SecureRandom available for GitHub authentication", e);
}
}
private static String generateRandomState() {
byte[] state = new byte[32];
randomState.nextBytes(state);
return Base64.encodeBase64URLSafeString(state);
}
@Override
public String toString() {
return "OAuthSession [token=" + token + ", user=" + user + "]";
}
public void setServiceProvider(OAuthServiceProvider provider) {
this.serviceProvider = provider;
}
public OAuthServiceProvider getServiceProvider() {
return serviceProvider;
}
}

View File

@ -0,0 +1,115 @@
// 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.
package com.google.gerrit.httpd.auth.openid;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.SortedMap;
import java.util.SortedSet;
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/** OAuth web filter uses active OAuth session to perform OAuth requests */
@Singleton
class OAuthWebFilterOverOpenID implements Filter {
static final String GERRIT_LOGIN = "/login";
private final Provider<CurrentUser> currentUserProvider;
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
private OAuthServiceProvider ssoProvider;
@Inject
OAuthWebFilterOverOpenID(Provider<CurrentUser> currentUserProvider,
DynamicMap<OAuthServiceProvider> oauthServiceProviders,
Provider<OAuthSessionOverOpenID> oauthSessionProvider) {
this.currentUserProvider = currentUserProvider;
this.oauthServiceProviders = oauthServiceProviders;
this.oauthSessionProvider = oauthSessionProvider;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
pickSSOServiceProvider();
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
if (currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) {
httpSession.invalidate();
}
chain.doFilter(request, response);
return;
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
OAuthServiceProvider service = ssoProvider == null
? oauthSession.getServiceProvider()
: ssoProvider;
if ((isGerritLogin(httpRequest)
|| oauthSession.isOAuthFinal(httpRequest))
&& !oauthSession.isLoggedIn()) {
if (service == null) {
throw new IllegalStateException("service is unknown");
}
oauthSession.setServiceProvider(service);
oauthSession.login(httpRequest, httpResponse, service);
} else {
chain.doFilter(httpRequest, response);
}
}
private void pickSSOServiceProvider() {
SortedSet<String> plugins = oauthServiceProviders.plugins();
if (plugins.size() == 1) {
SortedMap<String, Provider<OAuthServiceProvider>> services =
oauthServiceProviders.byPlugin(Iterables.getOnlyElement(plugins));
if (services.size() == 1) {
ssoProvider = Iterables.getOnlyElement(services.values()).get();
}
}
}
private static boolean isGerritLogin(HttpServletRequest request) {
return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
}
}

View File

@ -14,6 +14,8 @@
package com.google.gerrit.httpd.auth.openid; package com.google.gerrit.httpd.auth.openid;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.inject.servlet.ServletModule; import com.google.inject.servlet.ServletModule;
/** Servlets related to OpenID authentication. */ /** Servlets related to OpenID authentication. */
@ -21,9 +23,12 @@ public class OpenIdModule extends ServletModule {
@Override @Override
protected void configureServlets() { protected void configureServlets() {
serve("/login", "/login/*").with(LoginForm.class); serve("/login", "/login/*").with(LoginForm.class);
serve("/logout").with(OAuthOverOpenIDLogoutServlet.class);
filter("/oauth").through(OAuthWebFilterOverOpenID.class);
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class); serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class); serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class);
filter("/").through(XrdsFilter.class); filter("/").through(XrdsFilter.class);
bind(OpenIdServiceImpl.class); bind(OpenIdServiceImpl.class);
DynamicMap.mapOf(binder(), OAuthServiceProvider.class);
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -86,6 +86,36 @@ public class RefNames {
return r.toString(); return r.toString();
} }
/**
* Returns reference for this change edit with sharded user and change number:
* refs/users/UU/UUUU/edit-CCCC/P.
*
* @param accountId account id
* @param changeId change number
* @param psId patch set number
* @return reference for this change edit
*/
public static String refsEdit(Account.Id accountId, Change.Id changeId,
PatchSet.Id psId) {
return refsEditPrefix(accountId, changeId) + psId.get();
}
/**
* Returns reference prefix for this change edit with sharded user and
* change number: refs/users/UU/UUUU/edit-CCCC/.
*
* @param accountId account id
* @param changeId change number
* @return reference prefix for this change edit
*/
public static String refsEditPrefix(Account.Id accountId, Change.Id changeId) {
return new StringBuilder(refsUsers(accountId))
.append("/edit-")
.append(changeId.get())
.append("/")
.toString();
}
private RefNames() { private RefNames() {
} }
} }

View File

@ -22,6 +22,7 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription; import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction; import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.extensions.webui.UiActions; import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -36,13 +37,16 @@ import java.util.Map;
public class ActionJson { public class ActionJson {
private final Revisions revisions; private final Revisions revisions;
private final DynamicMap<RestView<ChangeResource>> changeViews; private final DynamicMap<RestView<ChangeResource>> changeViews;
private final RebaseChange rebaseChange;
@Inject @Inject
ActionJson( ActionJson(
Revisions revisions, Revisions revisions,
DynamicMap<RestView<ChangeResource>> changeViews) { DynamicMap<RestView<ChangeResource>> changeViews,
RebaseChange rebaseChange) {
this.revisions = revisions; this.revisions = revisions;
this.changeViews = changeViews; this.changeViews = changeViews;
this.rebaseChange = rebaseChange;
} }
public Map<String, ActionInfo> format(RevisionResource rsrc) { public Map<String, ActionInfo> format(RevisionResource rsrc) {
@ -69,7 +73,7 @@ public class ActionJson {
Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser()); Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser());
for (UiAction.Description d : UiActions.from( for (UiAction.Description d : UiActions.from(
changeViews, changeViews,
new ChangeResource(ctl), new ChangeResource(ctl, rebaseChange),
userProvider)) { userProvider)) {
out.put(d.getId(), new ActionInfo(d)); out.put(d.getId(), new ActionInfo(d));
} }

View File

@ -92,6 +92,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil; import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.WebLinks; import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.AccountLoader; import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.git.LabelNormalizer; import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListNotAvailableException; import com.google.gerrit.server.patch.PatchListNotAvailableException;
@ -142,6 +143,7 @@ public class ChangeJson {
private final PatchLineCommentsUtil plcUtil; private final PatchLineCommentsUtil plcUtil;
private final Provider<ConsistencyChecker> checkerProvider; private final Provider<ConsistencyChecker> checkerProvider;
private final ActionJson actionJson; private final ActionJson actionJson;
private final RebaseChange rebaseChange;
private AccountLoader accountLoader; private AccountLoader accountLoader;
private FixInput fix; private FixInput fix;
@ -163,7 +165,8 @@ public class ChangeJson {
ChangeMessagesUtil cmUtil, ChangeMessagesUtil cmUtil,
PatchLineCommentsUtil plcUtil, PatchLineCommentsUtil plcUtil,
Provider<ConsistencyChecker> checkerProvider, Provider<ConsistencyChecker> checkerProvider,
ActionJson actionJson) { ActionJson actionJson,
RebaseChange rebaseChange) {
this.db = db; this.db = db;
this.labelNormalizer = ln; this.labelNormalizer = ln;
this.userProvider = user; this.userProvider = user;
@ -180,6 +183,7 @@ public class ChangeJson {
this.plcUtil = plcUtil; this.plcUtil = plcUtil;
this.checkerProvider = checkerProvider; this.checkerProvider = checkerProvider;
this.actionJson = actionJson; this.actionJson = actionJson;
this.rebaseChange = rebaseChange;
options = EnumSet.noneOf(ListChangesOption.class); options = EnumSet.noneOf(ListChangesOption.class);
} }
@ -890,7 +894,7 @@ public class ChangeJson {
&& userProvider.get().isIdentifiedUser()) { && userProvider.get().isIdentifiedUser()) {
actionJson.addRevisionActions(out, actionJson.addRevisionActions(out,
new RevisionResource(new ChangeResource(ctl), in)); new RevisionResource(new ChangeResource(ctl, rebaseChange), in));
} }
if (has(DRAFT_COMMENTS) if (has(DRAFT_COMMENTS)

View File

@ -23,6 +23,7 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
@ -36,13 +37,16 @@ public class ChangeResource implements RestResource, HasETag {
new TypeLiteral<RestView<ChangeResource>>() {}; new TypeLiteral<RestView<ChangeResource>>() {};
private final ChangeControl control; private final ChangeControl control;
private final RebaseChange rebaseChange;
public ChangeResource(ChangeControl control) { public ChangeResource(ChangeControl control, RebaseChange rebaseChange) {
this.control = control; this.control = control;
this.rebaseChange = rebaseChange;
} }
protected ChangeResource(ChangeResource copy) { protected ChangeResource(ChangeResource copy) {
this.control = copy.control; this.control = copy.control;
this.rebaseChange = copy.rebaseChange;
} }
public ChangeControl getControl() { public ChangeControl getControl() {
@ -65,7 +69,8 @@ public class ChangeResource implements RestResource, HasETag {
.putInt(getChange().getRowVersion()) .putInt(getChange().getRowVersion())
.putInt(user.isIdentifiedUser() .putInt(user.isIdentifiedUser()
? ((IdentifiedUser) user).getAccountId().get() ? ((IdentifiedUser) user).getAccountId().get()
: 0); : 0)
.putBoolean(rebaseChange != null && rebaseChange.canRebase(this));
byte[] buf = new byte[20]; byte[] buf = new byte[20];
ObjectId noteId; ObjectId noteId;
try { try {

View File

@ -26,6 +26,7 @@ import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.index.ChangeIndexer; import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
@ -49,6 +50,7 @@ public class ChangesCollection implements
private final ChangeUtil changeUtil; private final ChangeUtil changeUtil;
private final CreateChange createChange; private final CreateChange createChange;
private final ChangeIndexer changeIndexer; private final ChangeIndexer changeIndexer;
private final RebaseChange rebaseChange;
@Inject @Inject
ChangesCollection( ChangesCollection(
@ -58,7 +60,8 @@ public class ChangesCollection implements
DynamicMap<RestView<ChangeResource>> views, DynamicMap<RestView<ChangeResource>> views,
ChangeUtil changeUtil, ChangeUtil changeUtil,
CreateChange createChange, CreateChange createChange,
ChangeIndexer changeIndexer) { ChangeIndexer changeIndexer,
RebaseChange rebaseChange) {
this.user = user; this.user = user;
this.changeControlFactory = changeControlFactory; this.changeControlFactory = changeControlFactory;
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
@ -66,6 +69,7 @@ public class ChangesCollection implements
this.changeUtil = changeUtil; this.changeUtil = changeUtil;
this.createChange = createChange; this.createChange = createChange;
this.changeIndexer = changeIndexer; this.changeIndexer = changeIndexer;
this.rebaseChange = rebaseChange;
} }
@Override @Override
@ -102,7 +106,7 @@ public class ChangesCollection implements
} catch (NoSuchChangeException e) { } catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(id); throw new ResourceNotFoundException(id);
} }
return new ChangeResource(control); return new ChangeResource(control, rebaseChange);
} }
public ChangeResource parse(Change.Id id) public ChangeResource parse(Change.Id id)
@ -112,7 +116,7 @@ public class ChangesCollection implements
} }
public ChangeResource parse(ChangeControl control) { public ChangeResource parse(ChangeControl control) {
return new ChangeResource(control); return new ChangeResource(control, rebaseChange);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -21,6 +21,7 @@ import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.restapi.ETagView; import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.query.change.InternalChangeQuery;
@ -39,14 +40,17 @@ public class GetRevisionActions implements ETagView<RevisionResource> {
private final ActionJson delegate; private final ActionJson delegate;
private final Provider<InternalChangeQuery> queryProvider; private final Provider<InternalChangeQuery> queryProvider;
private final Config config; private final Config config;
private final RebaseChange rebaseChange;
@Inject @Inject
GetRevisionActions( GetRevisionActions(
ActionJson delegate, ActionJson delegate,
Provider<InternalChangeQuery> queryProvider, Provider<InternalChangeQuery> queryProvider,
@GerritServerConfig Config config) { @GerritServerConfig Config config,
RebaseChange rebaseChange) {
this.delegate = delegate; this.delegate = delegate;
this.queryProvider = queryProvider; this.queryProvider = queryProvider;
this.config = config; this.config = config;
this.rebaseChange = rebaseChange;
} }
@Override @Override
@ -65,7 +69,7 @@ public class GetRevisionActions implements ETagView<RevisionResource> {
CurrentUser user = rsrc.getControl().getCurrentUser(); CurrentUser user = rsrc.getControl().getCurrentUser();
try { try {
for (ChangeData c : queryProvider.get().byTopicOpen(topic)) { for (ChangeData c : queryProvider.get().byTopicOpen(topic)) {
new ChangeResource(c.changeControl()).prepareETag(h, user); new ChangeResource(c.changeControl(), rebaseChange).prepareETag(h, user);
} }
} catch (OrmException e){ } catch (OrmException e){
throw new OrmRuntimeException(e); throw new OrmRuntimeException(e);

View File

@ -213,13 +213,19 @@ public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
@Override @Override
public UiAction.Description getDescription(RevisionResource resource) { public UiAction.Description getDescription(RevisionResource resource) {
return new UiAction.Description() UiAction.Description descr = new UiAction.Description()
.setLabel("Rebase") .setLabel("Rebase")
.setTitle("Rebase onto tip of branch or parent change") .setTitle("Rebase onto tip of branch or parent change")
.setVisible(resource.getChange().getStatus().isOpen() .setVisible(resource.getChange().getStatus().isOpen()
&& resource.isCurrent() && resource.isCurrent()
&& resource.getControl().canRebase() && resource.getControl().canRebase()
&& hasOneParent(resource.getPatchSet().getId())); && hasOneParent(resource.getPatchSet().getId()));
if (descr.isVisible()) {
// Disable the rebase button in the RebaseDialog if
// the change cannot be rebased.
descr.setEnabled(rebaseChange.get().canRebase(resource));
}
return descr;
} }
public static class CurrentRevision implements public static class CurrentRevision implements

View File

@ -22,11 +22,13 @@ import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage; import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor; import com.google.gerrit.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.PatchSetInserter; import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy; import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
import com.google.gerrit.server.change.RevisionResource; import com.google.gerrit.server.change.RevisionResource;
@ -356,10 +358,21 @@ public class RebaseChange {
return objectId; return objectId;
} }
public boolean canRebase(ChangeResource r) {
Change c = r.getChange();
return canRebase(c.getProject(), c.currentPatchSetId(), c.getDest());
}
public boolean canRebase(RevisionResource r) { public boolean canRebase(RevisionResource r) {
return canRebase(r.getChange().getProject(),
r.getPatchSet().getId(), r.getChange().getDest());
}
public boolean canRebase(Project.NameKey project,
PatchSet.Id patchSetId, Branch.NameKey branch) {
Repository git; Repository git;
try { try {
git = gitManager.openRepository(r.getChange().getProject()); git = gitManager.openRepository(project);
} catch (RepositoryNotFoundException err) { } catch (RepositoryNotFoundException err) {
return false; return false;
} catch (IOException err) { } catch (IOException err) {
@ -367,9 +380,9 @@ public class RebaseChange {
} }
try { try {
findBaseRevision( findBaseRevision(
r.getPatchSet().getId(), patchSetId,
db.get(), db.get(),
r.getChange().getDest(), branch,
git, git,
null, null,
null, null,

View File

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
@ -71,7 +72,7 @@ public class ChangeEdit {
} }
public String getRefName() { public String getRefName() {
return ChangeEditUtil.editRefName(user.getAccountId(), change.getId(), return RefNames.refsEdit(user.getAccountId(), change.getId(),
basePatchSet.getId()); basePatchSet.getId());
} }

View File

@ -16,8 +16,6 @@ package com.google.gerrit.server.edit;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.edit.ChangeEditUtil.editRefName;
import static com.google.gerrit.server.edit.ChangeEditUtil.editRefPrefix;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.base.Strings; import com.google.common.base.Strings;
@ -30,6 +28,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project; 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.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
@ -116,7 +115,7 @@ public class ChangeEditModifier {
} }
IdentifiedUser me = (IdentifiedUser) currentUser.get(); IdentifiedUser me = (IdentifiedUser) currentUser.get();
String refPrefix = editRefPrefix(me.getAccountId(), change.getId()); String refPrefix = RefNames.refsEditPrefix(me.getAccountId(), change.getId());
try (Repository repo = gitManager.openRepository(change.getProject())) { try (Repository repo = gitManager.openRepository(change.getProject())) {
Map<String, Ref> refs = repo.getRefDatabase().getRefs(refPrefix); Map<String, Ref> refs = repo.getRefDatabase().getRefs(refPrefix);
@ -126,7 +125,7 @@ public class ChangeEditModifier {
try (RevWalk rw = new RevWalk(repo)) { try (RevWalk rw = new RevWalk(repo)) {
ObjectId revision = ObjectId.fromString(ps.getRevision().get()); ObjectId revision = ObjectId.fromString(ps.getRevision().get());
String editRefName = editRefName(me.getAccountId(), change.getId(), String editRefName = RefNames.refsEdit(me.getAccountId(), change.getId(),
ps.getId()); ps.getId());
return update(repo, me, editRefName, rw, ObjectId.zeroId(), revision); return update(repo, me, editRefName, rw, ObjectId.zeroId(), revision);
} }
@ -152,7 +151,7 @@ public class ChangeEditModifier {
Change change = edit.getChange(); Change change = edit.getChange();
IdentifiedUser me = (IdentifiedUser) currentUser.get(); IdentifiedUser me = (IdentifiedUser) currentUser.get();
String refName = editRefName(me.getAccountId(), change.getId(), String refName = RefNames.refsEdit(me.getAccountId(), change.getId(),
current.getId()); current.getId());
try (Repository repo = gitManager.openRepository(change.getProject()); try (Repository repo = gitManager.openRepository(change.getProject());
RevWalk rw = new RevWalk(repo); RevWalk rw = new RevWalk(repo);

View File

@ -21,7 +21,6 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status; import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
@ -110,7 +109,7 @@ public class ChangeEditUtil {
public Optional<ChangeEdit> byChange(Change change, IdentifiedUser user) public Optional<ChangeEdit> byChange(Change change, IdentifiedUser user)
throws IOException { throws IOException {
try (Repository repo = gitManager.openRepository(change.getProject())) { try (Repository repo = gitManager.openRepository(change.getProject())) {
String editRefPrefix = editRefPrefix(user.getAccountId(), change.getId()); String editRefPrefix = RefNames.refsEditPrefix(user.getAccountId(), change.getId());
Map<String, Ref> refs = repo.getRefDatabase().getRefs(editRefPrefix); Map<String, Ref> refs = repo.getRefDatabase().getRefs(editRefPrefix);
if (refs.isEmpty()) { if (refs.isEmpty()) {
return Optional.absent(); return Optional.absent();
@ -190,34 +189,6 @@ public class ChangeEditUtil {
} }
} }
/**
* Returns reference for this change edit with sharded user and change number:
* refs/users/UU/UUUU/edit-CCCC/P.
*
* @param accountId accout id
* @param changeId change number
* @param psId patch set number
* @return reference for this change edit
*/
public static String editRefName(Account.Id accountId, Change.Id changeId,
PatchSet.Id psId) {
return editRefPrefix(accountId, changeId) + psId.get();
}
/**
* Returns reference prefix for this change edit with sharded user and
* change number: refs/users/UU/UUUU/edit-CCCC/.
*
* @param accountId accout id
* @param changeId change number
* @return reference prefix for this change edit
*/
static String editRefPrefix(Account.Id accountId, Change.Id changeId) {
return String.format("%s/edit-%d/",
RefNames.refsUsers(accountId),
changeId.get());
}
private RevCommit squashEdit(RevWalk rw, ObjectInserter inserter, private RevCommit squashEdit(RevWalk rw, ObjectInserter inserter,
RevCommit edit, PatchSet basePatchSet) RevCommit edit, PatchSet basePatchSet)
throws IOException, ResourceConflictException { throws IOException, ResourceConflictException {

View File

@ -40,15 +40,10 @@ public class EventTypes {
/** Register an event. /** Register an event.
* *
* @param event The event to register. * @param event The event to register.
* @throws IllegalArgumentException if the event's type is already
* registered. * registered.
**/ **/
public static void registerClass(Event event) { public static void registerClass(Event event) {
String type = event.getType(); String type = event.getType();
if (typesByString.containsKey(type)) {
throw new IllegalArgumentException(
"Event type already registered: " + type);
}
typesByString.put(type, event.getClass()); typesByString.put(type, event.getClass());
} }

View File

@ -2007,7 +2007,7 @@ public class ReceiveCommits {
cmd = new ReceiveCommand( cmd = new ReceiveCommand(
ObjectId.zeroId(), ObjectId.zeroId(),
newCommit, newCommit,
ChangeEditUtil.editRefName( RefNames.refsEdit(
currentUser.getAccountId(), currentUser.getAccountId(),
change.getId(), change.getId(),
newPatchSet.getId())); newPatchSet.getId()));

View File

@ -14,8 +14,7 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import static org.eclipse.jgit.lib.RefDatabase.ALL; import com.google.common.collect.Iterables;
import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.errors.InvalidRevisionException; import com.google.gerrit.common.errors.InvalidRevisionException;
import com.google.gerrit.extensions.api.projects.BranchInfo; import com.google.gerrit.extensions.api.projects.BranchInfo;
@ -43,6 +42,7 @@ import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.ObjectWalk;
@ -53,6 +53,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
public class CreateBranch implements RestModifyView<ProjectResource, Input> { public class CreateBranch implements RestModifyView<ProjectResource, Input> {
private static final Logger log = LoggerFactory.getLogger(CreateBranch.class); private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
@ -224,7 +225,15 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
} catch (IncorrectObjectTypeException err) { } catch (IncorrectObjectTypeException err) {
throw new InvalidRevisionException(); throw new InvalidRevisionException();
} }
for (final Ref r : repo.getRefDatabase().getRefs(ALL).values()) { RefDatabase refDb = repo.getRefDatabase();
Iterable<Ref> refs = Iterables.concat(
refDb.getRefs(Constants.R_HEADS).values(),
refDb.getRefs(Constants.R_TAGS).values());
Ref rc = refDb.getRef(RefNames.REFS_CONFIG);
if (rc != null) {
refs = Iterables.concat(refs, Collections.singleton(rc));
}
for (Ref r : refs) {
try { try {
rw.markUninteresting(rw.parseAny(r.getObjectId())); rw.markUninteresting(rw.parseAny(r.getObjectId()));
} catch (MissingObjectException err) { } catch (MissingObjectException err) {

View File

@ -299,8 +299,8 @@ public class CommentsTest {
update.commit(); update.commit();
ChangeControl ctl = stubChangeControl(change); ChangeControl ctl = stubChangeControl(change);
revRes1 = new RevisionResource(new ChangeResource(ctl), ps1); revRes1 = new RevisionResource(new ChangeResource(ctl, null), ps1);
revRes2 = new RevisionResource(new ChangeResource(ctl), ps2); revRes2 = new RevisionResource(new ChangeResource(ctl, null), ps2);
} }
private ChangeControl stubChangeControl(Change c) throws OrmException { private ChangeControl stubChangeControl(Change c) throws OrmException {

View File

@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
import org.junit.Test; import org.junit.Test;
@ -28,7 +29,7 @@ public class ChangeEditTest {
Account.Id accountId = new Account.Id(1000042); Account.Id accountId = new Account.Id(1000042);
Change.Id changeId = new Change.Id(56414); Change.Id changeId = new Change.Id(56414);
PatchSet.Id psId = new PatchSet.Id(changeId, 50); PatchSet.Id psId = new PatchSet.Id(changeId, 50);
String refName = ChangeEditUtil.editRefName(accountId, changeId, psId); String refName = RefNames.refsEdit(accountId, changeId, psId);
assertEquals("refs/users/42/1000042/edit-56414/50", refName); assertEquals("refs/users/42/1000042/edit-56414/50", refName);
} }
} }

View File

@ -15,7 +15,6 @@
package com.google.gerrit.server.events; package com.google.gerrit.server.events;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import org.junit.Test; import org.junit.Test;
@ -26,12 +25,6 @@ public class EventTypesTest {
} }
} }
public static class TestEvent2 extends Event {
public TestEvent2() {
super("test-event"); // Intentionally same as in TestEvent
}
}
public static class AnotherTestEvent extends Event { public static class AnotherTestEvent extends Event {
public AnotherTestEvent() { public AnotherTestEvent() {
super("another-test-event"); super("another-test-event");
@ -45,20 +38,6 @@ public class EventTypesTest {
assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class); assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
assertThat(EventTypes.getClass("another-test-event")) assertThat(EventTypes.getClass("another-test-event"))
.isEqualTo(AnotherTestEvent.class); .isEqualTo(AnotherTestEvent.class);
try {
EventTypes.registerClass(new TestEvent());
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
}
try {
EventTypes.registerClass(new TestEvent2());
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
}
} }
@Test @Test