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:
commit
409ce6a9fd
@ -907,7 +907,7 @@ Suggested access rights to grant:
|
||||
* 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_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
|
||||
sense to give them the freedom to push commits directly to a branch.
|
||||
|
@ -502,15 +502,17 @@ Values should use common unit suffixes to express their setting:
|
||||
* y, year, years (`1 year` is treated as `365 days`)
|
||||
|
||||
+
|
||||
--
|
||||
If a unit suffix is not specified, `seconds` is assumed. If 0 is
|
||||
supplied, the maximum age is infinite and items are never purged
|
||||
except when the cache is full.
|
||||
+
|
||||
|
||||
Default is `0`, meaning store forever with no expire, except:
|
||||
+
|
||||
|
||||
* `"adv_bases"`: default is `10 minutes`
|
||||
* `"ldap_groups"`: default is `1 hour`
|
||||
* `"web_sessions"`: default is `12 hours`
|
||||
--
|
||||
|
||||
[[cache.name.memoryLimit]]cache.<name>.memoryLimit::
|
||||
+
|
||||
@ -735,9 +737,11 @@ Values should use common unit suffixes to express their setting:
|
||||
* h, hr, hour, hours
|
||||
|
||||
+
|
||||
--
|
||||
If a unit suffix is not specified, `milliseconds` is assumed.
|
||||
+
|
||||
|
||||
Default is 5 seconds.
|
||||
--
|
||||
|
||||
[[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
|
||||
|
||||
+
|
||||
--
|
||||
If a unit suffix is not specified, `milliseconds` is assumed.
|
||||
+
|
||||
|
||||
Default is 5 seconds.
|
||||
--
|
||||
|
||||
[[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
|
||||
|
||||
+
|
||||
--
|
||||
If a unit suffix is not specified, `milliseconds` is assumed.
|
||||
+
|
||||
|
||||
Default is `30 seconds`.
|
||||
+
|
||||
|
||||
This setting only applies if
|
||||
<<database.connectionPool,database.connectionPool>> is true.
|
||||
--
|
||||
|
||||
[[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.
|
||||
|
||||
+
|
||||
--
|
||||
If multiple values are supplied, the daemon will listen on all
|
||||
of them.
|
||||
+
|
||||
|
||||
By default, http://*:8080.
|
||||
--
|
||||
|
||||
[[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`)
|
||||
|
||||
+
|
||||
--
|
||||
If a unit suffix is not specified, `minutes` is assumed. If 0
|
||||
is supplied, the maximum age is infinite and connections will not
|
||||
abort until the client disconnects.
|
||||
+
|
||||
|
||||
By default, 5 minutes.
|
||||
--
|
||||
|
||||
[[httpd.filterClass]]httpd.filterClass::
|
||||
+
|
||||
@ -3082,15 +3094,17 @@ default of 29418.
|
||||
* 'hostname':'port' (for example `review.example.com:29418`)
|
||||
* 'IPv4':'port' (for example `10.0.0.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
|
||||
of them.
|
||||
+
|
||||
|
||||
To disable the internal SSHD, set listenAddress to `off`.
|
||||
+
|
||||
|
||||
By default, *:29418.
|
||||
--
|
||||
|
||||
[[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
|
||||
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.
|
||||
+
|
||||
|
||||
* 'hostname':'port' (for example `review.example.com:22`)
|
||||
* 'IPv4':'port' (for example `10.0.0.1:29418`)
|
||||
* ['IPv6']:'port' (for example `[ff02::1]:29418`)
|
||||
|
||||
+
|
||||
--
|
||||
If multiple values are supplied, the daemon will advertise all
|
||||
of them.
|
||||
+
|
||||
|
||||
By default, sshd.listenAddress.
|
||||
--
|
||||
|
||||
[[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
|
||||
+
|
||||
|
@ -39,6 +39,7 @@ installed during the link:pgm-init.html[Gerrit initialization].
|
||||
The core plugins are developed and maintained by the Gerrit maintainers
|
||||
and the Gerrit community.
|
||||
|
||||
[[commit-message-length-validator]]
|
||||
=== commit-message-length-validator
|
||||
|
||||
This plugin checks the length of a commit’s 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[
|
||||
Configuration]
|
||||
|
||||
[[cookbook-plugin]]
|
||||
=== cookbook-plugin
|
||||
|
||||
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[
|
||||
Documentation]
|
||||
|
||||
[[download-commands]]
|
||||
=== download-commands
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[replication]]
|
||||
=== replication
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[reviewnotes]]
|
||||
=== reviewnotes
|
||||
|
||||
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[
|
||||
Documentation]
|
||||
|
||||
[[singleusergroup]]
|
||||
=== singleusergroup
|
||||
|
||||
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[
|
||||
gerrit-review].
|
||||
|
||||
[[admin-console]]
|
||||
=== admin-console
|
||||
|
||||
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[
|
||||
Documentation]
|
||||
|
||||
[[avatars-external]]
|
||||
=== avatars/external
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[avatars-gravatar]]
|
||||
=== avatars/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[
|
||||
Project]
|
||||
|
||||
[[branch-network]]
|
||||
=== branch-network
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[changemessage]]
|
||||
=== changemessage
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[codenvy]]
|
||||
=== codenvy
|
||||
|
||||
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[
|
||||
Project]
|
||||
|
||||
[[delete-project]]
|
||||
=== delete-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[
|
||||
Documentation]
|
||||
|
||||
[[egit]]
|
||||
=== 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[
|
||||
Documentation]
|
||||
|
||||
[[force-draft]]
|
||||
=== force-draft
|
||||
|
||||
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[
|
||||
Project]
|
||||
|
||||
[[gitblit]]
|
||||
=== gitblit
|
||||
|
||||
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[
|
||||
Project]
|
||||
|
||||
[[github]]
|
||||
=== github
|
||||
|
||||
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[
|
||||
Project]
|
||||
|
||||
[[gitiles]]
|
||||
=== gitiles
|
||||
|
||||
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[
|
||||
Project]
|
||||
|
||||
[[imagare]]
|
||||
=== imagare
|
||||
|
||||
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[
|
||||
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
|
||||
|
||||
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[
|
||||
its-base Configuration]
|
||||
|
||||
[[its-bugzilla]]
|
||||
==== its-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[
|
||||
Documentation]
|
||||
|
||||
[[its-jira]]
|
||||
==== its-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[
|
||||
Configuration]
|
||||
|
||||
[[its-rtc]]
|
||||
==== its-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[
|
||||
Configuration]
|
||||
|
||||
[[javamelody]]
|
||||
=== javamelody
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[menuextender]]
|
||||
=== menuextender
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[motd]]
|
||||
=== motd
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[oauth-authentication-provider]]
|
||||
=== OAuth authentication provider
|
||||
|
||||
This plugin enables Gerrit to use OAuth2 protocol for authentication.
|
||||
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/wiki/Getting-Started[Configuration]
|
||||
|
||||
[[project-download-commands]]
|
||||
=== project-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[
|
||||
Configuration]
|
||||
|
||||
[[quota]]
|
||||
=== quota
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[reviewers]]
|
||||
=== reviewers
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[reviewers-by-blame]]
|
||||
=== reviewers-by-blame
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[groovy-provider]]
|
||||
=== scripting/groovy-provider
|
||||
|
||||
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[
|
||||
Documentation]
|
||||
|
||||
[[scala-provider]]
|
||||
=== scripting/scala-provider
|
||||
|
||||
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[
|
||||
Documentation]
|
||||
|
||||
[[server-config]]
|
||||
=== 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[
|
||||
Project]
|
||||
|
||||
[[serviceuser]]
|
||||
=== serviceuser
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[uploadvalidator]]
|
||||
=== uploadvalidator
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[websession-flatfile]]
|
||||
=== websession-flatfile
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[wip]]
|
||||
=== wip
|
||||
|
||||
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[
|
||||
Configuration]
|
||||
|
||||
[[x-docs]]
|
||||
=== x-docs
|
||||
|
||||
This plugin serves project documentation as HTML pages.
|
||||
|
@ -12,7 +12,7 @@ OS are supported. Buck requires Python version 2.7 to be installed.
|
||||
Clone the git and build it:
|
||||
|
||||
----
|
||||
git clone https://gerrit.googlesource.com/buck
|
||||
git clone https://github.com/facebook/buck
|
||||
cd buck
|
||||
git checkout $(cat ../gerrit/.buckversion)
|
||||
ant
|
||||
|
@ -765,6 +765,14 @@ As workaround you may
|
||||
. link:#import-history[import the history of 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
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
|
11
ReleaseNotes/ReleaseNotes-2.10.3.1.txt
Normal file
11
ReleaseNotes/ReleaseNotes-2.10.3.1.txt
Normal 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.
|
124
ReleaseNotes/ReleaseNotes-2.10.3.txt
Normal file
124
ReleaseNotes/ReleaseNotes-2.10.3.txt
Normal 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.
|
@ -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]
|
||||
|
||||
Gerrit 2.11 includes the bug fixes done with
|
||||
link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1] and
|
||||
link:ReleaseNotes-2.10.2.html[Gerrit 2.10.2].
|
||||
link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1],
|
||||
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.
|
||||
|
||||
|
||||
@ -20,9 +21,38 @@ Important Notes
|
||||
*WARNING:* This release contains schema changes. To upgrade:
|
||||
----
|
||||
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
|
||||
----
|
||||
|
||||
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
|
||||
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.
|
||||
@ -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.
|
||||
|
||||
* 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=2974[Issue 2974]:
|
||||
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[
|
||||
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
|
||||
~~~~~~
|
||||
|
||||
@ -395,9 +419,6 @@ a change message on the created change.
|
||||
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
|
||||
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[
|
||||
`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
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -600,19 +611,15 @@ documented.
|
||||
Web UI
|
||||
~~~~~~
|
||||
|
||||
* link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]:
|
||||
Remove stripping `#` in login redirect.
|
||||
Change List
|
||||
^^^^^^^^^^^
|
||||
|
||||
* link:http://code.google.com/p/gerrit/issues/detail?id=3304[Issue 3304]:
|
||||
Always show a tooltip on the label column entries.
|
||||
|
||||
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]:
|
||||
Allow to disable muting of common path prefixes in the file list.
|
||||
+
|
||||
@ -843,8 +850,6 @@ Upgrades
|
||||
|
||||
* Update ASM to 5.0.3.
|
||||
|
||||
* Update Bouncycastle to 1.51.
|
||||
|
||||
* Update CodeMirror to 4.10.0-6-gd0a2dda.
|
||||
|
||||
* Update Guava to 18.0.
|
||||
@ -866,5 +871,3 @@ Upgrades
|
||||
* Update Parboiled to 1.1.7.
|
||||
|
||||
* Update Pegdown to 1.4.2.
|
||||
|
||||
* Update SSHD to 0.14.0.
|
||||
|
@ -9,6 +9,8 @@ Version 2.11.x
|
||||
[[2_10]]
|
||||
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.1.html[2.10.1]
|
||||
* link:ReleaseNotes-2.10.html[2.10]
|
||||
|
@ -19,6 +19,7 @@ import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class RestResponse extends HttpResponse {
|
||||
|
||||
@ -29,7 +30,9 @@ public class RestResponse extends HttpResponse {
|
||||
@Override
|
||||
public Reader getReader() throws IllegalStateException, IOException {
|
||||
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);
|
||||
}
|
||||
return reader;
|
||||
|
@ -72,6 +72,7 @@ import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@ -658,21 +659,21 @@ public class ChangeEditIT extends AbstractDaemonTest {
|
||||
private String newChange(PersonIdent ident) throws Exception {
|
||||
PushOneCommit push =
|
||||
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();
|
||||
}
|
||||
|
||||
private String amendChange(PersonIdent ident, String changeId) throws Exception {
|
||||
PushOneCommit push =
|
||||
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();
|
||||
}
|
||||
|
||||
private String newChange2(PersonIdent ident) throws Exception {
|
||||
PushOneCommit push =
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -210,6 +210,7 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
e.listenTo(legacycidInChangeTable);
|
||||
e.listenTo(muteCommonPathPrefixes);
|
||||
e.listenTo(diffView);
|
||||
e.listenTo(reviewCategoryStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,7 +130,10 @@ class Actions extends Composite {
|
||||
|
||||
a2b(actions, "cherrypick", cherrypick);
|
||||
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);
|
||||
for (String id : filterNonCore(actions)) {
|
||||
add(new ActionButton(changeInfo, revInfo, actions.get(id)));
|
||||
@ -176,7 +179,16 @@ class Actions extends Composite {
|
||||
|
||||
@UiHandler("rebase")
|
||||
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")
|
||||
|
@ -28,16 +28,18 @@ class AddFileAction {
|
||||
private final RevisionInfo revision;
|
||||
private final ChangeScreen.Style style;
|
||||
private final Widget addButton;
|
||||
private final FileTable files;
|
||||
|
||||
private AddFileBox addBox;
|
||||
private PopupPanel popup;
|
||||
|
||||
AddFileAction(Change.Id changeId, RevisionInfo revision,
|
||||
ChangeScreen.Style style, Widget addButton) {
|
||||
ChangeScreen.Style style, Widget addButton, FileTable files) {
|
||||
this.changeId = changeId;
|
||||
this.revision = revision;
|
||||
this.style = style;
|
||||
this.addButton = addButton;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public void onEdit() {
|
||||
@ -46,8 +48,9 @@ class AddFileAction {
|
||||
return;
|
||||
}
|
||||
|
||||
files.unregisterKeys();
|
||||
if (addBox == null) {
|
||||
addBox = new AddFileBox(changeId, revision);
|
||||
addBox = new AddFileBox(changeId, revision, files);
|
||||
}
|
||||
addBox.clearPath();
|
||||
|
||||
|
@ -41,6 +41,7 @@ class AddFileBox extends Composite {
|
||||
|
||||
private final Change.Id changeId;
|
||||
private final RevisionInfo revision;
|
||||
private final FileTable fileTable;
|
||||
|
||||
@UiField Button open;
|
||||
@UiField Button cancel;
|
||||
@ -48,9 +49,10 @@ class AddFileBox extends Composite {
|
||||
@UiField(provided = true)
|
||||
RemoteSuggestBox path;
|
||||
|
||||
AddFileBox(Change.Id changeId, RevisionInfo revision) {
|
||||
AddFileBox(Change.Id changeId, RevisionInfo revision, FileTable files) {
|
||||
this.changeId = changeId;
|
||||
this.revision = revision;
|
||||
this.fileTable = files;
|
||||
|
||||
path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision));
|
||||
path.addSelectionHandler(new SelectionHandler<String>() {
|
||||
@ -63,6 +65,7 @@ class AddFileBox extends Composite {
|
||||
@Override
|
||||
public void onClose(CloseEvent<RemoteSuggestBox> event) {
|
||||
hide();
|
||||
fileTable.registerKeys();
|
||||
}
|
||||
});
|
||||
|
||||
@ -92,6 +95,7 @@ class AddFileBox extends Composite {
|
||||
@UiHandler("cancel")
|
||||
void onCancel(@SuppressWarnings("unused") ClickEvent e) {
|
||||
hide();
|
||||
fileTable.registerKeys();
|
||||
}
|
||||
|
||||
private void hide() {
|
||||
|
@ -435,7 +435,7 @@ public class ChangeScreen extends Screen {
|
||||
reviewMode.setVisible(!editMode.isVisible());
|
||||
addFileAction = new AddFileAction(
|
||||
changeId, info.revision(revision),
|
||||
style, addFile);
|
||||
style, addFile, files);
|
||||
deleteFileAction = new DeleteFileAction(
|
||||
changeId, info.revision(revision),
|
||||
style, addFile);
|
||||
|
@ -19,11 +19,13 @@ import com.google.gerrit.client.FormatUtil;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.GitwebLink;
|
||||
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.changes.ChangeInfo;
|
||||
import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
|
||||
import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
|
||||
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.ui.CommentLinkProcessor;
|
||||
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.Image;
|
||||
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.safehtml.client.SafeHtmlBuilder;
|
||||
|
||||
@ -77,6 +80,7 @@ class CommitBox extends Composite {
|
||||
@UiField HTML text;
|
||||
@UiField ScrollPanel scroll;
|
||||
@UiField Button more;
|
||||
@UiField Element parentNotCurrentText;
|
||||
private boolean expanded;
|
||||
|
||||
CommitBox() {
|
||||
@ -121,7 +125,19 @@ class CommitBox extends Composite {
|
||||
if (revInfo.commit().parents().length() > 1) {
|
||||
mergeCommit.setVisible(true);
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -68,7 +68,7 @@ limitations under the License.
|
||||
padding: 0;
|
||||
width: 560px;
|
||||
}
|
||||
.header th { width: 70px; }
|
||||
.header th { width: 72px; }
|
||||
.header td { white-space: nowrap; }
|
||||
.date { width: 132px; }
|
||||
|
||||
@ -106,6 +106,16 @@ limitations under the License.
|
||||
height: 16px !important;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.parent {
|
||||
margin-right: 3px;
|
||||
float: left;
|
||||
}
|
||||
.parentNotCurrent {
|
||||
color: #FFA62F; <!-- orange -->
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</ui:style>
|
||||
<g:HTMLPanel>
|
||||
<g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'>
|
||||
@ -163,7 +173,15 @@ limitations under the License.
|
||||
</td>
|
||||
</tr>
|
||||
<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>
|
||||
<g:FlowPanel ui:field='parentCommits'/>
|
||||
</td>
|
||||
|
@ -224,6 +224,14 @@ public class FileTable extends FlowPanel {
|
||||
}
|
||||
}
|
||||
|
||||
void unregisterKeys() {
|
||||
register = false;
|
||||
|
||||
if (table != null) {
|
||||
table.setRegisterKeys(false);
|
||||
}
|
||||
}
|
||||
|
||||
void registerKeys() {
|
||||
register = true;
|
||||
|
||||
|
@ -27,10 +27,10 @@ import com.google.gwt.user.client.ui.PopupPanel;
|
||||
|
||||
class RebaseAction {
|
||||
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);
|
||||
|
||||
new RebaseDialog(project, branch, id) {
|
||||
new RebaseDialog(project, branch, id, enabled) {
|
||||
@Override
|
||||
public void onSend() {
|
||||
ChangeApi.rebase(id.get(), revision, getBase(), new GerritCallback<ChangeInfo>() {
|
||||
|
@ -167,6 +167,7 @@ public interface ChangeConstants extends Constants {
|
||||
|
||||
String buttonRebaseChangeSend();
|
||||
String rebaseConfirmMessage();
|
||||
String rebaseNotPossibleMessage();
|
||||
String rebasePlaceholderMessage();
|
||||
String rebaseTitle();
|
||||
|
||||
|
@ -153,6 +153,7 @@ cherryPickTitle = Code Review - Cherry Pick Change to Another Branch
|
||||
|
||||
buttonRebaseChangeSend = Rebase
|
||||
rebaseConfirmMessage = Change parent revision
|
||||
rebaseNotPossibleMessage = Change is already up to date
|
||||
rebasePlaceholderMessage = (subject, change number, or leave empty)
|
||||
rebaseTitle = Code Review - Rebase Change
|
||||
|
||||
|
@ -253,9 +253,6 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
||||
}
|
||||
col++;
|
||||
|
||||
boolean displayInfo = Gerrit.isSignedIn() && Gerrit.getUserAccount()
|
||||
.getGeneralPreferences().isShowInfoInReviewCategory();
|
||||
|
||||
for (int idx = 0; idx < labelNames.size(); idx++, col++) {
|
||||
String name = labelNames.get(idx);
|
||||
|
||||
@ -276,7 +273,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
||||
user = label.rejected().name();
|
||||
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
|
||||
label.rejected());
|
||||
if (displayInfo && info != null) {
|
||||
if (info != null) {
|
||||
FlowPanel panel = new FlowPanel();
|
||||
panel.add(new Image(Gerrit.RESOURCES.redNot()));
|
||||
panel.add(new InlineLabel(info));
|
||||
@ -288,7 +285,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
||||
user = label.approved().name();
|
||||
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
|
||||
label.approved());
|
||||
if (displayInfo && info != null) {
|
||||
if (info != null) {
|
||||
FlowPanel panel = new FlowPanel();
|
||||
panel.add(new Image(Gerrit.RESOURCES.greenCheck()));
|
||||
panel.add(new InlineLabel(info));
|
||||
@ -301,7 +298,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
||||
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
|
||||
label.disliked());
|
||||
String vstr = String.valueOf(label._value());
|
||||
if (displayInfo && info != null) {
|
||||
if (info != null) {
|
||||
vstr = vstr + " " + info;
|
||||
}
|
||||
fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
|
||||
@ -311,7 +308,7 @@ public class ChangeTable extends NavigationTable<ChangeInfo> {
|
||||
info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
|
||||
label.recommended());
|
||||
String vstr = "+" + label._value();
|
||||
if (displayInfo && info != null) {
|
||||
if (info != null) {
|
||||
vstr = vstr + " " + info;
|
||||
}
|
||||
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());
|
||||
|
||||
if ((!displayInfo || reviewCategoryStrategy == ReviewCategoryStrategy.ABBREV)
|
||||
&& user != null) {
|
||||
if (user != null) {
|
||||
// Some web browsers ignore the embedded newline; some like it;
|
||||
// so we include a space before the newline to accommodate both.
|
||||
fmt.getElement(row, col).setTitle(name + " \nby " + user);
|
||||
|
@ -347,7 +347,7 @@ public class EditScreen extends Screen {
|
||||
private void initEditor(HttpResponse<NativeString> file) {
|
||||
ModeInfo mode = null;
|
||||
String content = "";
|
||||
if (file != null) {
|
||||
if (file != null && file.getResult() != null) {
|
||||
content = file.getResult().asString();
|
||||
if (prefs.syntaxHighlighting()) {
|
||||
mode = ModeInfo.findMode(file.getContentType(), path);
|
||||
|
@ -36,10 +36,12 @@ public abstract class RebaseDialog extends CommentedActionDialog {
|
||||
private final SuggestBox base;
|
||||
private final CheckBox cb;
|
||||
private List<ChangeInfo> changes;
|
||||
private final boolean sendEnabled;
|
||||
|
||||
public RebaseDialog(final String project, final String branch,
|
||||
final Change.Id changeId) {
|
||||
final Change.Id changeId, final boolean sendEnabled) {
|
||||
super(Util.C.rebaseTitle(), null);
|
||||
this.sendEnabled = sendEnabled;
|
||||
sendButton.setText(Util.C.buttonRebaseChangeSend());
|
||||
|
||||
// create the suggestion box
|
||||
@ -63,7 +65,6 @@ public abstract class RebaseDialog extends CommentedActionDialog {
|
||||
done.onSuggestionsReady(request, new Response(suggestions));
|
||||
}
|
||||
});
|
||||
base.setEnabled(false);
|
||||
base.getElement().setAttribute("placeholder",
|
||||
Util.C.rebasePlaceholderMessage());
|
||||
base.setStyleName(Gerrit.RESOURCES.css().rebaseSuggestBox());
|
||||
@ -81,13 +82,11 @@ public abstract class RebaseDialog extends CommentedActionDialog {
|
||||
@Override
|
||||
public void onSuccess(ChangeList result) {
|
||||
changes = Natives.asList(result);
|
||||
base.setEnabled(true);
|
||||
base.setFocus(true);
|
||||
updateControls(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
base.setEnabled(false);
|
||||
sendButton.setFocus(true);
|
||||
updateControls(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -102,7 +101,26 @@ public abstract class RebaseDialog extends CommentedActionDialog {
|
||||
public void center() {
|
||||
super.center();
|
||||
GlobalKey.dialog(this);
|
||||
sendButton.setFocus(true);
|
||||
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);
|
||||
} else {
|
||||
sendButton.setTitle(Util.C.rebaseNotPossibleMessage());
|
||||
cancelButton.setFocus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getBase() {
|
||||
|
@ -71,7 +71,8 @@ class UrlModule extends ServletModule {
|
||||
}
|
||||
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("/signout").with(HttpLogoutServlet.class);
|
||||
}
|
||||
|
@ -363,15 +363,18 @@ class GitWebServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
final Map<String, String> params = getParameters(req);
|
||||
if (deniedActions.contains(params.get("a"))) {
|
||||
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
String a = params.get("a");
|
||||
if (a != null) {
|
||||
if (deniedActions.contains(a)) {
|
||||
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.get("a").equals(PROJECT_LIST_ACTION)) {
|
||||
rsp.sendRedirect(req.getContextPath() + "/#" + PageLinks.ADMIN_PROJECTS
|
||||
+ "?filter=" + Url.encode(params.get("pf") + "/"));
|
||||
return;
|
||||
if (a.equals(PROJECT_LIST_ACTION)) {
|
||||
rsp.sendRedirect(req.getContextPath() + "/#" + PageLinks.ADMIN_PROJECTS
|
||||
+ "?filter=" + Url.encode(params.get("pf") + "/"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String name = params.get("p");
|
||||
|
@ -583,6 +583,9 @@ class HttpPluginServlet extends HttpServlet
|
||||
if ("application/octet-stream".equals(contentType)
|
||||
&& entry.getName().endsWith(".js")) {
|
||||
contentType = "application/javascript";
|
||||
} else if ("application/x-pointplus".equals(contentType)
|
||||
&& entry.getName().endsWith(".css")) {
|
||||
contentType = "text/css";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.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.WebSession;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.server.account.AccountException;
|
||||
@ -36,8 +38,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
@ -53,17 +53,20 @@ class OAuthSession {
|
||||
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 redirectUrl;
|
||||
private String redirectToken;
|
||||
|
||||
@Inject
|
||||
OAuthSession(DynamicItem<WebSession> webSession,
|
||||
AccountManager accountManager) {
|
||||
AccountManager accountManager,
|
||||
CanonicalWebUrl urlProvider) {
|
||||
this.state = generateRandomState();
|
||||
this.webSession = webSession;
|
||||
this.accountManager = accountManager;
|
||||
this.urlProvider = urlProvider;
|
||||
}
|
||||
|
||||
boolean isLoggedIn() {
|
||||
@ -95,7 +98,7 @@ class OAuthSession {
|
||||
|
||||
if (isLoggedIn()) {
|
||||
log.debug("Login-SUCCESS " + this);
|
||||
authenticateAndRedirect(response);
|
||||
authenticateAndRedirect(request, response);
|
||||
return true;
|
||||
} else {
|
||||
response.sendError(SC_UNAUTHORIZED);
|
||||
@ -103,15 +106,22 @@ class OAuthSession {
|
||||
}
|
||||
} else {
|
||||
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() +
|
||||
"&state=" + state);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void authenticateAndRedirect(HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
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;
|
||||
@ -164,16 +174,17 @@ class OAuthSession {
|
||||
}
|
||||
|
||||
webSession.get().login(arsp, true);
|
||||
String suffix = redirectUrl.substring(
|
||||
String suffix = redirectToken.substring(
|
||||
OAuthWebFilter.GERRIT_LOGIN.length() + 1);
|
||||
suffix = URLDecoder.decode(suffix, StandardCharsets.UTF_8.name());
|
||||
rsp.sendRedirect(suffix);
|
||||
StringBuilder rdr = new StringBuilder(urlProvider.get(req));
|
||||
rdr.append(Url.decode(suffix));
|
||||
rsp.sendRedirect(rdr.toString());
|
||||
}
|
||||
|
||||
void logout() {
|
||||
token = null;
|
||||
user = null;
|
||||
redirectUrl = null;
|
||||
redirectToken = null;
|
||||
serviceProvider = null;
|
||||
}
|
||||
|
||||
|
@ -89,18 +89,22 @@ class OAuthWebFilter implements Filter {
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
|
||||
OAuthSession oauthSession = oauthSessionProvider.get();
|
||||
if (currentUserProvider.get().isIdentifiedUser()) {
|
||||
if (httpSession != null) {
|
||||
httpSession.invalidate();
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
} else {
|
||||
if (oauthSession.isLoggedIn()) {
|
||||
oauthSession.logout();
|
||||
}
|
||||
}
|
||||
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
String provider = httpRequest.getParameter("provider");
|
||||
OAuthSession oauthSession = oauthSessionProvider.get();
|
||||
OAuthServiceProvider service = ssoProvider == null
|
||||
? oauthSession.getServiceProvider()
|
||||
: ssoProvider;
|
||||
|
@ -12,6 +12,7 @@ java_library(
|
||||
'//gerrit-server:server',
|
||||
'//lib:guava',
|
||||
'//lib:gwtorm',
|
||||
'//lib/commons:codec',
|
||||
'//lib/guice:guice',
|
||||
'//lib/guice:guice-servlet',
|
||||
'//lib/jgit:jgit',
|
||||
|
@ -22,11 +22,14 @@ import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.auth.openid.OpenIdUrls;
|
||||
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.Url;
|
||||
import com.google.gerrit.httpd.HtmlDomUtil;
|
||||
import com.google.gerrit.httpd.LoginUrlToken;
|
||||
import com.google.gerrit.httpd.template.SiteHeaderFooter;
|
||||
import com.google.gerrit.reviewdb.client.AuthType;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
@ -61,10 +64,13 @@ class LoginForm extends HttpServlet {
|
||||
|
||||
private final ImmutableSet<String> suggestProviders;
|
||||
private final Provider<String> urlProvider;
|
||||
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
|
||||
private final OpenIdServiceImpl impl;
|
||||
private final int maxRedirectUrlLength;
|
||||
private final String ssoUrl;
|
||||
private final SiteHeaderFooter header;
|
||||
private final Provider<CurrentUser> currentUserProvider;
|
||||
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
|
||||
|
||||
@Inject
|
||||
LoginForm(
|
||||
@ -72,13 +78,19 @@ class LoginForm extends HttpServlet {
|
||||
@GerritServerConfig Config config,
|
||||
AuthConfig authConfig,
|
||||
OpenIdServiceImpl impl,
|
||||
SiteHeaderFooter header) {
|
||||
SiteHeaderFooter header,
|
||||
Provider<OAuthSessionOverOpenID> oauthSessionProvider,
|
||||
Provider<CurrentUser> currentUserProvider,
|
||||
DynamicMap<OAuthServiceProvider> oauthServiceProviders) {
|
||||
this.urlProvider = urlProvider;
|
||||
this.impl = impl;
|
||||
this.header = header;
|
||||
this.maxRedirectUrlLength = config.getInt(
|
||||
"openid", "maxRedirectUrlLength",
|
||||
10);
|
||||
this.oauthSessionProvider = oauthSessionProvider;
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
this.oauthServiceProviders = oauthServiceProviders;
|
||||
|
||||
if (urlProvider == null || Strings.isNullOrEmpty(urlProvider.get())) {
|
||||
log.error("gerrit.canonicalWebUrl must be set in gerrit.config");
|
||||
@ -152,7 +164,23 @@ class LoginForm extends HttpServlet {
|
||||
mode = SignInMode.SIGN_IN;
|
||||
}
|
||||
|
||||
discover(req, res, link, id, remember, token, mode);
|
||||
OAuthServiceProvider oauthProvider = lookupOAuthServiceProvider(id);
|
||||
|
||||
if (oauthProvider == null) {
|
||||
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,
|
||||
@ -267,6 +295,20 @@ class LoginForm extends HttpServlet {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
Cookie[] cookies = req.getCookies();
|
||||
if (cookies != null) {
|
||||
@ -296,4 +370,9 @@ class LoginForm extends HttpServlet {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isGerritLogin(HttpServletRequest request) {
|
||||
return request.getRequestURI().indexOf(
|
||||
OAuthSessionOverOpenID.GERRIT_LOGIN) >= 0;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@
|
||||
|
||||
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;
|
||||
|
||||
/** Servlets related to OpenID authentication. */
|
||||
@ -21,9 +23,12 @@ public class OpenIdModule extends ServletModule {
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
serve("/login", "/login/*").with(LoginForm.class);
|
||||
serve("/logout").with(OAuthOverOpenIDLogoutServlet.class);
|
||||
filter("/oauth").through(OAuthWebFilterOverOpenID.class);
|
||||
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
|
||||
serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class);
|
||||
filter("/").through(XrdsFilter.class);
|
||||
bind(OpenIdServiceImpl.class);
|
||||
DynamicMap.mapOf(binder(), OAuthServiceProvider.class);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -86,6 +86,36 @@ public class RefNames {
|
||||
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() {
|
||||
}
|
||||
}
|
||||
|
@ -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.UiAction;
|
||||
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.project.ChangeControl;
|
||||
import com.google.inject.Inject;
|
||||
@ -36,13 +37,16 @@ import java.util.Map;
|
||||
public class ActionJson {
|
||||
private final Revisions revisions;
|
||||
private final DynamicMap<RestView<ChangeResource>> changeViews;
|
||||
private final RebaseChange rebaseChange;
|
||||
|
||||
@Inject
|
||||
ActionJson(
|
||||
Revisions revisions,
|
||||
DynamicMap<RestView<ChangeResource>> changeViews) {
|
||||
DynamicMap<RestView<ChangeResource>> changeViews,
|
||||
RebaseChange rebaseChange) {
|
||||
this.revisions = revisions;
|
||||
this.changeViews = changeViews;
|
||||
this.rebaseChange = rebaseChange;
|
||||
}
|
||||
|
||||
public Map<String, ActionInfo> format(RevisionResource rsrc) {
|
||||
@ -69,7 +73,7 @@ public class ActionJson {
|
||||
Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser());
|
||||
for (UiAction.Description d : UiActions.from(
|
||||
changeViews,
|
||||
new ChangeResource(ctl),
|
||||
new ChangeResource(ctl, rebaseChange),
|
||||
userProvider)) {
|
||||
out.put(d.getId(), new ActionInfo(d));
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.PatchLineCommentsUtil;
|
||||
import com.google.gerrit.server.WebLinks;
|
||||
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.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.patch.PatchListNotAvailableException;
|
||||
@ -142,6 +143,7 @@ public class ChangeJson {
|
||||
private final PatchLineCommentsUtil plcUtil;
|
||||
private final Provider<ConsistencyChecker> checkerProvider;
|
||||
private final ActionJson actionJson;
|
||||
private final RebaseChange rebaseChange;
|
||||
|
||||
private AccountLoader accountLoader;
|
||||
private FixInput fix;
|
||||
@ -163,7 +165,8 @@ public class ChangeJson {
|
||||
ChangeMessagesUtil cmUtil,
|
||||
PatchLineCommentsUtil plcUtil,
|
||||
Provider<ConsistencyChecker> checkerProvider,
|
||||
ActionJson actionJson) {
|
||||
ActionJson actionJson,
|
||||
RebaseChange rebaseChange) {
|
||||
this.db = db;
|
||||
this.labelNormalizer = ln;
|
||||
this.userProvider = user;
|
||||
@ -180,6 +183,7 @@ public class ChangeJson {
|
||||
this.plcUtil = plcUtil;
|
||||
this.checkerProvider = checkerProvider;
|
||||
this.actionJson = actionJson;
|
||||
this.rebaseChange = rebaseChange;
|
||||
options = EnumSet.noneOf(ListChangesOption.class);
|
||||
}
|
||||
|
||||
@ -890,7 +894,7 @@ public class ChangeJson {
|
||||
&& userProvider.get().isIdentifiedUser()) {
|
||||
|
||||
actionJson.addRevisionActions(out,
|
||||
new RevisionResource(new ChangeResource(ctl), in));
|
||||
new RevisionResource(new ChangeResource(ctl, rebaseChange), in));
|
||||
}
|
||||
|
||||
if (has(DRAFT_COMMENTS)
|
||||
|
@ -23,6 +23,7 @@ import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
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.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
@ -36,13 +37,16 @@ public class ChangeResource implements RestResource, HasETag {
|
||||
new TypeLiteral<RestView<ChangeResource>>() {};
|
||||
|
||||
private final ChangeControl control;
|
||||
private final RebaseChange rebaseChange;
|
||||
|
||||
public ChangeResource(ChangeControl control) {
|
||||
public ChangeResource(ChangeControl control, RebaseChange rebaseChange) {
|
||||
this.control = control;
|
||||
this.rebaseChange = rebaseChange;
|
||||
}
|
||||
|
||||
protected ChangeResource(ChangeResource copy) {
|
||||
this.control = copy.control;
|
||||
this.rebaseChange = copy.rebaseChange;
|
||||
}
|
||||
|
||||
public ChangeControl getControl() {
|
||||
@ -65,7 +69,8 @@ public class ChangeResource implements RestResource, HasETag {
|
||||
.putInt(getChange().getRowVersion())
|
||||
.putInt(user.isIdentifiedUser()
|
||||
? ((IdentifiedUser) user).getAccountId().get()
|
||||
: 0);
|
||||
: 0)
|
||||
.putBoolean(rebaseChange != null && rebaseChange.canRebase(this));
|
||||
byte[] buf = new byte[20];
|
||||
ObjectId noteId;
|
||||
try {
|
||||
|
@ -26,6 +26,7 @@ import com.google.gerrit.extensions.restapi.TopLevelResource;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
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.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
@ -49,6 +50,7 @@ public class ChangesCollection implements
|
||||
private final ChangeUtil changeUtil;
|
||||
private final CreateChange createChange;
|
||||
private final ChangeIndexer changeIndexer;
|
||||
private final RebaseChange rebaseChange;
|
||||
|
||||
@Inject
|
||||
ChangesCollection(
|
||||
@ -58,7 +60,8 @@ public class ChangesCollection implements
|
||||
DynamicMap<RestView<ChangeResource>> views,
|
||||
ChangeUtil changeUtil,
|
||||
CreateChange createChange,
|
||||
ChangeIndexer changeIndexer) {
|
||||
ChangeIndexer changeIndexer,
|
||||
RebaseChange rebaseChange) {
|
||||
this.user = user;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.queryFactory = queryFactory;
|
||||
@ -66,6 +69,7 @@ public class ChangesCollection implements
|
||||
this.changeUtil = changeUtil;
|
||||
this.createChange = createChange;
|
||||
this.changeIndexer = changeIndexer;
|
||||
this.rebaseChange = rebaseChange;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,7 +106,7 @@ public class ChangesCollection implements
|
||||
} catch (NoSuchChangeException e) {
|
||||
throw new ResourceNotFoundException(id);
|
||||
}
|
||||
return new ChangeResource(control);
|
||||
return new ChangeResource(control, rebaseChange);
|
||||
}
|
||||
|
||||
public ChangeResource parse(Change.Id id)
|
||||
@ -112,7 +116,7 @@ public class ChangesCollection implements
|
||||
}
|
||||
|
||||
public ChangeResource parse(ChangeControl control) {
|
||||
return new ChangeResource(control);
|
||||
return new ChangeResource(control, rebaseChange);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -21,6 +21,7 @@ import com.google.gerrit.extensions.common.ActionInfo;
|
||||
import com.google.gerrit.extensions.restapi.ETagView;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
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.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
|
||||
@ -39,14 +40,17 @@ public class GetRevisionActions implements ETagView<RevisionResource> {
|
||||
private final ActionJson delegate;
|
||||
private final Provider<InternalChangeQuery> queryProvider;
|
||||
private final Config config;
|
||||
private final RebaseChange rebaseChange;
|
||||
@Inject
|
||||
GetRevisionActions(
|
||||
ActionJson delegate,
|
||||
Provider<InternalChangeQuery> queryProvider,
|
||||
@GerritServerConfig Config config) {
|
||||
@GerritServerConfig Config config,
|
||||
RebaseChange rebaseChange) {
|
||||
this.delegate = delegate;
|
||||
this.queryProvider = queryProvider;
|
||||
this.config = config;
|
||||
this.rebaseChange = rebaseChange;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,7 +69,7 @@ public class GetRevisionActions implements ETagView<RevisionResource> {
|
||||
CurrentUser user = rsrc.getControl().getCurrentUser();
|
||||
try {
|
||||
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){
|
||||
throw new OrmRuntimeException(e);
|
||||
|
@ -213,13 +213,19 @@ public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
|
||||
|
||||
@Override
|
||||
public UiAction.Description getDescription(RevisionResource resource) {
|
||||
return new UiAction.Description()
|
||||
UiAction.Description descr = new UiAction.Description()
|
||||
.setLabel("Rebase")
|
||||
.setTitle("Rebase onto tip of branch or parent change")
|
||||
.setVisible(resource.getChange().getStatus().isOpen()
|
||||
&& resource.isCurrent()
|
||||
&& resource.getControl().canRebase()
|
||||
&& 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
|
||||
|
@ -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.PatchSet;
|
||||
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.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
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.ValidatePolicy;
|
||||
import com.google.gerrit.server.change.RevisionResource;
|
||||
@ -356,10 +358,21 @@ public class RebaseChange {
|
||||
return objectId;
|
||||
}
|
||||
|
||||
public boolean canRebase(ChangeResource r) {
|
||||
Change c = r.getChange();
|
||||
return canRebase(c.getProject(), c.currentPatchSetId(), c.getDest());
|
||||
}
|
||||
|
||||
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;
|
||||
try {
|
||||
git = gitManager.openRepository(r.getChange().getProject());
|
||||
git = gitManager.openRepository(project);
|
||||
} catch (RepositoryNotFoundException err) {
|
||||
return false;
|
||||
} catch (IOException err) {
|
||||
@ -367,9 +380,9 @@ public class RebaseChange {
|
||||
}
|
||||
try {
|
||||
findBaseRevision(
|
||||
r.getPatchSet().getId(),
|
||||
patchSetId,
|
||||
db.get(),
|
||||
r.getChange().getDest(),
|
||||
branch,
|
||||
git,
|
||||
null,
|
||||
null,
|
||||
|
@ -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.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.client.RevId;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
|
||||
@ -71,7 +72,7 @@ public class ChangeEdit {
|
||||
}
|
||||
|
||||
public String getRefName() {
|
||||
return ChangeEditUtil.editRefName(user.getAccountId(), change.getId(),
|
||||
return RefNames.refsEdit(user.getAccountId(), change.getId(),
|
||||
basePatchSet.getId());
|
||||
}
|
||||
|
||||
|
@ -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.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 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.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
@ -116,7 +115,7 @@ public class ChangeEditModifier {
|
||||
}
|
||||
|
||||
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())) {
|
||||
Map<String, Ref> refs = repo.getRefDatabase().getRefs(refPrefix);
|
||||
@ -126,7 +125,7 @@ public class ChangeEditModifier {
|
||||
|
||||
try (RevWalk rw = new RevWalk(repo)) {
|
||||
ObjectId revision = ObjectId.fromString(ps.getRevision().get());
|
||||
String editRefName = editRefName(me.getAccountId(), change.getId(),
|
||||
String editRefName = RefNames.refsEdit(me.getAccountId(), change.getId(),
|
||||
ps.getId());
|
||||
return update(repo, me, editRefName, rw, ObjectId.zeroId(), revision);
|
||||
}
|
||||
@ -152,7 +151,7 @@ public class ChangeEditModifier {
|
||||
|
||||
Change change = edit.getChange();
|
||||
IdentifiedUser me = (IdentifiedUser) currentUser.get();
|
||||
String refName = editRefName(me.getAccountId(), change.getId(),
|
||||
String refName = RefNames.refsEdit(me.getAccountId(), change.getId(),
|
||||
current.getId());
|
||||
try (Repository repo = gitManager.openRepository(change.getProject());
|
||||
RevWalk rw = new RevWalk(repo);
|
||||
|
@ -21,7 +21,6 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
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.Status;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
@ -110,7 +109,7 @@ public class ChangeEditUtil {
|
||||
public Optional<ChangeEdit> byChange(Change change, IdentifiedUser user)
|
||||
throws IOException {
|
||||
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);
|
||||
if (refs.isEmpty()) {
|
||||
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,
|
||||
RevCommit edit, PatchSet basePatchSet)
|
||||
throws IOException, ResourceConflictException {
|
||||
|
@ -40,15 +40,10 @@ public class EventTypes {
|
||||
/** Register an event.
|
||||
*
|
||||
* @param event The event to register.
|
||||
* @throws IllegalArgumentException if the event's type is already
|
||||
* registered.
|
||||
**/
|
||||
public static void registerClass(Event event) {
|
||||
String type = event.getType();
|
||||
if (typesByString.containsKey(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Event type already registered: " + type);
|
||||
}
|
||||
typesByString.put(type, event.getClass());
|
||||
}
|
||||
|
||||
|
@ -2007,7 +2007,7 @@ public class ReceiveCommits {
|
||||
cmd = new ReceiveCommand(
|
||||
ObjectId.zeroId(),
|
||||
newCommit,
|
||||
ChangeEditUtil.editRefName(
|
||||
RefNames.refsEdit(
|
||||
currentUser.getAccountId(),
|
||||
change.getId(),
|
||||
newPatchSet.getId()));
|
||||
|
@ -14,8 +14,7 @@
|
||||
|
||||
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.errors.InvalidRevisionException;
|
||||
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.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefDatabase;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.ObjectWalk;
|
||||
@ -53,6 +53,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public class CreateBranch implements RestModifyView<ProjectResource, Input> {
|
||||
private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
|
||||
@ -224,7 +225,15 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
|
||||
} catch (IncorrectObjectTypeException err) {
|
||||
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 {
|
||||
rw.markUninteresting(rw.parseAny(r.getObjectId()));
|
||||
} catch (MissingObjectException err) {
|
||||
|
@ -299,8 +299,8 @@ public class CommentsTest {
|
||||
update.commit();
|
||||
|
||||
ChangeControl ctl = stubChangeControl(change);
|
||||
revRes1 = new RevisionResource(new ChangeResource(ctl), ps1);
|
||||
revRes2 = new RevisionResource(new ChangeResource(ctl), ps2);
|
||||
revRes1 = new RevisionResource(new ChangeResource(ctl, null), ps1);
|
||||
revRes2 = new RevisionResource(new ChangeResource(ctl, null), ps2);
|
||||
}
|
||||
|
||||
private ChangeControl stubChangeControl(Change c) throws OrmException {
|
||||
|
@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@ -28,7 +29,7 @@ public class ChangeEditTest {
|
||||
Account.Id accountId = new Account.Id(1000042);
|
||||
Change.Id changeId = new Change.Id(56414);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
package com.google.gerrit.server.events;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
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 AnotherTestEvent() {
|
||||
super("another-test-event");
|
||||
@ -45,20 +38,6 @@ public class EventTypesTest {
|
||||
assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
|
||||
assertThat(EventTypes.getClass("another-test-event"))
|
||||
.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
|
||||
|
Loading…
Reference in New Issue
Block a user