Merge branch 'stable-2.11'

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

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

View File

@ -907,7 +907,7 @@ Suggested access rights to grant:
* xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*'
* 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.

View File

@ -502,15 +502,17 @@ Values should use common unit suffixes to express their setting:
* y, year, years (`1 year` is treated as `365 days`)
+
--
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::
+

View File

@ -39,6 +39,7 @@ installed during the link:pgm-init.html[Gerrit initialization].
The core plugins are developed and maintained by the Gerrit maintainers
and the Gerrit community.
[[commit-message-length-validator]]
=== commit-message-length-validator
This plugin checks the length of a commits commit message subject and
@ -52,6 +53,7 @@ Documentation] |
link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
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.

View File

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

View File

@ -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]

View File

@ -45,7 +45,9 @@ While in edit mode, it is possible to add new files to the change by clicking
the 'Add...' button at the top of the file list.
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.

View File

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

View File

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

View File

@ -8,8 +8,9 @@ link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war]
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.

View File

@ -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]

View File

@ -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;

View File

@ -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();
}

View File

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

View File

@ -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")

View File

@ -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();

View File

@ -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() {

View File

@ -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);

View File

@ -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,

View File

@ -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>

View File

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

View File

@ -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>() {

View File

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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -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);
}

View File

@ -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");

View File

@ -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";
}
}

View File

@ -22,6 +22,8 @@ import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.auth.oauth.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;
}

View File

@ -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;

View File

@ -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',

View File

@ -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;
}
}

View File

@ -0,0 +1,55 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.auth.openid;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.HttpLogoutServlet;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
class OAuthOverOpenIDLogoutServlet extends HttpLogoutServlet {
private static final long serialVersionUID = 1L;
private final Provider<OAuthSessionOverOpenID> oauthSession;
@Inject
OAuthOverOpenIDLogoutServlet(AuthConfig authConfig,
DynamicItem<WebSession> webSession,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
AuditService audit,
Provider<OAuthSessionOverOpenID> oauthSession) {
super(authConfig, webSession, urlProvider, audit);
this.oauthSession = oauthSession;
}
@Override
protected void doLogout(HttpServletRequest req, HttpServletResponse rsp)
throws IOException {
super.doLogout(req, rsp);
oauthSession.get().logout();
}
}

View File

