Merge "Merge branch 'stable-2.11'"
This commit is contained in:
@@ -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.
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user