@ -0,0 +1,216 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.auth.openid;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.servlet.SessionScoped;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** OAuth protocol implementation */
@SessionScoped
class OAuthSessionOverOpenID {
static final String GERRIT_LOGIN = "/login";
private static final Logger log = LoggerFactory.getLogger(
OAuthSessionOverOpenID.class);
private static final SecureRandom randomState = newRandomGenerator();
private final String state;
private final DynamicItem<WebSession> webSession;
private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
private OAuthServiceProvider serviceProvider;
private OAuthToken token;
private OAuthUserInfo user;
private String redirectToken;
@Inject
OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
AccountManager accountManager,
CanonicalWebUrl urlProvider) {
this.state = generateRandomState();
this.webSession = webSession;
this.accountManager = accountManager;
this.urlProvider = urlProvider;
}
boolean isLoggedIn() {
return token != null && user != null;
}
boolean isOAuthFinal(HttpServletRequest request) {
return Strings.emptyToNull(request.getParameter("code")) != null;
}
boolean login(HttpServletRequest request, HttpServletResponse response,
OAuthServiceProvider oauth) throws IOException {
if (isLoggedIn()) {
return true;
}
log.debug("Login " + this);
if (isOAuthFinal(request)) {
if (!checkState(request)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return false;
}
log.debug("Login-Retrieve-User " + this);
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
user = oauth.getUserInfo(token);
if (isLoggedIn()) {
log.debug("Login-SUCCESS " + this);
authenticateAndRedirect(request, response);
return true;
} else {
response.sendError(SC_UNAUTHORIZED);
return false;
}
} else {
log.debug("Login-PHASE1 " + this);
redirectToken = LoginUrlToken.getToken(request);
response.sendRedirect(oauth.getAuthorizationUrl() +
"&state=" + state);
return false;
}
}
private void authenticateAndRedirect(HttpServletRequest req,
HttpServletResponse rsp) throws IOException {
com.google.gerrit.server.account.AuthRequest areq =
new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
AuthResult arsp = null;
try {
String claimedIdentifier = user.getClaimedIdentity();
Account.Id actualId = accountManager.lookup(user.getExternalId());
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
if (claimedId != null && actualId != null) {
if (claimedId.equals(actualId)) {
// Both link to the same account, that's what we expected.
} else {
// This is (for now) a fatal error. There are two records
// for what might be the same user.
//
log.error("OAuth accounts disagree over user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
+ "\n" + " Delgate ID: " + actualId + " is "
+ user.getExternalId());
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
} else if (claimedId != null && actualId == null) {
// Claimed account already exists: link to it.
//
try {
accountManager.link(claimedId, areq);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
+ " to user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
}
}
areq.setUserName(user.getUserName());
areq.setEmailAddress(user.getEmailAddress());
areq.setDisplayName(user.getDisplayName());
arsp = accountManager.authenticate(areq);
} catch (AccountException e) {
log.error("Unable to authenticate user \"" + user + "\"", e);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
webSession.get().login(arsp, true);
StringBuilder rdr = new StringBuilder(urlProvider.get(req));
rdr.append(Url.decode(redirectToken));
rsp.sendRedirect(rdr.toString());
}
void logout() {
token = null;
user = null;
redirectToken = null;
serviceProvider = null;
}
private boolean checkState(ServletRequest request) {
String s = Strings.nullToEmpty(request.getParameter("state"));
if (!s.equals(state)) {
log.error("Illegal request state '" + s + "' on OAuthProtocol " + this);
return false;
}
return true;
}
private static SecureRandom newRandomGenerator() {
try {
return SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(
"No SecureRandom available for GitHub authentication", e);
}
}
private static String generateRandomState() {
byte[] state = new byte[32];
randomState.nextBytes(state);
return Base64.encodeBase64URLSafeString(state);
}
@Override
public String toString() {
return "OAuthSession [token=" + token + ", user=" + user + "]";
}
public void setServiceProvider(OAuthServiceProvider provider) {
this.serviceProvider = provider;
}
public OAuthServiceProvider getServiceProvider() {
return serviceProvider;
}
}

View File

@ -0,0 +1,115 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.auth.openid;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.SortedMap;
import java.util.SortedSet;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/** OAuth web filter uses active OAuth session to perform OAuth requests */
@Singleton
class OAuthWebFilterOverOpenID implements Filter {
static final String GERRIT_LOGIN = "/login";
private final Provider<CurrentUser> currentUserProvider;
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
private OAuthServiceProvider ssoProvider;
@Inject
OAuthWebFilterOverOpenID(Provider<CurrentUser> currentUserProvider,
DynamicMap<OAuthServiceProvider> oauthServiceProviders,
Provider<OAuthSessionOverOpenID> oauthSessionProvider) {
this.currentUserProvider = currentUserProvider;
this.oauthServiceProviders = oauthServiceProviders;
this.oauthSessionProvider = oauthSessionProvider;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
pickSSOServiceProvider();
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
if (currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) {
httpSession.invalidate();
}
chain.doFilter(request, response);
return;
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
OAuthServiceProvider service = ssoProvider == null
? oauthSession.getServiceProvider()
: ssoProvider;
if ((isGerritLogin(httpRequest)
|| oauthSession.isOAuthFinal(httpRequest))
&& !oauthSession.isLoggedIn()) {
if (service == null) {
throw new IllegalStateException("service is unknown");
}
oauthSession.setServiceProvider(service);
oauthSession.login(httpRequest, httpResponse, service);
} else {
chain.doFilter(httpRequest, response);
}
}
private void pickSSOServiceProvider() {
SortedSet<String> plugins = oauthServiceProviders.plugins();
if (plugins.size() == 1) {
SortedMap<String, Provider<OAuthServiceProvider>> services =
oauthServiceProviders.byPlugin(Iterables.getOnlyElement(plugins));
if (services.size() == 1) {
ssoProvider = Iterables.getOnlyElement(services.values()).get();
}
}
}
private static boolean isGerritLogin(HttpServletRequest request) {
return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
}
}

View File

@ -14,6 +14,8 @@
package com.google.gerrit.httpd.auth.openid;
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

View File

@ -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() {
}
}

View File

@ -22,6 +22,7 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.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));
}

View File

@ -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)

View File

@ -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 {

View File

@ -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")

View File

@ -21,6 +21,7 @@ import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.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);

View File

@ -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

View File

@ -22,11 +22,13 @@ import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.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,

View File

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.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());
}

View File

@ -16,8 +16,6 @@ package com.google.gerrit.server.edit;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.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);

View File

@ -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 {

View File

@ -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());
}

View File

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

View File

@ -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) {

View File

@ -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 {

View File

@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.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);
}
}

View File

@ -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