Merge "Add streamlined workflow to patch screen"
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 5522239..4c64dfe 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -76,6 +76,7 @@
@$(ASCIIDOC) -a toc \
-a data-uri \
-a 'revision=$(REVISION)' \
+ -a 'newline=\n' \
-b xhtml11 \
-f asciidoc.conf \
$(ASCIIDOC_EXTRA) \
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index b9fb8b3..7027562 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -23,7 +23,7 @@
Users in the 'Administrators' group can perform any action under
the Admin menu, to any group or project, without further validation
-of any other access controls. In most installations only those
+or any other access controls. In most installations only those
users who have direct filesystem and database access would be
placed into this group.
@@ -444,6 +444,17 @@
A restart is required after making database changes.
See <<restart_changes,below>>.
+[[category_abandon]]
+Abandon
+~~~~
+
+This category controls whether users are allowed to abandon changes
+to projects in Gerrit. It can give permission to abandon a specific
+change to a given ref.
+
+This also grants the permission to restore a change if the change
+can be uploaded.
+
[[category_create]]
Create reference
~~~~~~~~~~~~~~~~
@@ -451,7 +462,7 @@
The create reference category controls whether it is possible to
create new references, branches or tags. This implies that the
reference must not already exist, it's not a destructive permission
-in that you can't overwrite or remove any previosuly existing
+in that you can't overwrite or remove any previously existing
references (and also discard any commits in the process).
It's probably most common to either permit the creation of a single
@@ -462,7 +473,7 @@
branch permissions, allowing the holder of both to create new branches
as well as bypass review for new commits on that branch.
-To push lightweight (non annotated) tags, grant
+To push lightweight (non-annotated) tags, grant
`Create Reference` for reference name `refs/tags/*`, as lightweight
tags are implemented just like branches in Git.
@@ -815,7 +826,7 @@
Below follows a set of typical roles on a server and which access
rights these roles typically should be granted. You may see them as
-general guide lines for a typical way to set up your project on a
+general guidelines for a typical way to set up your project on a
brand new Gerrit instance.
[[examples_contributor]]
@@ -898,8 +909,8 @@
project and how much the CI system can be trusted for accurate results, a
blocking label might not be feasible. A recommended alternative is to set the
label `Code-review` to -1 instead, as it isn't a blocking label but still
-shows a red label in the Gerrit UI. Optionally; to enable the possibility to
-deliver different results (build error vs unstable for instance) it's also
+shows a red label in the Gerrit UI. Optionally, to enable the possibility to
+deliver different results (build error vs unstable for instance), it's also
possible to set `Code-review` +1 as well.
If pushing new changes is granted, it's possible to automate cherry-pick of
@@ -1070,6 +1081,15 @@
either link:cmd-create-project.html[create new git projects via ssh]
or via the web UI.
+[[capability_emailReviewers]]
+Email Reviewers
+~~~~~~~~~~~~~~~
+
+Allow or deny sending email to change reviewers and watchers. This can be used
+to deny build bots from emailing reviewers and people who watch the change.
+Instead, only the authors of the change and those who starred it will be
+emailed. The allow rules are evaluated before deny rules, however the default
+is to allow emailing, if no explicit rule is matched.
[[capability_flushCaches]]
Flush Caches
diff --git a/Documentation/cmd-cherry-pick.txt b/Documentation/cmd-cherry-pick.txt
index 568c872..d051a9a 100644
--- a/Documentation/cmd-cherry-pick.txt
+++ b/Documentation/cmd-cherry-pick.txt
@@ -39,7 +39,7 @@
====
$ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
- $ curl http://review.example.com/tools/bin/gerrit-cherry-pick
+ $ curl -o ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
====
GERRIT
diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt
index 475d2c5..8d404ec 100644
--- a/Documentation/cmd-create-group.txt
+++ b/Documentation/cmd-create-group.txt
@@ -53,6 +53,13 @@
--member::
User name to become initial member of the group. Multiple --member
options may be specified to add more initial members.
++
+Trying to add a user that doesn't have an account in Gerrit fails,
+unless LDAP is used for authentication. If LDAP is used for
+authentication and the user is not found, Gerrit tries to authenticate
+the user against the LDAP backend. If the authentication is successful
+a user account is automatically created, so that the user can be added
+to the group.
--group::
Group name to include in the group. Multiple --group options may
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index 85d1a92..02aa078 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -19,7 +19,7 @@
[--use-signed-off-by | --so]
[--use-content-merge]
[--require-change-id | --id]
- [--branch <REF> | -b <REF>]
+ [[--branch <REF> | -b <REF>] ...]
[--empty-commit]
{ <NAME> | --name <NAME> }
@@ -59,8 +59,11 @@
--branch::
-b::
- Name of the initial branch in the newly created project.
- Defaults to 'master'.
+ Name of the initial branch(es) in the newly created project.
+ Several branches can be specified on the command line.
+ If several branches are specified then the first one becomes HEAD
+ of the project. If none branches are specified then default value
+ ('master') is used.
--owner::
-o::
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index 6318bba..39cb3a5 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -54,7 +54,7 @@
OBTAINING
---------
To obtain the 'commit-msg' script use scp, wget or curl to download it
-to your local system from your gerrit server.
+to your local system from your Gerrit server.
You can use either of the below commands:
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 936729e..c4f222b 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -12,8 +12,8 @@
$ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
$ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
- $ curl http://review.example.com/tools/bin/gerrit-cherry-pick
- $ curl http://review.example.com/tools/hooks/commit-msg
+ $ curl -o ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
+ $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
For more details on how to determine the correct SSH port number,
see link:user-upload.html#test_ssh[Testing Your SSH Connection].
@@ -105,6 +105,9 @@
link:cmd-create-project.html[gerrit create-project]::
Create a new project and associated Git repository.
+link:cmd-set-project.html[gerrit set-project]::
+ Change a project's settings.
+
link:cmd-flush-caches.html[gerrit flush-caches]::
Flush some/all server caches from memory.
diff --git a/Documentation/cmd-ls-groups.txt b/Documentation/cmd-ls-groups.txt
index 8564db2..a50657b 100644
--- a/Documentation/cmd-ls-groups.txt
+++ b/Documentation/cmd-ls-groups.txt
@@ -30,6 +30,12 @@
---------
This command is intended to be used in scripts.
+All non-printable characters (ASCII value 31 or less) are escaped
+according to the conventions used in languages like C, Python, and Perl,
+employing standard sequences like `\n` and `\t`, and `\xNN` for all
+others. In shell scripts, the `printf` command can be used to unescape
+the output.
+
OPTIONS
-------
--project::
@@ -65,10 +71,19 @@
+
--
`internal`:: Any group defined within Gerrit.
-`ldap`:: Any group defined by an external LDAP database.
`system`:: Any system defined and managed group.
--
+--verbose::
+-v::
+ Enable verbose output with tab-separated columns for the
+ group name, UUID, description, type (`SYSTEM` or `INTERNAL`),
+ owner group name, owner group UUID and whether the group is
+ visible to all (`true` or `false`).
++
+If a group has been "orphaned", i.e. its owner group UUID refers to a
+nonexistent group, the owner group name field will read `n/a`.
+
EXAMPLES
--------
@@ -91,6 +106,23 @@
Registered Users
=====
+Extract the UUID of the 'Administrators' group:
+
+=====
+ $ ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $2}'
+ ad463411db3eec4e1efb0d73f55183c1db2fd82a
+=====
+
+Extract and expand the multi-line description of the 'Administrators'
+group:
+
+=====
+ $ printf "$(ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $3}')\n"
+ This is a
+ multi-line
+ description.
+=====
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index c1b37fa..25cd9a9 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -46,8 +46,11 @@
Allows listing of projects together with their respective
description.
+
-Line-feeds are escaped to allow ls-project to keep the
-"one project per line"-style.
+For text format output, all non-printable characters (ASCII value 31 or
+less) are escaped according to the conventions used in languages like C,
+Python, and Perl, employing standard sequences like `\n` and `\t`, and
+`\xNN` for all others. In shell scripts, the `printf` command can be
+used to unescape the output.
--tree::
-t::
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index ac613e5..eed6902 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -52,7 +52,7 @@
--force-message::
Option which allows Gerrit to publish the --message, even
- when the labels could not be applied due to change being
+ when the labels could not be applied due to the change being
closed).
+
Used by some scripts/CI-systems, where the results (or links
@@ -69,11 +69,11 @@
complete listing of supported approval categories and values.
--abandon::
- Abandon the specified patch set(s).
+ Abandon the specified change(s).
(option is mutually exclusive with --submit and --restore)
--restore::
- Restore the specified abandoned patch set(s).
+ Restore the specified abandoned change(s).
(option is mutually exclusive with --abandon)
--submit::
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
new file mode 100644
index 0000000..c4b9b4f
--- /dev/null
+++ b/Documentation/cmd-set-project.txt
@@ -0,0 +1,111 @@
+gerrit set-project
+==================
+
+NAME
+----
+gerrit set-project - Change a project's settings.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit set-project'
+ [--description <DESC> | -d <DESC>]
+ [--submit-type <TYPE> | -t <TYPE>]
+ [--use|no-contributor-agreements | --ca|nca]
+ [--use|no-signed-off-by | --so|nso]
+ [--use|no-content-merge]
+ [--require|no-change-id | --id|nid]
+ [--project-state | --ps]
+ <NAME>
+
+DESCRIPTION
+-----------
+Modifies a given project's settings. This command can be useful to
+batch change projects.
+
+The command is argument-safe, that is, if no argument is given the
+previous settings are kept intact.
+
+ACCESS
+------
+Caller must be a member of the privileged 'Administrators' group.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+<NAME>::
+ Required; name of the project to edit. If name ends
+ with `.git` the suffix will be automatically removed.
+
+--description::
+-d::
+ New description of the project. If not specified,
+ the old description is kept.
++
+Description values containing spaces should be quoted in single quotes
+('). This most likely requires double quoting the value, for example
+`--description "'A description string'"`.
+
+--submit-type::
+-t::
+ Action used by Gerrit to submit an approved change to its
+ destination branch. Supported options are:
++
+* FAST_FORWARD_ONLY: produces a strictly linear history.
+* MERGE_IF_NECESSARY: create a merge commit when required.
+* MERGE_ALWAYS: always create a merge commit.
+* CHERRY_PICK: always cherry-pick the commit.
+
++
+For more details see
+link:project-setup.html#submit_type[Change Submit Actions].
+
+--use|no-content-merge::
+ If enabled, Gerrit will try to perform a 3-way merge of text
+ file content when a file has been modified by both the
+ destination branch and the change being submitted. This
+ option only takes effect if submit type is not
+ FAST_FORWARD_ONLY.
+
+--use|no-contributor-agreements::
+--ca|nca::
+ If enabled, authors must complete a contributor agreement
+ on the site before pushing any commits or changes to this
+ project.
+
+--use|no-signed-off-by::
+--so|nso:
+ If enabled, each change must contain a Signed-off-by line
+ from either the author or the uploader in the commit message.
+
+--require|no-change-id::
+--id|nid::
+ Require a valid link:user-changeid.html[Change-Id] footer
+ in any commit uploaded for review. This does not apply to
+ commits pushed directly to a branch or tag.
+
+--project-state::
+--ps::
+ Set project's visibility.
++
+* ACTIVE: project is regular and is the default value.
+* READ_ONLY: users can see the project if read permission
+is granted, but all modification operations are disabled.
+* HIDDEN: the project is not visible for those who are not owners
+
+EXAMPLES
+--------
+Change project `example` to be hidden, require change id, don't use content merge
+and use 'merge if necessary' as merge strategy:
+
+====
+ $ ssh -p 29418 review.example.com gerrit set-project example --submit-type MERGE_IF_NECESSARY\
+ --require-change-id --no-content-merge --project-state HIDDEN
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
\ No newline at end of file
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index bf78051..0fb27cc 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -41,9 +41,10 @@
SCHEMA
------
The JSON messages consist of nested objects referencing the *change*,
-*patchset*, *account* involved, and other attributes as appropriate.
+*patchSet*, *account* involved, and other attributes as appropriate.
The currently supported message types are *patchset-created*,
-*comment-added*, *change-merged*, and *change-abandoned*.
+*draft-published*, *change-abandoned*, *change-restored*,
+*change-merged*, *comment-added* and *ref-updated*.
Note that any field may be missing in the JSON messages, so consumers of
this JSON stream should deal with that appropriately.
@@ -56,6 +57,16 @@
change:: link:json.html#change[change attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
+
+uploader:: link:json.html#account[account attribute]
+
+Draft Published
+^^^^^^^^^^^^^^^
+type:: "draft-published"
+
+change:: link:json.html#change[change attribute]
+
patchset:: link:json.html#patchset[patchset attribute]
uploader:: link:json.html#account[account attribute]
@@ -66,27 +77,31 @@
change:: link:json.html#change[change attribute]
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
abandoner:: link:json.html#account[account attribute]
+reason:: Reason for abandoning the change.
+
Change Restored
-^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^
type:: "change-restored"
change:: link:json.html#change[change attribute]
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
restorer:: link:json.html#account[account attribute]
+reason:: Reason for restoring the change.
+
Change Merged
^^^^^^^^^^^^^
type:: "change-merged"
change:: link:json.html#change[change attribute]
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
submitter:: link:json.html#account[account attribute]
@@ -96,10 +111,12 @@
change:: link:json.html#change[change attribute]
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
author:: link:json.html#account[account attribute]
+approvals:: All link:json.html#approval[approval attributes] granted.
+
comment:: Comment text author had written
Ref Updated
@@ -108,7 +125,7 @@
submitter:: link:json.html#account[account attribute]
-refUpdate:: link:json.html#refupdate[refupdate attribute]
+refUpdate:: link:json.html#refUpdate[refUpdate attribute]
SEE ALSO
diff --git a/Documentation/config-contact.txt b/Documentation/config-contact.txt
index 5c633cf..4d8851f 100644
--- a/Documentation/config-contact.txt
+++ b/Documentation/config-contact.txt
@@ -48,7 +48,7 @@
The actual values chosen don't matter later, and are only to help
document the purpose of the key.
-Chose a fairly long expiration period, such as 20 years. For most
+Choose a fairly long expiration period, such as 20 years. For most
Gerrit instances, contact data will be written once, and rarely,
if ever, read back.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 2c050d4..8610dba 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -88,6 +88,12 @@
provider chosen by the end-user. For more information see
http://openid.net/[openid.net].
+
+* `OpenID_SSO`
++
+Supports OpenID from a single provider. There is no registration
+link, and the "Sign In" link sends the user directly to the provider's
+SSO entry point.
++
* `HTTP`
+
Gerrit relies upon data presented in the HTTP request. This includes
@@ -107,7 +113,7 @@
* `CLIENT_SSL_CERT_LDAP`
+
This authentication type is actually kind of SSO. Gerrit will configure
-Jetty's SSL channel to request client's SSL certificate. For this
+Jetty's SSL channel to request the client's SSL certificate. For this
authentication to work a Gerrit administrator has to import the root
certificate of the trust chain used to issue the client's certificate
into the <review-site>/etc/keystore.
@@ -161,7 +167,7 @@
+
List of permitted OpenID providers. A user may only authenticate
with an OpenID that matches this list. Only used if `auth.type`
-was set to OpenID (the default).
+is set to OpenID (the default).
+
Patterns may be either a
link:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html[standard
@@ -173,7 +179,7 @@
[[auth.trustedOpenID]]auth.trustedOpenID::
+
-List of trusted OpenID providers. Only used if `auth.type` was
+List of trusted OpenID providers. Only used if `auth.type` is
set to OpenID (the default).
+
In order for a user to take advantage of permissions beyond those
@@ -229,10 +235,17 @@
+
Default is 12 hours.
+[[auth.openIdSsoUrl]]auth.openIdSsoUrl::
++
+The SSO entry point URL. Only used if `auth.type` was set to
+OpenID_SSO.
++
+The "Sign In" link will send users directly to this URL.
+
[[auth.httpHeader]]auth.httpHeader::
+
HTTP header to trust the username from, or unset to select HTTP basic
-or digest authentication. Only used if `auth.type` was set to HTTP.
+or digest authentication. Only used if `auth.type` is set to HTTP.
[[auth.logoutUrl]]auth.logoutUrl::
+
@@ -319,6 +332,18 @@
+
By default this is set to false.
+[[auth.gitBasicAuth]]auth.gitBasicAuth::
++
+If true then Git over HTTP and HTTP/S traffic is authenticated using
+standard BasicAuth and credentials validated using the same auth
+method configured for Gerrit Web UI.
++
+This parameter only affects git over http traffic. If set to false
+then Gerrit will authenticate through DIGEST authentication and
+the randomly generated HTTP password in Gerrit DB.
++
+By default this is set to false.
+
[[auth.userNameToLowerCase]]auth.userNameToLowerCase::
+
If set the username that is received to authenticate a git operation
@@ -354,8 +379,8 @@
[[cache.name.maxAge]]cache.<name>.maxAge::
+
-Maximum age to keep an entry in the cache. If an entry has not
-been accessed in this period of time, it is removed from the cache.
+Maximum age to keep an entry in the cache. Entries are removed from
+the cache and refreshed from source data every maxAge interval.
Values should use common unit suffixes to express their setting:
+
* s, sec, second, seconds
@@ -371,7 +396,7 @@
supplied, the maximum age is infinite and items are never purged
except when the cache is full.
+
-Default is `90 days` for most caches, except:
+Default is `0`, meaning store forever with no expire, except:
+
* `"adv_bases"`: default is `10 minutes`
* `"ldap_groups"`: default is `1 hour`
@@ -379,33 +404,42 @@
[[cache.name.memoryLimit]]cache.<name>.memoryLimit::
+
-Maximum number of cache items to retain in memory. Keep in mind
-this is total number of items, not bytes of heap used.
+The total cost of entries to retain in memory. The cost computation
+varies by the cache. For most caches where the in-memory size of each
+entry is relatively the same, memoryLimit is currently defined to be
+the number of entries held by the cache (each entry costs 1).
++
+For caches where the size of an entry can vary significantly between
+individual entries (notably `"diff"`, `"diff_intraline"`), memoryLimit
+is an approximation of the total number of bytes stored by the cache.
+Larger entries that represent bigger patch sets or longer source files
+will consume a bigger portion of the memoryLimit. For these caches the
+memoryLimit should be set to roughly the amount of RAM (in bytes) the
+administrator can dedicate to the cache.
+
Default is 1024 for most caches, except:
+
* `"adv_bases"`: default is `4096`
-* `"diff"`: default is `128`
-* `"diff_intraline"`: default is `128`
+* `"diff"`: default is `10m` (10 MiB of memory)
+* `"diff_intraline"`: default is `10m` (10 MiB of memory)
+* `"plugin_resources"`: default is 2m (2 MiB of memory)
+
++
+If set to 0 the cache is disabled. Entries are removed immediately
+after being stored by the cache. This is primarily useful for testing.
[[cache.name.diskLimit]]cache.<name>.diskLimit::
+
-Maximum number of cache items to retain on disk, if this cache
-supports storing its items to disk. Like memoryLimit, this is
-total number of items, not bytes of disk used. If 0, disk storage
-for this cache is disabled.
+Total size in bytes of the keys and values stored on disk. Caches that
+have grown bigger than this size are scanned daily at 1 AM local
+server time to trim the cache. Entries are removed in least recently
+accessed order until the cache fits within this limit. Caches may
+grow larger than this during the day, as the size check is only
+performed once every 24 hours.
+
-Default is 16384.
-
-[[cache.name.diskBuffer]]cache.<name>.diskBuffer::
+Default is 128 MiB per cache.
+
-Number of bytes to buffer in memory before writing less frequently
-accessed cache items to disk, if this cache supports storing its
-items to disk.
-+
-Default is 5 MiB.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
+If 0, disk storage for the cache is disabled.
[[cache_names]]Standard Caches
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -447,14 +481,10 @@
directory and file levels. Gerrit uses this cache to accelerate
the display of affected file names, as well as file contents.
+
-Entries in this cache are relatively large, so the memory limit
-should not be set incredibly high. Administrators should try to
-target cache.diff.memoryLimit to be roughly the number of changes
-which their users will process in a 1 or 2 day span.
-+
-Keeping entries for 90 days gives sufficient time for most changes
-to be submitted or abandoned before their relevant difference items
-expire out.
+Entries in this cache are relatively large, so memoryLimit is an
+estimate in bytes of memory used. Administrators should try to target
+cache.diff.memoryLimit to fit all changes users will view in a 1 or 2
+day span.
cache `"diff_intraline"`::
+
@@ -462,14 +492,10 @@
between two commits. Gerrit uses this cache to accelerate display of
intraline differences when viewing a file.
+
-Entries in this cache are relatively large, so the memory limit
-should not be set incredibly high. Administrators should try to
-target cache.diff.memoryLimit to be roughly the number of changes
-which their users will process in a 1 or 2 day span.
-+
-Keeping entries for 90 days gives sufficient time for most changes
-to be submitted or abandoned before their relevant difference items
-expire out.
+Entries in this cache are relatively large, so memoryLimit is an
+estimate in bytes of memory used. Administrators should try to target
+cache.diff.memoryLimit to fit all files users will view in a 1 or 2
+day span.
cache `"git_tags"`::
+
@@ -512,11 +538,17 @@
cache `"permission_sort"`::
+
-Caches the order access control sections must be applied to a
+Caches the order in which access control sections must be applied to a
reference. Sorting the sections can be expensive when regular
expressions are used, so this cache remembers the ordering for
each branch.
+cache `"plugin_resources"`::
++
+Caches formatted plugin resources, such as plugin documentation that
+has been converted from Markdown to HTML. The memoryLimit refers to
+the bytes of memory dedicated to storing the documentation.
+
cache `"projects"`::
+
Caches the project description records, from the `projects` table
@@ -550,8 +582,8 @@
unable to persist the session information. Enabling a disk cache
is strongly recommended.
+
-Session storage is relatively inexpensive, the average entry in
-this cache is approximately 248 bytes, depending on the JVM.
+Session storage is relatively inexpensive. The average entry in
+this cache is approximately 346 bytes.
See also link:cmd-flush-caches.html[gerrit flush-caches].
@@ -563,7 +595,7 @@
Number of idle worker threads to maintain for the intraline difference
computations. There is no upper bound on how many concurrent requests
can occur at once, if additional threads are started to handle a peak
-load, only this many will remaining idle afterwards.
+load, only this many will remain idle afterwards.
+
Default is 1.5x number of available CPUs.
@@ -598,13 +630,6 @@
+
Default is true, enabled.
-cache.plugin_resources.memoryLimit::
-+
-Number of bytes of memory to use to cache formatted plugin resources,
-such as plugin documentation that has been converted from Markdown to
-HTML. Default is 2 MiB. Common unit suffixes of 'k', 'm', or 'g' are
-supported.
-
cache.projects.checkFrequency::
+
How often project configuration should be checked for update from Git.
@@ -646,7 +671,7 @@
to changes which reference it. The second configuration 'bugzilla'
will hyperlink terms such as 'bug 42' to an external bug tracker,
supplying the argument record number '42' for display. The third
-configuration 'tracker' uses raw HTML to more preciously control
+configuration 'tracker' uses raw HTML to more precisely control
how the replacement is displayed to the user.
----
@@ -854,6 +879,15 @@
Default on JGit is false. Although potentially slower, it yields
much more predictable behavior.
+[[core.asyncLoggingBufferSize]]core.asyncLoggingBufferSize::
++
+Size of the buffer to store logging events for asynchronous logging.
+Putting a larger value can protect threads from stalling when the
+AsyncAppender threads are not fast enough to consume the logging events
+from the buffer. It also protects from loosing log entries in this case.
++
+Default is 64 entries.
+
[[database]]Section database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1180,6 +1214,11 @@
Optional filename for the patchset created hook, if not specified then
`patchset-created` will be used.
+[[hooks.draftPublishedHook]]hooks.draftPublishedHook::
++
+Optional filename for the draft published hook, if not specified then
+`draft-published` will be used.
+
[[hooks.commentAddedHook]]hooks.commentAddedHook::
+
Optional filename for the comment added hook, if not specified then
@@ -1195,6 +1234,21 @@
Optional filename for the change abandoned hook, if not specified then
`change-abandoned` will be used.
+[[hooks.changeRestoredHook]]hooks.changeRestoredHook::
++
+Optional filename for the change restored hook, if not specified then
+`change-restored` will be used.
+
+[[hooks.refUpdatedHook]]hooks.refUpdatedHook::
++
+Optional filename for the ref updated hook, if not specified then
+`ref-updated` will be used.
+
+[[hooks.claSignedHook]]hooks.claSignedHook::
++
+Optional filename for the CLA signed hook, if not specified then
+`cla-signed` will be used.
+
[[http]]Section http
~~~~~~~~~~~~~~~~~~~~
@@ -1318,7 +1372,7 @@
[[httpd.sslKeyPassword]]httpd.sslKeyPassword::
+
Password used to decrypt the private portion of the sslKeyStore.
-Java key stores require a password, even if the administrator
+Java keystores require a password, even if the administrator
doesn't want to enable one.
+
If set to the empty string the embedded server will prompt for the
@@ -1338,7 +1392,7 @@
[[httpd.acceptorThreads]]httpd.acceptorThreads::
+
Number of worker threads dedicated to accepting new incoming TCP
-connections and allocate them connection-specific resources.
+connections and allocating them connection-specific resources.
+
By default, 2, which should be suitable for most high-traffic sites.
@@ -1366,7 +1420,7 @@
[[httpd.maxWait]]httpd.maxWait::
+
-Maximum amount of time a client will wait to for an available
+Maximum amount of time a client will wait for an available
thread to handle a project clone, fetch or push request over the
smart HTTP transport.
+
@@ -1391,7 +1445,7 @@
[[ldap]]Section ldap
~~~~~~~~~~~~~~~~~~~~
-LDAP integration is only enabled if `auth.type` was set to
+LDAP integration is only enabled if `auth.type` is set to
`HTTP_LDAP`, `LDAP` or `CLIENT_SSL_CERT_LDAP`. See above for a
detailed description of the auth.type settings and their
implications.
@@ -1459,7 +1513,7 @@
_(Optional)_ The read timeout for an LDAP operation. The value is
in the usual time-unit format like "1 s", "100 ms", etc...
A timeout can be used to avoid blocking all of the SSH command start
-threads in case when the LDAP server becomes slow.
+threads in case the LDAP server becomes slow.
+
By default there is no timeout and Gerrit will wait for the LDAP
server to respond until the TCP connection times out.
@@ -1506,8 +1560,8 @@
Typically this is the `displayName` property in LDAP, but could
also be `legalName` or `cn`.
+
-Attribute values may be concatenated with literal strings, for
-example to join given name and surname together use the pattern
+Attribute values may be concatenated with literal strings. For
+example to join given name and surname together, use the pattern
`${givenName} ${SN}`.
+
If set, users will be unable to modify their full name field, as
@@ -1528,7 +1582,7 @@
`${sAMAccountName.toLowerCase}@example.com`.
+
If set, the preferred email address will be prefilled from LDAP,
-but users may still be able to register additional email address,
+but users may still be able to register additional email addresses,
and select a different preferred email address.
+
Default is `mail`.
@@ -1618,7 +1672,7 @@
+
If set, it must be ensured that the local usernames for all existing
accounts are converted to lower case, otherwise a user that has a
-local username that contains upper case characters cannot login
+local username that contains upper case characters will not be able to login
anymore. The local usernames for the existing accounts can be
converted to lower case by running the server program
link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase].
@@ -1728,7 +1782,7 @@
and the push operation will fail. If set to zero then there is no
limit.
+
-Gerrit administrator can use this setting to prevent developers
+Gerrit administrators can use this setting to prevent developers
from pushing objects which are too large to Gerrit.
+
Default is zero.
@@ -1879,6 +1933,22 @@
+
By default, unset, permitting delivery to any email address.
+[[sendemail.includeDiff]]sendemail.includeDiff::
++
+If true, new change emails from Gerrit will include the complete
+unified diff of the change. Variable maxmimumDiffSize places an upper
+limit on how large the email can get when this option is enabled.
++
+By default, false.
+
+[[sendemail.maximumDiffSize]]sendemail.maximumDiffSize::
++
+Largest size of unified diff output to include in an email. When
+the diff exceeds this size the file paths will be listed instead.
+Standard byte unit suffixes are supported.
++
+By default, 256 KiB.
+
[[sendemail.importance]]sendemail.importance::
+
If present, emails sent from Gerrit will have the given level
@@ -1922,6 +1992,28 @@
and text results for changes. If false, the URL is disabled and
returns 404 to clients. Default is true, enabling `/query`.
+[[site.upgradeSchemaOnStartup]]site.upgradeSchemaOnStartup::
++
+Control whether schema upgrade should be done on Gerrit startup. The following
+values are supported:
++
+* `OFF`
++
+No automatic schema upgrade on startup.
++
+* `AUTO`
++
+Perform schema migration on startup, if necessary. If, as a result of
+schema migration, there would be any unused database objects they will
+be dropped automatically.
++
+* `AUTO_NO_PRUNE`
++
+Like `AUTO` but unused database objects will not be pruned.
+
++
+The default is `OFF`.
+
[[ssh-alias]] Section ssh-alias
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1995,7 +2087,7 @@
+
Number of threads to use when executing SSH command requests.
If additional requests are received while all threads are busy they
-are queued and serviced in a first-come-first-serve order.
+are queued and serviced in a first-come-first-served order.
+
By default, 1.5x the number of CPUs available to the JVM.
@@ -2062,7 +2154,7 @@
+
Maximum number of concurrent SSH sessions that a user account
may open at one time. This is the number of distinct SSH logins
-the each user may have active at one time, and is not related to
+that each user may have active at one time, and is not related to
the number of commands a user may issue over a single connection.
If set to 0, there is no limit.
+
@@ -2158,6 +2250,31 @@
+
By default a shade of yellow, `FFFFCC`.
+[[theme.changeTableOutdatedColor]]theme.changeTableOutdatedColor::
++
+Background color used for patch outdated messages. The value must be
+a valid HTML hex color code, or standard color name.
++
+By default a shade of red, `FF0000`.
+
+[[theme.tableOddRowColor]]theme.tableOddRowColor::
++
+Background color for tables such as lists of open reviews for odd
+rows. This is so you can have a different color for odd and even
+rows of the table. The value must be a valid HTML hex color code,
+or standard color name.
++
+By default transparent.
+
+[[theme.tableEvenRowColor]]theme.tableEvenRowColor::
++
+Background color for tables such as lists of open reviews for even
+rows. This is so you can have a different color for odd and even
+rows of the table. The value must be a valid HTML hex color code,
+or standard color name.
++
+By default transparent.
+
A different theme may be used for signed-in vs. signed-out user status
by using the "signed-in" and "signed-out" theme sections. Variables
not specified in a section are inherited from the default theme.
@@ -2210,7 +2327,7 @@
external tracking id part of the footer line. The match can
result in several entries in the DB. If grouping is used in the
regex the first group will be interpreted as the tracking id.
-Tracking ids > 20 char will be ignored.
+Tracking ids longer than 20 characters will be ignored.
+
The configuration file parser eats one level of backslashes, so the
character class `\s` requires `\\s` in the configuration file. The
@@ -2219,7 +2336,7 @@
[[trackingid.name.system]]trackingid.<name>.system::
+
-The name of the external tracking system(max 10 char).
+The name of the external tracking system (maximum 10 characters).
It is possible to have several trackingid entries for the same
tracking system.
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index a08eb87..35d5c0d 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -27,7 +27,7 @@
Alternatively, if Gerrit is served behind reverse proxy, it can
generate different URLs for gitweb's links (they need to be
rewritten to `<gerrit>/gitweb?args` on the web server). This allows
-for serving gitweb under different URL than the Gerrit instance.
+for serving gitweb under a different URL than the Gerrit instance.
To enable this feature, set both: `gitweb.cgi` and `gitweb.url`.
====
diff --git a/Documentation/config-headerfooter.txt b/Documentation/config-headerfooter.txt
index c06080b..ae5d8f7 100644
--- a/Documentation/config-headerfooter.txt
+++ b/Documentation/config-headerfooter.txt
@@ -42,7 +42,7 @@
or `GerritSite.css` by the relative URL `static/$name`
(e.g. `static/logo.png`).
-To simplify security management, only files are served from
+To simplify security management, files are only served from
`'$site_path'/static`. Subdirectories are explicitly forbidden from
being served from this location by enforcing the rule that file names
cannot contain `/` or `\`. (Client requests for `static/foo/bar`
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index ceb7c78..a5415a9 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -24,12 +24,21 @@
~~~~~~~~~~~~~~~~
This is called whenever a patchset is created (this includes new
-changes)
+changes and drafts).
====
patchset-created --change <change id> --change-url <change url> --project <project name> --branch <branch> --uploader <uploader> --commit <sha1> --patchset <patchset id>
====
+draft-published
+~~~~~~~~~~~~~~~
+
+This is called whenever a draft change is published.
+
+====
+ draft-published --change <change id> --change-url <change url> --project <project name> --branch <branch> --uploader <uploader> --commit <sha1> --patchset <patchset id>
+====
+
comment-added
~~~~~~~~~~~~~
@@ -58,7 +67,7 @@
====
change-restored
-~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~
Called whenever a change has been restored.
@@ -76,9 +85,9 @@
====
cla-signed
-~~~~~~~~~~~
+~~~~~~~~~~
-Called whenever a user signs a contributor license agreement
+Called whenever a user signs a contributor license agreement.
====
cla-signed --submitter <submitter> --user-id <user_id> --cla-id <cla_id>
@@ -88,13 +97,15 @@
Configuration Settings
----------------------
-It is possible to change where gerrit looks for hooks, and what
-filenames it looks for by adding a [hooks] section to gerrit.config.
+It is possible to change where Gerrit looks for hooks, and what
+filenames it looks for, by adding a [hooks] section in gerrit.config.
-Gerrit will use the value of hooks.path for the hooks directory, and
-the values of hooks.patchsetCreatedHook, hooks.commentAddedHook,
-hooks.changeMergedHook and hooks.changeAbandonedHook for the
-filenames for the hooks.
+Gerrit will use the value of hooks.path for the hooks directory.
+
+For the hook filenames, Gerrit will use the values of hooks.patchsetCreatedHook,
+hooks.draftPublishedHook, hooks.commentAddedHook, hooks.changeMergedHook,
+hooks.changeAbandonedHook, hooks.changeRestoredHook, hooks.refUpdatedHook and
+hooks.claSignedHook.
Missing Change URLs
-------------------
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 8aa7d08..bdf0c3a 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -20,7 +20,7 @@
Supported Mail Templates:
-------------------------
-Each mail that Gerrit sends out is controlled by at least one template, these
+Each mail that Gerrit sends out is controlled by at least one template. These
are listed below. Change emails are influenced by two additional templates,
one to set the subject line, and one to set the footer which gets appended to
all the change emails (see `ChangeSubject.vm` and `ChangeFooter.vm` below.)
@@ -36,7 +36,7 @@
~~~~~~~~~~~~~~~
The `ChangeFooter.vm` template will determine the contents of the footer
-text that will be appended to emails related to changes (all `ChangeEmails)`.
+text that will be appended to emails related to changes (all `ChangeEmail`s).
ChangeSubject.vm
~~~~~~~~~~~~~~~~
@@ -49,6 +49,7 @@
The `Comment.vm` template will determine the contents of the email related to
a user submitting comments on changes. It is a `ChangeEmail`: see
+`ChangeSubject.vm` and `ChangeFooter.vm`.
Merged.vm
~~~~~~~~~
@@ -62,6 +63,7 @@
The `MergeFail.vm` template will determine the contents of the email related
to a failure upon attempting to merge a change to the head. It is a
+`ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
NewChange.vm
~~~~~~~~~~~~
@@ -107,7 +109,7 @@
Warning
~~~~~~~
-Be aware that modifying templates can cause them to fail to parse and therefor
+Be aware that modifying templates can cause them to fail to parse and therefore
not send out the actual email, or worse, calling methods on the available
objects could have internal side effects which would adversely affect the
health of your Gerrit server and/or data.
@@ -125,7 +127,7 @@
$messageClass::
+
-A String containing the messageClass
+A String containing the messageClass.
$StringUtils::
+
@@ -139,35 +141,35 @@
$change::
+
-A reference to the current `Change` object
+A reference to the current `Change` object.
$changeId::
+
-Id of the current change (a `Change.Key`)
+Id of the current change (a `Change.Key`).
$coverLetter::
+
-The text of the `ChangeMessage`
+The text of the `ChangeMessage`.
$branch::
+
-A reference to the branch of this change (a `Branch.NameKey`)
+A reference to the branch of this change (a `Branch.NameKey`).
$fromName::
+
-The name of the from user
+The name of the from user.
$projectName::
+
-The name of this change's project
+The name of this change's project.
$patchSet::
+
-A reference to the current `PatchSet`
+A reference to the current `PatchSet`.
$patchSetInfo::
+
-A reference to the current `PatchSetInfo`
+A reference to the current `PatchSetInfo`.
See Also
diff --git a/Documentation/config-reverseproxy.txt b/Documentation/config-reverseproxy.txt
index 4eecf67..7161c4a 100644
--- a/Documentation/config-reverseproxy.txt
+++ b/Documentation/config-reverseproxy.txt
@@ -5,7 +5,7 @@
-----------
Gerrit can be configured to run behind a third-party web server.
-This allows the other web server to bind to the privileged ports 80
+This allows the other web server to bind to the privileged port 80
(or 443 for SSL), as well as offloads the SSL processing overhead
from Java to optimized native C code.
diff --git a/Documentation/config-sso.txt b/Documentation/config-sso.txt
index 9aa06be..e915ffb 100644
--- a/Documentation/config-sso.txt
+++ b/Documentation/config-sso.txt
@@ -146,7 +146,7 @@
The auth.type must always be HTTP, indicating the user identity
will be obtained from the HTTP authorization data.
-The auth.httpHeader indicates which HTTP header field the Siteminder
+The auth.httpHeader indicates in which HTTP header field the Siteminder
product has stored the username. Usually this is "SM_USER", but
may differ in your environment. Please refer to your organization's
single sign-on or security group to ensure the setting is correct.
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index 103ede8..ce2868c 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -37,7 +37,7 @@
Git is a distributed version control system, wherein each repository
is assumed to be owned/maintained by a single user. There are no
-inherit security controls built into Git, so the ability to read
+inherent security controls built into Git, so the ability to read
from or write to a repository is controlled entirely by the host's
filesystem access controls. When multiple maintainers collaborate
on a single shared repository a high degree of trust is required,
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index ca56da3..a19d85e 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -73,9 +73,15 @@
Running Hosted Mode
~~~~~~~~~~~~~~~~~~~
-Import the gerrit-gwtdebug project:
+To debug the GWT code executing in the web browser, three additional Git
+repositories need to be cloned.
-* Import gerrit-gwtdebug/pom.xml using General -> Maven Projects
+* https://gerrit.googlesource.com/gwtexpui
+* https://gerrit.googlesource.com/gwtjsonrpc
+* https://gerrit.googlesource.com/gwtorm
+
+In Eclipse, import the pom.xml file in the root directory of each of
+these cloned gits via General -> Maven Projects.
Duplicate the existing `gwtui_dbg` launch configuration:
@@ -97,12 +103,17 @@
Known problems
--------------
-When running Gerrit under the Eclipse debugger, code that attempts
+* When running Gerrit under the Eclipse debugger, code that attempts
to load Prolog code may erroneously raise ClassNotFoundException,
claiming that classes in the `Gerrit` package can't be found. The
error can often be resolved by rebuilding Gerrit with `mvn package`
and restarting the debug session.
+* OpenID authentication won't work in hosted mode, so you need to change
+the link:config-gerrit.html#auth.type[auth.type] configuration parameter
+to `DEVELOPMENT_BECOME_ANY_ACCOUNT` to disable OpenID and allow you to
+impersonate whatever account you otherwise would've used.
+
GERRIT
------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 586ae07..7832aa9 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -192,7 +192,7 @@
by PrintHello class will be available to users as:
----
-$ ssh -P 29418 review.example.com helloworld print
+$ ssh -p 29418 review.example.com helloworld print
----
HTTP Servlets
@@ -312,7 +312,7 @@
the plugin from this location to its own site path.
+
----
-$ ssh -P 29418 localhost gerrit plugin install -n name $(pwd)/my-plugin.jar
+$ ssh -p 29418 localhost gerrit plugin install -n name $(pwd)/my-plugin.jar
----
* Valid URL, including any HTTP or FTP site reachable by the
@@ -320,14 +320,14 @@
its own site path.
+
----
-$ ssh -P 29418 localhost gerrit plugin install -n name http://build-server/output/our-plugin.jar
+$ ssh -p 29418 localhost gerrit plugin install -n name http://build-server/output/our-plugin.jar
----
* As piped input to the plugin install command. The server will
copy input until EOF, and save a copy under its own site path.
+
----
-$ ssh -P 29418 localhost gerrit plugin install -n name - <target/name-0.1.jar
+$ ssh -p 29418 localhost gerrit plugin install -n name - <target/name-0.1.jar
----
Plugins can also be copied directly into the server's
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index a9d0553..f686d0c 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -4,43 +4,38 @@
Preparing a New Gerrit Subproject Snapshot for Publishing
---------------------------------------------------------
-* You will need to have the following in the pom.xml to make it deployable to:
-gerrit-maven-repository.googlecode.com
+* You will need to have the following in the pom.xml to make it
+ deployable to the gerrit-maven storage bucket:
+
----
<distributionManagement>
- <snapshotRepository>
- <id>gerrit-snapshot-repository</id>
- <name>gerrit Snapshot Repository</name>
- <url>dav:https://gerrit-maven-repository.googlecode.com/svn/</url>
- <uniqueVersion>true</uniqueVersion>
- </snapshotRepository>
-
<repository>
- <id>gerrit-maven-repository</id>
+ <id>gerrit-maven</id>
<name>gerrit Maven Repository</name>
- <url>dav:https://gerrit-maven-repository.googlecode.com/svn/</url>
+ <url>s3://gerrit-maven@commondatastorage.googleapis.com</url>
<uniqueVersion>true</uniqueVersion>
</repository>
</distributionManagement>
----
-* Since ubuntu maven is incomplete, also add this to the pom.xml:
+* Add this to the pom.xml to enable the wagon provider:
----
<build>
- <extensions>
- <extension>
- <groupId>org.apache.maven.wagon</groupId>
- <artifactId>wagon-webdav-jackrabbit</artifactId>
- <version>1.0-beta-6</version>
- </extension>
+ <extensions>
+ <extension>
+ <groupId>net.anzix.aws</groupId>
+ <artifactId>s3-maven-wagon</artifactId>
+ <version>3.2</version>
+ </extension>
</extensions>
</build>
----
-* Add your username and password to your ~/.m2/settings.xml file:
+* Add your username and password to your ~/.m2/settings.xml file.
+ These need to come from the link:https://code.google.com/apis/console/[API Console].
----
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
@@ -48,15 +43,9 @@
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
- <id>gerrit-maven-repository</id>
- <username>JohnDoe@example.com</username>
- <password>OpenSessame</password>
- </server>
-
- <server>
- <id>gerrit-snapshot-repository</id>
- <username>JohnDoe@example.com</username>
- <password>OpenSessame</password>
+ <id>gerrit-maven</id>
+ <username>GOOG..EXAMPLE.....EXAMPLE</username>
+ <password>EXAMPLE..EXAMPLE..EXAMPLE</password>
</server>
</servers>
</settings>
@@ -71,15 +60,15 @@
* Deploy the snapshot:
-----
+====
mvn deploy
-----
+====
Making a Gerrit Subproject Release
----------------------------------
-* First deploy (and test) the latest snapshot for this subprojects
+* First deploy (and test) the latest snapshot for the subproject
* Update the top level pom.xml in the subproject to reflect the new project
version (the exact value of the tag you will create below)
@@ -88,8 +77,18 @@
* Tag the version you just pushed (and push the tag)
+====
+ git tag -a -m "prolog-cafe 1.3" v1.3
+ git push gerrit-review refs/tags/v1.3:refs/tags/v1.3
+====
+
* Deploy the new release:
-----
+====
mvn deploy
-----
+====
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 7484795..e3a93cc 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -64,6 +64,9 @@
Create the Actual Release
---------------------------
+In the example commands below we assume that the last release was '2.4' and that
+we are preparing '2.5' release.
+
Prepare the Subprojects
~~~~~~~~~~~~~~~~~~~~~~~
@@ -75,20 +78,21 @@
Prepare Gerrit
~~~~~~~~~~~~~~
-* Update the top level pom in Gerrit to ensure that none of the Subprojects
- point to snapshot releases
+* In the 'stable-2.5' branch: Update the top level pom in Gerrit to ensure that
+none of the Subprojects point to snapshot releases
-* Update the poms for the Gerrit version, push for review, get merged
+* In the 'master' branch: Update the poms for the Gerrit version, push for
+review, get merged
====
- tools/version.sh --snapshot=2.3
+ tools/version.sh --snapshot=2.5
====
* Tag
====
- git tag -a -m "gerrit 2.2.2-rc0" v2.2.2-rc0
- git tag -a -m "gerrit 2.2.2.1" v2.2.2.1
+ git tag -a -m "gerrit 2.5-rc0" v2.5-rc0
+ git tag -a -m "gerrit 2.5" v2.5
====
* Build
@@ -126,7 +130,8 @@
* Push the New Tag
====
- git push google refs/tags/v2.2.2.1:refs/tags/v2.2.2.1
+ git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
+ git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
====
@@ -134,7 +139,7 @@
~~~~
====
- make -C Documentation PRIOR=2.2.2 update
+ make -C Documentation PRIOR=2.4 update
make -C ReleaseNotes update
====
@@ -142,7 +147,9 @@
* Update Google Code project links
** Go to http://code.google.com/p/gerrit/admin
-** Point the main page to the new docs
+** Point the main page to the new docs. The link to the documentation has to be
+updated at two places: in the project description and also in the Links
+section.
** Point the main page to the new release notes
[NOTE]
@@ -185,8 +192,7 @@
Mailing List
~~~~~~~~~~~~
-* Send an email to the mailing list to annouce the release
-* Consider including some or all of the following in the email:
+* Send an email to the mailing list to announce the release, consider including some or all of the following in the email:
** A link to the release and the release notes (if a final release)
** A link to the docs
** Describe the type of release (stable, bug fix, RC)
@@ -216,6 +222,23 @@
-Martin
----
+* Add an entry to the NEWS section of the main Gerrit project web page
+** Go to: http://code.google.com/p/gerrit/admin
+** Add entry like:
+----
+ * Jun 14, 2012 - Gerrit 2.4.1 [https://groups.google.com/d/topic/repo-discuss/jHg43gixqzs/discussion Released]
+----
+
+* Update the new discussion group announcement to be sticky
+** Go to: http://groups.google.com/group/repo-discuss/topics
+** Click on the announcement thread
+** Near the top right, click on options
+** Under options, cick the "Display this top first" checkbox
+** and Save
+
+* Update the previous discussion group announcement to no longer be sticky
+** See above (unclick checkbox)
+
Merging Stable Fixes to master
------------------------------
@@ -230,3 +253,8 @@
git branch -f stable origin/stable
git merge stable
====
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/error-branch-not-found.txt b/Documentation/error-branch-not-found.txt
index e2dcff1..2aad0e1 100644
--- a/Documentation/error-branch-not-found.txt
+++ b/Documentation/error-branch-not-found.txt
@@ -7,8 +7,8 @@
To push a change for code review the commit has to be pushed to the
project's magical `refs/for/'branch'` ref (for details have a look at
link:user-upload.html#push_create[Create Changes]).
-If you specify a non existing branch in the `refs/for/'branch'` ref
-the push is failing with the error message 'branch ... not found'.
+If you specify a non-existing branch in the `refs/for/'branch'` ref
+the push fails with the error message 'branch ... not found'.
To fix this problem verify
diff --git a/Documentation/error-change-closed.txt b/Documentation/error-change-closed.txt
index 7170a65..3244fb3 100644
--- a/Documentation/error-change-closed.txt
+++ b/Documentation/error-change-closed.txt
@@ -1,8 +1,11 @@
change ... closed
=================
-With this error message Gerrit rejects to push a commit to a change
-that is already closed.
+With this error message Gerrit rejects to push a commit or submit a
+review label (approval) to a change that is already closed.
+
+When Pushing a Commit
+---------------------
This error occurs if you are trying to push a commit that contains
the Change-Id of a closed change in its commit message. A change can
@@ -14,7 +17,7 @@
new change. To do this you have to remove the Change-Id from the
commit message as explained link:error-push-fails-due-to-commit-message.html[here] and ideally generate a new Change-Id
using the link:cmd-hook-commit-msg.html[commit hook] or EGit. Before pushing again it is also
-recommendable to do a link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] to base your commit on the submitted
+recommended to do a link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] to base your commit on the submitted
change. Pushing again should now create a new change in Gerrit.
If the change for which you wanted to upload a new patch set was
@@ -24,6 +27,14 @@
'Restore Change' button). Afterwards the push should succeed and a
new patch set for this change will be created.
+When Submitting a Review Label
+------------------------------
+
+This error occurs if you are trying to submit a review label (approval) using
+the link:cmd-review.html[ssh review command] after the change has been closed.
+A change can be closed because it was submitted and merged, because it was abandoned,
+or because the patchset to which you are submitting the review has been replaced
+by a newer patchset.
GERRIT
------
diff --git a/Documentation/error-change-does-not-belong-to-project.txt b/Documentation/error-change-does-not-belong-to-project.txt
index 29957e1..e747881 100644
--- a/Documentation/error-change-does-not-belong-to-project.txt
+++ b/Documentation/error-change-does-not-belong-to-project.txt
@@ -7,7 +7,7 @@
This error message means that the user explicitly pushed a commit to
a change that belongs to another project by specifying it as target
ref. This way of adding a new patch set to a change is deprecated as
-explained link:user-upload.html#manual_replacement_mapping[here]. It is recommended to only rely on Change-ID's for
+explained link:user-upload.html#manual_replacement_mapping[here]. It is recommended to only rely on Change-IDs for
link:user-upload.html#push_replace[replacing changes].
diff --git a/Documentation/error-change-not-found.txt b/Documentation/error-change-not-found.txt
index c9ac0d8..b6df13b 100644
--- a/Documentation/error-change-not-found.txt
+++ b/Documentation/error-change-not-found.txt
@@ -7,7 +7,7 @@
This error message means that the user explicitly pushed a commit to
a non-existing change by specifying it as target ref. This way of
adding a new patch set to a change is deprecated as explained link:user-upload.html#manual_replacement_mapping[here].
-It is recommended to only rely on Change-ID's for link:user-upload.html#push_replace[replacing changes].
+It is recommended to only rely on Change-IDs for link:user-upload.html#push_replace[replacing changes].
GERRIT
diff --git a/Documentation/error-you-are-not-author.txt b/Documentation/error-invalid-author.txt
similarity index 88%
rename from Documentation/error-you-are-not-author.txt
rename to Documentation/error-invalid-author.txt
index a245252..c484776 100644
--- a/Documentation/error-you-are-not-author.txt
+++ b/Documentation/error-invalid-author.txt
@@ -1,10 +1,10 @@
-you are not author ...
-======================
+invalid author
+==============
-Gerrit verifies for every pushed commit that the e-mail address of
+For every pushed commit Gerrit verifies that the e-mail address of
the author matches one of the registered e-mail addresses of the
pushing user. If this is not the case pushing the commit fails with
-the error message "you are not author ...". This policy can be
+the error message "invalid author". This policy can be
bypassed by having the access right
link:access-control.html#category_forge_author['Forge Author'].
@@ -17,8 +17,8 @@
Incorrect configuration of the e-mail address on client or server side
----------------------------------------------------------------------
-If pushing to Gerrit fails with the error message "you are not
-author ..." and you are the author of the commit for which the push
+If pushing to Gerrit fails with the error message "invalid author"
+and you are the author of the commit for which the push
fails, then either you have not successfully registered this e-mail
address for your Gerrit account or the author information of the
pushed commit is incorrect.
@@ -27,7 +27,7 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Check in Gerrit under 'Settings -> Identities' which e-mail addresses
-you've configured for your Gerrit account, if no e-mail address is
+you've configured for your Gerrit account. If no e-mail address is
registered go to 'Settings -> Contact Information' and register a new
e-mail address there. Make sure you confirm your e-mail address by
clicking on the link in the e-mail verification mail sent by Gerrit.
@@ -92,7 +92,7 @@
git rebase for the affected commits. While doing the interactive
rebase you have to choose 'edit' for those commits for which the
author should be rewritten. When the rebase stops at such a commit
-you have to amend the commit with explicitly setting the author
+you have to amend the commit, explicitly setting the author
before continuing the rebase.
Here is an example that shows how the interactive rebase is used to
@@ -131,8 +131,8 @@
Missing privileges to push commits of other users
-------------------------------------------------
-If pushing to Gerrit fails with the error message "you are not
-author ..." and somebody else is author of the commit for which the
+If pushing to Gerrit fails with the error message "invalid author"
+and somebody else is author of the commit for which the
push fails, then you have no permissions to forge the author
identity. In this case you may contact the project owner to request
the access right '+1 Forge Author Identity' in the 'Forge Identity'
diff --git a/Documentation/error-you-are-not-committer.txt b/Documentation/error-invalid-committer.txt
similarity index 87%
rename from Documentation/error-you-are-not-committer.txt
rename to Documentation/error-invalid-committer.txt
index b5b8c44..447064e 100644
--- a/Documentation/error-you-are-not-committer.txt
+++ b/Documentation/error-invalid-committer.txt
@@ -1,10 +1,10 @@
-you are not committer ...
-=========================
+invalid committer
+=================
-Gerrit verifies for every pushed commit that the e-mail address of
+For every pushed commit Gerrit verifies that the e-mail address of
the committer matches one of the registered e-mail addresses of the
pushing user. If this is not the case pushing the commit fails with
-the error message "you are not committer ...". This policy can be
+the error message "invalid committer". This policy can be
bypassed by having the access right
link:access-control.html#category_forge_committer['Forge Committer'].
@@ -19,8 +19,8 @@
Incorrect configuration of the e-mail address on client or server side
----------------------------------------------------------------------
-If pushing to Gerrit fails with the error message "you are not
-committer ..." and you committed the change for which the push fails,
+If pushing to Gerrit fails with the error message "invalid committer"
+and you committed the change for which the push fails,
then either you have not successfully registered this e-mail address
for your Gerrit account or the committer information of the pushed
commit is incorrect.
@@ -29,7 +29,7 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Check in Gerrit under 'Settings -> Identities' which e-mail addresses
-you've configured for your Gerrit account, if no e-mail address is
+you've configured for your Gerrit account. If no e-mail address is
registered go to 'Settings -> Contact Information' and register a new
e-mail address there. Make sure you confirm your e-mail address by
clicking on the link in the e-mail verification mail sent by Gerrit.
@@ -96,8 +96,8 @@
Missing privileges to push commits that were committed by other users
---------------------------------------------------------------------
-If pushing to Gerrit fails with the error message "you are not
-committer ..." and somebody else committed the change for which the
+If pushing to Gerrit fails with the error message "invalid committer"
+and somebody else committed the change for which the
push fails, then you have no permissions to forge the committer
identity. In this case you may contact the project owner to request
the access right '+2 Forge Committer or Tagger Identity' in the
diff --git a/Documentation/error-messages.txt b/Documentation/error-messages.txt
index f915a14..c9df883 100644
--- a/Documentation/error-messages.txt
+++ b/Documentation/error-messages.txt
@@ -15,7 +15,9 @@
* link:error-change-not-found.html[change ... not found]
* link:error-contains-banned-commit.html[contains banned commit ...]
* link:error-has-duplicates.html[... has duplicates]
+* link:error-invalid-author.html[invalid author]
* link:error-invalid-changeid-line.html[invalid Change-Id line format in commit message]
+* link:error-invalid-committer.html[invalid committer]
* link:error-missing-changeid.html[missing Change-Id in commit message]
* link:error-multiple-changeid-lines.html[multiple Change-Id lines in commit message]
* link:error-no-changes-made.html[no changes made]
@@ -33,8 +35,6 @@
* link:error-squash-commits-first.html[squash commits first]
* link:error-upload-denied.html[Upload denied for project \'...']
* link:error-not-allowed-to-upload-merges.html[you are not allowed to upload merges]
-* link:error-you-are-not-author.html[you are not author ...]
-* link:error-you-are-not-committer.html[you are not committer ...]
General Hints
diff --git a/Documentation/error-no-changes-made.txt b/Documentation/error-no-changes-made.txt
index 7ef7082..d0e1d4f 100644
--- a/Documentation/error-no-changes-made.txt
+++ b/Documentation/error-no-changes-made.txt
@@ -2,10 +2,10 @@
===============
With this error message Gerrit rejects to push a commit as a new
-patch set for a change, if the pushed commit is identical with the
+patch set for a change, if the pushed commit is identical to the
current patch set of this change.
-A pushed commit is considered to be identical with the current patch
+A pushed commit is considered to be identical to the current patch
set if
- the files in the commit,
diff --git a/Documentation/error-no-new-changes.txt b/Documentation/error-no-new-changes.txt
index 347c080..8e409ef 100644
--- a/Documentation/error-no-new-changes.txt
+++ b/Documentation/error-no-new-changes.txt
@@ -3,7 +3,7 @@
With this error message Gerrit rejects to push a commit if the pushed
commit was already successfully pushed to Gerrit. In this case there
-is no new change and consequently there is nothing to do for Gerrit.
+is no new change and consequently there is nothing for Gerrit to do.
If your push is failing with this error message, you normally
don't have to do anything since the commit was already successfully
diff --git a/Documentation/error-non-fast-forward.txt b/Documentation/error-non-fast-forward.txt
index 7dba51b..6604e10 100644
--- a/Documentation/error-non-fast-forward.txt
+++ b/Documentation/error-non-fast-forward.txt
@@ -1,15 +1,15 @@
non-fast forward
================
-With this error message Git rejects a push if the remote branch can't
+With this error message Gerrit rejects a push if the remote branch can't
be fast forwarded onto the pushed commit. This is the case if the
pushed commit is not based on the current tip of the remote branch.
If a non-fast forward update would be done, all commits from the
remote branch that succeed the base commit of the pushed commit would
be removed. This would be especially confusing for other users that
-have based their work on such a commit. Because of this Git is by
-default not allowing non-fast forward updates.
+have based their work on such a commit. Because of this Git by
+default does not allow non-fast forward updates.
When working with Gerrit, this error can only occur if
link:user-upload.html#bypass_review[code review is bypassed].
@@ -46,7 +46,7 @@
the commit to the correct project.
-Although it is considered as bad practice, it is possible to allow
+Although it is considered bad practice, it is possible to allow
non-fast forward updates with Git. For this the remote Git repository
has to be configured to not deny non-fast forward updates (set the
link:http://www.kernel.org/pub/software/scm/git/docs/git-config.html[Git configuration] parameter 'receive.denyNonFastForwards' to
diff --git a/Documentation/error-not-a-gerrit-administrator.txt b/Documentation/error-not-a-gerrit-administrator.txt
index 0468d83..b771af6 100644
--- a/Documentation/error-not-a-gerrit-administrator.txt
+++ b/Documentation/error-not-a-gerrit-administrator.txt
@@ -1,7 +1,7 @@
Not a Gerrit administrator
==========================
-With this error message Gerrit rejects to execute a SSH command that
+With this error message Gerrit rejects to execute an SSH command that
requires administrator privileges if the user is not a Gerrit
administrator.
diff --git a/Documentation/error-not-a-gerrit-project.txt b/Documentation/error-not-a-gerrit-project.txt
index 368a102..dac98ae 100644
--- a/Documentation/error-not-a-gerrit-project.txt
+++ b/Documentation/error-not-a-gerrit-project.txt
@@ -18,7 +18,7 @@
project is listed. If the project is not listed the project either
does not exist or you don't have
link:access-control.html#category_read['Read'] access for it. This
- means if you certain that the project name is right you should
+ means if you are certain that the project name is right you should
contact the Gerrit Administrator or project owner to request access
to the project.
diff --git a/Documentation/error-not-allowed-to-upload-merges.txt b/Documentation/error-not-allowed-to-upload-merges.txt
index 981ba91c..515eef5 100644
--- a/Documentation/error-not-allowed-to-upload-merges.txt
+++ b/Documentation/error-not-allowed-to-upload-merges.txt
@@ -2,11 +2,11 @@
====================================
With this error message Gerrit rejects to push a merge commit if the
-pushing user has no permissions to upload merge commits for the
+pushing user has no permission to upload merge commits for the
project to which the push is done.
If you need to upload merge commits, you can contact one of the
-project owners and request permissions to upload merge commits
+project owners and request permission to upload merge commits
(access right link:access-control.html#category_push_merge['Push Merge Commit'])
for this project.
diff --git a/Documentation/error-permission-denied.txt b/Documentation/error-permission-denied.txt
index 1cb5708..2ec0a3f 100644
--- a/Documentation/error-permission-denied.txt
+++ b/Documentation/error-permission-denied.txt
@@ -1,7 +1,7 @@
Permission denied (publickey)
=============================
-With this error message a SSH command to Gerrit is rejected if the
+With this error message an SSH command to Gerrit is rejected if the
SSH authentication is not successful.
The link:http://en.wikipedia.org/wiki/Secure_Shell[SSH] protocol uses link:http://en.wikipedia.org/wiki/Public-key_cryptography[Public-key Cryptography] for authentication.
diff --git a/Documentation/error-prohibited-by-gerrit.txt b/Documentation/error-prohibited-by-gerrit.txt
index 69f80c1..df46566 100644
--- a/Documentation/error-prohibited-by-gerrit.txt
+++ b/Documentation/error-prohibited-by-gerrit.txt
@@ -20,7 +20,7 @@
4. if you push a lightweight tag without the access right link:access-control.html#category_create['Create
Reference'] for the reference name 'refs/tags/*'
-For new users it happens often that they accidentally try to bypass
+For new users it often happens that they accidentally try to bypass
code review. The push then fails with the error message 'prohibited
by Gerrit' because the project didn't allow to bypass code review.
Bypassing the code review is done by pushing directly to refs/heads/*
diff --git a/Documentation/error-push-fails-due-to-commit-message.txt b/Documentation/error-push-fails-due-to-commit-message.txt
index 01e0a8e..172d64f 100644
--- a/Documentation/error-push-fails-due-to-commit-message.txt
+++ b/Documentation/error-push-fails-due-to-commit-message.txt
@@ -3,7 +3,7 @@
If Gerrit rejects pushing a commit it is often the case that there is
an issue with the commit message of the pushed commit. In this case
-often the problem can be resolved by fixing the commit message.
+the problem can often be resolved by fixing the commit message.
If the commit message of the last commit needs to be fixed you can
simply amend the last commit (please find a detailed description in
diff --git a/Documentation/error-squash-commits-first.txt b/Documentation/error-squash-commits-first.txt
index 138ad98..2181c52 100644
--- a/Documentation/error-squash-commits-first.txt
+++ b/Documentation/error-squash-commits-first.txt
@@ -9,7 +9,7 @@
prevents such dependencies between patch sets within the same change
to keep the review process simple. Otherwise reviewers would not only
have to review the latest patch set but also all the patch sets the
-latest one is depending on.
+latest one depends on.
This error is quite common, it appears when a user tries to address
review comments and creates a new commit instead of amending the
@@ -93,8 +93,8 @@
----
If it was the intention to create a patch series with multiple
-changes to be reviewed each commit message should contain the
-Change-ID of the corresponding change in Gerrit, if a change in
+changes to be reviewed, each commit message should contain the
+Change-ID of the corresponding change in Gerrit. If a change in
Gerrit does not exist yet, the Change-ID should be generated (either
by using a link:cmd-hook-commit-msg.html[commit hook] or by using EGit) or the Change-ID could be
removed (not recommended since then amending this commit to create
diff --git a/Documentation/i18n-readme.txt b/Documentation/i18n-readme.txt
index 080ecb6..a84c3dc 100644
--- a/Documentation/i18n-readme.txt
+++ b/Documentation/i18n-readme.txt
@@ -1,7 +1,7 @@
Gerrit Code Review - i18n
=========================
-Aside from actually writing translations, there's some issues with
+Aside from actually writing translations, there are some issues with
the way the code produces output. Most of the UI should support
right-to-left (RTL) languages.
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 2b53772..c99d26c 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -48,6 +48,7 @@
* link:dev-readme.html[Developer Setup]
* link:dev-eclipse.html[Eclipse Setup]
* link:dev-contributing.html[Contributing to Gerrit]
+* link:dev-plugins.html[Developing Plugins]
* link:dev-design.html[System Design]
* link:i18n-readme.html[i18n Support]
* link:dev-release.html[Developer Release]
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index 507d6c5..96814a0 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -44,7 +44,7 @@
+
If you enabled Bouncy Castle Crypto during 'init', copy the JAR
from `'$site_path'/lib` into your servlet container's extensions
-directory so its available to Gerrit Code Review.
+directory so it's available to Gerrit Code Review.
Jetty 7.x
diff --git a/Documentation/install-quick.txt b/Documentation/install-quick.txt
index 6bea7f8..c09c197 100644
--- a/Documentation/install-quick.txt
+++ b/Documentation/install-quick.txt
@@ -12,7 +12,7 @@
flavors or BSD.
It's also presumed that you have access to an OpenID enabled email address.
-Examples of OpenID enable email providers are gmail, yahoo and hotmail.
+Examples of OpenID enable email providers are Gmail, Yahoo! Mail and Hotmail.
It's also possible to register a custom email address with OpenID, but that is
outside the scope of this quick installation guide. For testing purposes one of
the above providers should be fine. Please note that network access to the
@@ -42,7 +42,7 @@
Create a user to host the Gerrit service
----------------------------------------
-We will run the service as a non privileged user on your system.
+We will run the service as a non-privileged user on your system.
First create the user and then become the user:
----
@@ -50,7 +50,7 @@
$ sudo su gerrit2
----
-If you don't have root privileges you could skip this step and run gerrit
+If you don't have root privileges you could skip this step and run Gerrit
as your own user as well.
@@ -58,7 +58,7 @@
Download Gerrit
---------------
-It's time to download the archive that contains the gerrit web and ssh service.
+It's time to download the archive that contains the Gerrit web and ssh service.
You can choose from different versions to download from here:
@@ -87,14 +87,28 @@
When the init is complete, you can review your settings in the
file `'$site_path/etc/gerrit.config'`.
-An important setting will be the canonicalWebUrl which will
-be needed later to access gerrit's web interface.
+Note that initialization also starts the server. If any settings changes are
+made, the server must be restarted before they will take effect.
----
- gerrit2@host:~$ cat ~/gerrit_testsite/etc/gerrit.config | grep canonical
- canonicalWebUrl = http://localhost:8080/
+ gerrit2@host:~$ ~/gerrit_testsite/bin/gerrit.sh restart
+ Stopping Gerrit Code Review: OK
+ Starting Gerrit Code Review: OK
gerrit2@host:~$
----
+
+The server can be also stopped and started by passing the `stop` and `start`
+commands to gerrit.sh.
+
+----
+ gerrit2@host:~$ ~/gerrit_testsite/bin/gerrit.sh stop
+ Stopping Gerrit Code Review: OK
+ gerrit2@host:~$
+ gerrit2@host:~$ ~/gerrit_testsite/bin/gerrit.sh start
+ Starting Gerrit Code Review: OK
+ gerrit2@host:~$
+----
+
[[usersetup]]
The first user
--------------
@@ -154,15 +168,32 @@
Registering your key in Gerrit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open a browser and enter the canonical url you got before when
-initializing Gerrit.
+Open a browser and enter the canonical url of your Gerrit server. You can
+find the url in the settings file.
----
- Canonical URL [http://localhost:8080/]:
+ gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config gerrit.canonicalWebUrl
+ http://localhost:8080/
+ gerrit2@host:~$
----
Register a new account in Gerrit through the web interface with the
email address of your choice.
+
+The default authentication type is OpenID. If your Gerrit server is behind a
+proxy, and you are using an external OpenID provider, you will need to add the
+proxy settings in the configuration file.
+
+----
+ gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config --add http.proxy http://proxy:8080
+ gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config --add http.proxyUsername username
+ gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config --add http.proxyPassword password
+----
+
+Refer to the Gerrit configuration guide for more detailed information about
+link:config-gerrit.html#auth[authentication] and
+link:config-gerrit.html#http.proxy[proxy] settings.
+
The first user to sign-in and register an account will be
automatically placed into the fully privileged Administrators group,
permitting server management over the web and over SSH. Subsequent
@@ -216,7 +247,7 @@
Your base Gerrit server is now running and you have a user that's ready
to interact with it. You now have two options, either you create a new
test project to work with or you already have a git with history that
-you would like to import into gerrit and try out code review on.
+you would like to import into Gerrit and try out code review on.
New project from scratch
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -231,14 +262,14 @@
user@host:~$
----
-This will create a repository that you could clone to work with.
+This will create a repository that you can clone to work with.
Already existing project
~~~~~~~~~~~~~~~~~~~~~~~~
The other alternative is if you already have a git project that you
want to try out Gerrit on.
-First you have to create the project, this is done via the SSH port:
+First you have to create the project. This is done via the SSH port:
----
user@host:~$ ssh -p 29418 user@localhost gerrit create-project --name demo-project
@@ -262,7 +293,7 @@
user@host:~/my-project$
----
-This will create a repository that you could clone to work with.
+This will create a repository that you can clone to work with.
My first change
@@ -294,7 +325,7 @@
Usually when you push to a remote git, you push to the reference
`'/refs/heads/branch'`, but when working with Gerrit you have to push to a
-virtual branch representing "code review before submittal to branch".
+virtual branch representing "code review before submission to branch".
This virtual name space is known as /refs/for/<branch>
----
@@ -319,11 +350,11 @@
---------------------------
This covers the scope of getting Gerrit started and your first change uploaded.
-It doesn't give any clue as to how the review workflow works, please find
+It doesn't give any clue as to how the review workflow works, please read
link:http://source.android.com/submit-patches/workflow[Default Workflow] to
learn more about the workflow of Gerrit.
-To read more on the installation of Gerrit please read link:install.html[the detailed
+To read more on the installation of Gerrit please see link:install.html[the detailed
installation page].
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 9da09e7..9926b8f 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -199,7 +199,7 @@
------------------
Gerrit Code Review supports some site-specific customization options.
-For more information, see the related topic in this manual:
+For more information, see the related topics in this manual:
* link:config-reverseproxy.html[Reverse Proxy]
* link:config-sso.html[Single Sign-On Systems]
diff --git a/Documentation/intro-quick.txt b/Documentation/intro-quick.txt
index 3d5cbcb..25f5d5e 100644
--- a/Documentation/intro-quick.txt
+++ b/Documentation/intro-quick.txt
@@ -27,7 +27,7 @@
simple for all committers on a project to ensure that changes are
checked over before they're actually applied. Because of this Gerrit
is equally useful where all users are trusted committers such as may
-the case with closed-source commercial development. Either way it's
+be the case with closed-source commercial development. Either way it's
still desirable to have code reviewed to improve the quality and
maintainability of the code. After all, if only one person has seen
the code it may be a little difficult to maintain when that person
@@ -337,7 +337,7 @@
Easy as that, we now have the change in our working copy to play with.
You might be interested in what the numbers of the refspec mean.
-* The first *68* is the id if the change +mod 100+. The only reason
+* The first *68* is the id of the change +mod 100+. The only reason
for this initial number is to reduce the number of files in any given
directory within the git repository.
* The second *68* is the full id of the change. You'll notice this in
@@ -379,7 +379,7 @@
that can be done by different users, Submission is a third operation
that can be limited down to another group of users.
-Activating the _Publish and Submit_ or _Submit Patch Set X_ button
+Clicking the _Publish and Submit_ or _Submit Patch Set X_ button
will merge the change into the main part of the repository so that it
becomes an accepted part of the project. After this anyone fetching
the git repository will receive this change as a part of the master
diff --git a/Documentation/json.txt b/Documentation/json.txt
index b1dbc32..32bfed5 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -11,22 +11,22 @@
------
The Gerrit change being reviewed, or that was already reviewed.
-project:: Project path in Gerrit
+project:: Project path in Gerrit.
-branch:: Branch name within project
+branch:: Branch name within project.
-topic:: Topic name specified by the uploader for this change series
+topic:: Topic name specified by the uploader for this change series.
id:: Change identifier, as scraped out of the Change-Id field in
the commit message, or as assigned by the server if it was missing.
-number:: Change number (deprecated)
+number:: Change number (deprecated).
-subject:: Description of change
+subject:: Description of change.
-owner:: Owner in <<account,account attribute>>
+owner:: Owner in <<account,account attribute>>.
-url:: Canonical URL to reach this change
+url:: Canonical URL to reach this change.
commitMessage:: The full commit message for the change.
@@ -53,9 +53,9 @@
message based on the server's
link:config-gerrit.html#trackingid[trackingid] sections.
-currentPatchSet:: Current <<patchset,patchset attribute>>.
+currentPatchSet:: Current <<patchSet,patchSet attribute>>.
-patchSets:: All <<patchset,patchset attribute>> for this change.
+patchSets:: All <<patchSet,patchSet attribute>> for this change.
[[trackingid]]
trackingid
@@ -76,8 +76,10 @@
email:: User's preferred email address.
-[[patchset]]
-patchset
+username:: User's username, if configured.
+
+[[patchSet]]
+patchSet
--------
Refers to a specific patchset within a <<change,change>>.
@@ -109,8 +111,8 @@
by:: Reviewer of the patch set in <<account,account attribute>>.
-[[refupdate]]
-refupdate
+[[refUpdate]]
+refUpdate
--------
Information about a ref that was updated.
@@ -118,7 +120,7 @@
newRev:: The new value the ref was updated to.
-project:: Project path in Gerrit
+project:: Project path in Gerrit.
refName:: Ref name within project.
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index e50979a..4186026 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -18,6 +18,7 @@
|Google Gson | <<apache2,Apache License 2.0>>
|Google Web Toolkit | <<apache2,Apache License 2.0>>
|Guice | <<apache2,Apache License 2.0>>
+|Guava Libraries | <<apache2,Apache License 2.0>>
|Apache Commons Codec | <<apache2,Apache License 2.0>>
|Apache Commons DBCP | <<apache2,Apache License 2.0>>
|Apache Commons Http Client | <<apache2,Apache License 2.0>>
@@ -33,7 +34,6 @@
|Apache Xerces | <<apache2,Apache License 2.0>>
|OpenID4Java | <<apache2,Apache License 2.0>>
|Neko HTML | <<apache2,Apache License 2.0>>
-|Ehcache | <<apache2,Apache License 2.0>>
|mime-util | <<apache2,Apache License 2.0>>
|Jetty | <<apache2,Apache License 2.0>>, or link:http://www.eclipse.org/legal/epl-v10.html[EPL]
|Prolog Cafe | <<prolog_cafe,EPL or GPL>>
diff --git a/Documentation/pgm-ExportReviewNotes.txt b/Documentation/pgm-ExportReviewNotes.txt
index 17cc862..b7670db 100644
--- a/Documentation/pgm-ExportReviewNotes.txt
+++ b/Documentation/pgm-ExportReviewNotes.txt
@@ -14,7 +14,7 @@
-----------
Scans every submitted change and creates an initial notes
branch detailing the previous submission information for
-each merged changed.
+each merged change.
This task can take quite some time, but can run in the background
concurrently to the server if the database is MySQL or PostgreSQL.
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index ae5e471..57decdd 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -19,7 +19,7 @@
for some basic setup prior to writing default configuration files
into a newly created `$site_path`.
-If run an an existing `$site_path`, init will upgrade some resources
+If run in an existing `$site_path`, init will upgrade some resources
as necessary.
OPTIONS
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index f07b9a9..1a359ac 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -32,7 +32,7 @@
----
JSON responses are encoded using UTF-8 and use content type
-`application/json`. The JSON response body starts with magic prefix
+`application/json`. The JSON response body starts with a magic prefix
line that must be stripped before feeding the rest of the response
body to a JSON parser:
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index 124ec31..a3015e1 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -4,7 +4,7 @@
Description
-----------
-Gerrit Code Review sometimes relies upon Change-Id lines in the
+Gerrit Code Review sometimes relies upon a Change-Id line at the
bottom of a commit message to uniquely identify a change across all
drafts of it. By including a unique Change-Id in the commit message,
Gerrit can automatically associate a new version of a change back
@@ -37,7 +37,7 @@
the commit name, `29a6...`, as the change may have been amended or
rebased to address reviewer comments since its initial inception.
-To avoid confusion with commit names, Change-Ids typically are with
+To avoid confusion with commit names, Change-Ids are typically prefixed with
an uppercase `I`.
Creation
@@ -46,11 +46,13 @@
Gerrit Code Review provides a standard 'commit-msg' hook which
can be installed in the local Git repository to automatically
create and insert a unique Change-Id line during `git commit`.
-To install the hook, copy it from Gerrit's daemon:
+To install the hook, copy it from Gerrit's daemon by executing
+one of the following commands while being in the root directory
+of the local Git repository:
$ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
- $ curl http://review.example.com/tools/hooks/commit-msg
+ $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
For more details, see link:cmd-hook-commit-msg.html[commit-msg].
@@ -125,7 +127,7 @@
already uploaded to Gerrit Code Review, and thus has a corresponding
change that reviewers have already examined and left comments on.
If you aren't sure which lines Gerrit knows about, try copying and
-pasting the lines into the search box in the top-right.
+pasting the lines into the search box at the top-right of the web interface.
If Gerrit already knows about more than one Change-Id, pick one
to keep in the squashed commit message, and manually abandon the
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 4fd6b2f..8a231fe 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -149,7 +149,7 @@
[[tr,bug]]
tr:'ID', bug:'ID'::
+
-Search for changes whose commit message contains 'ID' and matched
+Search for changes whose commit message contains 'ID' and matches
one or more of the
link:config-gerrit.html#trackingid[trackingid sections]
in the server's configuration file. This is typically used to
@@ -166,7 +166,7 @@
[[message]]
message:'MESSAGE'::
+
-Changes that matches 'MESSAGE' arbitrary string in body commit messages.
+Changes that match 'MESSAGE' arbitrary string in the commit message body.
[[file]]
file:^'REGEX'::
@@ -229,7 +229,7 @@
is:open::
+
-True if the change is other open or submitted, merge pending.
+True if the change is either open or submitted, merge pending.
is:draft::
+
@@ -246,7 +246,7 @@
[[status]]
status:open::
+
-True if the change state is other 'review in progress' or 'submitted,
+True if the change state is either 'review in progress' or 'submitted,
merge pending'.
status:reviewed::
@@ -268,7 +268,7 @@
status:abandoned::
+
-Change has been abandoned by the change owner, or administrator.
+Change has been abandoned.
Boolean Operators
@@ -304,7 +304,7 @@
[[labels]]
Labels
------
-Label operators can be used to match approval score given during
+Label operators can be used to match approval scores given during
a code review. The specific set of supported labels depends on
the server configuration, however `CodeReview` and `Verified`
are the default labels provided out of the box.
@@ -323,7 +323,7 @@
of change list pages. Example: `label:R` or `label:V`.
A label name must be followed by a score, or an operator and a score.
-The easiest way to explain these are by example.
+The easiest way to explain this is by example.
`label:CodeReview=2`::
`label:CodeReview=+2`::
@@ -400,7 +400,7 @@
draftby:'USER'::
+
-Matches changes that 'USER' has left unpublished drafts on.
+Matches changes that 'USER' has left unpublished draft comments on.
Since the drafts are unpublished, it is not possible to see the
draft text, or even how many drafts there are. The special case
of `draftby:self` will find changes where the caller has created
diff --git a/Documentation/user-submodules.txt b/Documentation/user-submodules.txt
index 3d14437..cfaf3e9 100644
--- a/Documentation/user-submodules.txt
+++ b/Documentation/user-submodules.txt
@@ -18,7 +18,7 @@
any gitlinks and .gitmodules file with required info) and if so,
a new submodule subscription is registered.
-When a new commit of a registered submodule is merged, gerrit
+When a new commit of a registered submodule is merged, Gerrit
automatically updates the subscribers to the submodule with a new
commit having the updated gitlinks.
@@ -31,7 +31,7 @@
in the official git submodule command documentation.
Imagine a repository called 'super' and another one called 'a'.
-Also consider 'a' available in a running gerrit instance on "server".
+Also consider 'a' available in a running Gerrit instance on "server".
With this feature, one could attach 'a' inside of 'super' repository
at path 'a' by executing the following command when being inside
'super':
@@ -86,12 +86,12 @@
gitlinks/.gitmodules file.
The branch field of a submodule section is a custom git submodule
-feature for gerrit use. One should always be sure to fill it in
+feature for Gerrit use. One should always be sure to fill it in
editing .gitmodules file after adding submodules to a super project,
-if it is the intention to make use of the gerrit feature introduced here.
+if it is the intention to make use of the Gerrit feature introduced here.
Any git submodules which are added and not have the branch field
-available in the .gitmodules file will not be subscribed by gerrit
+available in the .gitmodules file will not be subscribed by Gerrit
to automatically update the superproject.
Detecting and Subscribing Submodules
@@ -114,7 +114,7 @@
Imagine a superproject called 'super' having a branch called 'dev'
having subscribed to a submodule 'a' on a branch 'dev-of-a'. When a commit
-is merged in branch 'dev-of-a' of 'a' project, gerrit automatically
+is merged in branch 'dev-of-a' of 'a' project, Gerrit automatically
creates a new commit on branch 'dev' of 'super' updating the gitlink
to point to the just merged commit.
@@ -123,11 +123,11 @@
Gerrit will automatically update only the superprojects that added
the submodules of urls of the running server (the one described in
-the canonical web url value in gerrit configuration file).
+the canonical web url value in Gerrit configuration file).
The Gerrit instance administrator group should always certify to
provide the canonical web url value in its configuration file. Users
-should certify to use the url value of the running gerrit instance to
+should certify to use the url value of the running Gerrit instance to
add/subscribe submodules.
Removing Subscriptions
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 8e05e72..67799e4 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -46,7 +46,7 @@
[TIP]
Users who frequently upload changes will also want to consider
-starting a `ssh-agent`, and adding their private key to the list
+starting an `ssh-agent`, and adding their private key to the list
managed by the agent, to reduce the frequency of entering the
key's passphrase. Consult `man ssh-agent`, or your SSH client's
documentation, for more details on configuration of the agent
@@ -57,7 +57,7 @@
~~~~~~~~~~~~~~~~~~~
To verify your SSH key is working correctly, try using an SSH client
-to connect to Gerrit's SSHD port. By default Gerrit is running on
+to connect to Gerrit's SSHD port. By default Gerrit runs on
port 29418, using the same hostname as the web server:
====
@@ -104,7 +104,7 @@
Create Changes
~~~~~~~~~~~~~~
-To create new changes for review, simply push into the project's
+To create new changes for review, simply push to the project's
magical `refs/for/'branch'` ref using any Git client tool:
====
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.2.txt b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
new file mode 100644
index 0000000..db5d750
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
@@ -0,0 +1,24 @@
+Release notes for Gerrit 2.2.2.2
+================================
+
+Gerrit 2.2.2.2 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.2.2.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.2.2.war]
+
+There are no schema changes from 2.2.2, or 2.2.2.1.
+
+However, if upgrading from anything earlier, follow the upgrade
+procedure in the 2.2.2 link:ReleaseNotes-2.2.2.html[ReleaseNotes].
+
+Security Fixes
+--------------
+* Some access control sections may be ignored
++
+Gerrit sometimes ignored an access control section in a project
+if the exact same section name appeared in All-Projects. The bug
+required an unrelated project to have access.inheritFrom set to
+All-Projects and be accessed before the project that has the same
+section name as All-Projects. This is an unlikely scenario for
+most servers, as Gerrit does not normally set inheritFrom equal to
+All-Projects. The usual behavior is to not supply this property in
+project.config, and permit the implicit inheritence to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.3.1.txt b/ReleaseNotes/ReleaseNotes-2.3.1.txt
new file mode 100644
index 0000000..324a3c1
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.3.1.txt
@@ -0,0 +1,24 @@
+Release notes for Gerrit 2.3.1
+==============================
+
+Gerrit 2.3.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.3.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.3.1.war]
+
+There are no schema changes from 2.3.
+
+However, if upgrading from anything earlier, follow the upgrade
+procedure in the 2.3 link:ReleaseNotes-2.3.html[ReleaseNotes].
+
+Security Fixes
+--------------
+* Some access control sections may be ignored
++
+Gerrit sometimes ignored an access control section in a project
+if the exact same section name appeared in All-Projects. The bug
+required an unrelated project to have access.inheritFrom set to
+All-Projects and be accessed before the project that has the same
+section name as All-Projects. This is an unlikely scenario for
+most servers, as Gerrit does not normally set inheritFrom equal to
+All-Projects. The usual behavior is to not supply this property in
+project.config, and permit the implicit inheritence to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.4.1.txt b/ReleaseNotes/ReleaseNotes-2.4.1.txt
new file mode 100644
index 0000000..15dc1d3
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.4.1.txt
@@ -0,0 +1,55 @@
+Release notes for Gerrit 2.4.1
+==============================
+
+Gerrit 2.4.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.1.war]
+
+
+There are no schema changes from 2.4. However, if upgrading from
+anything but 2.4, follow the upgrade procedure in the 2.4
+link:ReleaseNotes-2.4.html[ReleaseNotes].
+
+
+Bug Fixes
+---------
+* Catch all exceptions when async emailing
++
+This fixes email notification issues reported
+link:https://groups.google.com/group/repo-discuss/browse_thread/thread/dd157ebc55b962ef/652822d6fbe61e71[here].
+
+* Fixed cleanup of propagated SshScopes
++
+This improves error reporting in case of email notification errors.
+
+* issue 1394 Fix lookup of the 'Commit Message' file in patch set
++
+There is an assumption that the commit message is always first in the list of
+files of a patch set. However, there was another place in Gerrit code, which
+did binary search through the list of the files, without taking this assumption
+into account. In case when a patch set contained a file which lexicographically
+sorted before '/COMMIT_MSG' (like '.gitignore' for example) it could have
+happened that the commit message was not found and, as a side effect, it wasn't
+possible to review it.
+
+* issue 1162 Fix deadlock on destroy of CommandFactoryProvider
+
+* Honor the sendmail.smtpUser from gerrit.config on upgrade
++
+If sendmail.smtpUser was not present in the gerrit.config then don't set it in
+site upgrade.
+
+* issue 1420 Forge committer bypassed
++
+It was possible to forge committer even without having permission for that.
+This was a regression from 2.3.
+
+* Make sure the "Object too large..." error message is printed when an object
+larger than receive.maxObjectSizeLimit is rejected by Gerrit
+
+* Display proper error if file diff fails because content is too large
+
+* Get around a log4j bug that causes AsyncAppender-Dispatcher thread to die and
+block other threads
+** Make async logging buffer size configurable
+** Make logging events discardable, prevent NPE in AsyncAppender-Dispatcher thread
diff --git a/ReleaseNotes/ReleaseNotes-2.4.2.txt b/ReleaseNotes/ReleaseNotes-2.4.2.txt
new file mode 100644
index 0000000..afa1d96
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.4.2.txt
@@ -0,0 +1,24 @@
+Release notes for Gerrit 2.4.2
+==============================
+
+Gerrit 2.4.2 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.2.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.2.war]
+
+There are no schema changes from 2.4, or 2.4.1.
+
+However, if upgrading from anything earlier, follow the upgrade
+procedure in the 2.4 link:ReleaseNotes-2.4.html[ReleaseNotes].
+
+Security Fixes
+--------------
+* Some access control sections may be ignored
++
+Gerrit sometimes ignored an access control section in a project
+if the exact same section name appeared in All-Projects. The bug
+required an unrelated project to have access.inheritFrom set to
+All-Projects and be accessed before the project that has the same
+section name as All-Projects. This is an unlikely scenario for
+most servers, as Gerrit does not normally set inheritFrom equal to
+All-Projects. The usual behavior is to not supply this property in
+project.config, and permit the implicit inheritence to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.4.txt b/ReleaseNotes/ReleaseNotes-2.4.txt
index 4f144f3..82f3ed4 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.txt
@@ -70,6 +70,11 @@
of the change we depend upon. A new patch set containing
the rebased commit will be produced and added to the
change.
++
+Rebasing of a change in web UI is restricted to change owner, submitter or
+those with the (new) 'rebase' permission.
+
+* Add a new permission 'rebase' to permit rebasing changes in the web UI
* Make a user's dashboard visible if any of the changes are visible to the
current user.
@@ -219,6 +224,7 @@
* issue 1353 Fix case check for project name so that symlinks work again
* Fix merging of access sections
* Fix inconsistent behaviour when replicating refs/meta/config
+* Fix duplicated results on status:open project:P branch:B
Documentation
-------------
diff --git a/ReleaseNotes/ReleaseNotes-2.5.txt b/ReleaseNotes/ReleaseNotes-2.5.txt
index 34af3dd..60c4f08 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.txt
@@ -14,3 +14,42 @@
Gerrit 2.5 no longer includes replication support out of the box.
Servers that reply upon `replication.config` to copy Git repository
data to other locations must also install the replication plugin.
+
+Cache Configuration
+~~~~~~~~~~~~~~~~~~~
+
+Disk caches are now backed by individual H2 databases, rather than
+Ehcache's own private format. Administrators are encouraged to clear
+the `'$site_path'/cache` directory before starting the new server.
+
+The `cache.NAME.diskLimit` configuration variable is now expressed in
+bytes of disk used. This is a change from previous versions of Gerrit,
+which expressed the limit as the number of entries rather than bytes.
+Bytes of disk is a more accurate way to size what is held. Admins that
+set this variable must update their configurations, as the old values
+are too small. For example a setting of `diskLimit = 65535` will only
+store 64 KiB worth of data on disk and can no longer hold 65,000 patch
+sets. It is recommended to delete the diskLimit variable (if set) and
+rely on the built-in default of `128m`.
+
+The `cache.diff.memoryLimit` and `cache.diff_intraline.memoryLimit`
+configuration variables are now expressed in bytes of memory used,
+rather than number of entries in the cache. This is a change from
+previous versions of Gerrit and gives administrators more control over
+how memory is partioned within a server. Admins that set this variable
+must update their configurations, as the old values are too small.
+For example a setting of `memoryLimit = 1024` now means only 1 KiB of
+data (which may not even hold 1 patch set), not 1024 patch sets. It
+is recommended to set these to `10m` for 10 MiB of memory, and
+increase as necessary.
+
+The `cache.NAME.maxAge` variable now means the maximum amount of time
+that can elapse between reads of the source data into the cache, no
+matter how often it is being accessed. In prior versions it meant how
+long an item could be held without being requested by a client before
+it was discarded. The new meaning of elapsed time before consulting
+the source data is more useful, as it enables a strict bound on how
+stale the cached data can be. This is especially useful for slave
+servers account and permission data, or the `ldap_groups` cache, where
+updates are often made to the source without telling Gerrit to reload
+the cache.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 30a85e8..5f8de28 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,17 +4,21 @@
[[2_4]]
Version 2.4.x
-------------
+* link:ReleaseNotes-2.4.2.html[2.4.2]
+* link:ReleaseNotes-2.4.1.html[2.4.1]
* link:ReleaseNotes-2.4.html[2.4]
[[2_3]]
Version 2.3.x
-------------
* link:ReleaseNotes-2.3.html[2.3]
+* link:ReleaseNotes-2.3.1.html[2.3.1]
[[2_2]]
Version 2.2.x
-------------
* link:ReleaseNotes-2.2.2.html[2.2.2],
+* link:ReleaseNotes-2.2.2.2.html[2.2.2.2],
* link:ReleaseNotes-2.2.2.1.html[2.2.2.1],
* link:ReleaseNotes-2.2.1.html[2.2.1],
* link:ReleaseNotes-2.2.0.html[2.2.0]
diff --git a/contrib/git-exproll.sh b/contrib/git-exproll.sh
new file mode 100644
index 0000000..9526d9f
--- /dev/null
+++ b/contrib/git-exproll.sh
@@ -0,0 +1,566 @@
+#!/bin/bash
+# Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# # Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# # Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+# # Neither the name of Code Aurora Forum, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+usage() { # error_message
+
+ cat <<-EOF
+ usage: $(basename $0) [-unvt] [--noref] [--nolosse] [-r|--ratio number]
+ [git gc option...] git.repo
+
+ -u|-h usage/help
+ -v verbose
+ -n dry-run don't actually repack anything
+ -t touch treat repo as if it had been touched
+ --noref avoid extra ref packing timestamp checking
+ --noloose do not run just because there are loose object dirs
+ (repacking may still run if they are referenced)
+ -r ratio <number> packfile ratio to aim for (default 10)
+
+ git gc option will be passed as args to git gc
+
+ git.repo to run gc against
+
+ Garbage collect using a pseudo logarithmic packfile maintenance
+ approach. This approach attempts to minimize packfile churn
+ by keeping several generations of varying sized packfiles around
+ and only consolidating packfiles (or loose objects) which are
+ either new packfiles, or packfiles close to the same size as
+ another packfile.
+
+ An estimate is used to predict when rollups (one consolidation
+ would cause another consolidation) would occur so that this
+ rollup can be done all at once via a single repack. This reduces
+ both the runtime and the pack file churn in rollup cases.
+
+ Approach: plan each consolidation by creating a table like this:
+
+ Id Keep Size Sha1(or consolidation list) Actions(repack down up note)
+ 1 - 11356 9052edfb7392646cd4e5f362b953675985f01f96 y - - New
+ 2 - 429088 010904d5c11cd26a79fda91b01ab454d1001b402 y - - New
+ c1 - 440444 [1,2] - - -
+
+ Id: numbers preceded by a c are estimated "c pack" files
+ Keep: - none, k private keep, o our keep
+ Size: in disk blocks (default du output)
+ Sha1: of packfile, or consolidation list of packfile ids
+ Actions
+ repack: - n no, y yes
+ down: - noop, ^ consolidate with a file above
+ up: - noop, v consolidate with a file below
+ note: Human description of script decisions:
+ New (file is a new packfile)
+ Consolidate with:<list of packfile ids>
+ (too far from:<list of packfile ids>)
+
+ On the first pass, always consolidate any new packfiles along
+ with loose objects and along with any packfiles which are within
+ the ratio size of their predecessors (note, the list is ordered
+ by increasing size). After each consolidation, insert a fake
+ consolidation, or "c pack", to naively represent the size and
+ ordered positioning of the anticipated new consolidated pack.
+ Every time a new pack is planned, rescan the list in case the
+ new "c pack" would cause more consolidation...
+
+ Once the packfiles which need consolidation are determined, the
+ packfiles which will not be consolidated are marked with a .keep
+ file, and those which will be consolidated will have their .keep
+ removed if they have one. Thus, the packfiles with a .keep will
+ not get repacked.
+
+ Packfile consolidation is determined by the --ratio parameter
+ (default is 10). This ratio is somewhat of a tradeoff. The
+ smaller the number, the more packfiles will be kept on average;
+ this increases disk utilization somewhat. However, a larger
+ ratio causes greater churn and may increase disk utilization due
+ to deleted packfiles not being reclaimed since they may still be
+ kept open by long running applications such as Gerrit. Sane
+ ratio values are probably between 2 and 10. Since most
+ consolidations actually end up smaller than the estimated
+ consolidated packfile size (due to compression), the true ratio
+ achieved will likely be 1 to 2 greater than the target ratio.
+ The smaller the target ratio, the greater this discrepancy.
+
+ Finally, attempt to skip garbage collection entirely on untouched
+ repos. In order to determine if a repo has been touched, use the
+ timestamp on the script's keep files, if any relevant file/dir
+ is newer than a keep marker file, assume that the repo has been
+ touched and gc needs to run. Also assume gc needs to run whenever
+ there are loose object dirs since they may contain untouched
+ unreferenced loose objects which need to be pruned (once they
+ expire).
+
+ In order to allow the keep files to be an effective timestamp
+ marker to detect relevant changes in a repo since the last run,
+ all relevant files and directories which may be modified during a
+ gc run (even during a noop gc run), must have their timestamps
+ reset to the same time as the keep files or gc will always run
+ even on untouched repos. The relevant files/dirs are all those
+ files and directories which garbage collection, object packing,
+ ref packing and pruning might change during noop actions.
+EOF
+
+ [ -n "$1" ] && info "ERROR $1"
+
+ exit
+}
+
+debug() { [ -n "$SW_V" ] && info "$1" ; }
+info() { echo "$1" >&2 ; }
+
+array_copy() { #v2 # array_src array_dst
+ local src=$1 dst=$2
+ local s i=0
+ eval s=\${#$src[@]}
+ while [ $i -lt $s ] ; do
+ eval $dst[$i]=\"\${$src[$i]}\"
+ i=$(($i + 1))
+ done
+}
+
+array_equals() { #v2 # array_name [vals...]
+ local a=$1 ; shift
+ local s=0 t=() val
+ array_copy "$a" t
+ for s in "${!t[@]}" ; do s=$((s+1)) ; done
+ [ "$s" -ne "$#" ] && return 1
+ for val in "${t[@]}" ; do
+ [ "$val" = "$1" ] || return 2
+ shift
+ done
+ return 0
+}
+
+packs_sizes() { # git.repo > "size pack"...
+ du -s "$1"/objects/pack/pack-$SHA1.pack | sort -n 2> /dev/null
+}
+
+is_ourkeep() { grep -q "$KEEP" "$1" 2> /dev/null ; } # keep
+has_ourkeep() { is_ourkeep "$(keep_for "$1")" ; } # pack
+has_keep() { [ -f "$(keep_for "$1")" ] ; } # pack
+is_repo() { [ -d "$1/objects" ] && [ -d "$1/refs/heads" ] ; } # git.repo
+
+keep() { # pack # returns true if we added our keep
+ keep=$(keep_for "$1")
+ [ -f "$keep" ] && return 1
+ echo "$KEEP" > "$keep"
+ return 0
+}
+
+keep_for() { # packfile > keepfile
+ local keep=$(echo "$1" | sed -es'/\.pack$/.keep/')
+ [ "${keep/.keep}" = "$keep" ] && return 1
+ echo "$keep"
+}
+
+idx_for() { # packfile > idxfile
+ local idx=$(echo "$1" | sed -es'/\.pack$/.idx/')
+ [ "${idx/.idx}" = "$idx" ] && return 1
+ echo "$idx"
+}
+
+# pack_or_keep_file > sha
+sha_for() { echo "$1" | sed -es'|\(.*/\)*pack-\([^.]*\)\..*$|\2|' ; }
+
+private_keeps() { # git.repo -> sets pkeeps
+ local repo=$1 ary=$2
+ local keep keeps=("$repo"/objects/pack/pack-$SHA1.keep)
+ pkeeps=()
+ for keep in "${keeps[@]}" ; do
+ is_ourkeep "$keep" || pkeeps=("${pkeeps[@]}" "$keep")
+ done
+}
+
+is_tooclose() { [ "$(($1 * $RATIO))" -gt "$2" ] ; } # smaller larger
+
+unique() { # [args...] > unique_words
+ local lines=$(while [ $# -gt 0 ] ; do echo "$1" ; shift ; done)
+ lines=$(echo "$lines" | sort -u)
+ echo $lines # as words
+}
+
+outfs() { # fs [args...] > argfs...
+ local fs=$1 ; shift
+ [ $# -gt 0 ] && echo -n "$1" ; shift
+ while [ $# -gt 0 ] ; do echo -n "$fs$1" ; shift ; done
+}
+
+sort_list() { # < list > formatted_list
+ # n has_keep size sha repack down up note
+ awk '{ note=$8; for(i=8;i<NF;i++) note=note " "$(i+1)
+ printf("%-5s %s %-14s %-40s %s %s %s %s\n", \
+ $1,$2, $3, $4, $5,$6,$7,note)}' |\
+ sort -k 3,3n -k 1,1n
+}
+
+is_touched() { # git.repo
+ local repo=$1
+ local loose keep ours newer
+ [ -n "$SW_T" ] && { debug "$SW_T -> treat as touched" ; return 0 ; }
+
+ if [ -z "$SW_LOOSE" ] ; then
+ # If there are loose objects, they may need to be pruned,
+ # run even if nothing has really been touched.
+ loose=$(find "$repo/objects" -type d \
+ -wholename "$repo/objects/[0-9][0-9]"
+ -print -quit 2>/dev/null)
+ [ -n "$loose" ] && { info "There are loose object directories" ; return 0 ; }
+ fi
+
+ # If we don't have a keep, the current packfiles may not have been
+ # compressed with the current gc policy (gc may never have been run),
+ # so run at least once to repack everything. Also, we need a marker
+ # file for timestamp tracking (a dir needs to detect changes within
+ # it, so it cannot be a marker) and our keeps are something we control,
+ # use them.
+ for keep in "$repo"/objects/pack/pack-$SHA1.keep ; do
+ is_ourkeep "$keep" && { ours=$keep ; break ; }
+ done
+ [ -z "$ours" ] && { info 'We have no keep (we have never run?): run' ; return 0 ; }
+
+ debug "Our timestamp keep: $ours"
+ # The wholename stuff seems to get touched by a noop git gc
+ newer=$(find "$repo/objects" "$repo/refs" "$repo/packed-refs" \
+ '!' -wholename "$repo/objects/info" \
+ '!' -wholename "$repo/objects/info/*" \
+ -newer "$ours" \
+ -print -quit 2>/dev/null)
+ [ -z "$newer" ] && return 1
+
+ info "Touched since last run: $newer"
+ return 0
+}
+
+touch_refs() { # git.repo start_date refs
+ local repo=$1 start_date=$2 refs=$3
+ (
+ debug "Setting start date($start_date) on unpacked refs:"
+ debug "$refs"
+ cd "$repo/refs" || return
+ # safe to assume no newlines in a ref name
+ echo "$refs" | xargs -d '\n' -n 1 touch -c -d "$start_date"
+ )
+}
+
+set_start_date() { # git.repo start_date refs refdirs packedrefs [packs]
+ local repo=$1 start_date=$2 refs=$3 refdirs=$4 packedrefs=$5 ; shift 5
+ local pack keep idx repacked
+
+ # This stuff is touched during object packs
+ while [ $# -gt 0 ] ; do
+ pack=$1 ; shift
+ keep="$(keep_for "$pack")"
+ idx="$(idx_for "$pack")"
+ touch -c -d "$start_date" "$pack" "$keep" "$idx"
+ debug "Setting start date on: $pack $keep $idx"
+ done
+ # This will prevent us from detecting any deletes in the pack dir
+ # since gc ran, except for private keeps which we are checking
+ # manually. But there really shouldn't be any other relevant deletes
+ # in this dir which should cause us to rerun next time, deleting a
+ # pack or index file by anything but gc would be bad!
+ debug "Setting start date on pack dir: $start_date"
+ touch -c -d "$start_date" "$repo/objects/pack"
+
+
+ if [ -z "$SW_REFS" ] ; then
+ repacked=$(find "$repo/packed-refs" -newer "$repo/objects/pack"
+ -print -quit 2>/dev/null)
+ if [ -n "$repacked" ] ; then
+ # The ref dirs and packed-ref files seem to get touched even on
+ # a noop refpacking
+ debug "Setting start date on packed-refs"
+ touch -c -d "$start_date" "$repo/packed-refs"
+ touch_refs "$repo" "$start_date" "$refdirs"
+
+ # A ref repack does not imply a ref change, but since it is
+ # hard to tell, simply assume so
+ if [ "$refs" != "$(cd "$repo/refs" ; find -depth)" ] || \
+ [ "$packedrefs" != "$(<"$repo/packed-refs")" ] ; then
+ # We retouch if needed (instead of simply checking then
+ # touching) to avoid a race between the check and the set.
+ debug " but refs actually got packed, so retouch packed-refs"
+ touch -c "$repo/packed-refs"
+ fi
+ fi
+ fi
+}
+
+note_consolidate() { # note entry > note (no duplicated consolidated entries)
+ local note=$1 entry=$2
+ local entries=() ifs=$IFS
+ if echo "$note" | grep -q 'Consolidate with:[0-9,c]' ; then
+ IFS=,
+ entries=( $(echo "$note" | sed -es'/^.*Consolidate with:\([0-9,c]*\).*$/\1/') )
+ note=( $(echo "$note" | sed -es'/Consolidate with:[0-9,c]*//') )
+ IFS=$ifs
+ fi
+ entries=( $(unique "${entries[@]}" "$entry") )
+ echo "$note Consolidate with:$(outfs , "${entries[@]}")"
+}
+
+note_toofar() { # note entry > note (no duplicated "too far" entries)
+ local note=$1 entry=$2
+ local entries=() ifs=$IFS
+ if echo "$note" | grep -q '(too far from:[0-9,c]*)' ; then
+ IFS=,
+ entries=( $(echo "$note" | sed -es'/^.*(too far from:\([0-9,c]*\)).*$/\1/') )
+ note=( $(echo "$note" | sed -es'/(too far from:[0-9,c]*)//') )
+ IFS=$ifs
+ fi
+ entries=( $(unique "${entries[@]}" "$entry") )
+ echo "$note (too far from:$(outfs , "${entries[@]}"))"
+}
+
+last_entry() { # isRepack pline repackline > last_rows_entry
+ local size_hit=$1 pline=$2 repackline=$3
+ if [ -n "$pline" ] ; then
+ if [ -n "$size_hit" ] ; then
+ echo "$repack_line"
+ else
+ echo "$pline"
+ fi
+ fi
+}
+
+init_list() { # git.repo > shortlist
+ local repo=$1
+ local file
+ local n has_keep size sha repack
+
+ packs_sizes "$1" | {
+ while read size file ; do
+ n=$((n+1))
+ repack=n
+ has_keep=-
+ if has_keep "$file" ; then
+ has_keep=k
+ has_ourkeep "$file" && has_keep=o
+ fi
+ sha=$(sha_for "$file")
+ echo "$n $has_keep $size $sha $repack"
+ done
+ } | sort_list
+}
+
+consolidate_list() { # run < list > list
+ local run=$1
+ local sum=0 psize=0 sum_size=0 size_hit pn clist pline repackline
+ local n has_keep size sha repack down up note
+
+ {
+ while read n has_keep size sha repack down up note; do
+ [ -z "$up" ] && up='-'
+ [ -z "$down" ] && down="-"
+
+ if [ "$has_keep" = "k" ] ; then
+ echo "$n $has_keep $size $sha $repack - - Private"
+ continue
+ fi
+
+ if [ "$repack" = "n" ] ; then
+ if is_tooclose $psize $size ; then
+ size_hit=y
+ repack=y
+ sum=$(($sum + $sum_size + $size))
+ sum_size=0 # Prevents double summing this entry
+ clist=($(unique "${clist[@]}" $pn $n))
+ down="^"
+ [ "$has_keep" = "-" ] && note="$note New +"
+ note=$(note_consolidate "$note" "$pn")
+ elif [ "$has_keep" = "-" ] ; then
+ repack=y
+ sum=$(($sum + $size))
+ sum_size=0 # Prevents double summing this entry
+ clist=($(unique "${clist[@]}" $n))
+ note="$note New"
+ elif [ $psize -ne 0 ] ; then
+ sum_size=$size
+ down="!"
+ note=$(note_toofar "$note" "$pn")
+ else
+ sum_size=$size
+ fi
+ else
+ sum_size=$size
+ fi
+
+ # By preventing "c files" (consolidated) from being marked
+ # "repack" they won't get keeps
+ repack2=y
+ [ "${n/c}" != "$n" ] && { repack=- ; repack2=- ; }
+
+ last_entry "$size_hit" "$pline" "$repack_line"
+ # Delay the printout until we know whether we are
+ # being consolidated with the entry following us
+ # (we won't know until the next iteration).
+ # size_hit is used to determine which of the lines
+ # below will actually get printed above on the next
+ # iteration.
+ pline="$n $has_keep $size $sha $repack $down $up $note"
+ repack_line="$n $has_keep $size $sha $repack2 $down v $note"
+
+ pn=$n ; psize=$size # previous entry data
+ size_hit='' # will not be consolidated up
+
+ done
+ last_entry "$size_hit" "$pline" "$repack_line"
+
+ [ $sum -gt 0 ] && echo "c$run - $sum [$(outfs , "${clist[@]}")] - - -"
+
+ } | sort_list
+}
+
+process_list() { # git.repo > list
+ local list=$(init_list "$1") plist run=0
+
+ while true ; do
+ plist=$list
+ run=$((run +1))
+ list=$(echo "$list" | consolidate_list "$run")
+ if [ "$plist" != "$list" ] ; then
+ debug "------------------------------------------------------------------------------------"
+ debug "$HEADER"
+ debug "$list"
+ else
+ break
+ fi
+ done
+ debug "------------------------------------------------------------------------------------"
+ echo "$list"
+}
+
+repack_list() { # git.repo < list
+ local repo=$1
+ local start_date newpacks=0 pkeeps keeps=1 refs refdirs rtn
+ local packedrefs=$(<"$repo/packed-refs")
+
+ # so they don't appear touched after a noop refpacking
+ if [ -z "$SW_REFS" ] ; then
+ refs=$(cd "$repo/refs" ; find -depth)
+ refdirs=$(cd "$repo/refs" ; find -type d -depth)
+ debug "Before refs:"
+ debug "$refs"
+ fi
+
+ # Find a private keep snapshot which has not changed from
+ # before our start_date so private keep deletions during gc
+ # can be detected
+ while ! array_equals pkeeps "${keeps[@]}" ; do
+ debug "Getting a private keep snapshot"
+ private_keeps "$repo"
+ keeps=("${pkeeps[@]}")
+ debug "before keeps: ${keeps[*]}"
+ start_date=$(date)
+ private_keeps "$repo"
+ debug "after keeps: ${pkeeps[*]}"
+ done
+
+ while read n has_keep size sha repack down up note; do
+ if [ "$repack" = "y" ] ; then
+ keep="$repo/objects/pack/pack-$sha.keep"
+ info "Repacking $repo/objects/pack/pack-$sha.pack"
+ [ -f "$keep" ] && rm -f "$keep"
+ fi
+ done
+
+ ( cd "$repo" && git gc "${GC_OPTS[@]}" ) ; rtn=$?
+
+ # Mark any files withoug a .keep with our .keep
+ packs=("$repo"/objects/pack/pack-$SHA1.pack)
+ for pack in "${packs[@]}" ; do
+ if keep "$pack" ; then
+ info "New pack: $pack"
+ newpacks=$((newpacks+1))
+ fi
+ done
+
+ # Record start_time. If there is more than 1 new packfile, we
+ # don't want to risk touching it with an older date since that
+ # would prevent consolidation on the next run. If the private
+ # keeps have changed, then we should run next time no matter what.
+ if [ $newpacks -le 1 ] || ! array_equals pkeeps "${keeps[@]}" ; then
+ set_start_date "$repo" "$start_date" "$refs" "$refdirs" "$packedrefs" "${packs[@]}"
+ fi
+
+ return $rtn # we really only care about the gc error code
+}
+
+git_gc() { # git.repo
+ local list=$(process_list "$1")
+ if [ -z "$SW_V" ] ; then
+ info "Running $PROG on $1. git gc options: ${GC_OPTS[@]}"
+ echo "$HEADER" >&2
+ echo "$list" >&2 ;
+ fi
+ echo "$list" | repack_list "$1"
+}
+
+
+PROG=$(basename "$0")
+HEADER="Id Keep Size Sha1(or consolidation list) Actions(repack down up note)"
+KEEP=git-exproll
+HEX='[0-9a-f]'
+HEX10=$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX
+SHA1=$HEX10$HEX10$HEX10$HEX10
+
+RATIO=10
+SW_N='' ; SW_V='' ; SW_T='' ; SW_REFS='' ; SW_LOOSE='' ; GC_OPTS=()
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ -u|-h) usage ;;
+ -n) SW_N="$1" ;;
+ -v) SW_V="$1" ;;
+
+ -t) SW_T="$1" ;;
+ --norefs) SW_REFS="$1" ;;
+ --noloose) SW_LOOSE="$1" ;;
+
+ -r|--ratio) shift ; RATIO="$1" ;;
+
+ *) [ $# -le 1 ] && break
+ GC_OPTS=( "${GC_OPTS[@]}" "$1" )
+ ;;
+ esac
+ shift
+done
+
+
+REPO="$1"
+if ! is_repo "$REPO" ; then
+ REPO=$REPO/.git
+ is_repo "$REPO" || usage "($1) is not likely a git repo"
+fi
+
+
+if [ -z "$SW_N" ] ; then
+ is_touched "$REPO" || { info "Repo untouched since last run" ; exit ; }
+ git_gc "$REPO"
+else
+ is_touched "$REPO" || info "Repo untouched since last run, analyze anyway."
+ process_list "$REPO" >&2
+fi
diff --git a/gerrit-antlr/.settings/org.eclipse.core.resources.prefs b/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-ehcache/.gitignore b/gerrit-cache-h2/.gitignore
similarity index 83%
copy from gerrit-ehcache/.gitignore
copy to gerrit-cache-h2/.gitignore
index fe190c9..cb430b8 100644
--- a/gerrit-ehcache/.gitignore
+++ b/gerrit-cache-h2/.gitignore
@@ -1,6 +1,6 @@
/target
/.classpath
/.project
-/.settings/org.eclipse.m2e.core.prefs
/.settings/org.maven.ide.eclipse.prefs
-/gerrit-ehcache.iml
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-cache-h2.iml
diff --git a/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs b/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
similarity index 73%
rename from gerrit-ehcache/.settings/org.eclipse.core.resources.prefs
rename to gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
index 97e731b..f9fe345 100644
--- a/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,4 @@
-#Tue May 15 09:21:09 PDT 2012
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
+encoding//src/test/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-ehcache/.settings/org.eclipse.core.runtime.prefs b/gerrit-cache-h2/.settings/org.eclipse.core.runtime.prefs
similarity index 100%
rename from gerrit-ehcache/.settings/org.eclipse.core.runtime.prefs
rename to gerrit-cache-h2/.settings/org.eclipse.core.runtime.prefs
diff --git a/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs b/gerrit-cache-h2/.settings/org.eclipse.jdt.core.prefs
similarity index 99%
rename from gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs
rename to gerrit-cache-h2/.settings/org.eclipse.jdt.core.prefs
index e89c048..470942d 100644
--- a/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs
+++ b/gerrit-cache-h2/.settings/org.eclipse.jdt.core.prefs
@@ -1,4 +1,4 @@
-#Thu Jan 19 12:55:44 PST 2012
+#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
diff --git a/gerrit-ehcache/.settings/org.eclipse.jdt.ui.prefs b/gerrit-cache-h2/.settings/org.eclipse.jdt.ui.prefs
similarity index 100%
rename from gerrit-ehcache/.settings/org.eclipse.jdt.ui.prefs
rename to gerrit-cache-h2/.settings/org.eclipse.jdt.ui.prefs
diff --git a/gerrit-ehcache/pom.xml b/gerrit-cache-h2/pom.xml
similarity index 76%
rename from gerrit-ehcache/pom.xml
rename to gerrit-cache-h2/pom.xml
index f9117b9..4d4303c 100644
--- a/gerrit-ehcache/pom.xml
+++ b/gerrit-cache-h2/pom.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
-Copyright (C) 2010 The Android Open Source Project
+Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -25,23 +25,28 @@
<version>2.5-SNAPSHOT</version>
</parent>
- <artifactId>gerrit-ehcache</artifactId>
- <name>Gerrit Code Review - Ehcache Bindings</name>
+ <artifactId>gerrit-cache-h2</artifactId>
+ <name>Gerrit Code Review - Guava + H2 caching</name>
<description>
- Bindings to Ehcache
+ Implementation of caching backed by Guava and H2
</description>
<dependencies>
<dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache-core</artifactId>
- </dependency>
-
- <dependency>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-server</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ </dependency>
</dependencies>
</project>
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
new file mode 100644
index 0000000..8bb0709
--- /dev/null
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
@@ -0,0 +1,121 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache.h2;
+
+import com.google.common.base.Strings;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.cache.CacheBinding;
+import com.google.gerrit.server.cache.MemoryCacheFactory;
+import com.google.gerrit.server.cache.PersistentCacheFactory;
+import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.concurrent.TimeUnit;
+
+public class DefaultCacheFactory implements MemoryCacheFactory {
+ public static class Module extends LifecycleModule {
+ @Override
+ protected void configure() {
+ bind(DefaultCacheFactory.class);
+ bind(MemoryCacheFactory.class).to(DefaultCacheFactory.class);
+ bind(PersistentCacheFactory.class).to(H2CacheFactory.class);
+ listener().to(H2CacheFactory.class);
+ }
+ }
+
+ private final Config cfg;
+
+ @Inject
+ public DefaultCacheFactory(@GerritServerConfig Config config) {
+ this.cfg = config;
+ }
+
+ @Override
+ public <K, V> Cache<K, V> build(CacheBinding<K, V> def) {
+ return create(def, false).build();
+ }
+
+ @Override
+ public <K, V> LoadingCache<K, V> build(
+ CacheBinding<K, V> def,
+ CacheLoader<K, V> loader) {
+ return create(def, false).build(loader);
+ }
+
+ @SuppressWarnings("unchecked")
+ <K, V> CacheBuilder<K, V> create(
+ CacheBinding<K, V> def,
+ boolean unwrapValueHolder) {
+ CacheBuilder<K,V> builder = newCacheBuilder();
+ builder.recordStats();
+ builder.maximumWeight(cfg.getLong(
+ "cache", def.name(), "memoryLimit",
+ def.maximumWeight()));
+
+ Weigher<K, V> weigher = def.weigher();
+ if (weigher != null && unwrapValueHolder) {
+ final Weigher<K, V> impl = weigher;
+ weigher = (Weigher<K, V>) new Weigher<K, ValueHolder<V>> () {
+ @Override
+ public int weigh(K key, ValueHolder<V> value) {
+ return impl.weigh(key, value.value);
+ }
+ };
+ } else if (weigher == null) {
+ weigher = unitWeight();
+ }
+ builder.weigher(weigher);
+
+ Long age = def.expireAfterWrite(TimeUnit.SECONDS);
+ if (has(def.name(), "maxAge")) {
+ builder.expireAfterWrite(ConfigUtil.getTimeUnit(cfg,
+ "cache", def.name(), "maxAge",
+ age != null ? age : 0,
+ TimeUnit.SECONDS), TimeUnit.SECONDS);
+ } else if (age != null) {
+ builder.expireAfterWrite(age, TimeUnit.SECONDS);
+ }
+
+ return builder;
+ }
+
+ private boolean has(String name, String var) {
+ return !Strings.isNullOrEmpty(cfg.getString("cache", name, var));
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private static <K, V> CacheBuilder<K, V> newCacheBuilder() {
+ CacheBuilder builder = CacheBuilder.newBuilder();
+ return builder;
+ }
+
+ private static <K, V> Weigher<K, V> unitWeight() {
+ return new Weigher<K, V>() {
+ @Override
+ public int weigh(K key, V value) {
+ return 1;
+ }
+ };
+ }
+}
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
new file mode 100644
index 0000000..27da20f
--- /dev/null
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -0,0 +1,198 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache.h2;
+
+import com.google.common.base.Preconditions;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.cache.CacheBinding;
+import com.google.gerrit.server.cache.PersistentCacheFactory;
+import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
+import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Singleton
+class H2CacheFactory implements PersistentCacheFactory, LifecycleListener {
+ static final Logger log = LoggerFactory.getLogger(H2CacheFactory.class);
+
+ private final DefaultCacheFactory defaultFactory;
+ private final Config config;
+ private final File cacheDir;
+ private final List<H2CacheImpl<?, ?>> caches;
+ private final ExecutorService executor;
+ private final ScheduledExecutorService cleanup;
+ private volatile boolean started;
+
+ @Inject
+ H2CacheFactory(
+ DefaultCacheFactory defaultCacheFactory,
+ @GerritServerConfig Config cfg,
+ SitePaths site) {
+ defaultFactory = defaultCacheFactory;
+ config = cfg;
+
+ File loc = site.resolve(cfg.getString("cache", null, "directory"));
+ if (loc == null) {
+ cacheDir = null;
+ } else if (loc.exists() || loc.mkdirs()) {
+ if (loc.canWrite()) {
+ log.info("Enabling disk cache " + loc.getAbsolutePath());
+ cacheDir = loc;
+ } else {
+ log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
+ cacheDir = null;
+ }
+ } else {
+ log.warn("Can't create disk cache: " + loc.getAbsolutePath());
+ cacheDir = null;
+ }
+
+ caches = Lists.newLinkedList();
+
+ if (cacheDir != null) {
+ executor = Executors.newFixedThreadPool(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat("DiskCache-Store-%d")
+ .build());
+ cleanup = Executors.newScheduledThreadPool(
+ 1,
+ new ThreadFactoryBuilder()
+ .setNameFormat("DiskCache-Prune-%d")
+ .setDaemon(true)
+ .build());
+ } else {
+ executor = null;
+ cleanup = null;
+ }
+ }
+
+ @Override
+ public void start() {
+ started = true;
+ if (executor != null) {
+ for (final H2CacheImpl<?, ?> cache : caches) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ cache.start();
+ }
+ });
+
+ cleanup.schedule(new Runnable() {
+ @Override
+ public void run() {
+ cache.prune(cleanup);
+ }
+ }, 30, TimeUnit.SECONDS);
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (executor != null) {
+ try {
+ cleanup.shutdownNow();
+
+ List<Runnable> pending = executor.shutdownNow();
+ if (executor.awaitTermination(15, TimeUnit.MINUTES)) {
+ if (pending != null && !pending.isEmpty()) {
+ log.info(String.format("Finishing %d disk cache updates", pending.size()));
+ for (Runnable update : pending) {
+ update.run();
+ }
+ }
+ } else {
+ log.info("Timeout waiting for disk cache to close");
+ }
+ } catch (InterruptedException e) {
+ log.warn("Interrupted waiting for disk cache to shutdown");
+ }
+ }
+ for (H2CacheImpl<?, ?> cache : caches) {
+ cache.stop();
+ }
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes", "cast"})
+ @Override
+ public <K, V> Cache<K, V> build(CacheBinding<K, V> def) {
+ Preconditions.checkState(!started, "cache must be built before start");
+ long limit = config.getLong("cache", def.name(), "diskLimit", 128 << 20);
+
+ if (cacheDir == null || limit <= 0) {
+ return defaultFactory.build(def);
+ }
+
+ SqlStore<K, V> store = newSqlStore(def.name(), def.keyType(), limit);
+ H2CacheImpl<K, V> cache = new H2CacheImpl<K, V>(
+ executor, store, def.keyType(),
+ (Cache<K, ValueHolder<V>>) defaultFactory.create(def, true).build());
+ caches.add(cache);
+ return cache;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <K, V> LoadingCache<K, V> build(
+ CacheBinding<K, V> def,
+ CacheLoader<K, V> loader) {
+ Preconditions.checkState(!started, "cache must be built before start");
+ long limit = config.getLong("cache", def.name(), "diskLimit", 128 << 20);
+
+ if (cacheDir == null || limit <= 0) {
+ return defaultFactory.build(def, loader);
+ }
+
+ SqlStore<K, V> store = newSqlStore(def.name(), def.keyType(), limit);
+ Cache<K, ValueHolder<V>> mem = (Cache<K, ValueHolder<V>>)
+ defaultFactory.create(def, true)
+ .build((CacheLoader<K, V>) new H2CacheImpl.Loader<K, V>(
+ executor, store, loader));
+ H2CacheImpl<K, V> cache = new H2CacheImpl<K, V>(
+ executor, store, def.keyType(), mem);
+ caches.add(cache);
+ return cache;
+ }
+
+ private <V, K> SqlStore<K, V> newSqlStore(
+ String name,
+ TypeLiteral<K> keyType,
+ long maxSize) {
+ File db = new File(cacheDir, name).getAbsoluteFile();
+ String url = "jdbc:h2:" + db.toURI().toString();
+ return new SqlStore<K, V>(url, keyType, maxSize);
+ }
+}
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
new file mode 100644
index 0000000..ad437b7
--- /dev/null
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -0,0 +1,709 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.google.gerrit.server.cache.h2;
+
+import com.google.common.cache.AbstractLoadingCache;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.CacheStats;
+import com.google.common.cache.LoadingCache;
+import com.google.common.hash.BloomFilter;
+import com.google.common.hash.Funnel;
+import com.google.common.hash.Funnels;
+import com.google.common.hash.PrimitiveSink;
+import com.google.inject.TypeLiteral;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Hybrid in-memory and database backed cache built on H2.
+ * <p>
+ * This cache can be used as either a recall cache, or a loading cache if a
+ * CacheLoader was supplied to its constructor at build time. Before creating an
+ * entry the in-memory cache is checked for the item, then the database is
+ * checked, and finally the CacheLoader is used to construct the item. This is
+ * mostly useful for CacheLoaders that are computationally intensive, such as
+ * the PatchListCache.
+ * <p>
+ * Cache stores and invalidations are performed on a background thread, hiding
+ * the latency associated with serializing the key and value pairs and writing
+ * them to the database log.
+ * <p>
+ * A BloomFilter is used around the database to reduce the number of SELECTs
+ * issued against the database for new cache items that have not been seen
+ * before, a common operation for the PatchListCache. The BloomFilter is sized
+ * when the cache starts to be 64,000 entries or double the number of items
+ * currently in the database table.
+ * <p>
+ * This cache does not export its items as a ConcurrentMap.
+ *
+ * @see H2CacheFactory
+ */
+public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> {
+ private static final Logger log = LoggerFactory.getLogger(H2CacheImpl.class);
+
+ private final Executor executor;
+ private final SqlStore<K, V> store;
+ private final TypeLiteral<K> keyType;
+ private final Cache<K, ValueHolder<V>> mem;
+
+ H2CacheImpl(Executor executor,
+ SqlStore<K, V> store,
+ TypeLiteral<K> keyType,
+ Cache<K, ValueHolder<V>> mem) {
+ this.executor = executor;
+ this.store = store;
+ this.keyType = keyType;
+ this.mem = mem;
+ }
+
+ @Override
+ public V getIfPresent(Object objKey) {
+ if (!keyType.getRawType().isInstance(objKey)) {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ K key = (K) objKey;
+
+ ValueHolder<V> h = mem.getIfPresent(key);
+ if (h != null) {
+ return h.value;
+ }
+
+ if (store.mightContain(key)) {
+ h = store.getIfPresent(key);
+ if (h != null) {
+ mem.put(key, h);
+ return h.value;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public V get(K key) throws ExecutionException {
+ if (mem instanceof LoadingCache) {
+ return ((LoadingCache<K, ValueHolder<V>>) mem).get(key).value;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void put(final K key, V val) {
+ final ValueHolder<V> h = new ValueHolder<V>(val);
+ h.created = System.currentTimeMillis();
+ mem.put(key, h);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ store.put(key, h);
+ }
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void invalidate(final Object key) {
+ if (keyType.getRawType().isInstance(key) && store.mightContain((K) key)) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ store.invalidate((K) key);
+ }
+ });
+ }
+ mem.invalidate(key);
+ }
+
+ @Override
+ public void invalidateAll() {
+ store.invalidateAll();
+ mem.invalidateAll();
+ }
+
+ @Override
+ public long size() {
+ return mem.size();
+ }
+
+ @Override
+ public CacheStats stats() {
+ return mem.stats();
+ }
+
+ public DiskStats diskStats() {
+ return store.diskStats();
+ }
+
+ void start() {
+ store.open();
+ }
+
+ void stop() {
+ for (Map.Entry<K, ValueHolder<V>> e : mem.asMap().entrySet()) {
+ ValueHolder<V> h = e.getValue();
+ if (!h.clean) {
+ store.put(e.getKey(), h);
+ }
+ }
+ store.close();
+ }
+
+ void prune(final ScheduledExecutorService service) {
+ store.prune(mem);
+
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, 01);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ cal.add(Calendar.DAY_OF_MONTH, 1);
+
+ long delay = cal.getTimeInMillis() - System.currentTimeMillis();
+ service.schedule(new Runnable() {
+ @Override
+ public void run() {
+ prune(service);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+ }
+
+ public static class DiskStats {
+ long size;
+ long space;
+ long hitCount;
+ long missCount;
+
+ public long size() {
+ return size;
+ }
+
+ public long space() {
+ return space;
+ }
+
+ public long hitCount() {
+ return hitCount;
+ }
+
+ public long requestCount() {
+ return hitCount + missCount;
+ }
+ }
+
+ static class ValueHolder<V> {
+ final V value;
+ long created;
+ volatile boolean clean;
+
+ ValueHolder(V value) {
+ this.value = value;
+ }
+ }
+
+ static class Loader<K, V> extends CacheLoader<K, ValueHolder<V>> {
+ private final Executor executor;
+ private final SqlStore<K, V> store;
+ private final CacheLoader<K, V> loader;
+
+ Loader(Executor executor, SqlStore<K, V> store, CacheLoader<K, V> loader) {
+ this.executor = executor;
+ this.store = store;
+ this.loader = loader;
+ }
+
+ @Override
+ public ValueHolder<V> load(final K key) throws Exception {
+ if (store.mightContain(key)) {
+ ValueHolder<V> h = store.getIfPresent(key);
+ if (h != null) {
+ return h;
+ }
+ }
+
+ final ValueHolder<V> h = new ValueHolder<V>(loader.load(key));
+ h.created = System.currentTimeMillis();
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ store.put(key, h);
+ }
+ });
+ return h;
+ }
+ }
+
+ private static class KeyType<K> {
+ String columnType() {
+ return "OTHER";
+ }
+
+ @SuppressWarnings("unchecked")
+ K get(ResultSet rs, int col) throws SQLException {
+ return (K) rs.getObject(col);
+ }
+
+ void set(PreparedStatement ps, int col, K value) throws SQLException {
+ ps.setObject(col, value);
+ }
+
+ Funnel<K> funnel() {
+ return new Funnel<K>() {
+ @Override
+ public void funnel(K from, PrimitiveSink into) {
+ try {
+ ObjectOutputStream ser =
+ new ObjectOutputStream(new SinkOutputStream(into));
+ ser.writeObject(from);
+ ser.flush();
+ } catch (IOException err) {
+ throw new RuntimeException("Cannot hash as Serializable", err);
+ }
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ static <K> KeyType<K> create(TypeLiteral<K> type) {
+ if (type.getRawType() == String.class) {
+ return (KeyType<K>) STRING;
+ }
+ return (KeyType<K>) OTHER;
+ }
+
+ static final KeyType<?> OTHER = new KeyType<Object>();
+ static final KeyType<String> STRING = new KeyType<String>() {
+ @Override
+ String columnType() {
+ return "VARCHAR(4096)";
+ }
+
+ @Override
+ String get(ResultSet rs, int col) throws SQLException {
+ return rs.getString(col);
+ }
+
+ @Override
+ void set(PreparedStatement ps, int col, String value)
+ throws SQLException {
+ ps.setString(col, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Funnel<String> funnel() {
+ Funnel<?> s = Funnels.stringFunnel();
+ return (Funnel<String>) s;
+ }
+ };
+ }
+
+ static class SqlStore<K, V> {
+ private final String url;
+ private final KeyType<K> keyType;
+ private final long maxSize;
+ private final BlockingQueue<SqlHandle> handles;
+ private final AtomicLong hitCount = new AtomicLong();
+ private final AtomicLong missCount = new AtomicLong();
+ private volatile BloomFilter<K> bloomFilter;
+ private int estimatedSize;
+
+ SqlStore(String jdbcUrl, TypeLiteral<K> keyType, long maxSize) {
+ this.url = jdbcUrl;
+ this.keyType = KeyType.create(keyType);
+ this.maxSize = maxSize;
+
+ int cores = Runtime.getRuntime().availableProcessors();
+ int keep = Math.min(cores, 16);
+ this.handles = new ArrayBlockingQueue<SqlHandle>(keep);
+ }
+
+ synchronized void open() {
+ if (bloomFilter == null) {
+ bloomFilter = buildBloomFilter();
+ }
+ }
+
+ void close() {
+ SqlHandle h;
+ while ((h = handles.poll()) != null) {
+ h.close();
+ }
+ }
+
+ boolean mightContain(K key) {
+ BloomFilter<K> b = bloomFilter;
+ if (b == null) {
+ synchronized (this) {
+ b = bloomFilter;
+ if (b == null) {
+ b = buildBloomFilter();
+ bloomFilter = b;
+ }
+ }
+ }
+ return b == null || b.mightContain(key);
+ }
+
+ private BloomFilter<K> buildBloomFilter() {
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ Statement s = c.conn.createStatement();
+ try {
+ ResultSet r;
+ if (estimatedSize <= 0) {
+ r = s.executeQuery("SELECT COUNT(*) FROM data");
+ try {
+ estimatedSize = r.next() ? r.getInt(1) : 0;
+ } finally {
+ r.close();
+ }
+ }
+
+ BloomFilter<K> b = newBloomFilter();
+ r = s.executeQuery("SELECT k FROM data");
+ try {
+ while (r.next()) {
+ b.put(keyType.get(r, 1));
+ }
+ } finally {
+ r.close();
+ }
+ return b;
+ } finally {
+ s.close();
+ }
+ } catch (SQLException e) {
+ log.warn("Cannot build BloomFilter for " + url, e);
+ c = close(c);
+ return null;
+ } finally {
+ release(c);
+ }
+ }
+
+ ValueHolder<V> getIfPresent(K key) {
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ if (c.get == null) {
+ c.get = c.conn.prepareStatement("SELECT v FROM data WHERE k=?");
+ }
+ keyType.set(c.get, 1, key);
+ ResultSet r = c.get.executeQuery();
+ try {
+ if (!r.next()) {
+ missCount.incrementAndGet();
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ V val = (V) r.getObject(1);
+ ValueHolder<V> h = new ValueHolder<V>(val);
+ h.clean = true;
+ hitCount.incrementAndGet();
+ touch(c, key);
+ return h;
+ } finally {
+ r.close();
+ c.get.clearParameters();
+ }
+ } catch (SQLException e) {
+ log.warn("Cannot read cache " + url + " for " + key, e);
+ c = close(c);
+ return null;
+ } finally {
+ release(c);
+ }
+ }
+
+ private void touch(SqlHandle c, K key) throws SQLException {
+ if (c.touch == null) {
+ c.touch =c.conn.prepareStatement("UPDATE data SET accessed=? WHERE k=?");
+ }
+ try {
+ c.touch.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
+ keyType.set(c.touch, 2, key);
+ c.touch.executeUpdate();
+ } finally {
+ c.touch.clearParameters();
+ }
+ }
+
+ void put(K key, ValueHolder<V> holder) {
+ if (holder.clean) {
+ return;
+ }
+
+ BloomFilter<K> b = bloomFilter;
+ if (b != null) {
+ b.put(key);
+ bloomFilter = b;
+ }
+
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ if (c.put == null) {
+ c.put = c.conn.prepareStatement("MERGE INTO data VALUES(?,?,?,?)");
+ }
+ try {
+ keyType.set(c.put, 1, key);
+ c.put.setObject(2, holder.value);
+ c.put.setTimestamp(3, new Timestamp(holder.created));
+ c.put.setTimestamp(4, new Timestamp(System.currentTimeMillis()));
+ c.put.executeUpdate();
+ holder.clean = true;
+ } finally {
+ c.put.clearParameters();
+ }
+ } catch (SQLException e) {
+ log.warn("Cannot put into cache " + url, e);
+ c = close(c);
+ } finally {
+ release(c);
+ }
+ }
+
+ void invalidate(K key) {
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ invalidate(c, key);
+ } catch (SQLException e) {
+ log.warn("Cannot invalidate cache " + url, e);
+ c = close(c);
+ } finally {
+ release(c);
+ }
+ }
+
+ private void invalidate(SqlHandle c, K key) throws SQLException {
+ if (c.invalidate == null) {
+ c.invalidate = c.conn.prepareStatement("DELETE FROM data WHERE k=?");
+ }
+ try {
+ keyType.set(c.invalidate, 1, key);
+ c.invalidate.executeUpdate();
+ } finally {
+ c.invalidate.clearParameters();
+ }
+ }
+
+ void invalidateAll() {
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ Statement s = c.conn.createStatement();
+ try {
+ s.executeUpdate("DELETE FROM data");
+ } finally {
+ s.close();
+ }
+ bloomFilter = newBloomFilter();
+ } catch (SQLException e) {
+ log.warn("Cannot invalidate cache " + url, e);
+ c = close(c);
+ } finally {
+ release(c);
+ }
+ }
+
+ void prune(Cache<K, ?> mem) {
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ Statement s = c.conn.createStatement();
+ try {
+ long used = 0;
+ ResultSet r = s.executeQuery("SELECT"
+ + " SUM(OCTET_LENGTH(k) + OCTET_LENGTH(v))"
+ + " FROM data");
+ try {
+ used = r.next() ? r.getLong(1) : 0;
+ } finally {
+ r.close();
+ }
+ if (used <= maxSize) {
+ return;
+ }
+
+ r = s.executeQuery("SELECT"
+ + " k"
+ + ",OCTET_LENGTH(k) + OCTET_LENGTH(v)"
+ + " FROM data"
+ + " ORDER BY accessed");
+ try {
+ while (maxSize < used && r.next()) {
+ K key = keyType.get(r, 1);
+ if (mem.getIfPresent(key) != null) {
+ touch(c, key);
+ } else {
+ invalidate(c, key);
+ used -= r.getLong(2);
+ }
+ }
+ } finally {
+ r.close();
+ }
+ } finally {
+ s.close();
+ }
+ } catch (SQLException e) {
+ log.warn("Cannot prune cache " + url, e);
+ c = close(c);
+ } finally {
+ release(c);
+ }
+ }
+
+ DiskStats diskStats() {
+ DiskStats d = new DiskStats();
+ d.hitCount = hitCount.get();
+ d.missCount = missCount.get();
+ SqlHandle c = null;
+ try {
+ c = acquire();
+ Statement s = c.conn.createStatement();
+ try {
+ ResultSet r = s.executeQuery("SELECT"
+ + " COUNT(*)"
+ + ",SUM(OCTET_LENGTH(k) + OCTET_LENGTH(v))"
+ + " FROM data");
+ try {
+ if (r.next()) {
+ d.size = r.getLong(1);
+ d.space = r.getLong(2);
+ }
+ } finally {
+ r.close();
+ }
+ } finally {
+ s.close();
+ }
+ } catch (SQLException e) {
+ log.warn("Cannot get DiskStats for " + url, e);
+ c = close(c);
+ } finally {
+ release(c);
+ }
+ return d;
+ }
+
+ private SqlHandle acquire() throws SQLException {
+ SqlHandle h = handles.poll();
+ return h != null ? h : new SqlHandle(url, keyType);
+ }
+
+ private void release(SqlHandle h) {
+ if (h != null && !handles.offer(h)) {
+ h.close();
+ }
+ }
+
+ private SqlHandle close(SqlHandle h) {
+ if (h != null) {
+ h.close();
+ }
+ return null;
+ }
+
+ private BloomFilter<K> newBloomFilter() {
+ int cnt = Math.max(64 * 1024, 2 * estimatedSize);
+ return BloomFilter.create(keyType.funnel(), cnt);
+ }
+ }
+
+ static class SqlHandle {
+ private final String url;
+ Connection conn;
+ PreparedStatement get;
+ PreparedStatement put;
+ PreparedStatement touch;
+ PreparedStatement invalidate;
+
+ SqlHandle(String url, KeyType<?> type) throws SQLException {
+ this.url = url;
+ this.conn = org.h2.Driver.load().connect(url, null);
+ Statement stmt = conn.createStatement();
+ try {
+ stmt.execute("CREATE TABLE IF NOT EXISTS data"
+ + "(k " + type.columnType() + " NOT NULL PRIMARY KEY HASH"
+ + ",v OTHER NOT NULL"
+ + ",created TIMESTAMP NOT NULL"
+ + ",accessed TIMESTAMP NOT NULL"
+ + ")");
+ } finally {
+ stmt.close();
+ }
+ }
+
+ void close() {
+ get = closeStatement(get);
+ put = closeStatement(put);
+ touch = closeStatement(touch);
+ invalidate = closeStatement(invalidate);
+
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ log.warn("Cannot close connection to " + url, e);
+ } finally {
+ conn = null;
+ }
+ }
+ }
+
+ private PreparedStatement closeStatement(PreparedStatement ps) {
+ if (ps != null) {
+ try {
+ ps.close();
+ } catch (SQLException e) {
+ log.warn("Cannot close statement for " + url, e);
+ }
+ }
+ return null;
+ }
+ }
+
+ private static class SinkOutputStream extends OutputStream {
+ private final PrimitiveSink sink;
+
+ SinkOutputStream(PrimitiveSink sink) {
+ this.sink = sink;
+ }
+
+ @Override
+ public void write(int b) {
+ sink.putByte((byte)b);
+ }
+
+ @Override
+ public void write(byte[] b, int p, int n) {
+ sink.putBytes(b, p, n);
+ }
+ }
+}
diff --git a/gerrit-common/.settings/org.eclipse.core.resources.prefs b/gerrit-common/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-common/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-common/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index c0c317c..10b1924 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -40,6 +40,7 @@
public static final String ADMIN_GROUPS = "/admin/groups/";
public static final String ADMIN_PROJECTS = "/admin/projects/";
public static final String ADMIN_CREATE_PROJECT = "/admin/create-project/";
+ public static final String ADMIN_PLUGINS = "/admin/plugins/";
public static String toChange(final ChangeInfo c) {
return toChange(c.getId());
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index cd64b0a..a9b5e85 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -118,4 +118,13 @@
public String toString() {
return "AccessSection[" + getName() + "]";
}
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!super.equals(obj) || !(obj instanceof AccessSection)) {
+ return false;
+ }
+ return new HashSet<Permission>(permissions).equals(new HashSet<Permission>(
+ ((AccessSection) obj).permissions));
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
index 9a4d9fb..92c2d6c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
@@ -65,4 +65,51 @@
public void setPreferredEmail(final String email) {
preferredEmail = email;
}
+
+ /**
+ * Formats an account name.
+ * <p>
+ * If the account has a full name, it returns only the full name. Otherwise it
+ * returns a longer form that includes the email address.
+ */
+ public String getName(String anonymousCowardName) {
+ if (getFullName() != null) {
+ return getFullName();
+ }
+ if (getPreferredEmail() != null) {
+ return getPreferredEmail();
+ }
+ return getNameEmail(anonymousCowardName);
+ }
+
+ /**
+ * Formats an account as an name and an email address.
+ * <p>
+ * Example output:
+ * <ul>
+ * <li><code>A U. Thor <author@example.com></code>: full populated</li>
+ * <li><code>A U. Thor (12)</code>: missing email address</li>
+ * <li><code>Anonymous Coward <author@example.com></code>: missing name</li>
+ * <li><code>Anonymous Coward (12)</code>: missing name and email address</li>
+ * </ul>
+ */
+ public String getNameEmail(String anonymousCowardName) {
+ String name = getFullName();
+ if (name == null) {
+ name = anonymousCowardName;
+ }
+
+ final StringBuilder b = new StringBuilder();
+ b.append(name);
+ if (getPreferredEmail() != null) {
+ b.append(" <");
+ b.append(getPreferredEmail());
+ b.append(">");
+ } else if (getId() != null) {
+ b.append(" (");
+ b.append(getId().get());
+ b.append(")");
+ }
+ return b.toString();
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
index f646bc6..0ddd239 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
@@ -15,33 +15,14 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
-import java.util.Set;
-
@RpcImpl(version = Version.V2_0)
public interface ChangeListService extends RemoteJsonService {
- /** Get all changes which match an arbitrary query string. */
- void allQueryPrev(String query, String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get all changes which match an arbitrary query string. */
- void allQueryNext(String query, String pos, int limit,
- AsyncCallback<SingleListChangeInfo> callback);
-
- /** Get the data to show AccountDashboardScreen for an account. */
- void forAccount(Account.Id id, AsyncCallback<AccountDashboardInfo> callback);
-
- /** Get the ids of all changes starred by the caller. */
- @SignInRequired
- void myStarredChangeIds(AsyncCallback<Set<Change.Id>> callback);
-
/**
* Add and/or remove changes from the set of starred changes of the caller.
*
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index 07a8534..89de3b4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -28,6 +28,7 @@
public class GerritConfig implements Cloneable {
protected String registerUrl;
protected String httpPasswordUrl;
+ protected String openIdSsoUrl;
protected List<OpenIdProviderPattern> allowedOpenIDs;
protected GitwebConfig gitweb;
@@ -72,6 +73,14 @@
httpPasswordUrl = url;
}
+ public String getOpenIdSsoUrl() {
+ return openIdSsoUrl;
+ }
+
+ public void setOpenIdSsoUrl(final String u) {
+ openIdSsoUrl = u;
+ }
+
public List<OpenIdProviderPattern> getAllowedOpenIDs() {
return allowedOpenIDs;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 64444b4..81d4fc9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -42,12 +42,13 @@
public static final String CREATE_PROJECT = "createProject";
/**
- * Denotes who may email change reviewers.
+ * Denotes who may email change reviewers and watchers.
* <p>
* This can be used to deny build bots from emailing reviewers and people who
- * have starred the changed. Instead, only the authors of the change will be
- * emailed. The allow rules are evaluated before deny rules, however the
- * default is to allow emailing, if no explicit rule is matched.
+ * watch the change. Instead, only the authors of the change and those who
+ * starred it will be emailed. The allow rules are evaluated before deny
+ * rules, however the default is to allow emailing, if no explicit rule is
+ * matched.
*/
public static final String EMAIL_REVIEWERS = "emailReviewers";
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
index f385e27..ffa1e3e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
@@ -24,7 +24,6 @@
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
-import java.util.List;
import java.util.Set;
@RpcImpl(version = Version.V2_0)
@@ -60,14 +59,6 @@
AsyncCallback<VoidResult> callback);
@SignInRequired
- void changeExternalGroup(AccountGroup.Id groupId,
- AccountGroup.ExternalNameKey bindTo, AsyncCallback<VoidResult> callback);
-
- @SignInRequired
- void searchExternalGroups(String searchFilter,
- AsyncCallback<List<AccountGroup.ExternalNameKey>> callback);
-
- @SignInRequired
void addGroupMember(AccountGroup.Id groupId, String nameOrEmail,
AsyncCallback<GroupDetail> callback);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDescription.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDescription.java
new file mode 100644
index 0000000..828bf24
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDescription.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+/**
+ * Group methods exposed by the GroupBackend.
+ */
+public class GroupDescription {
+ /**
+ * The Basic information required to be exposed by any Group.
+ */
+ public interface Basic {
+ /** @return the non-null UUID of the group. */
+ AccountGroup.UUID getGroupUUID();
+
+ /** @return the non-null name of the group. */
+ String getName();
+
+ /** @return whether the group is visible to all accounts. */
+ boolean isVisibleToAll();
+ }
+
+ /**
+ * The extended information exposed by internal groups backed by an
+ * AccountGroup.
+ */
+ public interface Internal extends Basic {
+ /** @return the backing AccountGroup. */
+ AccountGroup getAccountGroup();
+ }
+
+ private GroupDescription() {
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDescriptions.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDescriptions.java
new file mode 100644
index 0000000..e0bc7d8
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDescriptions.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+import javax.annotation.Nullable;
+
+/**
+ * Utility class for building GroupDescription objects.
+ */
+public class GroupDescriptions {
+
+ @Nullable
+ public static AccountGroup toAccountGroup(GroupDescription.Basic group) {
+ if (group instanceof GroupDescription.Internal) {
+ return ((GroupDescription.Internal) group).getAccountGroup();
+ }
+ return null;
+ }
+
+ public static GroupDescription.Internal forAccountGroup(final AccountGroup group) {
+ return new GroupDescription.Internal() {
+ @Override
+ public AccountGroup.UUID getGroupUUID() {
+ return group.getGroupUUID();
+ }
+
+ @Override
+ public String getName() {
+ return group.getName();
+ }
+
+ @Override
+ public boolean isVisibleToAll() {
+ return group.isVisibleToAll();
+ }
+
+ @Override
+ public AccountGroup getAccountGroup() {
+ return group;
+ }
+ };
+ }
+
+ private GroupDescriptions() {
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupReference.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupReference.java
index f05d1b9..c261fdd 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupReference.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupReference.java
@@ -23,6 +23,10 @@
return new GroupReference(group.getGroupUUID(), group.getName());
}
+ public static GroupReference forGroup(GroupDescription.Basic group) {
+ return new GroupReference(group.getGroupUUID(), group.getName());
+ }
+
protected String uuid;
protected String name;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
index c3d3f1e..f991f4c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
@@ -31,5 +31,8 @@
public String textColor;
public String trimColor;
public String selectionColor;
+ public String changeTableOutdatedColor;
+ public String tableOddRowColor;
+ public String tableEvenRowColor;
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
index 68676cf..2a70d6c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
@@ -15,6 +15,7 @@
package com.google.gerrit.common.data;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -62,28 +63,9 @@
raw.append(pattern.substring(i, b));
ops.add(new Constant(pattern.substring(i, b)));
- String expr = pattern.substring(b + 2, e);
- String parameterName = "";
- List<Function> functions = new ArrayList<Function>();
- if (!expr.contains(".")) {
- parameterName = expr;
- } else {
- int firstDot = expr.indexOf('.');
- parameterName = expr.substring(0, firstDot);
- String actionsStr = expr.substring(firstDot + 1);
- String[] actions = actionsStr.split("\\.");
+ // "${parameter[.functions...]}" -> "parameter[.functions...]"
+ final Parameter p = new Parameter(pattern.substring(b + 2, e));
- for (String action : actions) {
- Function function = FUNCTIONS.get(action);
- if (function == null) {
- function = NOOP;
- }
- functions.add(function);
- }
- }
-
- final Parameter p =
- new Parameter(parameterName, Collections.unmodifiableList(functions));
raw.append("{" + prs.size() + "}");
prs.add(p);
ops.add(p);
@@ -184,9 +166,25 @@
private final String name;
private final List<Function> functions;
- Parameter(final String name, final List<Function> functions) {
- this.name = name;
- this.functions = functions;
+ Parameter(final String parameter) {
+ // "parameter[.functions...]" -> (parameter, functions...)
+ final List<String> names = Arrays.asList(parameter.split("\\."));
+ final List<Function> functs = new ArrayList<Function>(names.size());
+
+ if (names.isEmpty()) {
+ name = "";
+ } else {
+ name = names.get(0);
+
+ for (String fname : names.subList(1, names.size())) {
+ final Function function = FUNCTIONS.get(fname);
+ if (function != null) {
+ functs.add(function);
+ }
+ }
+ }
+
+ functions = Collections.unmodifiableList(functs);
}
@Override
@@ -207,12 +205,6 @@
}
private static final Map<String, Function> FUNCTIONS = initFunctions();
- private static final Function NOOP = new Function() {
- @Override
- String apply(String a) {
- return a;
- }
- };
private static Map<String, Function> initFunctions() {
final HashMap<String, Function> m = new HashMap<String, Function>();
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
index 075d558..3c5c688 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
@@ -20,6 +20,9 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
public class PatchSetPublishDetail {
@@ -28,6 +31,8 @@
protected Change change;
protected List<PatchLineComment> drafts;
protected List<PermissionRange> labels;
+ protected List<ApprovalDetail> approvals;
+ protected List<SubmitRecord> submitRecords;
protected List<PatchSetApproval> given;
protected boolean canSubmit;
@@ -39,6 +44,23 @@
this.labels = labels;
}
+ public List<ApprovalDetail> getApprovals() {
+ return approvals;
+ }
+
+ public void setApprovals(Collection<ApprovalDetail> list) {
+ approvals = new ArrayList<ApprovalDetail>(list);
+ Collections.sort(approvals, ApprovalDetail.SORT);
+ }
+
+ public void setSubmitRecords(List<SubmitRecord> all) {
+ submitRecords = all;
+ }
+
+ public List<SubmitRecord> getSubmitRecords() {
+ return submitRecords;
+ }
+
public List<PatchSetApproval> getGiven() {
return given;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 20261de..5cb7787 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -15,11 +15,13 @@
package com.google.gerrit.common.data;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
/** A single permission within an {@link AccessSection} of a project. */
public class Permission implements Comparable<Permission> {
+ public static final String ABANDON = "abandon";
public static final String CREATE = "create";
public static final String FORGE_AUTHOR = "forgeAuthor";
public static final String FORGE_COMMITTER = "forgeCommitter";
@@ -40,6 +42,7 @@
NAMES_LC = new ArrayList<String>();
NAMES_LC.add(OWNER.toLowerCase());
NAMES_LC.add(READ.toLowerCase());
+ NAMES_LC.add(ABANDON.toLowerCase());
NAMES_LC.add(CREATE.toLowerCase());
NAMES_LC.add(FORGE_AUTHOR.toLowerCase());
NAMES_LC.add(FORGE_COMMITTER.toLowerCase());
@@ -73,6 +76,16 @@
return LABEL + labelName;
}
+ public static boolean canBeOnAllProjects(String ref, String permissionName) {
+ if (AccessSection.ALL.equals(ref)) {
+ return !OWNER.equals(permissionName);
+ }
+ if (AccessSection.REF_CONFIG.equals(ref)) {
+ return !PUSH.equals(permissionName);
+ }
+ return true;
+ }
+
protected String name;
protected boolean exclusiveGroup;
protected List<PermissionRule> rules;
@@ -208,4 +221,23 @@
int index = NAMES_LC.indexOf(a.getName().toLowerCase());
return 0 <= index ? index : NAMES_LC.size();
}
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof Permission)) {
+ return false;
+ }
+
+ final Permission other = (Permission) obj;
+ if (!name.equals(other.name) || exclusiveGroup != other.exclusiveGroup) {
+ return false;
+ }
+ return new HashSet<PermissionRule>(rules)
+ .equals(new HashSet<PermissionRule>(other.rules));
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
index 9b6695e..5960165 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
@@ -257,4 +257,19 @@
}
return Integer.parseInt(value);
}
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof PermissionRule)) {
+ return false;
+ }
+ final PermissionRule other = (PermissionRule)obj;
+ return action.equals(other.action) && force == other.force
+ && min == other.min && max == other.max && group.equals(other.group);
+ }
+
+ @Override
+ public int hashCode() {
+ return group.hashCode();
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index f935c03..1893843 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -26,6 +26,7 @@
protected List<AccessSection> local;
protected Set<String> ownerOf;
protected boolean isConfigVisible;
+ protected boolean canUpload;
public ProjectAccess() {
}
@@ -94,4 +95,12 @@
public void setConfigVisible(boolean isConfigVisible) {
this.isConfigVisible = isConfigVisible;
}
+
+ public boolean canUpload() {
+ return canUpload;
+ }
+
+ public void setCanUpload(boolean canUpload) {
+ this.canUpload = canUpload;
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index 1b504b0..a650117 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -28,10 +29,7 @@
@RpcImpl(version = Version.V2_0)
public interface ProjectAdminService extends RemoteJsonService {
- void visibleProjects(AsyncCallback<ProjectList> callback);
-
void visibleProjectDetails(AsyncCallback<List<ProjectDetail>> callback);
- void suggestParentCandidates(AsyncCallback<List<Project>> callback);
void projectDetail(Project.NameKey projectName,
AsyncCallback<ProjectDetail> callback);
@@ -53,6 +51,11 @@
String message, List<AccessSection> sections,
AsyncCallback<ProjectAccess> callback);
+ @SignInRequired
+ void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
+ String message, List<AccessSection> sections,
+ AsyncCallback<Change.Id> callback);
+
void listBranches(Project.NameKey projectName,
AsyncCallback<ListBranchesResult> callback);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectList.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectList.java
deleted file mode 100644
index 8511460..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectList.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License
-
-package com.google.gerrit.common.data;
-
-import com.google.gerrit.reviewdb.client.Project;
-
-import java.util.List;
-
-public class ProjectList {
- protected List<Project> projects;
- protected boolean canCreateProject;
-
- public ProjectList() {
- }
-
- public List<Project> getProjects() {
- return projects;
- }
-
- public void setProjects(List<Project> projects) {
- this.projects = projects;
- }
-
- public boolean canCreateProject() {
- return canCreateProject;
- }
-
- public void setCanCreateProject(boolean canCreateProject) {
- this.canCreateProject = canCreateProject;
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java
index 490378e..810e906 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java
@@ -21,6 +21,9 @@
/** Pattern that matches all branches in a project. */
public static final String HEADS = "refs/heads/*";
+ /** Configuration settings for a project {@code refs/meta/config} */
+ public static final String REF_CONFIG = "refs/meta/config";
+
/** Prefix that triggers a regular expression pattern. */
public static final String REGEX_PREFIX = "^";
@@ -45,4 +48,17 @@
public void setName(String name) {
this.name = name;
}
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof RefConfigSection)) {
+ return false;
+ }
+ return name.equals(((RefConfigSection) obj).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
index 28cf49b..c0bf818 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
@@ -63,6 +63,9 @@
/** Review operation invalid because change is closed. */
CHANGE_IS_CLOSED,
+ /** Review operation invalid because change is not abandoned. */
+ CHANGE_NOT_ABANDONED,
+
/** Not permitted to publish this draft patch set */
PUBLISH_NOT_PERMITTED,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
index 5049ba4..365f6a9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
@@ -68,6 +68,12 @@
NEED,
/**
+ * The label may be set, but it's neither necessary for submission
+ * nor does it block submission if set.
+ */
+ MAY,
+
+ /**
* The label is required for submission, but is impossible to complete.
* The likely cause is access has not been granted correctly by the
* project owner or site administrator.
@@ -78,5 +84,34 @@
public String label;
public Status status;
public Account.Id appliedBy;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(label).append(": ").append(status);
+ if (appliedBy != null) {
+ sb.append(" by ").append(appliedBy);
+ }
+ return sb.toString();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(status);
+ if (status == Status.RULE_ERROR && errorMessage != null) {
+ sb.append('(').append(errorMessage).append(')');
+ }
+ sb.append('[');
+ if (labels != null) {
+ String delimiter = "";
+ for (Label label : labels) {
+ sb.append(delimiter).append(label);
+ delimiter = ", ";
+ }
+ }
+ sb.append(']');
+ return sb.toString();
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
index d52a724..7205b74 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
@@ -29,9 +29,16 @@
void suggestAccount(String query, Boolean enabled, int limit,
AsyncCallback<List<AccountInfo>> callback);
+ /**
+ * @see #suggestAccountGroup(com.google.gerrit.reviewdb.client.Project.NameKey, String, int, AsyncCallback)
+ */
+ @Deprecated
void suggestAccountGroup(String query, int limit,
AsyncCallback<List<GroupReference>> callback);
+ void suggestAccountGroupForProject(Project.NameKey project, String query,
+ int limit, AsyncCallback<List<GroupReference>> callback);
+
/**
* @see #suggestChangeReviewer(com.google.gerrit.reviewdb.client.Change.Id, String, int, AsyncCallback)
*/
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
deleted file mode 100644
index db421ea..0000000
--- a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.ehcache;
-
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.CachePool;
-import com.google.gerrit.server.cache.CacheProvider;
-import com.google.gerrit.server.cache.EntryCreator;
-import com.google.gerrit.server.cache.EvictionPolicy;
-import com.google.gerrit.server.cache.ProxyCache;
-import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.config.CacheConfiguration;
-import net.sf.ehcache.config.Configuration;
-import net.sf.ehcache.config.DiskStoreConfiguration;
-import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
-
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-/** Pool of all declared caches created by {@link CacheModule}s. */
-@Singleton
-public class EhcachePoolImpl implements CachePool {
- private static final Logger log =
- LoggerFactory.getLogger(EhcachePoolImpl.class);
-
- public static class Module extends LifecycleModule {
- @Override
- protected void configure() {
- bind(CachePool.class).to(EhcachePoolImpl.class);
- bind(EhcachePoolImpl.class);
- listener().to(EhcachePoolImpl.Lifecycle.class);
- }
- }
-
- public static class Lifecycle implements LifecycleListener {
- private final EhcachePoolImpl cachePool;
-
- @Inject
- Lifecycle(final EhcachePoolImpl cachePool) {
- this.cachePool = cachePool;
- }
-
- @Override
- public void start() {
- cachePool.start();
- }
-
- @Override
- public void stop() {
- cachePool.stop();
- }
- }
-
- private final Config config;
- private final SitePaths site;
-
- private final Object lock = new Object();
- private final Map<String, CacheProvider<?, ?>> caches;
- private CacheManager manager;
-
- @Inject
- EhcachePoolImpl(@GerritServerConfig final Config cfg, final SitePaths site) {
- this.config = cfg;
- this.site = site;
- this.caches = new HashMap<String, CacheProvider<?, ?>>();
- }
-
- @SuppressWarnings({"rawtypes", "unchecked"})
- private void start() {
- synchronized (lock) {
- if (manager != null) {
- throw new IllegalStateException("Cache pool has already been started");
- }
-
- try {
- System.setProperty("net.sf.ehcache.skipUpdateCheck", "" + true);
- } catch (SecurityException e) {
- // Ignore it, the system is just going to ping some external page
- // using a background thread and there's not much we can do about
- // it now.
- }
-
- manager = new CacheManager(new Factory().toConfiguration());
- for (CacheProvider<?, ?> p : caches.values()) {
- Ehcache eh = manager.getEhcache(p.getName());
- EntryCreator<?, ?> c = p.getEntryCreator();
- if (c != null) {
- p.bind(new PopulatingCache(eh, c));
- } else {
- p.bind(new SimpleCache(eh));
- }
- }
- }
- }
-
- private void stop() {
- synchronized (lock) {
- if (manager != null) {
- manager.shutdown();
- }
- }
- }
-
- /** <i>Discouraged</i> Get the underlying cache descriptions, for statistics. */
- public CacheManager getCacheManager() {
- synchronized (lock) {
- return manager;
- }
- }
-
- public <K, V> ProxyCache<K, V> register(final CacheProvider<K, V> provider) {
- synchronized (lock) {
- if (manager != null) {
- throw new IllegalStateException("Cache pool has already been started");
- }
-
- final String n = provider.getName();
- if (caches.containsKey(n) && caches.get(n) != provider) {
- throw new IllegalStateException("Cache \"" + n + "\" already defined");
- }
- caches.put(n, provider);
- return new ProxyCache<K, V>();
- }
- }
-
- private class Factory {
- private static final int MB = 1024 * 1024;
- private final Configuration mgr = new Configuration();
-
- Configuration toConfiguration() {
- configureDiskStore();
- configureDefaultCache();
-
- for (CacheProvider<?, ?> p : caches.values()) {
- final String name = p.getName();
- final CacheConfiguration c = newCache(name);
- c.setMemoryStoreEvictionPolicyFromObject(toPolicy(p.evictionPolicy()));
-
- c.setMaxElementsInMemory(getInt(name, "memorylimit", p.memoryLimit()));
-
- c.setTimeToIdleSeconds(0);
- c.setTimeToLiveSeconds(getSeconds(name, "maxage", p.maxAge()));
- c.setEternal(c.getTimeToLiveSeconds() == 0);
-
- if (p.disk() && mgr.getDiskStoreConfiguration() != null) {
- c.setMaxElementsOnDisk(getInt(name, "disklimit", p.diskLimit()));
-
- int v = c.getDiskSpoolBufferSizeMB() * MB;
- v = getInt(name, "diskbuffer", v) / MB;
- c.setDiskSpoolBufferSizeMB(Math.max(1, v));
- c.setOverflowToDisk(c.getMaxElementsOnDisk() > 0);
- c.setDiskPersistent(c.getMaxElementsOnDisk() > 0);
- }
-
- mgr.addCache(c);
- }
-
- return mgr;
- }
-
- private MemoryStoreEvictionPolicy toPolicy(final EvictionPolicy policy) {
- switch (policy) {
- case LFU:
- return MemoryStoreEvictionPolicy.LFU;
-
- case LRU:
- return MemoryStoreEvictionPolicy.LRU;
-
- default:
- throw new IllegalArgumentException("Unsupported " + policy);
- }
- }
-
- private int getInt(String n, String s, int d) {
- return config.getInt("cache", n, s, d);
- }
-
- private long getSeconds(String n, String s, long d) {
- d = MINUTES.convert(d, SECONDS);
- long m = ConfigUtil.getTimeUnit(config, "cache", n, s, d, MINUTES);
- return SECONDS.convert(m, MINUTES);
- }
-
- private void configureDiskStore() {
- boolean needDisk = false;
- for (CacheProvider<?, ?> p : caches.values()) {
- if (p.disk()) {
- needDisk = true;
- break;
- }
- }
- if (!needDisk) {
- return;
- }
-
- File loc = site.resolve(config.getString("cache", null, "directory"));
- if (loc == null) {
- } else if (loc.exists() || loc.mkdirs()) {
- if (loc.canWrite()) {
- final DiskStoreConfiguration c = new DiskStoreConfiguration();
- c.setPath(loc.getAbsolutePath());
- mgr.addDiskStore(c);
- log.info("Enabling disk cache " + loc.getAbsolutePath());
- } else {
- log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
- }
- } else {
- log.warn("Can't create disk cache: " + loc.getAbsolutePath());
- }
- }
-
- private CacheConfiguration newConfiguration() {
- CacheConfiguration c = new CacheConfiguration();
-
- c.setMaxElementsInMemory(1024);
- c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);
-
- c.setTimeToIdleSeconds(0);
- c.setTimeToLiveSeconds(0 /* infinite */);
- c.setEternal(true);
-
- if (mgr.getDiskStoreConfiguration() != null) {
- c.setMaxElementsOnDisk(16384);
- c.setOverflowToDisk(false);
- c.setDiskPersistent(false);
-
- c.setDiskSpoolBufferSizeMB(5);
- c.setDiskExpiryThreadIntervalSeconds(60 * 60);
- }
- return c;
- }
-
- private void configureDefaultCache() {
- mgr.setDefaultCacheConfiguration(newConfiguration());
- }
-
- private CacheConfiguration newCache(final String name) {
- CacheConfiguration c = newConfiguration();
- c.setName(name);
- return c;
- }
- }
-}
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/PopulatingCache.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/PopulatingCache.java
deleted file mode 100644
index f5c6c45..0000000
--- a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/PopulatingCache.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.ehcache;
-
-import com.google.gerrit.server.cache.Cache;
-import com.google.gerrit.server.cache.EntryCreator;
-
-import net.sf.ehcache.CacheException;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Element;
-import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A decorator for {@link Cache} which automatically constructs missing entries.
- * <p>
- * On a cache miss {@link EntryCreator#createEntry(Object)} is invoked, allowing
- * the application specific subclass to compute the entry and return it for
- * caching. During a miss the cache takes a lock related to the missing key,
- * ensuring that at most one thread performs the creation work, and other
- * threads wait for the result. Concurrent creations are possible if two
- * different keys miss and hash to different locks in the internal lock table.
- *
- * @param <K> type of key used to name cache entries.
- * @param <V> type of value stored within a cache entry.
- */
-class PopulatingCache<K, V> implements Cache<K, V> {
- private static final Logger log =
- LoggerFactory.getLogger(PopulatingCache.class);
-
- private final net.sf.ehcache.constructs.blocking.SelfPopulatingCache self;
- private final EntryCreator<K, V> creator;
-
- PopulatingCache(Ehcache s, EntryCreator<K, V> entryCreator) {
- creator = entryCreator;
- final CacheEntryFactory f = new CacheEntryFactory() {
- @SuppressWarnings("unchecked")
- @Override
- public Object createEntry(Object key) throws Exception {
- return creator.createEntry((K) key);
- }
- };
- self = new net.sf.ehcache.constructs.blocking.SelfPopulatingCache(s, f);
- }
-
- /**
- * Get the element from the cache, or {@link EntryCreator#missing(Object)} if not found.
- * <p>
- * The {@link EntryCreator#missing(Object)} method is only invoked if:
- * <ul>
- * <li>{@code key == null}, in which case the application should return a
- * suitable return value that callers can accept, or throw a RuntimeException.
- * <li>{@code createEntry(key)} threw an exception, in which case the entry
- * was not stored in the cache. An entry was recorded in the application log,
- * but a return value is still required.
- * <li>The cache has been shutdown, and access is forbidden.
- * </ul>
- *
- * @param key key to locate.
- * @return either the cached entry, or {@code missing(key)} if not found.
- */
- @SuppressWarnings("unchecked")
- public V get(final K key) {
- if (key == null) {
- return creator.missing(key);
- }
-
- final Element m;
- try {
- m = self.get(key);
- } catch (IllegalStateException err) {
- log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return creator.missing(key);
- } catch (CacheException err) {
- log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return creator.missing(key);
- }
- return m != null ? (V) m.getObjectValue() : creator.missing(key);
- }
-
- public void remove(final K key) {
- if (key != null) {
- self.remove(key);
- }
- }
-
- /** Remove all cached items, forcing them to be created again on demand. */
- public void removeAll() {
- self.removeAll();
- }
-
- public void put(K key, V value) {
- self.put(new Element(key, value));
- }
-
- @Override
- public String toString() {
- return "Cache[" + self.getName() + "]";
- }
-}
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SimpleCache.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SimpleCache.java
deleted file mode 100644
index e4428e3..0000000
--- a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SimpleCache.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.ehcache;
-
-import com.google.gerrit.server.cache.Cache;
-
-import net.sf.ehcache.CacheException;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Element;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A fast in-memory and/or on-disk based cache.
- *
- * @type <K> type of key used to lookup entries in the cache.
- * @type <V> type of value stored within each cache entry.
- */
-final class SimpleCache<K, V> implements Cache<K, V> {
- private static final Logger log = LoggerFactory.getLogger(SimpleCache.class);
-
- private final Ehcache self;
-
- SimpleCache(final Ehcache self) {
- this.self = self;
- }
-
- Ehcache getEhcache() {
- return self;
- }
-
- @SuppressWarnings("unchecked")
- public V get(final K key) {
- if (key == null) {
- return null;
- }
- final Element m;
- try {
- m = self.get(key);
- } catch (IllegalStateException err) {
- log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return null;
- } catch (CacheException err) {
- log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return null;
- }
- return m != null ? (V) m.getObjectValue() : null;
- }
-
- public void put(final K key, final V value) {
- self.put(new Element(key, value));
- }
-
- public void remove(final K key) {
- if (key != null) {
- self.remove(key);
- }
- }
-
- public void removeAll() {
- self.removeAll();
- }
-
- @Override
- public String toString() {
- return "Cache[" + self.getName() + "]";
- }
-}
diff --git a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/RequiresCapability.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
similarity index 85%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/RequiresCapability.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
index cc41a79..382f4ea 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/RequiresCapability.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd;
+package com.google.gerrit.extensions.annotations;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -21,7 +21,8 @@
import java.lang.annotation.Target;
/**
- * Annotation on {@link SshCommand} declaring a capability must be granted.
+ * Annotation on {@link SshCommand} or {@link RestApiServlet} declaring a
+ * capability must be granted.
*/
@Target({ElementType.TYPE})
@Retention(RUNTIME)
diff --git a/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
index 36e1448..e9441bb 100644
--- a/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:38 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 40ffc7d..7a84b20 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.common.PageLinks.ADMIN_CREATE_PROJECT;
import static com.google.gerrit.common.PageLinks.ADMIN_GROUPS;
import static com.google.gerrit.common.PageLinks.ADMIN_PROJECTS;
+import static com.google.gerrit.common.PageLinks.ADMIN_PLUGINS;
import static com.google.gerrit.common.PageLinks.MINE;
import static com.google.gerrit.common.PageLinks.REGISTER;
import static com.google.gerrit.common.PageLinks.SETTINGS;
@@ -48,6 +49,7 @@
import com.google.gerrit.client.admin.AccountGroupScreen;
import com.google.gerrit.client.admin.CreateProjectScreen;
import com.google.gerrit.client.admin.GroupListScreen;
+import com.google.gerrit.client.admin.PluginListScreen;
import com.google.gerrit.client.admin.ProjectAccessScreen;
import com.google.gerrit.client.admin.ProjectBranchesScreen;
import com.google.gerrit.client.admin.ProjectInfoScreen;
@@ -621,6 +623,10 @@
} else if (matchPrefix("/admin/projects/", token)) {
Gerrit.display(token, selectProject());
+ } else if (matchPrefix(ADMIN_PLUGINS, token)
+ || matchExact("/admin/plugins", token)) {
+ Gerrit.display(token, new PluginListScreen());
+
} else if (matchExact(ADMIN_CREATE_PROJECT, token)
|| matchExact("/admin/create-project", token)) {
Gerrit.display(token, new CreateProjectScreen());
@@ -710,8 +716,7 @@
return new ProjectInfoScreen(k);
}
- if (ProjectScreen.BRANCH.equals(panel)
- && !k.equals(Gerrit.getConfig().getWildProject())) {
+ if (ProjectScreen.BRANCH.equals(panel)) {
return new ProjectBranchesScreen(k);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 701e1fc..267419f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -14,7 +14,11 @@
package com.google.gerrit.client;
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+
+import com.google.gerrit.client.account.AccountCapabilities;
import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
+import com.google.gerrit.client.auth.openid.OpenIdSsoPanel;
import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
import com.google.gerrit.client.changes.ChangeConstants;
import com.google.gerrit.client.changes.ChangeListScreen;
@@ -255,6 +259,13 @@
Location.assign(selfRedirect("/become"));
break;
+ case OPENID_SSO:
+ final RootPanel gBody = RootPanel.get("gerrit_body");
+ OpenIdSsoPanel singleSignOnPanel = new OpenIdSsoPanel();
+ gBody.add(singleSignOnPanel);
+ singleSignOnPanel.authenticate(SignInMode.SIGN_IN, token);
+ break;
+
case OPENID:
new OpenIdSignInDialog(SignInMode.SIGN_IN, token, null).center();
break;
@@ -275,7 +286,7 @@
return selfRedirect("/login/" + token);
}
- private static String selfRedirect(String suffix) {
+ public static String selfRedirect(String suffix) {
// Clean up the path. Users seem to like putting extra slashes into the URL
// which can break redirections by misinterpreting at either client or server.
String path = Location.getPath();
@@ -574,10 +585,18 @@
addDiffLink(diffBar, C.menuDiffFiles(), PatchScreen.TopView.FILES);
if (signedIn) {
- m = new LinkMenuBar();
- addLink(m, C.menuGroups(), PageLinks.ADMIN_GROUPS);
- addLink(m, C.menuProjects(), PageLinks.ADMIN_PROJECTS);
- menuLeft.add(m, C.menuAdmin());
+ final LinkMenuBar menuBar = new LinkMenuBar();
+ addLink(menuBar, C.menuGroups(), PageLinks.ADMIN_GROUPS);
+ addLink(menuBar, C.menuProjects(), PageLinks.ADMIN_PROJECTS);
+ AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
+ @Override
+ public void onSuccess(AccountCapabilities result) {
+ if (result.canPerform(ADMINISTRATE_SERVER)) {
+ addLink(menuBar, C.menuPlugins(), PageLinks.ADMIN_PLUGINS);
+ }
+ }
+ }, ADMINISTRATE_SERVER);
+ menuLeft.add(menuBar, C.menuAdmin());
}
if (getConfig().isDocumentationAvailable()) {
@@ -616,6 +635,14 @@
});
break;
+ case OPENID_SSO:
+ menuRight.addItem(C.menuSignIn(), new Command() {
+ public void execute() {
+ doSignIn(History.getToken());
+ }
+ });
+ break;
+
case LDAP:
case LDAP_BIND:
case CUSTOM_EXTENSION:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index f716814..09e6b84 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -72,6 +72,7 @@
String menuPeople();
String menuGroups();
String menuProjects();
+ String menuPlugins();
String menuDocumentation();
String menuDocumentationIndex();
@@ -98,4 +99,7 @@
String jumpMineWatched();
String jumpMineStarred();
String jumpMineDraftComments();
+
+ String projectAccessError();
+ String projectAccessProposeForReviewHint();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 8e3ca6c..294ba49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -55,6 +55,7 @@
menuPeople = People
menuGroups = Groups
menuProjects = Projects
+menuPlugins = Plugins
menuDocumentation = Documentation
menuDocumentationIndex = Index
@@ -80,4 +81,7 @@
jumpMineWatched = Go to watched changes
jumpMineDrafts = Go to drafts
jumpMineStarred = Go to starred changes
-jumpMineDraftComments = Go to draft comments
\ No newline at end of file
+jumpMineDraftComments = Go to draft comments
+
+projectAccessError = You don't have permissions to modify the access rights for the following refs:
+projectAccessProposeForReviewHint = You may propose these modifications to the project owners by clicking on 'Save for Review'.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 574f58e..8f38f51 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -168,7 +168,7 @@
String patchSetRevision();
String patchSetUserIdentity();
String patchSizeCell();
- String permalink();
+ String pluginsTable();
String posscore();
String projectAdminApprovalCategoryRangeLine();
String projectAdminApprovalCategoryValue();
@@ -182,6 +182,7 @@
String rpcStatusPanel();
String screen();
String screenHeader();
+ String screenNoHeader();
String searchPanel();
String sectionHeader();
String sideBySideScreenLinkTable();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
index 1334d87..d38015c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchedProjectsScreen.java
@@ -15,12 +15,11 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.ProjectListPopup;
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
-import com.google.gerrit.client.ui.ProjectsTable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccountProjectWatchInfo;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -28,60 +27,37 @@
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.logical.shared.ResizeEvent;
-import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
-import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.globalkey.client.HidePopupPanelCommand;
-import com.google.gwtexpui.user.client.PluginSafeDialogBox;
import java.util.List;
-public class MyWatchedProjectsScreen extends SettingsScreen implements
- ResizeHandler {
+public class MyWatchedProjectsScreen extends SettingsScreen {
private Button addNew;
private HintTextBox nameBox;
private SuggestBox nameTxt;
private HintTextBox filterTxt;
private MyWatchesTable watchesTab;
private Button browse;
- private PluginSafeDialogBox popup;
- private Button close;
- private ProjectsTable projectsTab;
private Button delSel;
-
- private PopupPanel.PositionCallback popupPosition;
- private HandlerRegistration regWindowResize;
-
- private int preferredPopupWidth = -1;
-
private boolean submitOnSelection;
- private boolean firstPopupLoad = true;
- private boolean popingUp;
-
- private ScrollPanel sp;
+ private Grid grid;
+ private ProjectListPopup projectsPopup;
@Override
protected void onInitUI() {
super.onInitUI();
createWidgets();
-
/* top table */
-
- final Grid grid = new Grid(2, 2);
+ grid = new Grid(2, 2);
grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
grid.setText(0, 0, Util.C.watchedProjectName());
grid.setWidget(0, 1, nameTxt);
@@ -105,62 +81,27 @@
/* bottom table */
-
add(watchesTab);
add(delSel);
/* popup */
-
- final FlowPanel pfp = new FlowPanel();
- sp = new ScrollPanel(projectsTab);
- pfp.add(sp);
- pfp.add(close);
- popup.setWidget(pfp);
-
- popupPosition = new PopupPanel.PositionCallback() {
- public void setPosition(int offsetWidth, int offsetHeight) {
- if (preferredPopupWidth == -1) {
- preferredPopupWidth = offsetWidth;
+ projectsPopup = new ProjectListPopup() {
+ @Override
+ protected void onMovePointerTo(String projectName) {
+ // prevent user input from being overwritten by simply poping up
+ if (!projectsPopup.isPopingUp() || "".equals(nameBox.getText())) {
+ nameBox.setText(projectName);
}
+ }
- int top = grid.getAbsoluteTop() - 50; // under page header
-
- // Try to place it to the right of everything else, but not
- // right justified
- int left = 5 + Math.max(
- grid.getAbsoluteLeft() + grid.getOffsetWidth(),
- watchesTab.getAbsoluteLeft() + watchesTab.getOffsetWidth() );
-
- if (top + offsetHeight > Window.getClientHeight()) {
- top = Window.getClientHeight() - offsetHeight;
- }
- if (left + offsetWidth > Window.getClientWidth()) {
- left = Window.getClientWidth() - offsetWidth;
- }
-
- if (top < 0) {
- sp.setHeight((sp.getOffsetHeight() + top) + "px");
- top = 0;
- }
- if (left < 0) {
- sp.setWidth((sp.getOffsetWidth() + left) + "px");
- left = 0;
- }
-
- popup.setPopupPosition(left, top);
+ @Override
+ protected void openRow(String projectName) {
+ nameBox.setText(projectName);
+ doAddNew();
}
};
- }
-
- @Override
- public void onResize(final ResizeEvent event) {
- sp.setSize("100%","100%");
-
- // For some reason keeping track of preferredWidth keeps the width better,
- // but using 100% for height works better.
- popup.setHeight("100%");
- popupPosition.setPosition(preferredPopupWidth, popup.getOffsetHeight());
+ projectsPopup.initPopup(Util.C.projects(), PageLinks.SETTINGS_PROJECTS);
}
protected void createWidgets() {
@@ -213,49 +154,18 @@
}
});
- projectsTab = new ProjectsTable() {
- {
- keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.projectListOpen()));
- keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
- Util.C.projectListOpen()));
- }
-
- @Override
- protected void movePointerTo(final int row, final boolean scroll) {
- super.movePointerTo(row, scroll);
-
- // prevent user input from being overwritten by simply poping up
- if (! popingUp || "".equals(nameBox.getText()) ) {
- nameBox.setText(getRowItem(row).name());
- }
- }
-
- @Override
- protected void onOpenRow(final int row) {
- super.onOpenRow(row);
- nameBox.setText(getRowItem(row).name());
- doAddNew();
- }
- };
- projectsTab.setSavePointerId(PageLinks.SETTINGS_PROJECTS);
-
- close = new Button(Util.C.projectsClose());
- close.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- closePopup();
- }
- });
-
- popup = new PluginSafeDialogBox();
- popup.setModal(false);
- popup.setText(Util.C.projects());
-
browse = new Button(Util.C.buttonBrowseProjects());
browse.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
- displayPopup();
+ int top = grid.getAbsoluteTop() - 50; // under page header
+ // Try to place it to the right of everything else, but not
+ // right justified
+ int left =
+ 5 + Math.max(grid.getAbsoluteLeft() + grid.getOffsetWidth(),
+ watchesTab.getAbsoluteLeft() + watchesTab.getOffsetWidth());
+ projectsPopup.setPreferredCoordinates(top, left);
+ projectsPopup.displayPopup();
}
});
@@ -279,37 +189,7 @@
@Override
protected void onUnload() {
super.onUnload();
- closePopup();
- }
-
- protected void displayPopup() {
- popingUp = true;
- if (firstPopupLoad) { // For sizing/positioning, delay display until loaded
- populateProjects();
- } else {
- popup.setPopupPositionAndShow(popupPosition);
-
- GlobalKey.dialog(popup);
- GlobalKey.addApplication(popup, new HidePopupPanelCommand(0,
- KeyCodes.KEY_ESCAPE, popup));
- projectsTab.setRegisterKeys(true);
-
- projectsTab.finishDisplay();
-
- if (regWindowResize == null) {
- regWindowResize = Window.addResizeHandler(this);
- }
-
- popingUp = false;
- }
- }
-
- protected void closePopup() {
- popup.hide();
- if (regWindowResize != null) {
- regWindowResize.removeHandler();
- regWindowResize = null;
- }
+ projectsPopup.closePopup();
}
protected void doAddNew() {
@@ -359,17 +239,4 @@
}
});
}
-
- protected void populateProjects() {
- ProjectMap.all(new GerritCallback<ProjectMap>() {
- @Override
- public void onSuccess(final ProjectMap result) {
- projectsTab.display(result);
- if (firstPopupLoad) { // Display was delayed until table was loaded
- firstPopupLoad = false;
- displayPopup();
- }
- }
- });
- }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 72ac1a4..a9aa418 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -182,7 +182,7 @@
Collections.sort(value.getPermissions());
this.value = value;
- this.readOnly = !editing || !projectAccess.isOwnerOf(value);
+ this.readOnly = !editing || !(projectAccess.isOwnerOf(value) || projectAccess.canUpload());
name.setEnabled(!readOnly);
deleteSection.setVisible(!readOnly);
@@ -223,22 +223,16 @@
if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
for (String varName : Util.C.capabilityNames().keySet()) {
- if (value.getPermission(varName) == null) {
- perms.add(varName);
- }
+ addPermission(varName, perms);
}
} else if (RefConfigSection.isValid(value.getName())) {
for (ApprovalType t : Gerrit.getConfig().getApprovalTypes()
.getApprovalTypes()) {
String varName = Permission.LABEL + t.getCategory().getLabelName();
- if (value.getPermission(varName) == null) {
- perms.add(varName);
- }
+ addPermission(varName, perms);
}
for (String varName : Util.C.permissionNames().keySet()) {
- if (value.getPermission(varName) == null) {
- perms.add(varName);
- }
+ addPermission(varName, perms);
}
}
if (perms.isEmpty()) {
@@ -251,6 +245,19 @@
}
}
+ private void addPermission(final String permissionName,
+ final List<String> permissionList) {
+ if (value.getPermission(permissionName) != null) {
+ return;
+ }
+ if (Gerrit.getConfig().getWildProject()
+ .equals(projectAccess.getProjectName())
+ && !Permission.canBeOnAllProjects(value.getName(), permissionName)) {
+ return;
+ }
+ permissionList.add(permissionName);
+ }
+
@Override
public void flush() {
List<Permission> src = permissions.getList();
@@ -276,7 +283,8 @@
private class PermissionEditorSource extends EditorSource<PermissionEditor> {
@Override
public PermissionEditor create(int index) {
- PermissionEditor subEditor = new PermissionEditor(readOnly, value);
+ PermissionEditor subEditor =
+ new PermissionEditor(projectAccess.getProjectName(), readOnly, value);
permissionContainer.insert(subEditor, index);
return subEditor;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
index 130241e..ea7c04b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -27,17 +27,10 @@
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.clippy.client.CopyableLabel;
@@ -45,8 +38,6 @@
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.common.VoidResult;
-import java.util.List;
-
public class AccountGroupInfoScreen extends AccountGroupScreen {
private CopyableLabel groupUUIDLabel;
@@ -64,12 +55,6 @@
private ListBox typeSelect;
private Button saveType;
- private Panel externalPanel;
- private Label externalName;
- private NpTextBox externalNameFilter;
- private Button externalNameSearch;
- private Grid externalMatches;
-
private CheckBox visibleToAllCheckBox;
private Button saveGroupOptions;
@@ -86,8 +71,6 @@
initDescription();
initGroupOptions();
initGroupType();
-
- initExternal();
}
private void enableForm(final boolean canModify) {
@@ -95,8 +78,6 @@
ownerTxtBox.setEnabled(canModify);
descTxt.setEnabled(canModify);
typeSelect.setEnabled(canModify);
- externalNameFilter.setEnabled(canModify);
- externalNameSearch.setEnabled(canModify);
visibleToAllCheckBox.setEnabled(canModify);
}
@@ -243,7 +224,6 @@
typeSelect = new ListBox();
typeSelect.setStyleName(Gerrit.RESOURCES.css().groupTypeSelectListBox());
typeSelect.addItem(Util.C.groupType_INTERNAL(), AccountGroup.Type.INTERNAL.name());
- typeSelect.addItem(Util.C.groupType_LDAP(), AccountGroup.Type.LDAP.name());
typeSelect.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
@@ -279,54 +259,12 @@
add(fp);
}
- private void initExternal() {
- externalName = new Label();
-
- externalNameFilter = new NpTextBox();
- externalNameFilter.setStyleName(Gerrit.RESOURCES.css()
- .groupExternalNameFilterTextBox());
- externalNameFilter.setVisibleLength(30);
- externalNameFilter.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(final KeyPressEvent event) {
- if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- doExternalSearch();
- }
- }
- });
-
- externalNameSearch = new Button(Gerrit.C.searchButton());
- externalNameSearch.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- doExternalSearch();
- }
- });
-
- externalMatches = new Grid();
- externalMatches.setStyleName(Gerrit.RESOURCES.css().infoTable());
- externalMatches.setVisible(false);
-
- final FlowPanel searchLine = new FlowPanel();
- searchLine.add(externalNameFilter);
- searchLine.add(externalNameSearch);
-
- externalPanel = new VerticalPanel();
- externalPanel.add(new SmallHeading(Util.C.headingExternalGroup()));
- externalPanel.add(externalName);
- externalPanel.add(searchLine);
- externalPanel.add(externalMatches);
- add(externalPanel);
- }
-
private void setType(final AccountGroup.Type newType) {
final boolean system = newType == AccountGroup.Type.SYSTEM;
typeSystem.setVisible(system);
typeSelect.setVisible(!system);
saveType.setVisible(!system);
- externalPanel.setVisible(newType == AccountGroup.Type.LDAP);
- externalNameFilter.setText(groupNameTxt.getText());
if (!system) {
for (int i = 0; i < typeSelect.getItemCount(); i++) {
@@ -367,77 +305,6 @@
});
}
- private void doExternalSearch() {
- externalNameFilter.setEnabled(false);
- externalNameSearch.setEnabled(false);
- Util.GROUP_SVC.searchExternalGroups(externalNameFilter.getText(),
- new GerritCallback<List<AccountGroup.ExternalNameKey>>() {
- @Override
- public void onSuccess(List<AccountGroup.ExternalNameKey> result) {
- try {
- final CellFormatter fmt = externalMatches.getCellFormatter();
-
- if (result.isEmpty()) {
- externalMatches.resize(1, 1);
- externalMatches.setText(0, 0, Util.C.errorNoMatchingGroups());
- fmt.setStyleName(0, 0, Gerrit.RESOURCES.css().header());
- return;
- }
-
- externalMatches.resize(1 + result.size(), 2);
-
- externalMatches.setText(0, 0, Util.C.columnGroupName());
- externalMatches.setText(0, 1, "");
- fmt.setStyleName(0, 0, Gerrit.RESOURCES.css().header());
- fmt.setStyleName(0, 1, Gerrit.RESOURCES.css().header());
-
- for (int row = 0; row < result.size(); row++) {
- final AccountGroup.ExternalNameKey key = result.get(row);
- final Button b = new Button(Util.C.buttonSelectGroup());
- b.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- setExternalGroup(key);
- }
- });
- externalMatches.setText(1 + row, 0, key.get());
- externalMatches.setWidget(1 + row, 1, b);
- fmt.setStyleName(1 + row, 1, Gerrit.RESOURCES.css().rightmost());
- }
- } finally {
- externalMatches.setVisible(true);
- externalNameFilter.setEnabled(true);
- externalNameSearch.setEnabled(true);
- }
- }
-
- @Override
- public void onFailure(Throwable caught) {
- externalNameFilter.setEnabled(true);
- externalNameSearch.setEnabled(true);
- super.onFailure(caught);
- }
- });
- }
-
- private void setExternalGroup(final AccountGroup.ExternalNameKey key) {
- externalMatches.setVisible(false);
-
- Util.GROUP_SVC.changeExternalGroup(getGroupId(), key,
- new GerritCallback<VoidResult>() {
- @Override
- public void onSuccess(VoidResult result) {
- externalName.setText(key.get());
- }
-
- @Override
- public void onFailure(Throwable caught) {
- externalMatches.setVisible(true);
- super.onFailure(caught);
- }
- });
- }
-
@Override
protected void display(final GroupDetail groupDetail) {
final AccountGroup group = groupDetail.group;
@@ -452,13 +319,6 @@
visibleToAllCheckBox.setValue(group.isVisibleToAll());
- switch (group.getType()) {
- case LDAP:
- externalName.setText(group.getExternalNameKey() != null ? group
- .getExternalNameKey().get() : Util.C.noGroupSelected());
- break;
- }
-
setType(group.getType());
enableForm(groupDetail.canModify);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index d049ff6..74a24ce 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -48,6 +48,8 @@
String suggestedGroupLabel();
String parentSuggestions();
+ String buttonBrowseProjects();
+ String projects();
String headingGroupUUID();
String headingOwner();
String headingDescription();
@@ -104,6 +106,12 @@
String projectAdminTabBranches();
String projectAdminTabAccess();
+ String plugins();
+ String pluginTabInstalled();
+
+ String columnPluginName();
+ String columnPluginVersion();
+
String noGroupSelected();
String errorNoMatchingGroups();
String errorNoGitRepository();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 4330513..30375a2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -17,6 +17,8 @@
buttonSaveChanges = Save Changes
checkBoxEmptyCommit = Create initial empty commit
checkBoxPermissionsOnly = Only serve as parent for other projects
+buttonBrowseProjects = Browse
+projects = All projects
useContentMerge = Automatically resolve conflicts
useContributorAgreements = Require a valid contributor agreement to upload
useSignedOffBy = Require <a href="http://gerrit.googlecode.com/svn/documentation/2.0/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
@@ -84,6 +86,11 @@
projectAdminTabBranches = Branches
projectAdminTabAccess = Access
+plugins = Plugins
+pluginTabInstalled = Installed
+columnPluginName = Plugin Name
+columnPluginVersion = Version
+
noGroupSelected = (No group selected)
errorNoMatchingGroups = No Matching Groups
errorNoGitRepository = No Git Repository
@@ -93,6 +100,7 @@
# Permission Names
permissionNames = \
+ abandon, \
create, \
forgeAuthor, \
forgeCommitter, \
@@ -104,6 +112,7 @@
read, \
rebase, \
submit
+abandon = Abandon
create = Create Reference
forgeAuthor = Forge Author Identity
forgeCommitter = Forge Committer Identity
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
index 43558be..7a67a06 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
@@ -21,9 +21,11 @@
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.ProjectListPopup;
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
import com.google.gerrit.client.ui.ProjectsTable;
import com.google.gerrit.client.ui.Screen;
+import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -41,13 +43,16 @@
import com.google.gwtjsonrpc.common.VoidResult;
public class CreateProjectScreen extends Screen {
+ private Grid grid;
private NpTextBox project;
private Button create;
+ private Button browse;
private HintTextBox parent;
private SuggestBox sugestParent;
private CheckBox emptyCommit;
private CheckBox permissionsOnly;
private ProjectsTable suggestedParentsTab;
+ private ProjectListPopup projectsPopup;
public CreateProjectScreen() {
super();
@@ -61,10 +66,28 @@
}
@Override
+ protected void onUnload() {
+ super.onUnload();
+ projectsPopup.closePopup();
+ }
+
+ @Override
protected void onInitUI() {
super.onInitUI();
setPageTitle(Util.C.createProjectTitle());
addCreateProjectPanel();
+
+ /* popup */
+ projectsPopup = new ProjectListPopup() {
+ @Override
+ protected void onMovePointerTo(String projectName) {
+ // prevent user input from being overwritten by simply poping up
+ if (!projectsPopup.isPopingUp() || "".equals(sugestParent.getText())) {
+ sugestParent.setText(projectName);
+ }
+ }
+ };
+ projectsPopup.initPopup(Util.C.projects(), PageLinks.ADMIN_PROJECTS);
}
private void addCreateProjectPanel() {
@@ -82,12 +105,11 @@
fp.add(emptyCommit);
fp.add(permissionsOnly);
fp.add(create);
- VerticalPanel vp=new VerticalPanel();
+ VerticalPanel vp = new VerticalPanel();
vp.add(fp);
initSuggestedParents();
vp.add(suggestedParentsTab);
add(vp);
-
}
private void initCreateTxt() {
@@ -111,6 +133,23 @@
doCreateProject();
}
});
+
+ browse = new Button(Util.C.buttonBrowseProjects());
+ browse.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ int top = grid.getAbsoluteTop() - 50; // under page header
+ // Try to place it to the right of everything else, but not
+ // right justified
+ int left =
+ 5 + Math.max(
+ grid.getAbsoluteLeft() + grid.getOffsetWidth(),
+ suggestedParentsTab.getAbsoluteLeft()
+ + suggestedParentsTab.getOffsetWidth());
+ projectsPopup.setPreferredCoordinates(top, left);
+ projectsPopup.displayPopup();
+ }
+ });
}
private void initParentBox() {
@@ -145,26 +184,26 @@
};
suggestedParentsTab.setVisible(false);
- ProjectMap.permissions(new GerritCallback<ProjectMap>() {
- @Override
- public void onSuccess(ProjectMap list) {
- if (!list.isEmpty()) {
- suggestedParentsTab.setVisible(true);
- suggestedParentsTab.display(list);
- suggestedParentsTab.finishDisplay();
- }
- }
- });
+ ProjectMap.parentCandidates(new GerritCallback<ProjectMap>() {
+ @Override
+ public void onSuccess(ProjectMap list) {
+ if (!list.isEmpty()) {
+ suggestedParentsTab.setVisible(true);
+ suggestedParentsTab.display(list);
+ suggestedParentsTab.finishDisplay();
+ }
+ }
+ });
}
private void addGrid(final VerticalPanel fp) {
- final Grid grid = new Grid(2, 2);
+ grid = new Grid(2, 3);
grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
grid.setText(0, 0, Util.C.columnProjectName() + ":");
grid.setWidget(0, 1, project);
grid.setText(1, 0, Util.C.headingParentProjectName() + ":");
grid.setWidget(1, 1, sugestParent);
-
+ grid.setWidget(1, 2, browse);
fp.add(grid);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
index 9da9c22..4ddc9a8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
@@ -17,6 +17,7 @@
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -140,4 +141,8 @@
public void setAccessKey(char key) {
suggestBox.setAccessKey(key);
}
+
+ public void setProject(Project.NameKey projectName) {
+ oracle.setProject(projectName);
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index d10afd1..2c43233 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -25,6 +25,7 @@
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -98,20 +99,25 @@
@UiField
DivElement deleted;
+ private final Project.NameKey projectName;
private final boolean readOnly;
private final AccessSection section;
private Permission value;
private PermissionRange.WithDefaults validRange;
private boolean isDeleted;
- public PermissionEditor(boolean readOnly, AccessSection section) {
+ public PermissionEditor(Project.NameKey projectName,
+ boolean readOnly,
+ AccessSection section) {
this.readOnly = readOnly;
this.section = section;
+ this.projectName = projectName;
normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
initWidget(uiBinder.createAndBindUi(this));
+ groupToAdd.setProject(projectName);
rules = ListEditor.of(new RuleEditorSource());
exclusiveGroup.setEnabled(!readOnly);
@@ -223,7 +229,8 @@
// If the oracle didn't get to complete a UUID, resolve it now.
//
addRule.setEnabled(false);
- SuggestUtil.SVC.suggestAccountGroup(ref.getName(), 1,
+ SuggestUtil.SVC.suggestAccountGroupForProject(
+ projectName, ref.getName(), 1,
new GerritCallback<List<GroupReference>>() {
@Override
public void onSuccess(List<GroupReference> result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
index e4cced7..5dd8b3c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
@@ -25,6 +25,7 @@
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.SpanElement;
@@ -178,16 +179,21 @@
@Override
public void setValue(PermissionRule value) {
GroupReference ref = value.getGroup();
- if (ref.getUUID() != null) {
+
+ boolean link;
+ if (ref.getUUID() != null && AccountGroup.isInternalGroup(ref.getUUID())) {
+ groupNameLink.setText(ref.getName());
groupNameLink.setTargetHistoryToken(Dispatcher.toGroup(ref.getUUID()));
+ link = true;
+ } else {
+ groupNameSpan.setInnerText(ref.getName());
+ groupNameSpan.setTitle(ref.getUUID() != null ? ref.getUUID().get() : "");
+ link = false;
}
- groupNameLink.setText(ref.getName());
- groupNameSpan.setInnerText(ref.getName());
deletedGroupName.setInnerText(ref.getName());
-
- groupNameLink.setVisible(ref.getUUID() != null);
- UIObject.setVisible(groupNameSpan, ref.getUUID() == null);
+ groupNameLink.setVisible(link);
+ UIObject.setVisible(groupNameSpan, !link);
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
new file mode 100644
index 0000000..814ae51
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.admin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.plugins.PluginInfo;
+import com.google.gerrit.client.plugins.PluginMap;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Panel;
+
+public class PluginListScreen extends PluginScreen {
+
+ private Panel pluginPanel;
+ private PluginTable pluginTable;
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ initPluginList();
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ PluginMap.all(new ScreenLoadCallback<PluginMap>(this) {
+ @Override
+ protected void preDisplay(final PluginMap result) {
+ pluginTable.display(result);
+ }
+ });
+ }
+
+ private void initPluginList() {
+ pluginTable = new PluginTable();
+ pluginTable.addStyleName(Gerrit.RESOURCES.css().pluginsTable());
+
+ pluginPanel = new FlowPanel();
+ pluginPanel.setWidth("500px");
+ pluginPanel.add(pluginTable);
+ add(pluginPanel);
+ }
+
+ private class PluginTable extends FancyFlexTable<PluginInfo> {
+ PluginTable() {
+ table.setText(0, 1, Util.C.columnPluginName());
+ table.setText(0, 2, Util.C.columnPluginVersion());
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ void display(final PluginMap plugins) {
+ while (1 < table.getRowCount()) {
+ table.removeRow(table.getRowCount() - 1);
+ }
+
+ for (final PluginInfo p : plugins.values().asList()) {
+ final int row = table.getRowCount();
+ table.insertRow(row);
+ applyDataRowStyle(row);
+ populate(row, p);
+ }
+ }
+
+ void populate(final int row, final PluginInfo plugin) {
+ table.setWidget(
+ row,
+ 1,
+ new Anchor(plugin.name(), Gerrit.selfRedirect("/plugins/"
+ + plugin.name() + "/")));
+ table.setText(row, 2, plugin.version());
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
+
+ setRowItem(row, plugin);
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
new file mode 100644
index 0000000..72cd7f9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.admin;
+
+import static com.google.gerrit.common.PageLinks.ADMIN_PLUGINS;
+
+import com.google.gerrit.client.ui.MenuScreen;
+
+public abstract class PluginScreen extends MenuScreen {
+
+ public PluginScreen() {
+ setRequiresSignIn(true);
+
+ link(Util.C.pluginTabInstalled(), ADMIN_PLUGINS);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ setPageTitle(Util.C.plugins());
+ display();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
index e3bf555..32bc469 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -120,7 +120,7 @@
history.getStyle().setDisplay(Display.NONE);
}
- addSection.setVisible(value != null && editing && !value.getOwnerOf().isEmpty());
+ addSection.setVisible(value != null && editing && (!value.getOwnerOf().isEmpty() || value.canUpload()));
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 1ed919b..4403ce6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -18,7 +18,9 @@
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
@@ -30,9 +32,15 @@
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextArea;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
public class ProjectAccessScreen extends ProjectScreen {
interface Binder extends UiBinder<HTMLPanel, ProjectAccessScreen> {
}
@@ -57,6 +65,9 @@
Button cancel2;
@UiField
+ VerticalPanel error;
+
+ @UiField
ProjectAccessEditor accessEditor;
@UiField
@@ -68,6 +79,9 @@
@UiField
Button commit;
+ @UiField
+ Button review;
+
private Driver driver;
private ProjectAccess access;
@@ -101,8 +115,8 @@
private void displayReadOnly(ProjectAccess access) {
this.access = access;
accessEditor.setEditing(false);
- UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty());
- edit.setEnabled(!access.getOwnerOf().isEmpty());
+ UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
+ edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
cancel1.setVisible(false);
UIObject.setVisible(commitTools, false);
driver.edit(access);
@@ -110,13 +124,29 @@
@UiHandler("edit")
void onEdit(ClickEvent event) {
+ resetEditors();
+
edit.setEnabled(false);
cancel1.setVisible(true);
UIObject.setVisible(commitTools, true);
+ commit.setVisible(!access.getOwnerOf().isEmpty());
+ review.setVisible(access.canUpload());
accessEditor.setEditing(true);
driver.edit(access);
}
+ private void resetEditors() {
+ // Push an empty instance through the driver before pushing the real
+ // data. This will force GWT to delete and recreate the editors, which
+ // is required to build initialize them as editable vs. read-only.
+ ProjectAccess mock = new ProjectAccess();
+ mock.setProjectName(access.getProjectName());
+ mock.setRevision(access.getRevision());
+ mock.setLocal(Collections.<AccessSection> emptyList());
+ mock.setOwnerOf(Collections.<String> emptySet());
+ driver.edit(mock);
+ }
+
@UiHandler(value={"cancel1", "cancel2"})
void onCancel(ClickEvent event) {
Gerrit.display(PageLinks.toProjectAcceess(getProjectKey()));
@@ -124,7 +154,7 @@
@UiHandler("commit")
void onCommit(ClickEvent event) {
- ProjectAccess access = driver.flush();
+ final ProjectAccess access = driver.flush();
if (driver.hasErrors()) {
Window.alert(Util.C.errorsMustBeFixed());
@@ -144,14 +174,88 @@
access.getLocal(), //
new GerritCallback<ProjectAccess>() {
@Override
- public void onSuccess(ProjectAccess access) {
+ public void onSuccess(ProjectAccess newAccess) {
enable(true);
commitMessage.setText("");
- displayReadOnly(access);
+ error.clear();
+ final Set<String> diffs = getDiffs(access, newAccess);
+ if (diffs.isEmpty()) {
+ displayReadOnly(newAccess);
+ } else {
+ error.add(new Label(Gerrit.C.projectAccessError()));
+ for (final String diff : diffs) {
+ error.add(new Label(diff));
+ }
+ if (access.canUpload()) {
+ error.add(new Label(Gerrit.C.projectAccessProposeForReviewHint()));
+ }
+ }
+ }
+
+ private Set<String> getDiffs(ProjectAccess wantedAccess,
+ ProjectAccess newAccess) {
+ final HashSet<AccessSection> same =
+ new HashSet<AccessSection>(wantedAccess.getLocal());
+ final HashSet<AccessSection> different =
+ new HashSet<AccessSection>(wantedAccess.getLocal().size()
+ + newAccess.getLocal().size());
+ different.addAll(wantedAccess.getLocal());
+ different.addAll(newAccess.getLocal());
+ same.retainAll(newAccess.getLocal());
+ different.removeAll(same);
+
+ final Set<String> differentNames = new HashSet<String>();
+ for (final AccessSection s : different) {
+ differentNames.add(s.getName());
+ }
+ return differentNames;
}
@Override
public void onFailure(Throwable caught) {
+ error.clear();
+ enable(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ @UiHandler("review")
+ void onReview(ClickEvent event) {
+ final ProjectAccess access = driver.flush();
+
+ if (driver.hasErrors()) {
+ Window.alert(Util.C.errorsMustBeFixed());
+ return;
+ }
+
+ String message = commitMessage.getText().trim();
+ if ("".equals(message)) {
+ message = null;
+ }
+
+ enable(false);
+ Util.PROJECT_SVC.reviewProjectAccess( //
+ getProjectKey(), //
+ access.getRevision(), //
+ message, //
+ access.getLocal(), //
+ new GerritCallback<Change.Id>() {
+ @Override
+ public void onSuccess(Change.Id changeId) {
+ enable(true);
+ commitMessage.setText("");
+ error.clear();
+ if (changeId != null) {
+ Gerrit.display(PageLinks.toChange(changeId));
+ } else {
+ displayReadOnly(access);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ error.clear();
enable(true);
super.onFailure(caught);
}
@@ -160,7 +264,8 @@
private void enable(boolean enabled) {
commitMessage.setEnabled(enabled);
- commit.setEnabled(enabled);
+ commit.setEnabled(enabled ? !access.getOwnerOf().isEmpty() : false);
+ review.setEnabled(enabled ? access.canUpload() : false);
cancel1.setEnabled(enabled);
cancel2.setEnabled(enabled);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
index 2536159..a664191 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
@@ -32,6 +32,11 @@
.commitMessage .gwt-TextArea {
margin: 5px 5px 5px 5px;
}
+ .errorMessage {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ color: red;
+ }
</ui:style>
<g:HTMLPanel>
@@ -58,12 +63,21 @@
spellCheck='true'
/>
</div>
+ <g:VerticalPanel
+ styleName='{style.errorMessage}'
+ ui:field='error'>
+ </g:VerticalPanel>
<g:Button
ui:field='commit'
text='Save Changes'>
<ui:attribute name='text'/>
</g:Button>
<g:Button
+ ui:field='review'
+ text='Save for Review'>
+ <ui:attribute name='text'/>
+ </g:Button>
+ <g:Button
ui:field='cancel2'
text='Cancel'>
<ui:attribute name='text'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
index ccfe2e6..dd5b070 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
@@ -16,7 +16,6 @@
import static com.google.gerrit.client.Dispatcher.toProjectAdmin;
-import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.MenuScreen;
import com.google.gerrit.reviewdb.client.Project;
@@ -30,12 +29,8 @@
public ProjectScreen(final Project.NameKey toShow) {
name = toShow;
- final boolean isWild = toShow.equals(Gerrit.getConfig().getWildProject());
-
link(Util.C.projectAdminTabGeneral(), toProjectAdmin(name, INFO));
- if (!isWild) {
- link(Util.C.projectAdminTabBranches(), toProjectAdmin(name, BRANCH));
- }
+ link(Util.C.projectAdminTabBranches(), toProjectAdmin(name, BRANCH));
link(Util.C.projectAdminTabAccess(), toProjectAdmin(name, ACCESS));
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
new file mode 100644
index 0000000..3dd54a7
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.auth.openid;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gerrit.common.auth.SignInMode;
+import com.google.gerrit.common.auth.openid.DiscoveryResult;
+import com.google.gerrit.common.auth.openid.OpenIdUrls;
+import com.google.gwt.dom.client.FormElement;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Hidden;
+
+import java.util.Map;
+
+public class OpenIdSsoPanel extends FlowPanel {
+ private final FormPanel redirectForm;
+ private final FlowPanel redirectBody;
+ private final String ssoUrl;
+
+ public OpenIdSsoPanel() {
+ super();
+ redirectBody = new FlowPanel();
+ redirectBody.setVisible(false);
+ redirectForm = new FormPanel();
+ redirectForm.add(redirectBody);
+
+ add(redirectForm);
+
+ ssoUrl = Gerrit.getConfig().getOpenIdSsoUrl();
+ }
+
+ public void authenticate(SignInMode requestedMode, final String token) {
+ OpenIdUtil.SVC.discover(ssoUrl, requestedMode, /* remember */ false, token,
+ new GerritCallback<DiscoveryResult>() {
+ public void onSuccess(final DiscoveryResult result) {
+ onDiscovery(result);
+ }
+ });
+ }
+
+ private void onDiscovery(final DiscoveryResult result) {
+ switch (result.status) {
+ case VALID:
+ redirectForm.setMethod(FormPanel.METHOD_POST);
+ redirectForm.setAction(result.providerUrl);
+ redirectBody.clear();
+ for (final Map.Entry<String, String> e : result.providerArgs.entrySet()) {
+ redirectBody.add(new Hidden(e.getKey(), e.getValue()));
+ }
+ FormElement.as(redirectForm.getElement()).setTarget("_top");
+ redirectForm.submit();
+ break;
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index dead713..05dd5d9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -49,6 +49,7 @@
outgoing.setTitleText(Util.C.outgoingReviews());
incoming.setTitleText(Util.C.incomingReviews());
+ incoming.setHighlightUnreviewed(true);
closed.setTitleText(Util.C.recentlyClosed());
table.addSection(outgoing);
@@ -111,8 +112,7 @@
}
}
- Collections.sort(out.asList(), compare());
- Collections.sort(in.asList(), compare());
+ Collections.sort(out.asList(), outComparator());
table.updateColumnsForLabels(out, in, done);
outgoing.display(out);
@@ -121,18 +121,11 @@
table.finishDisplay();
}
- private Comparator<ChangeInfo> compare() {
+ private Comparator<ChangeInfo> outComparator() {
return new Comparator<ChangeInfo>() {
@Override
public int compare(ChangeInfo a, ChangeInfo b) {
- int cmp = a.project().compareTo(b.project());
- if (cmp != 0) return cmp;
- cmp = a.branch().compareTo(b.branch());
- if (cmp != 0) return cmp;
-
- String at = a.topic() != null ? a.topic() : "";
- String bt = b.topic() != null ? b.topic() : "";
- cmp = at.compareTo(bt);
+ int cmp = a.created().compareTo(b.created());
if (cmp != 0) return cmp;
return a._number() - b._number();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index c0c9ce8..73036ff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -29,6 +29,7 @@
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.common.data.ReviewerResult;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
@@ -133,12 +134,22 @@
return AccountLink.link(accountCache, id);
}
+ void display(PatchSetPublishDetail detail) {
+ doDisplay(detail.getChange(), detail.getApprovals(),
+ detail.getSubmitRecords());
+ }
+
void display(ChangeDetail detail) {
- changeId = detail.getChange().getId();
+ doDisplay(detail.getChange(), detail.getApprovals(),
+ detail.getSubmitRecords());
+ }
+
+ private void doDisplay(Change change, List<ApprovalDetail> approvals,
+ List<SubmitRecord> submitRecords) {
+ changeId = change.getId();
reviewerSuggestOracle.setChange(changeId);
List<String> columns = new ArrayList<String>();
- List<ApprovalDetail> rows = detail.getApprovals();
final Element missingList = missing.getElement();
while (DOM.getChildCount(missingList) > 0) {
@@ -146,16 +157,16 @@
}
missing.setVisible(false);
- if (detail.getSubmitRecords() != null) {
+ if (submitRecords != null) {
HashSet<String> reportedMissing = new HashSet<String>();
HashMap<Account.Id, ApprovalDetail> byUser =
new HashMap<Account.Id, ApprovalDetail>();
- for (ApprovalDetail ad : detail.getApprovals()) {
+ for (ApprovalDetail ad : approvals) {
byUser.put(ad.getAccount(), ad);
}
- for (SubmitRecord rec : detail.getSubmitRecords()) {
+ for (SubmitRecord rec : submitRecords) {
if (rec.labels == null) {
continue;
}
@@ -182,6 +193,9 @@
break;
}
+ case MAY:
+ break;
+
case NEED:
case IMPOSSIBLE:
if (reportedMissing.add(lbl.label)) {
@@ -197,17 +211,19 @@
missing.setVisible(!reportedMissing.isEmpty());
} else {
- for (ApprovalDetail ad : rows) {
+ for (ApprovalDetail ad : approvals) {
for (PatchSetApproval psa : ad.getPatchSetApprovals()) {
ApprovalType legacyType = types.byId(psa.getCategoryId());
if (legacyType == null) {
continue;
}
String labelName = legacyType.getCategory().getLabelName();
- if (psa.getValue() == legacyType.getMax().getValue()) {
- ad.approved(labelName);
- } else if (psa.getValue() == legacyType.getMin().getValue()) {
- ad.rejected(labelName);
+ if (psa.getValue() != 0 ) {
+ if (psa.getValue() == legacyType.getMax().getValue()) {
+ ad.approved(labelName);
+ } else if (psa.getValue() == legacyType.getMin().getValue()) {
+ ad.rejected(labelName);
+ }
}
if (!columns.contains(labelName)) {
columns.add(labelName);
@@ -231,13 +247,13 @@
}
}
- if (rows.isEmpty()) {
+ if (approvals.isEmpty()) {
table.setVisible(false);
} else {
displayHeader(columns);
- table.resizeRows(1 + rows.size());
- for (int i = 0; i < rows.size(); i++) {
- displayRow(i + 1, rows.get(i), detail.getChange(), columns);
+ table.resizeRows(1 + approvals.size());
+ for (int i = 0; i < approvals.size(); i++) {
+ displayRow(i + 1, approvals.get(i), change, columns);
}
table.setVisible(true);
}
@@ -245,7 +261,7 @@
addReviewer.setVisible(Gerrit.isSignedIn());
if (Gerrit.getConfig().testChangeMerge()
- && !detail.getChange().isMergeable()) {
+ && !change.isMergeable()) {
Element li = DOM.createElement("li");
li.setClassName(Gerrit.RESOURCES.css().missingApproval());
DOM.setInnerText(li, Util.C.messageNeedsRebaseOrHasDependency());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
index 9282709..c8b2a66 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
@@ -19,14 +19,15 @@
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
public class ChangeDescriptionBlock extends Composite {
private final ChangeInfoBlock infoBlock;
private final CommitMessageBlock messageBlock;
- public ChangeDescriptionBlock() {
+ public ChangeDescriptionBlock(KeyCommandSet keysAction) {
infoBlock = new ChangeInfoBlock();
- messageBlock = new CommitMessageBlock();
+ messageBlock = new CommitMessageBlock(keysAction);
final HorizontalPanel hp = new HorizontalPanel();
hp.add(infoBlock);
@@ -34,9 +35,9 @@
initWidget(hp);
}
- public void display(final Change chg, final PatchSetInfo info,
+ public void display(Change chg, Boolean starred, PatchSetInfo info,
final AccountInfoCache acc) {
infoBlock.display(chg, acc);
- messageBlock.display(info.getMessage());
+ messageBlock.display(chg.getId(), starred, info.getMessage());
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 06c8e61..0c8e03e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -33,6 +33,18 @@
return new Change.Id(_number());
}
+ public final Timestamp created() {
+ Timestamp ts = _get_cts();
+ if (ts == null) {
+ ts = JavaSqlTimestamp_JsonSerializer.parseTimestamp(createdRaw());
+ _set_cts(ts);
+ }
+ return ts;
+ }
+
+ private final native Timestamp _get_cts() /*-{ return this._cts; }-*/;
+ private final native void _set_cts(Timestamp ts) /*-{ this._cts = ts; }-*/;
+
public final Timestamp updated() {
return JavaSqlTimestamp_JsonSerializer.parseTimestamp(updatedRaw());
}
@@ -56,8 +68,10 @@
private final native String statusRaw() /*-{ return this.status; }-*/;
public final native String subject() /*-{ return this.subject; }-*/;
public final native AccountInfo owner() /*-{ return this.owner; }-*/;
+ private final native String createdRaw() /*-{ return this.created; }-*/;
private final native String updatedRaw() /*-{ return this.updated; }-*/;
public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
+ public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
public final native String _sortkey() /*-{ return this._sortkey; }-*/;
private final native JavaScriptObject labels0() /*-{ return this.labels; }-*/;
public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
@@ -81,6 +95,8 @@
return SubmitRecord.Label.Status.OK;
} else if (rejected() != null) {
return SubmitRecord.Label.Status.REJECT;
+ } else if (optional()) {
+ return SubmitRecord.Label.Status.MAY;
} else {
return SubmitRecord.Label.Status.NEED;
}
@@ -92,6 +108,7 @@
public final native AccountInfo recommended() /*-{ return this.recommended; }-*/;
public final native AccountInfo disliked() /*-{ return this.disliked; }-*/;
+ public final native boolean optional() /*-{ return this.optional ? true : false; }-*/;
final native short _value()
/*-{
if (this.value) return this.value;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
index 865e389..3ffacc3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
@@ -19,13 +19,11 @@
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.AccountLink;
import com.google.gerrit.client.ui.BranchLink;
-import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.ProjectLink;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
@@ -40,18 +38,15 @@
private static final int R_UPDATED = 6;
private static final int R_STATUS = 7;
private static final int R_MERGE_TEST = 8;
- private final int R_PERMALINK;
- private static final int R_CNT = 10;
+ private static final int R_CNT = 9;
private final Grid table;
public ChangeInfoBlock() {
if (Gerrit.getConfig().testChangeMerge()) {
table = new Grid(R_CNT, 2);
- R_PERMALINK = 9;
} else {
table = new Grid(R_CNT - 1, 2);
- R_PERMALINK = 8;
}
table.setStyleName(Gerrit.RESOURCES.css().infoBlock());
table.addStyleName(Gerrit.RESOURCES.css().changeInfoBlock());
@@ -73,8 +68,6 @@
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(R_CHANGE_ID, 1, Gerrit.RESOURCES.css().changeid());
fmt.addStyleName(R_CNT - 2, 0, Gerrit.RESOURCES.css().bottomheader());
- fmt.addStyleName(R_PERMALINK, 0, Gerrit.RESOURCES.css().permalink());
- fmt.addStyleName(R_PERMALINK, 1, Gerrit.RESOURCES.css().permalink());
initWidget(table);
}
@@ -101,20 +94,21 @@
table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
+ final Change.Status status = chg.getStatus();
if (Gerrit.getConfig().testChangeMerge()) {
- table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
- .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
+ if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
+ table.getRowFormatter().setVisible(R_MERGE_TEST, true);
+ table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
+ .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
+ } else {
+ table.getRowFormatter().setVisible(R_MERGE_TEST, false);
+ }
}
- if (chg.getStatus().isClosed()) {
+ if (status.isClosed()) {
table.getCellFormatter().addStyleName(R_STATUS, 1, Gerrit.RESOURCES.css().closedstate());
} else {
table.getCellFormatter().removeStyleName(R_STATUS, 1, Gerrit.RESOURCES.css().closedstate());
}
-
- final FlowPanel fp = new FlowPanel();
- fp.add(new ChangeLink(Util.C.changePermalink(), chg.getId()));
- fp.add(new CopyableLabel(ChangeLink.permalink(chg.getId()), false));
- table.setWidget(R_PERMALINK, 1, fp);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index 15c1150..234f937 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -153,6 +153,7 @@
detailCache.addValueChangeHandler(this);
addStyleName(Gerrit.RESOURCES.css().changeScreen());
+ addStyleName(Gerrit.RESOURCES.css().screenNoHeader());
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
@@ -160,16 +161,11 @@
keysNavigation.add(new ExpandCollapseDependencySectionKeyCommand(0, 'd', Util.C.expandCollapseDependencies()));
if (Gerrit.isSignedIn()) {
- StarredChanges.Icon star = StarredChanges.createIcon(changeId, false);
- star.setStyleName(Gerrit.RESOURCES.css().changeScreenStarIcon());
- setTitleWest(star);
-
- keysAction.add(StarredChanges.newKeyCommand(star));
keysAction.add(new PublishCommentsKeyCommand(0, 'r', Util.C
.keyPublishComments()));
}
- descriptionBlock = new ChangeDescriptionBlock();
+ descriptionBlock = new ChangeDescriptionBlock(keysAction);
add(descriptionBlock);
approvals = new ApprovalTable();
@@ -243,6 +239,7 @@
}
}
setPageTitle(titleBuf.toString());
+ setHeaderVisible(false);
}
@Override
@@ -265,8 +262,10 @@
dependencies.setAccountInfoCache(detail.getAccounts());
approvals.setAccountInfoCache(detail.getAccounts());
- descriptionBlock.display(detail.getChange(), detail
- .getCurrentPatchSetDetail().getInfo(), detail.getAccounts());
+ descriptionBlock.display(detail.getChange(),
+ detail.isStarred(),
+ detail.getCurrentPatchSetDetail().getInfo(),
+ detail.getAccounts());
dependsOn.display(detail.getDependsOn());
neededBy.display(detail.getNeededBy());
approvals.display(detail);
@@ -291,18 +290,28 @@
addComments(detail);
// If any dependency change is still open, or is outdated,
+ // or the change is needed by a change that is new or submitted,
// show our dependency list.
//
boolean depsOpen = false;
int outdated = 0;
- if (!detail.getChange().getStatus().isClosed()
- && detail.getDependsOn() != null) {
- for (final ChangeInfo ci : detail.getDependsOn()) {
- if (! ci.isLatest()) {
- depsOpen = true;
- outdated++;
- } else if (ci.getStatus() != Change.Status.MERGED) {
- depsOpen = true;
+ if (!detail.getChange().getStatus().isClosed()) {
+ if (detail.getDependsOn() != null) {
+ for (final ChangeInfo ci : detail.getDependsOn()) {
+ if (!ci.isLatest()) {
+ depsOpen = true;
+ outdated++;
+ } else if (ci.getStatus() != Change.Status.MERGED) {
+ depsOpen = true;
+ }
+ }
+ }
+ if (detail.getNeededBy() != null) {
+ for (final ChangeInfo ci : detail.getNeededBy()) {
+ if ((ci.getStatus() == Change.Status.NEW) ||
+ (ci.getStatus() == Change.Status.SUBMITTED)) {
+ depsOpen = true;
+ }
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 44a49a8..97bb4ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -202,10 +202,7 @@
}
table.setWidget(row, C_ID, new TableChangeLink(idstr, c));
- String s = c.getSubject();
- if (s.length() > 80) {
- s = s.substring(0, 80);
- }
+ String s = Util.cropSubject(c.getSubject());
if (c.getStatus() != null && c.getStatus() != Change.Status.NEW) {
s += " (" + c.getStatus().name() + ")";
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index 1b9db39..0dd0b0f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -26,16 +26,19 @@
import com.google.gerrit.client.ui.ProjectLink;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.UIObject;
import java.util.ArrayList;
import java.util.Collections;
@@ -188,7 +191,8 @@
}
}
- private void populateChangeRow(final int row, final ChangeInfo c) {
+ private void populateChangeRow(final int row, final ChangeInfo c,
+ boolean highlightUnreviewed) {
if (Gerrit.isSignedIn()) {
table.setWidget(row, C_STAR, StarredChanges.createIcon(
c.legacy_id(),
@@ -196,10 +200,7 @@
}
table.setWidget(row, C_ID, new TableChangeLink(c.id_abbreviated(), c));
- String subject = c.subject();
- if (subject.length() > 80) {
- subject = subject.substring(0, 80);
- }
+ String subject = Util.cropSubject(c.subject());
Change.Status status = c.status();
if (status != Change.Status.NEW) {
subject += " (" + Util.toLongString(status) + ")";
@@ -284,10 +285,13 @@
}
}
- // TODO(sop): Highlight changes I haven't reviewed on my dashboard.
- // final Element tr = DOM.getParent(fmt.getElement(row, 0));
- // UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
- // !haveReview && highlightUnreviewed);
+ boolean needHighlight = false;
+ if (highlightUnreviewed && !c.reviewed()) {
+ needHighlight = true;
+ }
+ final Element tr = DOM.getParent(fmt.getElement(row, 0));
+ UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
+ needHighlight);
setRowItem(row, c);
}
@@ -368,6 +372,11 @@
int titleRow = -1;
int dataBegin;
int rows;
+ private boolean highlightUnreviewed;
+
+ public void setHighlightUnreviewed(boolean value) {
+ this.highlightUnreviewed = value;
+ }
public void setTitleText(final String text) {
titleText = text;
@@ -399,7 +408,8 @@
rows++;
}
for (int i = 0; i < sz; i++) {
- parent.populateChangeRow(dataBegin + i, changeList.get(i));
+ parent.populateChangeRow(dataBegin + i, changeList.get(i),
+ highlightUnreviewed);
}
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
index 1a6ea3e..6a364e0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
@@ -15,28 +15,96 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.ui.ChangeLink;
import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.PreElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
public class CommitMessageBlock extends Composite {
- private final HTML description;
+ interface Binder extends UiBinder<HTMLPanel, CommitMessageBlock> {
+ }
+
+ private static Binder uiBinder = GWT.create(Binder.class);
+
+ private KeyCommandSet keysAction;
+
+ @UiField
+ SimplePanel starPanel;
+ @UiField
+ FlowPanel permalinkPanel;
+ @UiField
+ PreElement commitSummaryPre;
+ @UiField
+ PreElement commitBodyPre;
public CommitMessageBlock() {
- description = new HTML();
- description.setStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
- initWidget(description);
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ public CommitMessageBlock(KeyCommandSet keysAction) {
+ this.keysAction = keysAction;
+ initWidget(uiBinder.createAndBindUi(this));
}
public void display(final String commitMessage) {
- SafeHtml msg = new SafeHtmlBuilder().append(commitMessage);
- msg = msg.linkify();
- msg = CommentLinkProcessor.apply(msg);
- msg = new SafeHtmlBuilder().openElement("p").append(msg).closeElement("p");
- msg = msg.replaceAll("\n\n", "</p><p>");
- msg = msg.replaceAll("\n", "<br />");
- SafeHtml.set(description, msg);
+ display(null, null, commitMessage);
+ }
+
+ public void display(Change.Id changeId, Boolean starred, String commitMessage) {
+ starPanel.clear();
+
+ if (changeId != null && starred != null && Gerrit.isSignedIn()) {
+ StarredChanges.Icon star = StarredChanges.createIcon(changeId, starred);
+ star.setStyleName(Gerrit.RESOURCES.css().changeScreenStarIcon());
+ starPanel.add(star);
+
+ if (keysAction != null) {
+ keysAction.add(StarredChanges.newKeyCommand(star));
+ }
+ }
+
+ permalinkPanel.clear();
+ if (changeId != null) {
+ permalinkPanel.add(new ChangeLink(Util.C.changePermalink(), changeId));
+ permalinkPanel.add(new CopyableLabel(ChangeLink.permalink(changeId), false));
+ }
+
+ String[] splitCommitMessage = commitMessage.split("\n", 2);
+
+ String commitSummary = splitCommitMessage[0];
+ String commitBody = "";
+ if (splitCommitMessage.length > 1) {
+ commitBody = splitCommitMessage[1];
+ }
+
+ // Linkify commit summary
+ SafeHtml commitSummaryLinkified = new SafeHtmlBuilder().append(commitSummary);
+ commitSummaryLinkified = commitSummaryLinkified.linkify();
+ commitSummaryLinkified = CommentLinkProcessor.apply(commitSummaryLinkified);
+ commitSummaryPre.setInnerHTML(commitSummaryLinkified.asString());
+
+ // Hide commit body if there is no body
+ if (commitBody.trim().isEmpty()) {
+ commitBodyPre.getStyle().setDisplay(Display.NONE);
+ } else {
+ // Linkify commit body
+ SafeHtml commitBodyLinkified = new SafeHtmlBuilder().append(commitBody);
+ commitBodyLinkified = commitBodyLinkified.linkify();
+ commitBodyLinkified = CommentLinkProcessor.apply(commitBodyLinkified);
+ commitBodyPre.setInnerHTML(commitBodyLinkified.asString());
+ }
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
new file mode 100644
index 0000000..16d1da5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+
+ <ui:with field='res' type='com.google.gerrit.client.GerritResources'/>
+ <ui:style>
+ @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+ @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+ @eval backgroundColor com.google.gerrit.client.Gerrit.getTheme().backgroundColor;
+
+ .commitMessageTable {
+ border-collapse: separate;
+ border-spacing: 0;
+ margin-bottom: 10px;
+ }
+
+ .header {
+ background-color: trimColor;
+ white-space: nowrap;
+ color: textColor;
+ font-size: 10pt;
+ font-style: italic;
+ padding: 2px 6px 1px;
+ }
+
+ .contents {
+ border-bottom: 1px solid trimColor;
+ border-left: 1px solid trimColor;
+ border-right: 1px solid trimColor;
+ padding: 5px;
+ }
+
+ .contents span {
+ font-weight: bold;
+ }
+
+ .contents pre {
+ margin: 0;
+ }
+
+ .commitSummary {
+ font-weight: bold;
+ }
+
+ .commitBody {
+ margin-top: 10px;
+ }
+
+ .starPanel {
+ float: left;
+ }
+
+ .boxTitle {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .permalinkPanel {
+ float: right;
+ }
+
+ .permalinkPanel a {
+ float: left;
+ }
+
+ .permalinkPanel div {
+ display: inline;
+ }
+ </ui:style>
+
+ <g:HTMLPanel>
+ <table class='{style.commitMessageTable}'>
+ <tr><td class='{style.header}'>
+ <g:SimplePanel styleName='{style.starPanel}' ui:field='starPanel'></g:SimplePanel>
+ <div class='{style.boxTitle}'>Commit Message</div>
+ <g:FlowPanel styleName='{style.permalinkPanel}' ui:field='permalinkPanel'></g:FlowPanel>
+ </td></tr>
+ <tr><td class='{style.contents}'>
+ <pre class='{style.commitSummary} {res.css.changeScreenDescription}' ui:field='commitSummaryPre'/>
+ <pre class='{style.commitBody} {res.css.changeScreenDescription}' ui:field='commitBodyPre'/>
+ </td></tr>
+ </table>
+ </g:HTMLPanel>
+</ui:UiBinder>
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 9b53c73..ca8aedf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -27,7 +27,6 @@
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AuthType;
@@ -425,7 +424,8 @@
parentsTable.setWidget(row, 0, new InlineLabel(parent.id.get()));
ptfmt.addStyleName(row, 0, Gerrit.RESOURCES.css().noborder());
ptfmt.addStyleName(row, 0, Gerrit.RESOURCES.css().monospace());
- parentsTable.setWidget(row, 1, new InlineLabel(parent.shortMessage));
+ parentsTable.setWidget(row, 1,
+ new InlineLabel(Util.cropSubject(parent.shortMessage)));
ptfmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
row++;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 0c08491..4705aad 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -64,6 +64,7 @@
private final PatchSet.Id patchSetId;
private Collection<ValueRadioButton> approvalButtons;
private ChangeDescriptionBlock descBlock;
+ private ApprovalTable approvals;
private Panel approvalPanel;
private NpTextArea message;
private FlowPanel draftsPanel;
@@ -83,9 +84,12 @@
addStyleName(Gerrit.RESOURCES.css().publishCommentsScreen());
approvalButtons = new ArrayList<ValueRadioButton>();
- descBlock = new ChangeDescriptionBlock();
+ descBlock = new ChangeDescriptionBlock(null);
add(descBlock);
+ approvals = new ApprovalTable();
+ add(approvals);
+
final FormPanel form = new FormPanel();
final FlowPanel body = new FlowPanel();
form.setWidget(body);
@@ -270,10 +274,15 @@
private void display(final PatchSetPublishDetail r) {
setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
patchSetId.get()));
- descBlock.display(r.getChange(), r.getPatchSetInfo(), r.getAccounts());
+ descBlock.display(r.getChange(), null, r.getPatchSetInfo(), r.getAccounts());
if (r.getChange().getStatus().isOpen()) {
initApprovals(r, approvalPanel);
+
+ approvals.setAccountInfoCache(r.getAccounts());
+ approvals.display(r);
+ } else {
+ approvals.setVisible(false);
}
if (lastState != null && patchSetId.equals(lastState.patchSetId)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index e84cac8..590ad87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -30,6 +30,10 @@
public static final ChangeListService LIST_SVC;
public static final ChangeManageService MANAGE_SVC;
+ private static final int SUBJECT_MAX_LENGTH = 80;
+ private static final String SUBJECT_CROP_APPENDIX = "...";
+ private static final int SUBJECT_CROP_RANGE = 10;
+
static {
DETAIL_SVC = GWT.create(ChangeDetailService.class);
JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
@@ -60,4 +64,40 @@
return status.name();
}
}
+
+ /**
+ * Crops the given change subject if needed so that it has at most
+ * {@link #SUBJECT_MAX_LENGTH} characters.
+ *
+ * If the given subject is not longer than {@link #SUBJECT_MAX_LENGTH}
+ * characters it is returned unchanged.
+ *
+ * If the length of the given subject exceeds {@link #SUBJECT_MAX_LENGTH}
+ * characters it is cropped. In this case {@link #SUBJECT_CROP_APPENDIX} is
+ * appended to the cropped subject, the cropped subject including the appendix
+ * has at most {@link #SUBJECT_MAX_LENGTH} characters.
+ *
+ * If cropping is needed, the subject will be cropped after the last space
+ * character that is found within the last {@link #SUBJECT_CROP_RANGE}
+ * characters of the potentially visible characters. If no such space is
+ * found, the subject will be cropped so that the cropped subject including
+ * the appendix has exactly {@link #SUBJECT_MAX_LENGTH} characters.
+ *
+ * @return the subject, cropped if needed
+ */
+ @SuppressWarnings("deprecation")
+ public static String cropSubject(final String subject) {
+ if (subject.length() > SUBJECT_MAX_LENGTH) {
+ final int maxLength = SUBJECT_MAX_LENGTH - SUBJECT_CROP_APPENDIX.length();
+ for (int cropPosition = maxLength; cropPosition > maxLength - SUBJECT_CROP_RANGE; cropPosition--) {
+ // Character.isWhitespace(char) can't be used because this method is not supported by GWT,
+ // see https://developers.google.com/web-toolkit/doc/1.6/RefJreEmulation#Package_java_lang
+ if (Character.isSpace(subject.charAt(cropPosition - 1))) {
+ return subject.substring(0, cropPosition) + SUBJECT_CROP_APPENDIX;
+ }
+ }
+ return subject.substring(0, maxLength) + SUBJECT_CROP_APPENDIX;
+ }
+ return subject;
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index f588044..446b71d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -43,7 +43,9 @@
@eval textColor com.google.gerrit.client.Gerrit.getTheme().textColor;
@eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
@eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
-
+@eval changeTableOutdatedColor com.google.gerrit.client.Gerrit.getTheme().changeTableOutdatedColor;
+@eval tableOddRowColor com.google.gerrit.client.Gerrit.getTheme().tableOddRowColor;
+@eval tableEvenRowColor com.google.gerrit.client.Gerrit.getTheme().tableEvenRowColor;
@sprite .greenCheckClass {
gwt-image: "greenCheck";
@@ -401,6 +403,9 @@
overflow: hidden;
}
+.screenNoHeader {
+ margin-top: 5px;
+}
/** ChangeTable **/
.changeTable {
@@ -408,8 +413,16 @@
border-spacing: 0;
}
+.changeTable tr:nth-child\(even\) {
+ background: tableEvenRowColor;
+}
+
+.changeTable tr:nth-child\(odd\) {
+ background: tableOddRowColor;
+}
+
.changeTable .outdated {
- background: red;
+ background: changeTableOutdatedColor;
}
.changeTable .iconCell {
@@ -479,7 +492,6 @@
.accountDashboard.changeTable tr {
color: #444444;
- background: #F6F6F6;
}
.accountDashboard.changeTable tr a {
color: #444444;
@@ -489,13 +501,12 @@
.accountDashboard.changeTable .needsReview a {
font-weight: bold;
color: textColor;
- background: backgroundColor;
}
.changeTable .activeRow,
.accountDashboard.changeTable .activeRow,
.accountDashboard.changeTable .activeRow a {
- background: selectionColor;
+ background: selectionColor !important;
}
.changeTable .cID {
@@ -911,15 +922,6 @@
font-weight: bold;
}
-.infoBlock td.permalink {
- border-right: 1px none;
- border-bottom: 1px none;
- text-align: right;
-}
-.infoBlock td.permalink div div {
- display: inline;
-}
-
.infoBlock td.useridentity {
white-space: nowrap;
}
@@ -952,7 +954,7 @@
margin-top: 5px;
}
-.changeScreen .approvalTable {
+.approvalTable {
margin-top: 1em;
margin-bottom: 1em;
}
@@ -1048,6 +1050,10 @@
display: table;
}
+.sideBySideScreenSideBySideTable .fileLine {
+ width: 50%;
+}
+
.sideBySideScreenLinkTable {
width: 100%;
}
@@ -1386,3 +1392,7 @@
font-style: italic;
padding: 2px 6px 1px;
}
+
+/** PluginListScreen **/
+.pluginsTable {
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index e2cff92..70dcf75 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -31,6 +31,7 @@
String patchHeaderPatchSet();
String patchHeaderOld();
String patchHeaderNew();
+ String patchSet();
String patchHistoryTitle();
String disabledOnLargeFiles();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 2278e5a..694ccb4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -15,6 +15,7 @@
patchHeaderOld = Old Version
patchHeaderNew = New Version
patchHistoryTitle = Patch History
+patchSet = Patch Set
disabledOnLargeFiles = Disabled on very large source files.
intralineFailure = Intraline difference not available due to server error.
illegalNumberOfColumns = The number of columns cannot be zero or negative
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 3f75915..05d471b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -115,6 +115,7 @@
private HistoryTable historyTable;
private FlowPanel topPanel;
private FlowPanel contentPanel;
+ private PatchTableHeader header;
private Label noDifference;
private AbstractPatchContentTable contentTable;
private CommitMessageBlock commitMessageBlock;
@@ -317,6 +318,8 @@
topPanel = new FlowPanel();
add(topPanel);
+ header = new PatchTableHeader(getPatchScreenType());
+
noDifference = new Label(PatchUtil.C.noDifference());
noDifference.setStyleName(Gerrit.RESOURCES.css().patchNoDifference());
noDifference.setVisible(false);
@@ -331,6 +334,7 @@
contentPanel = new FlowPanel();
contentPanel.setStyleName(Gerrit.RESOURCES.css()
.sideBySideScreenSideBySideTable());
+ contentPanel.add(header);
contentPanel.add(noDifference);
contentPanel.add(contentTable);
add(contentPanel);
@@ -364,6 +368,7 @@
@Override
protected void onLoad() {
super.onLoad();
+
if (patchSetDetail == null) {
Util.DETAIL_SVC.patchSetDetail(idSideB,
new GerritCallback<PatchSetDetail>() {
@@ -435,6 +440,9 @@
final int rpcseq = ++rpcSequence;
lastScript = null;
settingsPanel.setEnabled(false);
+ if (isFirst && fileList != null) {
+ fileList.movePointerTo(patchKey);
+ }
PatchUtil.DETAIL_SVC.patchScript(patchKey, idSideA, idSideB, //
settingsPanel.getValue(), new ScreenLoadCallback<PatchScript>(this) {
@Override
@@ -502,6 +510,8 @@
setToken(Dispatcher.toPatchUnified(idSideA, patchKey));
}
+ header.display(patchSetDetail, script, patchKey, idSideA, idSideB);
+
if (hasDifferences) {
contentTable.display(patchKey, idSideA, idSideB, script);
contentTable.display(script.getCommentDetail(), script.isExpandAllComments());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
new file mode 100644
index 0000000..5dd4e1f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
@@ -0,0 +1,169 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.patches;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.common.data.PatchSetDetail;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwtorm.client.KeyUtil;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class PatchSetSelectBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, PatchSetSelectBox> {
+ }
+
+ private static Binder uiBinder = GWT.create(Binder.class);
+
+ interface BoxStyle extends CssResource {
+ String selected();
+
+ String hidden();
+ }
+
+ public enum Side {
+ A, B
+ }
+
+ PatchScript script;
+ Patch.Key patchKey;
+ PatchSet.Id idSideA;
+ PatchSet.Id idSideB;
+ PatchSet.Id idActive;
+ Side side;
+ PatchScreen.Type screenType;
+ List<Anchor> links;
+
+ @UiField
+ HTMLPanel linkPanel;
+
+ @UiField
+ BoxStyle style;
+
+ @UiField
+ SpanElement sideMarker;
+
+ public PatchSetSelectBox(Side side, final PatchScreen.Type type) {
+ this.side = side;
+ this.screenType = type;
+
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ public void display(final PatchSetDetail detail, final PatchScript script, Patch.Key key,
+ PatchSet.Id idSideA, PatchSet.Id idSideB) {
+ this.script = script;
+ this.patchKey = key;
+ this.idSideA = idSideA;
+ this.idSideB = idSideB;
+ this.idActive = (side == Side.A) ? idSideA : idSideB;
+ this.links = new LinkedList<Anchor>();
+
+ if (screenType == PatchScreen.Type.UNIFIED) {
+ sideMarker.setInnerText((side == Side.A) ? "(-)" : "(+)");
+ }
+
+ if (detail.getInfo().getParents().size() > 1) {
+ addLink(PatchUtil.C.patchBaseAutoMerge(), null);
+ } else {
+ addLink(PatchUtil.C.patchBase(), null);
+ }
+
+ if (side == Side.B) {
+ links.get(0).setStyleName(style.hidden());
+ }
+
+ for (Patch patch : script.getHistory()) {
+ PatchSet.Id psId = patch.getKey().getParentKey();
+ addLink(Integer.toString(psId.get()), psId);
+ }
+
+ if (idActive == null && side == Side.A) {
+ links.get(0).setStyleName(style.selected());
+ } else {
+ links.get(idActive.get()).setStyleName(style.selected());
+ }
+
+ Anchor downloadLink = getDownloadLink();
+ if (downloadLink != null) {
+ linkPanel.add(new Label(" - "));
+ linkPanel.add(downloadLink);
+ }
+ }
+
+ private void addLink(String label, final PatchSet.Id id) {
+ final Anchor anchor = new Anchor(label);
+ anchor.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (side == Side.A) {
+ idSideA = id;
+ } else {
+ idSideB = id;
+ }
+
+ Patch.Key keySideB = new Patch.Key(idSideB, patchKey.get());
+
+ switch (screenType) {
+ case SIDE_BY_SIDE:
+ Gerrit.display(Dispatcher.toPatchSideBySide(idSideA, keySideB));
+ break;
+ case UNIFIED:
+ Gerrit.display(Dispatcher.toPatchUnified(idSideA, keySideB));
+ break;
+ }
+ }
+
+ });
+
+ links.add(anchor);
+ linkPanel.add(anchor);
+ }
+
+ private Anchor getDownloadLink() {
+ boolean isCommitMessage = Patch.COMMIT_MSG.equals(script.getNewName());
+
+ if (isCommitMessage || (side == Side.A && 0 >= script.getA().size())
+ || (side == Side.B && 0 >= script.getB().size())) {
+ return null;
+ }
+
+ Patch.Key key =
+ (idSideA == null) ? patchKey : (new Patch.Key(idSideA, patchKey.get()));
+
+ String sideURL = (side == Side.A) ? "1" : "0";
+ final String base = GWT.getHostPageBaseURL() + "cat/";
+
+ final Anchor anchor = new Anchor(PatchUtil.C.download());
+ anchor.setHref(base + KeyUtil.encode(key.toString()) + "^" + sideURL);
+
+ return anchor;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml
new file mode 100644
index 0000000..2c4bd5d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+
+ <ui:with field='res' type='com.google.gerrit.client.GerritResources'/>
+ <ui:with field='cons' type='com.google.gerrit.client.patches.PatchConstants'/>
+ <ui:style type='com.google.gerrit.client.patches.PatchSetSelectBox.BoxStyle'>
+ @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+ @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+ @eval backgroundColor com.google.gerrit.client.Gerrit.getTheme().backgroundColor;
+
+ .wrapper {
+ width: 100%;
+ }
+
+ .patchSetLabel {
+ font-weight: bold;
+ }
+
+ .linkPanel > div {
+ display: inline-block;
+ padding: 3px;
+ }
+
+ .linkPanel {
+ font-size: 12px;
+ }
+
+ .linkPanel > a {
+ padding: 3px;
+ display: inline-block;
+ text-decoration: none;
+ }
+
+ .selected {
+ font-weight: bold;
+ background-color: selectionColor;
+ }
+
+ .sideMarker {
+ font-family: monospace;
+ }
+
+ .hidden {
+ visibility: hidden;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName='wrapper'>
+ <g:HTMLPanel styleName='{style.linkPanel}' ui:field='linkPanel'><span class='{style.patchSetLabel}'><ui:text from="{cons.patchSet}" /></span> <span class='{style.sideMarker}' ui:field='sideMarker'></span>: </g:HTMLPanel>
+ </g:HTMLPanel>
+</ui:UiBinder>
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeader.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeader.java
new file mode 100644
index 0000000..3dd8908
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeader.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.patches;
+
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.common.data.PatchSetDetail;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class PatchTableHeader extends Composite {
+
+ @UiTemplate("PatchTableHeaderSideBySide.ui.xml")
+ interface SideBySideBinder extends UiBinder<HTMLPanel, PatchTableHeader> {
+ }
+
+ @UiTemplate("PatchTableHeaderUnified.ui.xml")
+ interface UnifiedBinder extends UiBinder<HTMLPanel, PatchTableHeader> {
+ }
+
+ private static SideBySideBinder uiBinderS = GWT.create(SideBySideBinder.class);
+ private static UnifiedBinder uiBinderU = GWT.create(UnifiedBinder.class);
+
+ @UiField
+ SimplePanel sideAPanel;
+
+ @UiField
+ SimplePanel sideBPanel;
+
+ PatchSetSelectBox listA;
+ PatchSetSelectBox listB;
+
+ public PatchTableHeader(PatchScreen.Type type) {
+ listA = new PatchSetSelectBox(PatchSetSelectBox.Side.A, type);
+ listB = new PatchSetSelectBox(PatchSetSelectBox.Side.B, type);
+
+ if (type == PatchScreen.Type.SIDE_BY_SIDE) {
+ initWidget(uiBinderS.createAndBindUi(this));
+ } else {
+ initWidget(uiBinderU.createAndBindUi(this));
+ }
+
+ sideAPanel.add(listA);
+ sideBPanel.add(listB);
+ }
+
+
+ public void display(final PatchSetDetail detail, PatchScript script, final Patch.Key patchKey,
+ final PatchSet.Id idSideA, final PatchSet.Id idSideB) {
+ listA.display(detail, script, patchKey, idSideA, idSideB);
+ listB.display(detail, script, patchKey, idSideA, idSideB);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml
new file mode 100644
index 0000000..424e6e5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+
+ <ui:style>
+ @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+ .wrapper {
+ width: 100%;
+ background-color: trimColor;
+ overflow: hidden;
+ }
+
+ .wrapper .box {
+ width: 100%;
+ text-align: center;
+ }
+
+ .leftWrapper {
+ width: 50%;
+ float: left;
+ }
+
+ .rightWrapper {
+ width: 50%;
+ overflow: hidden;
+ }
+
+ .leftBox {
+ float:left;
+ }
+
+ .rightBox {
+ float: right;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.wrapper}">
+ <div class='{style.leftWrapper}'>
+ <g:SimplePanel addStyleNames='{style.box} {style.leftBox}' ui:field='sideAPanel'/>
+ </div>
+ <div class='{style.rightWrapper}'>
+ <g:SimplePanel addStyleNames='{style.box} {style.rightBox}' ui:field='sideBPanel'/>
+ </div>
+ </g:HTMLPanel>
+</ui:UiBinder>
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml
new file mode 100644
index 0000000..e26e96a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:style>
+ @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+ .wrapper {
+ width: 100%;
+ background-color: trimColor;
+ }
+
+ .wrapper .box {
+ width: 100%;
+ text-align: left;
+ margin-left: 3px;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.wrapper}">
+ <g:SimplePanel addStyleNames='{style.box}' ui:field='sideAPanel'/>
+ <g:SimplePanel addStyleNames='{style.box}' ui:field='sideBPanel'/>
+ </g:HTMLPanel>
+</ui:UiBinder>
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index 964ba4b..ec63a83 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -25,10 +25,7 @@
import com.google.gerrit.common.data.PatchScript.FileMode;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseHtmlFile;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Anchor;
@@ -38,8 +35,6 @@
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtorm.client.KeyUtil;
-
import org.eclipse.jgit.diff.Edit;
import java.util.ArrayList;
@@ -89,9 +84,6 @@
script.getDiffPrefs().isIntralineDifference()
&& script.hasIntralineDifference();
- appendHeader(script, nc);
- lines.add(null);
-
if (script.getFileModeA() != FileMode.FILE
|| script.getFileModeB() != FileMode.FILE) {
openLine(nc);
@@ -262,71 +254,6 @@
return row;
}
- private void appendHeader(PatchScript script, final SafeHtmlBuilder m) {
- boolean isCommitMessage = Patch.COMMIT_MSG.equals(script.getNewName());
-
- m.openTr();
-
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().iconCell());
- m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.closeTd();
-
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.addStyleName(Gerrit.RESOURCES.css().lineNumber());
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.setAttribute("width", "50%");
- if (script.getChangeType() == ChangeType.RENAMED
- || script.getChangeType() == ChangeType.COPIED) {
- m.append(script.getOldName());
- } else {
- m.append(PatchUtil.C.patchHeaderOld());
- }
- if (!isCommitMessage) {
- m.br();
- if (0 < script.getA().size()) {
- if (idSideA == null) {
- downloadLink(m, patchKey, "1");
- } else {
- downloadLink(m, new Patch.Key(idSideA, patchKey.get()), "0");
- }
- }
- }
- m.closeTd();
-
- m.openTd();
- m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.setAttribute("width", "50%");
- m.append(PatchUtil.C.patchHeaderNew());
- if (!isCommitMessage) {
- m.br();
- if (0 < script.getB().size()) {
- downloadLink(m, new Patch.Key(idSideB, patchKey.get()), "0");
- }
- }
- m.closeTd();
-
- m.openTd();
- m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
- m.addStyleName(Gerrit.RESOURCES.css().lineNumber());
- m.closeTd();
-
- m.closeTr();
- }
-
- private void downloadLink(final SafeHtmlBuilder m, final Patch.Key key,
- final String side) {
- final String base = GWT.getHostPageBaseURL() + "cat/";
- m.openAnchor();
- m.setAttribute("href", base + KeyUtil.encode(key.toString()) + "^" + side);
- m.append(PatchUtil.C.download());
- m.closeAnchor();
- }
-
private void appendSkipLine(final SafeHtmlBuilder m, final int skipCnt) {
m.openTr();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
similarity index 65%
copy from gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
index 204d777..454c97b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
@@ -12,12 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.client.plugins;
-public class IncompleteUserInfoException extends Exception {
- private static final long serialVersionUID = 1L;
+import com.google.gwt.core.client.JavaScriptObject;
- public IncompleteUserInfoException(final String userName, final String missingInfo) {
- super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
+public class PluginInfo extends JavaScriptObject {
+
+ public final native String name() /*-{ return this.name; }-*/;
+ public final native String version() /*-{ return this.version; }-*/;
+
+ protected PluginInfo() {
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java
new file mode 100644
index 0000000..0f2ab4c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.plugins;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwtjsonrpc.common.AsyncCallback;
+
+/** Plugins available from {@code /plugins/}. */
+public class PluginMap extends NativeMap<PluginInfo> {
+ public static void all(AsyncCallback<PluginMap> callback) {
+ new RestApi("/plugins/")
+ .send(NativeMap.copyKeysIntoChildren(callback));
+ }
+
+ protected PluginMap() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
index 55bb902..408919e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
@@ -37,6 +37,14 @@
.send(NativeMap.copyKeysIntoChildren(callback));
}
+ public static void parentCandidates(AsyncCallback<ProjectMap> callback) {
+ new RestApi("/projects/")
+ .addParameterRaw("type", "PARENT_CANDIDATES")
+ .addParameterTrue("all")
+ .addParameterTrue("d") // description
+ .send(NativeMap.copyKeysIntoChildren(callback));
+ }
+
public static void suggest(String prefix, int limit, AsyncCallback<ProjectMap> cb) {
new RestApi("/projects/" + URL.encode(prefix).replaceAll("[?]", "%3F"))
.addParameterRaw("type", "ALL")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index 98ae46f..dce5bb6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -37,8 +37,11 @@
new NotSignedInDialog().center();
} else if (isNoSuchEntity(caught)) {
- new ErrorDialog(Gerrit.C.notFoundBody()).center();
-
+ if (Gerrit.isSignedIn()) {
+ new ErrorDialog(Gerrit.C.notFoundBody()).center();
+ } else {
+ new NotSignedInDialog().center();
+ }
} else if (isInactiveAccount(caught)) {
new ErrorDialog(Gerrit.C.inactiveAccountBody()).center();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java
index 3d99c9e..a6c609c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java
@@ -36,7 +36,8 @@
if (parser == null) {
parser = bestJsonParser();
}
- return parse0(parser, json);
+ // javac generics bug
+ return Natives.<T>parse0(parser, json);
}
private static native <T extends JavaScriptObject>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
index bd69092..e1fb883 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
@@ -124,7 +124,8 @@
T data;
try {
- data = Natives.parseJSON(json.substring(JSON_MAGIC.length()));
+ // javac generics bug
+ data = Natives.<T>parseJSON(json.substring(JSON_MAGIC.length()));
} catch (RuntimeException e) {
cb.onFailure(new RemoteJsonException("Invalid JSON"));
return;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
index 885f53b..5da00cd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
@@ -18,6 +18,7 @@
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
@@ -31,11 +32,14 @@
private Map<String, AccountGroup.UUID> priorResults =
new HashMap<String, AccountGroup.UUID>();
+ private Project.NameKey projectName;
+
@Override
public void onRequestSuggestions(final Request req, final Callback callback) {
RpcStatus.hide(new Runnable() {
public void run() {
- SuggestUtil.SVC.suggestAccountGroup(req.getQuery(), req.getLimit(),
+ SuggestUtil.SVC.suggestAccountGroupForProject(
+ projectName, req.getQuery(), req.getLimit(),
new GerritCallback<List<GroupReference>>() {
public void onSuccess(final List<GroupReference> result) {
priorResults.clear();
@@ -52,6 +56,10 @@
});
}
+ public void setProject(Project.NameKey projectName) {
+ this.projectName = projectName;
+ }
+
private static class AccountGroupSuggestion implements
SuggestOracle.Suggestion {
private final GroupReference info;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
new file mode 100644
index 0000000..217ca5a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
@@ -0,0 +1,160 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gerrit.client.account.Util;
+import com.google.gerrit.client.projects.ProjectMap;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.HidePopupPanelCommand;
+import com.google.gwtexpui.user.client.PluginSafeDialogBox;
+
+/** It creates a popup containing all the projects. */
+public class ProjectListPopup {
+ private ProjectsTable projectsTab;
+ private PluginSafeDialogBox popup;
+ private Button close;
+ private ScrollPanel sp;
+ private PopupPanel.PositionCallback popupPosition;
+ private int preferredTop;
+ private int preferredLeft;
+ private boolean popingUp;
+ private boolean firstPopupLoad = true;
+
+ public void initPopup(final String popupText, final String currentPageLink) {
+ createWidgets(popupText, currentPageLink);
+ final FlowPanel pfp = new FlowPanel();
+ sp = new ScrollPanel(projectsTab);
+ sp.setSize("100%", "100%");
+ pfp.add(sp);
+ pfp.add(close);
+ popup.setWidget(pfp);
+ popup.setHeight("100%");
+ popupPosition = getPositionCallback();
+ }
+
+ protected PopupPanel.PositionCallback getPositionCallback() {
+ return new PopupPanel.PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight) {
+ if (preferredTop + offsetHeight > Window.getClientWidth()) {
+ preferredTop = Window.getClientWidth() - offsetHeight;
+ }
+ if (preferredLeft + offsetWidth > Window.getClientWidth()) {
+ preferredLeft = Window.getClientWidth() - offsetWidth;
+ }
+
+ if (preferredTop < 0) {
+ sp.setHeight((sp.getOffsetHeight() + preferredTop) + "px");
+ preferredTop = 0;
+ }
+ if (preferredLeft < 0) {
+ sp.setWidth((sp.getOffsetWidth() + preferredLeft) + "px");
+ preferredLeft = 0;
+ }
+
+ popup.setPopupPosition(preferredLeft, preferredTop);
+ }
+ };
+ }
+
+ protected void onMovePointerTo(String projectName) {
+ }
+
+ protected void openRow(String projectName) {
+ }
+
+ public boolean isPopingUp() {
+ return popingUp;
+ }
+
+ private void createWidgets(final String popupText,
+ final String currentPageLink) {
+ projectsTab = new ProjectsTable() {
+ @Override
+ protected void movePointerTo(final int row, final boolean scroll) {
+ super.movePointerTo(row, scroll);
+ onMovePointerTo(getRowItem(row).name());
+ }
+
+ @Override
+ protected void onOpenRow(final int row) {
+ super.onOpenRow(row);
+ openRow(getRowItem(row).name());
+ }
+ };
+ projectsTab.setSavePointerId(currentPageLink);
+
+ close = new Button(Util.C.projectsClose());
+ close.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ closePopup();
+ }
+ });
+
+ popup = new PluginSafeDialogBox();
+ popup.setModal(false);
+ popup.setText(popupText);
+ }
+
+ public void displayPopup() {
+ popingUp = true;
+ if (firstPopupLoad) { // For sizing/positioning, delay display until loaded
+ populateProjects();
+ } else {
+ popup.setPopupPositionAndShow(popupPosition);
+ GlobalKey.dialog(popup);
+ try {
+ GlobalKey.addApplication(popup, new HidePopupPanelCommand(0,
+ KeyCodes.KEY_ESCAPE, popup));
+ } catch (Throwable e) {
+ }
+ projectsTab.setRegisterKeys(true);
+ projectsTab.finishDisplay();
+ popingUp = false;
+ }
+ }
+
+ public void closePopup() {
+ popup.hide();
+ }
+
+ public void setPreferredCoordinates(final int top, final int left) {
+ this.preferredTop = top;
+ this.preferredLeft = left;
+ }
+
+ protected void populateProjects() {
+ ProjectMap.all(new GerritCallback<ProjectMap>() {
+ @Override
+ public void onSuccess(final ProjectMap result) {
+ projectsTab.display(result);
+ if (firstPopupLoad) { // Display was delayed until table was loaded
+ firstPopupLoad = false;
+ displayPopup();
+ }
+ }
+ });
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
index 7f5eead..845a046 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
@@ -95,6 +95,10 @@
}
}
+ protected void setHeaderVisible(boolean value) {
+ header.setVisible(value);
+ }
+
protected void setTitleEast(final Widget w) {
header.setWidget(0, Cols.East.ordinal(), w);
}
diff --git a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs b/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
index 9df523e..839d647 100644
--- a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 7914991..3805961 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -25,15 +25,13 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.account.AuthResult;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
-import com.google.inject.TypeLiteral;
import com.google.inject.servlet.RequestScoped;
import javax.servlet.http.Cookie;
@@ -49,13 +47,9 @@
return new CacheModule() {
@Override
protected void configure() {
- final String cacheName = WebSessionManager.CACHE_NAME;
- final TypeLiteral<Cache<Key, Val>> type =
- new TypeLiteral<Cache<Key, Val>>() {};
- disk(type, cacheName) //
- .memoryLimit(1024) // reasonable default for many sites
- .maxAge(MAX_AGE_MINUTES, MINUTES) // expire sessions if they are inactive
- .evictionPolicy(EvictionPolicy.LRU) // keep most recently used
+ persist(WebSessionManager.CACHE_NAME, String.class, Val.class)
+ .maximumWeight(1024) // reasonable default for many sites
+ .expireAfterWrite(MAX_AGE_MINUTES, MINUTES) // expire sessions if they are inactive
;
bind(WebSessionManager.class);
bind(WebSession.class)
@@ -73,6 +67,7 @@
private final IdentifiedUser.RequestFactory identified;
private AccessPath accessPath = AccessPath.WEB_UI;
private Cookie outCookie;
+ private AuthMethod authMethod;
private Key key;
private Val val;
@@ -98,6 +93,7 @@
key = null;
val = null;
}
+ authMethod = isSignedIn() ? AuthMethod.COOKIE : AuthMethod.NONE;
if (isSignedIn() && val.needsCookieRefresh()) {
// Cookie is more than half old. Send the cookie again to the
@@ -149,7 +145,8 @@
return anonymousProvider.get();
}
- public void login(final AuthResult res, final boolean rememberMe) {
+ public void login(final AuthResult res, final AuthMethod meth,
+ final boolean rememberMe) {
final Account.Id id = res.getAccountId();
final AccountExternalId.Key identity = res.getExternalId();
@@ -160,6 +157,8 @@
key = manager.createKey(id);
val = manager.createVal(key, id, rememberMe, identity, null);
saveCookie();
+
+ authMethod = meth;
}
/** Change the access path from the default of {@link AccessPath#WEB_UI}. */
@@ -168,9 +167,10 @@
}
/** Set the user account for this current request only. */
- public void setUserAccountId(Account.Id id) {
+ public void setUserAccountId(Account.Id id, AuthMethod method) {
key = new Key("id:" + id);
val = new Val(id, 0, false, null, "", 0);
+ authMethod = method;
}
public void logout() {
@@ -217,4 +217,8 @@
private static boolean isSecure(final HttpServletRequest req) {
return req.isSecure() || "https".equals(req.getScheme());
}
+
+ public AuthMethod getAuthMethod() {
+ return authMethod;
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java
index 29b5d95..9ce2298 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java
@@ -19,6 +19,7 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -99,7 +100,9 @@
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
- session.get().setUserAccountId(who.getAccount().getId());
+ session.get().setUserAccountId(
+ who.getAccount().getId(),
+ AuthMethod.PASSWORD);
return true;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index f92f13d..72bb0c2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -90,6 +90,10 @@
config.setAllowedOpenIDs(authConfig.getAllowedOpenIDs());
break;
+ case OPENID_SSO:
+ config.setOpenIdSsoUrl(authConfig.getOpenIdSsoUrl());
+ break;
+
case LDAP:
case LDAP_BIND:
config.setRegisterUrl(cfg.getString("auth", null, "registerurl"));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
index aa004e3..6ca9949 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -34,6 +34,8 @@
Class<? extends Filter> authFilter;
if (authConfig.isTrustContainerAuth()) {
authFilter = ContainerAuthFilter.class;
+ } else if (authConfig.isGitBasichAuth()) {
+ authFilter = ProjectBasicAuthFilter.class;
} else {
authFilter = ProjectDigestFilter.class;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 1f26227..1c9c521 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -14,13 +14,13 @@
package com.google.gerrit.httpd;
+import com.google.common.cache.Cache;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.git.AsyncReceiveCommits;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -99,11 +99,11 @@
install(new CacheModule() {
@Override
protected void configure() {
- TypeLiteral<Cache<AdvertisedObjectsCacheKey, Set<ObjectId>>> cache =
- new TypeLiteral<Cache<AdvertisedObjectsCacheKey, Set<ObjectId>>>() {};
- core(cache, ID_CACHE)
- .memoryLimit(4096)
- .maxAge(10, TimeUnit.MINUTES);
+ cache(ID_CACHE,
+ AdvertisedObjectsCacheKey.class,
+ new TypeLiteral<Set<ObjectId>>() {})
+ .maximumWeight(4096)
+ .expireAfterWrite(10, TimeUnit.MINUTES);
}
});
}
@@ -320,12 +320,12 @@
if (isGet) {
rc.advertiseHistory();
- cache.remove(cacheKey);
+ cache.invalidate(cacheKey);
} else {
- Set<ObjectId> ids = cache.get(cacheKey);
+ Set<ObjectId> ids = cache.getIfPresent(cacheKey);
if (ids != null) {
rp.getAdvertisedObjects().addAll(ids);
- cache.remove(cacheKey);
+ cache.invalidate(cacheKey);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
index 4c88240..8ef826b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
@@ -17,7 +17,6 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.util.RequestContext;
import com.google.inject.Inject;
-import com.google.inject.Provider;
class HttpRequestContext implements RequestContext {
private final WebSession session;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
new file mode 100644
index 0000000..5b39cb2
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -0,0 +1,202 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthMethod;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Locale;
+
+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.HttpServletResponseWrapper;
+
+/**
+ * Authenticates the current user by HTTP basic authentication.
+ * <p>
+ * The current HTTP request is authenticated by looking up the username and
+ * password from the Base64 encoded Authorization header and validating them
+ * against any username/password configured authentication system in Gerrit.
+ * This filter is intended only to protect the {@link ProjectServlet} and its
+ * handled URLs, which provide remote repository access over HTTP.
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>
+ */
+@Singleton
+class ProjectBasicAuthFilter implements Filter {
+ private static final Logger log = LoggerFactory
+ .getLogger(ProjectBasicAuthFilter.class);
+
+ public static final String REALM_NAME = "Gerrit Code Review";
+ private static final String AUTHORIZATION = "Authorization";
+ private static final String LIT_BASIC = "Basic ";
+
+ private final Provider<WebSession> session;
+ private final AccountCache accountCache;
+ private final AccountManager accountManager;
+ private final AuthConfig authConfig;
+
+ @Inject
+ ProjectBasicAuthFilter(Provider<WebSession> session,
+ AccountCache accountCache, AccountManager accountManager,
+ AuthConfig authConfig) {
+ this.session = session;
+ this.accountCache = accountCache;
+ this.accountManager = accountManager;
+ this.authConfig = authConfig;
+ }
+
+ @Override
+ public void init(FilterConfig config) {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest req = (HttpServletRequest) request;
+ Response rsp = new Response((HttpServletResponse) response);
+
+ if (verify(req, rsp)) {
+ chain.doFilter(req, rsp);
+ }
+ }
+
+ private boolean verify(HttpServletRequest req, Response rsp)
+ throws IOException {
+ final String hdr = req.getHeader(AUTHORIZATION);
+ if (hdr == null) {
+ // Allow an anonymous connection through, or it might be using a
+ // session cookie instead of basic authentication.
+ //
+ return true;
+ }
+
+ final byte[] decoded =
+ Base64.decodeBase64(hdr.substring(LIT_BASIC.length()));
+ String usernamePassword = new String(decoded, encoding(req));
+ int splitPos = usernamePassword.indexOf(':');
+ if (splitPos < 1) {
+ rsp.sendError(SC_UNAUTHORIZED);
+ return false;
+ }
+
+ String username = usernamePassword.substring(0, splitPos);
+ String password = usernamePassword.substring(splitPos + 1);
+ if (Strings.isNullOrEmpty(password)) {
+ rsp.sendError(SC_UNAUTHORIZED);
+ return false;
+ }
+ if (authConfig.isUserNameToLowerCase()) {
+ username = username.toLowerCase(Locale.US);
+ }
+
+ final AccountState who = accountCache.getByUsername(username);
+ if (who == null || !who.getAccount().isActive()) {
+ log.warn("Authentication failed for " + username
+ + ": account inactive or not provisioned in Gerrit");
+ rsp.sendError(SC_UNAUTHORIZED);
+ return false;
+ }
+
+ AuthRequest whoAuth = AuthRequest.forUser(username);
+ whoAuth.setPassword(password);
+
+ try {
+ AuthResult whoAuthResult = accountManager.authenticate(whoAuth);
+ session.get().setUserAccountId(whoAuthResult.getAccountId(),
+ AuthMethod.PASSWORD);
+ return true;
+ } catch (AccountException e) {
+ log.warn("Authentication failed for " + username, e);
+ rsp.sendError(SC_UNAUTHORIZED);
+ return false;
+ }
+ }
+
+ private String encoding(HttpServletRequest req) {
+ return Objects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
+ }
+
+ class Response extends HttpServletResponseWrapper {
+ private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
+
+ Response(HttpServletResponse rsp) {
+ super(rsp);
+ }
+
+ private void status(int sc) {
+ if (sc == SC_UNAUTHORIZED) {
+ StringBuilder v = new StringBuilder();
+ v.append(LIT_BASIC);
+ v.append("realm=\"" + REALM_NAME + "\"");
+ setHeader(WWW_AUTHENTICATE, v.toString());
+ } else if (containsHeader(WWW_AUTHENTICATE)) {
+ setHeader(WWW_AUTHENTICATE, null);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ status(sc);
+ super.sendError(sc, msg);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ status(sc);
+ super.sendError(sc);
+ }
+
+ @Override
+ public void setStatus(int sc, String sm) {
+ status(sc);
+ super.setStatus(sc, sm);
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ status(sc);
+ super.setStatus(sc);
+ }
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index 9e12e8c..84aa532 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtjsonrpc.server.SignedToken;
@@ -164,7 +165,9 @@
if (expect.equals(response)) {
try {
if (tokens.checkToken(nonce, "") != null) {
- session.get().setUserAccountId(who.getAccount().getId());
+ session.get().setUserAccountId(
+ who.getAccount().getId(),
+ AuthMethod.PASSWORD);
return true;
} else {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
index 8105e25..bb1ad69 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
@@ -14,11 +14,17 @@
package com.google.gerrit.httpd;
+import com.google.common.base.Objects;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.util.cli.CmdLineParser;
-import com.google.gwtjsonrpc.server.RPCServletUtils;
import com.google.gwtjsonrpc.common.JsonConstants;
+import com.google.gwtjsonrpc.server.RPCServletUtils;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import org.kohsuke.args4j.CmdLineException;
import org.slf4j.Logger;
@@ -62,12 +68,24 @@
}
}
+ private final Provider<CurrentUser> currentUser;
+
+ @Inject
+ protected RestApiServlet(final Provider<CurrentUser> currentUser) {
+ this.currentUser = currentUser;
+ }
+
@Override
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
noCache(res);
try {
+ checkRequiresCapability();
super.service(req, res);
+ } catch (RequireCapabilityException err) {
+ res.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ noCache(res);
+ sendText(req, res, err.getMessage());
} catch (Error err) {
handleError(err, req, res);
} catch (RuntimeException err) {
@@ -75,6 +93,22 @@
}
}
+ private void checkRequiresCapability() throws RequireCapabilityException {
+ RequiresCapability rc = getClass().getAnnotation(RequiresCapability.class);
+ if (rc != null) {
+ CurrentUser user = currentUser.get();
+ CapabilityControl ctl = user.getCapabilities();
+ if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
+ String msg = String.format(
+ "fatal: %s does not have \"%s\" capability.",
+ Objects.firstNonNull(user.getUserName(),
+ ((IdentifiedUser)user).getNameEmail()),
+ rc.value());
+ throw new RequireCapabilityException(msg);
+ }
+ }
+ }
+
private static void noCache(HttpServletResponse res) {
res.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
res.setHeader("Pragma", "no-cache");
@@ -175,4 +209,11 @@
return true;
}
}
+
+ @SuppressWarnings("serial") // Never serialized or thrown out of this class.
+ private static class RequireCapabilityException extends Exception {
+ public RequireCapabilityException(String msg) {
+ super(msg);
+ }
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 852caae..1a48bb5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -25,6 +25,7 @@
import com.google.gerrit.httpd.gitweb.GitWebModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.CmdLineParserModule;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.ChangeUserName;
@@ -38,7 +39,6 @@
import com.google.gerrit.server.contact.ContactStoreProvider;
import com.google.gerrit.server.util.GuiceRequestScopePropagator;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.util.cli.CmdLineParser;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -108,6 +108,7 @@
break;
case OPENID:
+ case OPENID_SSO:
// OpenID support is bound in WebAppInitializer and Daemon.
case CUSTOM_EXTENSION:
break;
@@ -134,7 +135,7 @@
bind(ChangeUserName.CurrentUser.class);
factory(ChangeUserName.Factory.class);
factory(ClearPassword.Factory.class);
- factory(CmdLineParser.Factory.class);
+ install(new CmdLineParserModule());
factory(GeneratePassword.Factory.class);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
index 2925896..88c1420 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
@@ -18,9 +18,12 @@
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.account.AuthResult;
public interface WebSession {
+ public AuthMethod getAuthMethod();
+
public boolean isSignedIn();
public String getToken();
@@ -31,13 +34,13 @@
public CurrentUser getCurrentUser();
- public void login(AuthResult res, boolean rememberMe);
+ public void login(AuthResult res, AuthMethod meth, boolean rememberMe);
/** Change the access path from the default of {@link AccessPath#WEB_UI}. */
public void setAccessPath(AccessPath path);
/** Set the user account for this current request only. */
- public void setUserAccountId(Account.Id id);
+ public void setUserAccountId(Account.Id id, AuthMethod method);
public void logout();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
index ee02ef4..4b4edf4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -26,9 +26,9 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
+import com.google.common.cache.Cache;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
@@ -55,11 +55,11 @@
private final long sessionMaxAgeMillis;
private final SecureRandom prng;
- private final Cache<Key, Val> self;
+ private final Cache<String, Val> self;
@Inject
WebSessionManager(@GerritServerConfig Config cfg,
- @Named(CACHE_NAME) final Cache<Key, Val> cache) {
+ @Named(CACHE_NAME) final Cache<String, Val> cache) {
prng = new SecureRandom();
self = cache;
@@ -76,7 +76,7 @@
prng.nextBytes(rnd);
buf = new ByteArrayOutputStream(3 + nonceLen);
- writeVarInt32(buf, (int) Key.serialVersionUID);
+ writeVarInt32(buf, (int) Val.serialVersionUID);
writeVarInt32(buf, who.get());
writeBytes(buf, rnd);
@@ -120,7 +120,7 @@
Val val = new Val(who, refreshCookieAt, remember,
lastLogin, xsrfToken, expiresAt);
- self.put(key, val);
+ self.put(key.token, val);
return val;
}
@@ -141,21 +141,19 @@
}
Val get(final Key key) {
- Val val = self.get(key);
+ Val val = self.getIfPresent(key.token);
if (val != null && val.expiresAt <= now()) {
- self.remove(key);
+ self.invalidate(key.token);
return null;
}
return val;
}
void destroy(final Key key) {
- self.remove(key);
+ self.invalidate(key.token);
}
- static final class Key implements Serializable {
- static final long serialVersionUID = 2L;
-
+ static final class Key {
private transient String token;
Key(final String t) {
@@ -175,18 +173,10 @@
public boolean equals(Object obj) {
return obj instanceof Key && token.equals(((Key) obj).token);
}
-
- private void writeObject(final ObjectOutputStream out) throws IOException {
- writeString(out, token);
- }
-
- private void readObject(final ObjectInputStream in) throws IOException {
- token = readString(in);
- }
}
static final class Val implements Serializable {
- static final long serialVersionUID = Key.serialVersionUID;
+ static final long serialVersionUID = 2L;
private transient Account.Id accountId;
private transient long refreshCookieAt;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 4710c39..0821496 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException;
@@ -113,7 +114,7 @@
}
if (res != null) {
- webSession.get().login(res, false);
+ webSession.get().login(res, AuthMethod.BACKDOOR, false);
final StringBuilder rdr = new StringBuilder();
rdr.append(req.getContextPath());
if (IS_DEV && req.getParameter("gwt.codesvr") != null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index 5df004e..9b7eaf5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -19,6 +19,7 @@
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.config.AuthConfig;
@@ -135,7 +136,8 @@
}
rdr.append(token);
- webSession.get().login(arsp, true /* persistent cookie */);
+ webSession.get().login(arsp, AuthMethod.COOKIE,
+ true /* persistent cookie */);
rsp.sendRedirect(rdr.toString());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
index 381daa8..ff0eb29 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
@@ -17,6 +17,7 @@
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.inject.Inject;
@@ -84,7 +85,7 @@
log.error(err, e);
throw new ServletException(err, e);
}
- webSession.get().login(arsp, true);
+ webSession.get().login(arsp, AuthMethod.COOKIE, true);
chain.doFilter(req, rsp);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
index 9d14872..348ecbb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountUserNameException;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
@@ -29,11 +30,17 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
class UserPassAuthServiceImpl implements UserPassAuthService {
private final Provider<WebSession> webSession;
private final AccountManager accountManager;
private final AuthType authType;
+ private static final Logger log = LoggerFactory
+ .getLogger(UserPassAuthServiceImpl.class);
+
@Inject
UserPassAuthServiceImpl(final Provider<WebSession> webSession,
final AccountManager accountManager, final AuthConfig authConfig) {
@@ -72,6 +79,7 @@
callback.onSuccess(result);
return;
} catch (AccountException e) {
+ log.info(String.format("'%s' failed to sign in: %s", username, e.getMessage()));
result.setError(LoginResult.Error.INVALID_LOGIN);
callback.onSuccess(result);
return;
@@ -79,7 +87,8 @@
result.success = true;
result.isNew = res.isNew();
- webSession.get().login(res, true /* persistent cookie */);
+ webSession.get().login(res, AuthMethod.PASSWORD,
+ true /* persistent cookie */);
callback.onSuccess(result);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
index 2e5001b..bb47b8b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.plugins;
+import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.plugins.ModuleGenerator;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
@@ -21,6 +22,8 @@
import com.google.inject.servlet.ServletModule;
public class HttpPluginModule extends ServletModule {
+ static final String PLUGIN_RESOURCES = "plugin_resources";
+
@Override
protected void configureServlets() {
bind(HttpPluginServlet.class);
@@ -36,5 +39,14 @@
bind(ModuleGenerator.class)
.to(HttpAutoRegisterModuleGenerator.class);
+
+ install(new CacheModule() {
+ @Override
+ protected void configure() {
+ cache(PLUGIN_RESOURCES, ResourceKey.class, Resource.class)
+ .maximumWeight(2 << 20)
+ .weigher(ResourceWeigher.class);
+ }
+ });
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index b5c228e..e737700 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -16,11 +16,10 @@
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.Weigher;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.httpd.rpc.plugin.ListPluginsServlet;
import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -32,6 +31,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import com.google.inject.name.Named;
import com.google.inject.servlet.GuiceFilter;
import org.eclipse.jgit.lib.Config;
@@ -57,7 +57,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.annotation.Nullable;
import javax.servlet.FilterChain;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
@@ -81,6 +80,7 @@
private final Cache<ResourceKey, Resource> resourceCache;
private final String sshHost;
private final int sshPort;
+ private final ListPluginsServlet listServlet;
private List<Plugin> pending = Lists.newArrayList();
private String base;
@@ -90,22 +90,13 @@
@Inject
HttpPluginServlet(MimeUtilFileTypeRegistry mimeUtil,
@CanonicalWebUrl Provider<String> webUrl,
+ @Named(HttpPluginModule.PLUGIN_RESOURCES) Cache<ResourceKey, Resource> cache,
@GerritServerConfig Config cfg,
- SshInfo sshInfo) {
+ SshInfo sshInfo, ListPluginsServlet listServlet) {
this.mimeUtil = mimeUtil;
this.webUrl = webUrl;
-
- this.resourceCache = CacheBuilder.newBuilder()
- .maximumWeight(cfg.getInt(
- "cache", "plugin_resources", "memoryLimit",
- 2 * 1024 * 1024))
- .weigher(new Weigher<ResourceKey, Resource>() {
- @Override
- public int weigh(ResourceKey key, Resource value) {
- return key.weight() + value.weight();
- }
- })
- .build();
+ this.resourceCache = cache;
+ this.listServlet = listServlet;
String sshHost = "review.example.com";
int sshPort = 29418;
@@ -197,6 +188,10 @@
public void service(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
String name = extractName(req);
+ if (name.equals("")) {
+ listServlet.service(req, res);
+ return;
+ }
final PluginHolder holder = plugins.get(name);
if (holder == null) {
noCache(res);
@@ -230,8 +225,12 @@
String uri = req.getRequestURI();
String ctx = req.getContextPath();
- String file = uri.substring(ctx.length() + 1);
+ if (uri.length() <= ctx.length()) {
+ Resource.NOT_FOUND.send(req, res);
+ return;
+ }
+ String file = uri.substring(ctx.length() + 1);
ResourceKey key = new ResourceKey(holder.plugin, file);
Resource rsc = resourceCache.getIfPresent(key);
if (rsc != null) {
@@ -247,8 +246,8 @@
if (exists(entry)) {
sendResource(jar, entry, key, res);
} else {
- resourceCache.put(key, NOT_FOUND);
- NOT_FOUND.send(req, res);
+ resourceCache.put(key, Resource.NOT_FOUND);
+ Resource.NOT_FOUND.send(req, res);
}
} else if (file.equals("Documentation")) {
res.sendRedirect(uri + "/index.html");
@@ -268,12 +267,12 @@
} else if (exists(entry)) {
sendResource(jar, entry, key, res);
} else {
- resourceCache.put(key, NOT_FOUND);
- NOT_FOUND.send(req, res);
+ resourceCache.put(key, Resource.NOT_FOUND);
+ Resource.NOT_FOUND.send(req, res);
}
} else {
- resourceCache.put(key, NOT_FOUND);
- NOT_FOUND.send(req, res);
+ resourceCache.put(key, Resource.NOT_FOUND);
+ Resource.NOT_FOUND.send(req, res);
}
}
@@ -559,7 +558,7 @@
return 0 <= s ? path.substring(1, s) : path.substring(1);
}
- private static void noCache(HttpServletResponse res) {
+ static void noCache(HttpServletResponse res) {
res.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
res.setHeader("Pragma", "no-cache");
res.setHeader("Cache-Control", "no-cache, must-revalidate");
@@ -576,99 +575,6 @@
}
}
- private static final class ResourceKey {
- private final Plugin.CacheKey plugin;
- private final String resource;
-
- ResourceKey(Plugin p, String r) {
- this.plugin = p.getCacheKey();
- this.resource = r;
- }
-
- int weight() {
- return 28 + resource.length();
- }
-
- @Override
- public int hashCode() {
- return plugin.hashCode() * 31 + resource.hashCode();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof ResourceKey) {
- ResourceKey rk = (ResourceKey) other;
- return plugin == rk.plugin && resource.equals(rk.resource);
- }
- return false;
- }
- }
-
- private static abstract class Resource {
- abstract int weight();
- abstract void send(HttpServletRequest req, HttpServletResponse res)
- throws IOException;
- }
-
- private static final class SmallResource extends Resource {
- private final byte[] data;
- private String contentType;
- private String characterEncoding;
- private long lastModified;
-
- SmallResource(byte[] data) {
- this.data = data;
- }
-
- SmallResource setLastModified(long when) {
- this.lastModified = when;
- return this;
- }
-
- SmallResource setContentType(String contentType) {
- this.contentType = contentType;
- return this;
- }
-
- SmallResource setCharacterEncoding(@Nullable String enc) {
- this.characterEncoding = enc;
- return this;
- }
-
- @Override
- int weight() {
- return data.length;
- }
-
- @Override
- void send(HttpServletRequest req, HttpServletResponse res)
- throws IOException {
- if (0 < lastModified) {
- res.setDateHeader("Last-Modified", lastModified);
- }
- res.setContentType(contentType);
- if (characterEncoding != null) {
- res.setCharacterEncoding(characterEncoding);
- }
- res.setContentLength(data.length);
- res.getOutputStream().write(data);
- }
- }
-
- private static final Resource NOT_FOUND = new Resource() {
- @Override
- int weight() {
- return 4;
- }
-
- @Override
- void send(HttpServletRequest req, HttpServletResponse res)
- throws IOException {
- noCache(res);
- res.sendError(HttpServletResponse.SC_NOT_FOUND);
- }
- };
-
private static class WrappedRequest extends HttpServletRequestWrapper {
private final String contextPath;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/Resource.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/Resource.java
new file mode 100644
index 0000000..05970af
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/Resource.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.plugins;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+abstract class Resource {
+ static final Resource NOT_FOUND = new Resource() {
+ @Override
+ int weigh() {
+ return 0;
+ }
+
+ @Override
+ void send(HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ HttpPluginServlet.noCache(res);
+ res.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ };
+
+ abstract int weigh();
+ abstract void send(HttpServletRequest req, HttpServletResponse res)
+ throws IOException;
+}
\ No newline at end of file
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceKey.java
new file mode 100644
index 0000000..068d6b4
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceKey.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.plugins;
+
+import com.google.gerrit.server.plugins.Plugin;
+
+final class ResourceKey {
+ private final Plugin.CacheKey plugin;
+ private final String resource;
+
+ ResourceKey(Plugin p, String r) {
+ this.plugin = p.getCacheKey();
+ this.resource = r;
+ }
+
+ int weigh() {
+ return resource.length() * 2;
+ }
+
+ @Override
+ public int hashCode() {
+ return plugin.hashCode() * 31 + resource.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ResourceKey) {
+ ResourceKey rk = (ResourceKey) other;
+ return plugin == rk.plugin && resource.equals(rk.resource);
+ }
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceWeigher.java
similarity index 65%
copy from gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
copy to gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceWeigher.java
index 204d777..2514272 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/ResourceWeigher.java
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.httpd.plugins;
-public class IncompleteUserInfoException extends Exception {
- private static final long serialVersionUID = 1L;
+import com.google.common.cache.Weigher;
- public IncompleteUserInfoException(final String userName, final String missingInfo) {
- super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
+class ResourceWeigher implements Weigher<ResourceKey, Resource> {
+ @Override
+ public int weigh(ResourceKey key, Resource value) {
+ return key.weigh() + value.weigh();
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java
new file mode 100644
index 0000000..e408f72
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.plugins;
+
+import java.io.IOException;
+
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+final class SmallResource extends Resource {
+ private final byte[] data;
+ private String contentType;
+ private String characterEncoding;
+ private long lastModified;
+
+ SmallResource(byte[] data) {
+ this.data = data;
+ }
+
+ SmallResource setLastModified(long when) {
+ this.lastModified = when;
+ return this;
+ }
+
+ SmallResource setContentType(String contentType) {
+ this.contentType = contentType;
+ return this;
+ }
+
+ SmallResource setCharacterEncoding(@Nullable String enc) {
+ this.characterEncoding = enc;
+ return this;
+ }
+
+ @Override
+ int weigh() {
+ return contentType.length() * 2 + data.length;
+ }
+
+ @Override
+ void send(HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ if (0 < lastModified) {
+ res.setDateHeader("Last-Modified", lastModified);
+ }
+ res.setContentType(contentType);
+ if (characterEncoding != null) {
+ res.setCharacterEncoding(characterEncoding);
+ }
+ res.setContentLength(data.length);
+ res.getOutputStream().write(data);
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java
index 68379d7..a2f4c99 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java
@@ -43,6 +43,9 @@
theme.trimColor = color(name, "trimColor", "#D4E9A9");
theme.selectionColor = color(name, "selectionColor", "#FFFFCC");
theme.topMenuColor = color(name, "topMenuColor", theme.trimColor);
+ theme.changeTableOutdatedColor = color(name, "changeTableOutdatedColor", "#FF0000");
+ theme.tableOddRowColor = color(name, "tableOddRowColor", "transparent");
+ theme.tableEvenRowColor = color(name, "tableEvenRowColor", "transparent");
return theme;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
index 26db6f9..62506f0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.OrmRuntimeException;
import com.google.inject.Provider;
/** Support for services which require a {@link ReviewDb} instance. */
@@ -70,20 +71,14 @@
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchGroupException e) {
callback.onFailure(new NoSuchEntityException());
-
- } catch (OrmException e) {
- if (e.getCause() instanceof Failure) {
- callback.onFailure(e.getCause().getCause());
-
- } else if (e.getCause() instanceof CorruptEntityException) {
- callback.onFailure(e.getCause());
-
- } else if (e.getCause() instanceof NoSuchEntityException) {
- callback.onFailure(e.getCause());
-
- } else {
- callback.onFailure(e);
+ } catch (OrmRuntimeException e) {
+ Exception ex = e;
+ if (e.getCause() instanceof OrmException) {
+ ex = (OrmException) e.getCause();
}
+ handleOrmException(callback, ex);
+ } catch (OrmException e) {
+ handleOrmException(callback, e);
} catch (Failure e) {
if (e.getCause() instanceof NoSuchProjectException
|| e.getCause() instanceof NoSuchChangeException) {
@@ -95,6 +90,19 @@
}
}
+ private static <T> void handleOrmException(
+ final AsyncCallback<T> callback, Exception e) {
+ if (e.getCause() instanceof Failure) {
+ callback.onFailure(e.getCause().getCause());
+ } else if (e.getCause() instanceof CorruptEntityException) {
+ callback.onFailure(e.getCause());
+ } else if (e.getCause() instanceof NoSuchEntityException) {
+ callback.onFailure(e.getCause());
+ } else {
+ callback.onFailure(e);
+ }
+ }
+
/** Exception whose cause is passed into onFailure. */
public static class Failure extends Exception {
private static final long serialVersionUID = 1L;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
index 66b2dc0..0b54db1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
@@ -14,258 +14,32 @@
package com.google.gerrit.httpd.rpc;
-import com.google.gerrit.common.data.AccountDashboardInfo;
-import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.ChangeListService;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.data.SingleListChangeInfo;
import com.google.gerrit.common.data.ToggleStarRequest;
-import com.google.gerrit.common.errors.InvalidQueryException;
-import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.StarredChange;
-import com.google.gerrit.reviewdb.server.ChangeAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountInfoCacheFactory;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeDataSource;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.query.change.ChangeQueryRewriter;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ChangeListServiceImpl extends BaseServiceImplementation implements
ChangeListService {
- private static final Comparator<ChangeInfo> ID_COMP =
- new Comparator<ChangeInfo>() {
- public int compare(final ChangeInfo o1, final ChangeInfo o2) {
- return o1.getId().get() - o2.getId().get();
- }
- };
- private static final Comparator<ChangeInfo> SORT_KEY_COMP =
- new Comparator<ChangeInfo>() {
- public int compare(final ChangeInfo o1, final ChangeInfo o2) {
- return o2.getSortKey().compareTo(o1.getSortKey());
- }
- };
- private static final Comparator<Change> QUERY_PREV =
- new Comparator<Change>() {
- public int compare(final Change a, final Change b) {
- return a.getSortKey().compareTo(b.getSortKey());
- }
- };
- private static final Comparator<Change> QUERY_NEXT =
- new Comparator<Change>() {
- public int compare(final Change a, final Change b) {
- return b.getSortKey().compareTo(a.getSortKey());
- }
- };
-
private final Provider<CurrentUser> currentUser;
- private final ChangeControl.Factory changeControlFactory;
- private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
-
- private final ChangeQueryBuilder.Factory queryBuilder;
- private final Provider<ChangeQueryRewriter> queryRewriter;
@Inject
ChangeListServiceImpl(final Provider<ReviewDb> schema,
- final Provider<CurrentUser> currentUser,
- final ChangeControl.Factory changeControlFactory,
- final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
- final ChangeQueryBuilder.Factory queryBuilder,
- final Provider<ChangeQueryRewriter> queryRewriter) {
+ final Provider<CurrentUser> currentUser) {
super(schema, currentUser);
this.currentUser = currentUser;
- this.changeControlFactory = changeControlFactory;
- this.accountInfoCacheFactory = accountInfoCacheFactory;
- this.queryBuilder = queryBuilder;
- this.queryRewriter = queryRewriter;
- }
-
- private boolean canRead(final Change c, final ReviewDb db) throws OrmException {
- try {
- return changeControlFactory.controlFor(c).isVisible(db);
- } catch (NoSuchChangeException e) {
- return false;
- }
- }
-
- @Override
- public void allQueryPrev(final String query, final String pos,
- final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
- try {
- run(callback, new QueryPrev(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int lim, String key)
- throws OrmException, InvalidQueryException {
- return searchQuery(db, query, lim, key, QUERY_PREV);
- }
- });
- } catch (InvalidQueryException e) {
- callback.onFailure(e);
- }
- }
-
- @Override
- public void allQueryNext(final String query, final String pos,
- final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
- try {
- run(callback, new QueryNext(pageSize, pos) {
- @Override
- ResultSet<Change> query(ReviewDb db, int lim, String key)
- throws OrmException, InvalidQueryException {
- return searchQuery(db, query, lim, key, QUERY_NEXT);
- }
- });
- } catch (InvalidQueryException e) {
- callback.onFailure(e);
- }
- }
-
- @SuppressWarnings("unchecked")
- private ResultSet<Change> searchQuery(final ReviewDb db, String query,
- final int limit, final String key, final Comparator<Change> cmp)
- throws OrmException, InvalidQueryException {
- try {
- final ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
- final Predicate<ChangeData> visibleToMe = builder.is_visible();
- Predicate<ChangeData> q = builder.parse(query);
- q = Predicate.and(q, //
- cmp == QUERY_PREV //
- ? builder.sortkey_after(key) //
- : builder.sortkey_before(key), //
- builder.limit(limit), //
- visibleToMe //
- );
-
- ChangeQueryRewriter rewriter = queryRewriter.get();
- Predicate<ChangeData> s = rewriter.rewrite(q);
- if (!(s instanceof ChangeDataSource)) {
- s = rewriter.rewrite(Predicate.and(builder.status_open(), q));
- }
-
- if (s instanceof ChangeDataSource) {
- ArrayList<Change> r = new ArrayList<Change>();
- HashSet<Change.Id> want = new HashSet<Change.Id>();
- for (ChangeData d : ((ChangeDataSource) s).read()) {
- if (d.hasChange()) {
- // Checking visibleToMe here should be unnecessary, the
- // query should have already performed it. But we don't
- // want to trust the query rewriter that much yet.
- //
- if (visibleToMe.match(d)) {
- r.add(d.getChange());
- }
- } else {
- want.add(d.getId());
- }
- }
-
- // Here we have to check canRead. Its impossible to
- // do that test without the change object, and it being
- // missing above means we have to compute it ourselves.
- //
- if (!want.isEmpty()) {
- for (Change c : db.changes().get(want)) {
- if (canRead(c, db)) {
- r.add(c);
- }
- }
- }
-
- Collections.sort(r, cmp);
- return new ListResultSet<Change>(r);
- } else {
- throw new InvalidQueryException("Not Supported", s.toString());
- }
- } catch (QueryParseException e) {
- throw new InvalidQueryException(e.getMessage(), query);
- }
- }
-
- public void forAccount(final Account.Id id,
- final AsyncCallback<AccountDashboardInfo> callback) {
- final Account.Id me = getAccountId();
- final Account.Id target = id != null ? id : me;
- if (target == null) {
- callback.onFailure(new NoSuchEntityException());
- return;
- }
-
- run(callback, new Action<AccountDashboardInfo>() {
- public AccountDashboardInfo run(final ReviewDb db) throws OrmException,
- Failure {
- final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
- final Account user = ac.get(target);
- if (user == null) {
- throw new Failure(new NoSuchEntityException());
- }
-
- final Set<Change.Id> stars = currentUser.get().getStarredChanges();
- final ChangeAccess changes = db.changes();
- final AccountDashboardInfo d;
-
- final Set<Change.Id> openReviews = new HashSet<Change.Id>();
- final Set<Change.Id> closedReviews = new HashSet<Change.Id>();
- for (final PatchSetApproval ca : db.patchSetApprovals().openByUser(id)) {
- openReviews.add(ca.getPatchSetId().getParentKey());
- }
- for (final PatchSetApproval ca : db.patchSetApprovals()
- .closedByUser(id)) {
- closedReviews.add(ca.getPatchSetId().getParentKey());
- }
-
- d = new AccountDashboardInfo(target);
- d.setByOwner(filter(changes.byOwnerOpen(target), stars, ac, db));
- d.setClosed(filter(changes.byOwnerClosed(target), stars, ac, db));
-
- for (final ChangeInfo c : d.getByOwner()) {
- openReviews.remove(c.getId());
- }
- d.setForReview(filter(changes.get(openReviews), stars, ac, db));
- Collections.sort(d.getForReview(), ID_COMP);
-
- for (final ChangeInfo c : d.getClosed()) {
- closedReviews.remove(c.getId());
- }
- if (!closedReviews.isEmpty()) {
- d.getClosed().addAll(filter(changes.get(closedReviews), stars, ac, db));
- Collections.sort(d.getClosed(), SORT_KEY_COMP);
- }
-
- // User dashboards are visible to other users, if the current user
- // can see any of the changes in the dashboard.
- if (!target.equals(me)
- && d.getByOwner().isEmpty()
- && d.getClosed().isEmpty()
- && d.getForReview().isEmpty()) {
- throw new Failure(new NoSuchEntityException());
- }
-
- d.setAccounts(ac.create());
- return d;
- }
- });
}
public void toggleStars(final ToggleStarRequest req,
@@ -297,97 +71,4 @@
}
});
}
-
- public void myStarredChangeIds(final AsyncCallback<Set<Change.Id>> callback) {
- callback.onSuccess(currentUser.get().getStarredChanges());
- }
-
- private int safePageSize(final int pageSize) throws InvalidQueryException {
- int maxLimit = currentUser.get().getCapabilities()
- .getRange(GlobalCapability.QUERY_LIMIT)
- .getMax();
- if (maxLimit <= 0) {
- throw new InvalidQueryException("Search Disabled");
- }
- return 0 < pageSize && pageSize <= maxLimit ? pageSize : maxLimit;
- }
-
- private List<ChangeInfo> filter(final ResultSet<Change> rs,
- final Set<Change.Id> starred, final AccountInfoCacheFactory accts,
- final ReviewDb db) throws OrmException {
- final ArrayList<ChangeInfo> r = new ArrayList<ChangeInfo>();
- for (final Change c : rs) {
- if (canRead(c, db)) {
- final ChangeInfo ci = new ChangeInfo(c);
- accts.want(ci.getOwner());
- ci.setStarred(starred.contains(ci.getId()));
- r.add(ci);
- }
- }
- return r;
- }
-
- private abstract class QueryNext implements Action<SingleListChangeInfo> {
- protected final String pos;
- protected final int limit;
- protected final int slim;
-
- QueryNext(final int pageSize, final String pos) throws InvalidQueryException {
- this.pos = pos;
- this.limit = safePageSize(pageSize);
- this.slim = limit + 1;
- }
-
- public SingleListChangeInfo run(final ReviewDb db) throws OrmException,
- InvalidQueryException {
- final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
- final SingleListChangeInfo d = new SingleListChangeInfo();
- final Set<Change.Id> starred = currentUser.get().getStarredChanges();
-
- final ArrayList<ChangeInfo> list = new ArrayList<ChangeInfo>();
- final ResultSet<Change> rs = query(db, slim, pos);
- for (final Change c : rs) {
- if (!canRead(c, db)) {
- continue;
- }
- final ChangeInfo ci = new ChangeInfo(c);
- ac.want(ci.getOwner());
- ci.setStarred(starred.contains(ci.getId()));
- list.add(ci);
- if (list.size() == slim) {
- rs.close();
- break;
- }
- }
-
- final boolean atEnd = finish(list);
- d.setChanges(list, atEnd);
- d.setAccounts(ac.create());
- return d;
- }
-
- boolean finish(final ArrayList<ChangeInfo> list) {
- final boolean atEnd = list.size() <= limit;
- if (list.size() == slim) {
- list.remove(limit);
- }
- return atEnd;
- }
-
- abstract ResultSet<Change> query(final ReviewDb db, final int slim,
- String sortKey) throws OrmException, InvalidQueryException;
- }
-
- private abstract class QueryPrev extends QueryNext {
- QueryPrev(int pageSize, String pos) throws InvalidQueryException {
- super(pageSize, pos);
- }
-
- @Override
- boolean finish(final ArrayList<ChangeInfo> list) {
- final boolean atEnd = super.finish(list);
- Collections.reverse(list);
- return atEnd;
- }
- }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index c1c9169..1baa49b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.rpc;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.ReviewerInfo;
@@ -21,8 +23,6 @@
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -31,14 +31,14 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountVisibility;
-import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.patch.AddReviewer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -53,41 +53,43 @@
import java.util.Map;
import java.util.Set;
+import javax.annotation.Nullable;
+
class SuggestServiceImpl extends BaseServiceImplementation implements
SuggestService {
private static final String MAX_SUFFIX = "\u9fa5";
private final Provider<ReviewDb> reviewDbProvider;
private final AccountCache accountCache;
- private final GroupControl.Factory groupControlFactory;
private final GroupMembers.Factory groupMembersFactory;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final AccountControl.Factory accountControlFactory;
private final ChangeControl.Factory changeControlFactory;
+ private final ProjectControl.Factory projectControlFactory;
private final Config cfg;
- private final GroupCache groupCache;
+ private final GroupBackend groupBackend;
private final boolean suggestAccounts;
@Inject
SuggestServiceImpl(final Provider<ReviewDb> schema,
final AccountCache accountCache,
- final GroupControl.Factory groupControlFactory,
final GroupMembers.Factory groupMembersFactory,
final Provider<CurrentUser> currentUser,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final AccountControl.Factory accountControlFactory,
final ChangeControl.Factory changeControlFactory,
- @GerritServerConfig final Config cfg, final GroupCache groupCache) {
+ final ProjectControl.Factory projectControlFactory,
+ @GerritServerConfig final Config cfg, final GroupBackend groupBackend) {
super(schema, currentUser);
this.reviewDbProvider = schema;
this.accountCache = accountCache;
- this.groupControlFactory = groupControlFactory;
this.groupMembersFactory = groupMembersFactory;
this.identifiedUserFactory = identifiedUserFactory;
this.accountControlFactory = accountControlFactory;
this.changeControlFactory = changeControlFactory;
+ this.projectControlFactory = projectControlFactory;
this.cfg = cfg;
- this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
if ("OFF".equals(cfg.getString("suggest", null, "accounts"))) {
this.suggestAccounts = false;
@@ -176,33 +178,31 @@
public void suggestAccountGroup(final String query, final int limit,
final AsyncCallback<List<GroupReference>> callback) {
+ suggestAccountGroupForProject(null, query, limit, callback);
+ }
+
+ public void suggestAccountGroupForProject(final Project.NameKey project,
+ final String query, final int limit,
+ final AsyncCallback<List<GroupReference>> callback) {
run(callback, new Action<List<GroupReference>>() {
- public List<GroupReference> run(final ReviewDb db) throws OrmException {
- return suggestAccountGroup(db, query, limit);
+ public List<GroupReference> run(final ReviewDb db) {
+ ProjectControl projectControl = null;
+ if (project != null) {
+ try {
+ projectControl = projectControlFactory.controlFor(project);
+ } catch (NoSuchProjectException e) {
+ return Collections.emptyList();
+ }
+ }
+ return suggestAccountGroup(projectControl, query, limit);
}
});
}
- private List<GroupReference> suggestAccountGroup(final ReviewDb db,
- final String query, final int limit) throws OrmException {
- final String a = query;
- final String b = a + MAX_SUFFIX;
- final int max = 10;
- final int n = limit <= 0 ? max : Math.min(limit, max);
- List<GroupReference> r = new ArrayList<GroupReference>(n);
- for (AccountGroupName group : db.accountGroupNames().suggestByName(a, b, n)) {
- try {
- if (groupControlFactory.controlFor(group.getId()).isVisible()) {
- AccountGroup g = groupCache.get(group.getId());
- if (g != null && g.getGroupUUID() != null) {
- r.add(GroupReference.forGroup(g));
- }
- }
- } catch (NoSuchGroupException e) {
- continue;
- }
- }
- return r;
+ private List<GroupReference> suggestAccountGroup(
+ @Nullable final ProjectControl projectControl, final String query, final int limit) {
+ final int n = limit <= 0 ? 10 : Math.min(limit, 10);
+ return Lists.newArrayList(Iterables.limit(groupBackend.suggest(query), n));
}
@Override
@@ -243,7 +243,7 @@
reviewer.add(new ReviewerInfo(a));
}
final List<GroupReference> suggestedAccountGroups =
- suggestAccountGroup(db, query, limit);
+ suggestAccountGroup(changeControl.getProjectControl(), query, limit);
for (final GroupReference g : suggestedAccountGroups) {
if (suggestGroupAsReviewer(changeControl.getProject().getNameKey(), g)) {
reviewer.add(new ReviewerInfo(g));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountCapabilitiesServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountCapabilitiesServlet.java
index 0d0ffe7..a33c209 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountCapabilitiesServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountCapabilitiesServlet.java
@@ -58,8 +58,9 @@
private final Provider<Impl> factory;
@Inject
- AccountCapabilitiesServlet(
+ AccountCapabilitiesServlet(final Provider<CurrentUser> currentUser,
ParameterParser paramParser, Provider<Impl> factory) {
+ super(currentUser);
this.paramParser = paramParser;
this.factory = factory;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
index d9f327d..aca2e05 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -18,6 +18,7 @@
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.common.data.GroupOptions;
+import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
@@ -30,33 +31,38 @@
import com.google.gerrit.reviewdb.client.AccountGroupIncludeAudit;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
-import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
class GroupAdminServiceImpl extends BaseServiceImplementation implements
GroupAdminService {
private final AccountCache accountCache;
private final AccountResolver accountResolver;
- private final Realm accountRealm;
+ private final AccountManager accountManager;
+ private final AuthType authType;
private final GroupCache groupCache;
+ private final GroupBackend groupBackend;
private final GroupIncludeCache groupIncludeCache;
private final GroupControl.Factory groupControlFactory;
@@ -70,8 +76,11 @@
final Provider<IdentifiedUser> currentUser,
final AccountCache accountCache,
final GroupIncludeCache groupIncludeCache,
- final AccountResolver accountResolver, final Realm accountRealm,
+ final AccountResolver accountResolver,
+ final AccountManager accountManager,
+ final AuthConfig authConfig,
final GroupCache groupCache,
+ final GroupBackend groupBackend,
final GroupControl.Factory groupControlFactory,
final CreateGroup.Factory createGroupFactory,
final RenameGroup.Factory renameGroupFactory,
@@ -81,8 +90,10 @@
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
this.accountResolver = accountResolver;
- this.accountRealm = accountRealm;
+ this.accountManager = accountManager;
+ this.authType = authConfig.getAuthType();
this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
this.groupControlFactory = groupControlFactory;
this.createGroupFactory = createGroupFactory;
this.renameGroupFactory = renameGroupFactory;
@@ -145,13 +156,13 @@
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
- AccountGroup owner =
- groupCache.get(new AccountGroup.NameKey(newOwnerName));
+ GroupReference owner =
+ GroupBackends.findExactSuggestion(groupBackend, newOwnerName);
if (owner == null) {
throw new Failure(new NoSuchEntityException());
}
- group.setOwnerGroupUUID(owner.getGroupUUID());
+ group.setOwnerGroupUUID(owner.getUUID());
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
@@ -178,43 +189,13 @@
});
}
- public void changeExternalGroup(final AccountGroup.Id groupId,
- final AccountGroup.ExternalNameKey bindTo,
- final AsyncCallback<VoidResult> callback) {
- run(callback, new Action<VoidResult>() {
- public VoidResult run(final ReviewDb db) throws OrmException, Failure {
- final AccountGroup group = db.accountGroups().get(groupId);
- assertAmGroupOwner(db, group);
- group.setExternalNameKey(bindTo);
- db.accountGroups().update(Collections.singleton(group));
- groupCache.evict(group);
- return VoidResult.INSTANCE;
- }
- });
- }
-
- public void searchExternalGroups(final String searchFilter,
- final AsyncCallback<List<AccountGroup.ExternalNameKey>> callback) {
- final ArrayList<AccountGroup.ExternalNameKey> matches =
- new ArrayList<AccountGroup.ExternalNameKey>(
- accountRealm.lookupGroups(searchFilter));
- Collections.sort(matches, new Comparator<AccountGroup.ExternalNameKey>() {
- @Override
- public int compare(AccountGroup.ExternalNameKey a,
- AccountGroup.ExternalNameKey b) {
- return a.get().compareTo(b.get());
- }
- });
- callback.onSuccess(matches);
- }
-
public void addGroupMember(final AccountGroup.Id groupId,
final String nameOrEmail, final AsyncCallback<GroupDetail> callback) {
run(callback, new Action<GroupDetail>() {
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
- if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
+ if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -249,7 +230,7 @@
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
- if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
+ if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -282,7 +263,7 @@
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
- if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
+ if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -336,7 +317,7 @@
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
- if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
+ if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -396,13 +377,38 @@
private Account findAccount(final String nameOrEmail) throws OrmException,
Failure {
- final Account r = accountResolver.find(nameOrEmail);
+ Account r = accountResolver.find(nameOrEmail);
if (r == null) {
- throw new Failure(new NoSuchAccountException(nameOrEmail));
+ switch (authType) {
+ case HTTP_LDAP:
+ case CLIENT_SSL_CERT_LDAP:
+ case LDAP:
+ r = createAccountByLdap(nameOrEmail);
+ break;
+ default:
+ }
+ if (r == null) {
+ throw new Failure(new NoSuchAccountException(nameOrEmail));
+ }
}
return r;
}
+ private Account createAccountByLdap(String user) {
+ if (!user.matches(Account.USER_NAME_PATTERN)) {
+ return null;
+ }
+
+ try {
+ final AuthRequest req = AuthRequest.forUser(user);
+ req.setSkipAuthentication(true);
+ return accountCache.get(accountManager.authenticate(req).getAccountId())
+ .getAccount();
+ } catch (AccountException e) {
+ return null;
+ }
+ }
+
private AccountGroup findGroup(final String name) throws OrmException,
Failure {
final AccountGroup g = groupCache.get(new AccountGroup.NameKey(name));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java
index 91fc5b0..b501d43 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/ListChangesServlet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc.change;
import com.google.gerrit.httpd.RestApiServlet;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ListChanges;
@@ -43,7 +44,9 @@
private final Provider<ListChanges> factory;
@Inject
- ListChangesServlet(ParameterParser paramParser, Provider<ListChanges> ls) {
+ ListChangesServlet(final Provider<CurrentUser> currentUser,
+ ParameterParser paramParser, Provider<ListChanges> ls) {
+ super(currentUser);
this.paramParser = paramParser;
this.factory = ls;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
index 1d4d3e2..2110885 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -40,7 +41,7 @@
AbandonChangeHandler create(PatchSet.Id patchSetId, String message);
}
- private final AbandonChange.Factory abandonChangeFactory;
+ private final Provider<AbandonChange> abandonChangeProvider;
private final ChangeDetailFactory.Factory changeDetailFactory;
private final PatchSet.Id patchSetId;
@@ -48,11 +49,11 @@
private final String message;
@Inject
- AbandonChangeHandler(final AbandonChange.Factory abandonChangeFactory,
+ AbandonChangeHandler(final Provider<AbandonChange> abandonChangeProvider,
final ChangeDetailFactory.Factory changeDetailFactory,
@Assisted final PatchSet.Id patchSetId,
@Assisted @Nullable final String message) {
- this.abandonChangeFactory = abandonChangeFactory;
+ this.abandonChangeProvider = abandonChangeProvider;
this.changeDetailFactory = changeDetailFactory;
this.patchSetId = patchSetId;
@@ -64,8 +65,10 @@
EmailException, NoSuchEntityException, InvalidChangeOperationException,
PatchSetInfoNotAvailableException, RepositoryNotFoundException,
IOException {
- final ReviewResult result =
- abandonChangeFactory.create(patchSetId.getParentKey(), message).call();
+ final AbandonChange abandonChange = abandonChangeProvider.get();
+ abandonChange.setChangeId(patchSetId.getParentKey());
+ abandonChange.setMessage(message);
+ final ReviewResult result = abandonChange.call();
if (result.getErrors().size() > 0) {
throw new NoSuchChangeException(result.getChangeId());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index d765f39..6660a3d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -32,6 +32,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
@@ -41,6 +42,7 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.server.OrmException;
@@ -143,20 +145,21 @@
detail.setCanEdit(control.getRefControl().canWrite());
- if (detail.getChange().getStatus().isOpen()) {
- List<SubmitRecord> submitRecords = control.canSubmit(db, patch);
- for (SubmitRecord rec : submitRecords) {
- if (rec.labels != null) {
- for (SubmitRecord.Label lbl : rec.labels) {
- aic.want(lbl.appliedBy);
- }
- }
- if (rec.status == SubmitRecord.Status.OK && control.getRefControl().canSubmit()) {
- detail.setCanSubmit(true);
+ List<SubmitRecord> submitRecords = control.getSubmitRecords(db, patch);
+ for (SubmitRecord rec : submitRecords) {
+ if (rec.labels != null) {
+ for (SubmitRecord.Label lbl : rec.labels) {
+ aic.want(lbl.appliedBy);
}
}
- detail.setSubmitRecords(submitRecords);
+ if (detail.getChange().getStatus().isOpen()
+ && rec.status == SubmitRecord.Status.OK
+ && control.getRefControl().canSubmit()
+ && ProjectUtil.branchExists(repoManager, change.getDest())) {
+ detail.setCanSubmit(true);
+ }
}
+ detail.setSubmitRecords(submitRecords);
patchsetsById = new HashMap<PatchSet.Id, PatchSet>();
loadPatchSets();
@@ -203,7 +206,9 @@
}
private void load() throws OrmException, NoSuchChangeException {
- if (detail.getChange().getStatus().equals(Change.Status.NEW) && testMerge) {
+ final Change.Status status = detail.getChange().getStatus();
+ if ((status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) &&
+ testMerge) {
ChangeUtil.testMerge(opFactory, detail.getChange());
}
@@ -249,6 +254,15 @@
detail.setApprovals(ad.values());
}
+ private boolean isReviewer(Change change) {
+ // Return true if the currently logged in user is a reviewer of the change.
+ try {
+ return control.isReviewer(db, new ChangeData(change));
+ } catch (OrmException e) {
+ return false;
+ }
+ }
+
private void loadCurrentPatchSet() throws OrmException,
NoSuchEntityException, PatchSetInfoNotAvailableException,
NoSuchChangeException {
@@ -274,32 +288,48 @@
}
}
- final RevId cprev = loader.patchSet.getRevision();
- final Set<Change.Id> descendants = new HashSet<Change.Id>();
- if (cprev != null) {
- for (PatchSetAncestor a : db.patchSetAncestors().descendantsOf(cprev)) {
- final Change.Id ck = a.getPatchSet().getParentKey();
- if (descendants.add(ck)) {
- changesToGet.add(a.getPatchSet().getParentKey());
+ final Set<PatchSet.Id> descendants = new HashSet<PatchSet.Id>();
+ RevId cprev;
+ for (PatchSet p : detail.getPatchSets()) {
+ cprev = p.getRevision();
+ if (cprev != null) {
+ for (PatchSetAncestor a : db.patchSetAncestors().descendantsOf(cprev)) {
+ if (descendants.add(a.getPatchSet())) {
+ changesToGet.add(a.getPatchSet().getParentKey());
+ }
}
}
}
final Map<Change.Id, Change> m =
db.changes().toMap(db.changes().get(changesToGet));
+ final CurrentUser currentUser = control.getCurrentUser();
+ Account.Id currentUserId = null;
+ if (currentUser instanceof IdentifiedUser) {
+ currentUserId = ((IdentifiedUser) currentUser).getAccountId();
+ }
+
final ArrayList<ChangeInfo> dependsOn = new ArrayList<ChangeInfo>();
for (final Change.Id a : ancestorOrder) {
final Change ac = m.get(a);
- if (ac != null) {
- dependsOn.add(newChangeInfo(ac, ancestorPatchIds));
+ if (ac != null && ac.getProject().equals(detail.getChange().getProject())) {
+ if (ac.getStatus().getCode() != Change.STATUS_DRAFT
+ || ac.getOwner().equals(currentUserId)
+ || isReviewer(ac)) {
+ dependsOn.add(newChangeInfo(ac, ancestorPatchIds));
+ }
}
}
final ArrayList<ChangeInfo> neededBy = new ArrayList<ChangeInfo>();
- for (final Change.Id a : descendants) {
- final Change ac = m.get(a);
- if (ac != null) {
- neededBy.add(newChangeInfo(ac, null));
+ for (final PatchSet.Id a : descendants) {
+ final Change ac = m.get(a.getParentKey());
+ if (ac != null && ac.currentPatchSetId().equals(a)) {
+ if (ac.getStatus().getCode() != Change.STATUS_DRAFT
+ || ac.getOwner().equals(currentUserId)
+ || isReviewer(ac)) {
+ neededBy.add(newChangeInfo(ac, null));
+ }
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index de3ff2f..95a8e26 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
@@ -108,18 +109,19 @@
final PatchList list;
- if (psIdBase != null) {
- oldId = toObjectId(psIdBase);
- newId = toObjectId(psIdNew);
+ try {
+ if (psIdBase != null) {
+ oldId = toObjectId(psIdBase);
+ newId = toObjectId(psIdNew);
- projectKey = control.getProject().getNameKey();
+ projectKey = control.getProject().getNameKey();
- list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
- } else { // OK, means use base to compare
- list = patchListCache.get(control.getChange(), patchSet);
- if (list == null) {
- throw new NoSuchEntityException();
+ list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
+ } else { // OK, means use base to compare
+ list = patchListCache.get(control.getChange(), patchSet);
}
+ } catch (PatchListNotAvailableException e) {
+ throw new NoSuchEntityException();
}
final List<Patch> patches = list.toPatchList(patchSet.getId());
@@ -185,7 +187,8 @@
return new PatchListKey(projectKey, oldId, newId, whitespace);
}
- private PatchList listFor(final PatchListKey key) {
+ private PatchList listFor(PatchListKey key)
+ throws PatchListNotAvailableException {
return patchListCache.get(key);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index 183b5f6..50baf97 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -14,12 +14,14 @@
package com.google.gerrit.httpd.rpc.changedetail;
+import com.google.gerrit.common.data.ApprovalDetail;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -32,6 +34,8 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.workflow.CategoryFunction;
+import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -49,6 +53,7 @@
private final PatchSetInfoFactory infoFactory;
private final ReviewDb db;
+ private final FunctionState.Factory functionState;
private final ChangeControl.Factory changeControlFactory;
private final ApprovalTypes approvalTypes;
private final AccountInfoCacheFactory aic;
@@ -64,11 +69,13 @@
PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
final ReviewDb db,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
+ final FunctionState.Factory functionState,
final ChangeControl.Factory changeControlFactory,
final ApprovalTypes approvalTypes,
final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
this.infoFactory = infoFactory;
this.db = db;
+ this.functionState = functionState;
this.changeControlFactory = changeControlFactory;
this.approvalTypes = approvalTypes;
this.aic = accountInfoCacheFactory.create();
@@ -130,6 +137,8 @@
int ok = 0;
for (SubmitRecord.Label lbl : rec.labels) {
+ aic.want(lbl.appliedBy);
+
boolean canMakeOk = false;
PermissionRange range = rangeByName.get(lbl.label);
if (range != null) {
@@ -145,6 +154,7 @@
switch (lbl.status) {
case OK:
+ case MAY:
ok++;
break;
@@ -166,12 +176,60 @@
if (couldSubmit && control.getRefControl().canSubmit()) {
detail.setCanSubmit(true);
}
+
+ detail.setSubmitRecords(submitRecords);
}
detail.setLabels(allowed);
detail.setGiven(given);
+ loadApprovals(detail, control);
+
detail.setAccounts(aic.create());
return detail;
}
+
+ private void loadApprovals(final PatchSetPublishDetail detail,
+ final ChangeControl control) throws OrmException {
+ final PatchSet.Id psId = detail.getChange().currentPatchSetId();
+ final Change.Id changeId = patchSetId.getParentKey();
+ final List<PatchSetApproval> allApprovals =
+ db.patchSetApprovals().byChange(changeId).toList();
+
+ if (detail.getChange().getStatus().isOpen()) {
+ final FunctionState fs = functionState.create(control, psId, allApprovals);
+
+ for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
+ CategoryFunction.forCategory(at.getCategory()).run(at, fs);
+ }
+ }
+
+ final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() //
+ && control.getCurrentUser() instanceof IdentifiedUser;
+ final HashMap<Account.Id, ApprovalDetail> ad =
+ new HashMap<Account.Id, ApprovalDetail>();
+ for (PatchSetApproval ca : allApprovals) {
+ ApprovalDetail d = ad.get(ca.getAccountId());
+ if (d == null) {
+ d = new ApprovalDetail(ca.getAccountId());
+ d.setCanRemove(canRemoveReviewers);
+ ad.put(d.getAccount(), d);
+ }
+ if (d.canRemove()) {
+ d.setCanRemove(control.canRemoveReviewer(ca));
+ }
+ if (ca.getPatchSetId().equals(psId)) {
+ d.add(ca);
+ }
+ }
+
+ final Account.Id owner = detail.getChange().getOwner();
+ if (ad.containsKey(owner)) {
+ // Ensure the owner always sorts to the top of the table
+ ad.get(owner).sortFirst();
+ }
+
+ aic.want(ad.keySet());
+ detail.setApprovals(ad.values());
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
index 5d7fe32..e4571fd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -39,7 +40,7 @@
RestoreChangeHandler create(PatchSet.Id patchSetId, String message);
}
- private final RestoreChange.Factory restoreChangeFactory;
+ private final Provider<RestoreChange> restoreChangeProvider;
private final ChangeDetailFactory.Factory changeDetailFactory;
private final PatchSet.Id patchSetId;
@@ -47,11 +48,11 @@
private final String message;
@Inject
- RestoreChangeHandler(final RestoreChange.Factory restoreChangeFactory,
+ RestoreChangeHandler(final Provider<RestoreChange> restoreChangeProvider,
final ChangeDetailFactory.Factory changeDetailFactory,
@Assisted final PatchSet.Id patchSetId,
@Assisted @Nullable final String message) {
- this.restoreChangeFactory = restoreChangeFactory;
+ this.restoreChangeProvider = restoreChangeProvider;
this.changeDetailFactory = changeDetailFactory;
this.patchSetId = patchSetId;
@@ -63,8 +64,10 @@
EmailException, NoSuchEntityException, InvalidChangeOperationException,
PatchSetInfoNotAvailableException, RepositoryNotFoundException,
IOException {
- final ReviewResult result =
- restoreChangeFactory.create(patchSetId.getParentKey(), message).call();
+ final RestoreChange restoreChange = restoreChangeProvider.get();
+ restoreChange.setChangeId(patchSetId.getParentKey());
+ restoreChange.setMessage(message);
+ final ReviewResult result = restoreChange.call();
if (result.getErrors().size() > 0) {
throw new NoSuchChangeException(result.getChangeId());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index ac00b8d..97d850a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -31,10 +31,12 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
@@ -120,7 +122,8 @@
}
@Override
- public PatchScript call() throws OrmException, NoSuchChangeException {
+ public PatchScript call() throws OrmException, NoSuchChangeException,
+ LargeObjectException {
validatePatchSetId(psa);
validatePatchSetId(psb);
@@ -154,12 +157,14 @@
content.getOldName(), //
content.getNewName());
- try {
return b.toPatchScript(content, comments, history);
- } catch (IOException e) {
- log.error("File content unavailable", e);
- throw new NoSuchChangeException(changeId, e);
- }
+ } catch (PatchListNotAvailableException e) {
+ throw new NoSuchChangeException(changeId, e);
+ } catch (IOException e) {
+ log.error("File content unavailable", e);
+ throw new NoSuchChangeException(changeId, e);
+ } catch (org.eclipse.jgit.errors.LargeObjectException err) {
+ throw new LargeObjectException("File content is too large", err);
} finally {
git.close();
}
@@ -169,7 +174,8 @@
return new PatchListKey(projectKey, aId, bId, whitespace);
}
- private PatchList listFor(final PatchListKey key) {
+ private PatchList listFor(final PatchListKey key)
+ throws PatchListNotAvailableException {
return patchListCache.get(key);
}
@@ -250,7 +256,7 @@
break;
case DELETED:
- loadPublished(byKey, aic, oldName);
+ loadPublished(byKey, aic, newName);
break;
case COPIED:
@@ -272,7 +278,7 @@
break;
case DELETED:
- loadDrafts(byKey, aic, me, oldName);
+ loadDrafts(byKey, aic, me, newName);
break;
case COPIED:
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/plugin/ListPluginsServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/plugin/ListPluginsServlet.java
new file mode 100644
index 0000000..5e8145c
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/plugin/ListPluginsServlet.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.plugin;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.httpd.RestApiServlet;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.plugins.ListPlugins;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+public class ListPluginsServlet extends RestApiServlet {
+ private static final long serialVersionUID = 1L;
+ private final ParameterParser paramParser;
+ private final Provider<ListPlugins> factory;
+
+ @Inject
+ ListPluginsServlet(final Provider<CurrentUser> currentUser,
+ ParameterParser paramParser, Provider<ListPlugins> ls) {
+ super(currentUser);
+ this.paramParser = paramParser;
+ this.factory = ls;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ ListPlugins impl = factory.get();
+ if (acceptsJson(req)) {
+ impl.setFormat(OutputFormat.JSON_COMPACT);
+ }
+ if (paramParser.parse(impl, req, res)) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ if (impl.getFormat().isJson()) {
+ res.setContentType(JSON_TYPE);
+ buf.write(JSON_MAGIC);
+ } else {
+ res.setContentType("text/plain");
+ }
+ impl.display(buf);
+ res.setCharacterEncoding("UTF-8");
+ send(req, res, buf.toByteArray());
+ }
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index 777329b..72b5e3a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -15,41 +15,26 @@
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.ProjectAccess;
-import com.google.gerrit.common.errors.InvalidNameException;
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
-import com.google.gwtorm.server.OrmConcurrencyException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import javax.annotation.Nullable;
-class ChangeProjectAccess extends Handler<ProjectAccess> {
+class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
interface Factory {
ChangeProjectAccess create(@Assisted Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@@ -58,152 +43,29 @@
}
private final ProjectAccessFactory.Factory projectAccessFactory;
- private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
- private final GroupCache groupCache;
- private final MetaDataUpdate.User metaDataUpdateFactory;
-
- private final Project.NameKey projectName;
- private final ObjectId base;
- private List<AccessSection> sectionList;
- private String message;
@Inject
ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectControl.Factory projectControlFactory,
- final ProjectCache projectCache, final GroupCache groupCache,
+ final ProjectCache projectCache, final GroupBackend groupBackend,
final MetaDataUpdate.User metaDataUpdateFactory,
@Assisted final Project.NameKey projectName,
@Nullable @Assisted final ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted String message) {
+ super(projectControlFactory, groupBackend, metaDataUpdateFactory,
+ projectName, base, sectionList, message, true);
this.projectAccessFactory = projectAccessFactory;
- this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
- this.groupCache = groupCache;
- this.metaDataUpdateFactory = metaDataUpdateFactory;
-
- this.projectName = projectName;
- this.base = base;
- this.sectionList = sectionList;
- this.message = message;
}
@Override
- public ProjectAccess call() throws NoSuchProjectException, IOException,
- ConfigInvalidException, InvalidNameException, NoSuchGroupException,
- OrmConcurrencyException {
- final ProjectControl projectControl =
- projectControlFactory.controlFor(projectName);
-
- final MetaDataUpdate md;
- try {
- md = metaDataUpdateFactory.create(projectName);
- } catch (RepositoryNotFoundException notFound) {
- throw new NoSuchProjectException(projectName);
- }
- try {
- ProjectConfig config = ProjectConfig.read(md, base);
- Set<String> toDelete = scanSectionNames(config);
-
- for (AccessSection section : mergeSections(sectionList)) {
- String name = section.getName();
-
- if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
- if (!projectControl.isOwner()) {
- continue;
- }
- replace(config, toDelete, section);
-
- } else if (AccessSection.isValid(name)) {
- if (!projectControl.controlForRef(name).isOwner()) {
- continue;
- }
-
- RefControl.validateRefPattern(name);
-
- replace(config, toDelete, section);
- }
- }
-
- for (String name : toDelete) {
- if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
- if (projectControl.isOwner()) {
- config.remove(config.getAccessSection(name));
- }
-
- } else if (projectControl.controlForRef(name).isOwner()) {
- config.remove(config.getAccessSection(name));
- }
- }
-
- if (message != null && !message.isEmpty()) {
- if (!message.endsWith("\n")) {
- message += "\n";
- }
- md.setMessage(message);
- } else {
- md.setMessage("Modify access rules\n");
- }
-
- if (config.commit(md)) {
- projectCache.evict(config.getProject());
- return projectAccessFactory.create(projectName).call();
-
- } else {
- throw new OrmConcurrencyException("Cannot update " + projectName);
- }
- } finally {
- md.close();
- }
- }
-
- private void replace(ProjectConfig config, Set<String> toDelete,
- AccessSection section) throws NoSuchGroupException {
- for (Permission permission : section.getPermissions()) {
- for (PermissionRule rule : permission.getRules()) {
- lookupGroup(rule);
- }
- }
- config.replace(section);
- toDelete.remove(section.getName());
- }
-
- private static List<AccessSection> mergeSections(List<AccessSection> src) {
- Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
- for (AccessSection section : src) {
- if (section.getPermissions().isEmpty()) {
- continue;
- }
-
- AccessSection prior = map.get(section.getName());
- if (prior != null) {
- prior.mergeFrom(section);
- } else {
- map.put(section.getName(), section);
- }
- }
- return new ArrayList<AccessSection>(map.values());
- }
-
- private static Set<String> scanSectionNames(ProjectConfig config) {
- Set<String> names = new HashSet<String>();
- for (AccessSection section : config.getAccessSections()) {
- names.add(section.getName());
- }
- return names;
- }
-
- private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
- GroupReference ref = rule.getGroup();
- if (ref.getUUID() == null) {
- AccountGroup.NameKey name = new AccountGroup.NameKey(ref.getName());
- AccountGroup group = groupCache.get(name);
- if (group == null) {
- throw new NoSuchGroupException(name);
- }
- ref.setUUID(group.getGroupUUID());
- }
+ protected ProjectAccess updateProjectConfig(ProjectConfig config,
+ MetaDataUpdate md) throws IOException, NoSuchProjectException, ConfigInvalidException {
+ config.commit(md);
+ projectCache.evict(config.getProject());
+ return projectAccessFactory.create(projectName).call();
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
index a2b62cc..41354aa 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
@@ -86,10 +86,11 @@
config.getProject().copySettingsFrom(update);
md.setMessage("Modified project settings\n");
- if (config.commit(md)) {
+ try {
+ config.commit(md);
mgr.setProjectDescription(projectName, update.getDescription());
userCache.get().evict(config.getProject());
- } else {
+ } catch (IOException e) {
throw new OrmConcurrencyException("Cannot update " + projectName);
}
} catch (ConfigInvalidException err) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java
index 039a301..141f332 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java
@@ -26,7 +26,7 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.lib.Constants;
+import java.util.Collections;
public class CreateProjectHandler extends Handler<VoidResult> {
@@ -74,7 +74,7 @@
}
args.projectDescription = "";
args.submitType = SubmitType.MERGE_IF_NECESSARY;
- args.branch = Constants.MASTER;
+ args.branch = Collections.emptyList();
args.createEmptyCommit = emptyCommit;
args.permissionsOnly = permissionsOnly;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListProjectsServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListProjectsServlet.java
index 2757640..d327d35 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListProjectsServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListProjectsServlet.java
@@ -16,6 +16,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.httpd.RestApiServlet;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.project.ListProjects;
import com.google.inject.Inject;
@@ -36,7 +37,9 @@
private final Provider<ListProjects> factory;
@Inject
- ListProjectsServlet(ParameterParser paramParser, Provider<ListProjects> ls) {
+ ListProjectsServlet(final Provider<CurrentUser> currentUser,
+ ParameterParser paramParser, Provider<ListProjects> ls) {
+ super(currentUser);
this.paramParser = paramParser;
this.factory = ls;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 7ac4ec3..250f5e3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -23,7 +23,7 @@
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -51,7 +51,7 @@
ProjectAccessFactory create(@Assisted Project.NameKey name);
}
- private final GroupCache groupCache;
+ private final GroupBackend groupBackend;
private final ProjectCache projectCache;
private final ProjectControl.Factory projectControlFactory;
private final GroupControl.Factory groupControlFactory;
@@ -62,7 +62,7 @@
private ProjectControl pc;
@Inject
- ProjectAccessFactory(final GroupCache groupCache,
+ ProjectAccessFactory(final GroupBackend groupBackend,
final ProjectCache projectCache,
final ProjectControl.Factory projectControlFactory,
final GroupControl.Factory groupControlFactory,
@@ -70,7 +70,7 @@
final AllProjectsName allProjectsName,
@Assisted final Project.NameKey name) {
- this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
this.projectCache = projectCache;
this.projectControlFactory = projectControlFactory;
this.groupControlFactory = groupControlFactory;
@@ -94,12 +94,11 @@
try {
config = ProjectConfig.read(md);
- if (config.updateGroupNames(groupCache)) {
+ if (config.updateGroupNames(groupBackend)) {
md.setMessage("Update group names\n");
- if (config.commit(md)) {
- projectCache.evict(config.getProject());
- pc = open();
- }
+ config.commit(md);
+ projectCache.evict(config.getProject());
+ pc = open();
} else if (config.getRevision() != null
&& !config.getRevision().equals(
pc.getProjectState().getConfig().getRevision())) {
@@ -110,6 +109,7 @@
md.close();
}
+ final RefControl metaConfigControl = pc.controlForRef(GitRepositoryManager.REF_CONFIG);
List<AccessSection> local = new ArrayList<AccessSection>();
Set<String> ownerOf = new HashSet<String>();
Map<AccountGroup.UUID, Boolean> visibleGroups =
@@ -125,7 +125,7 @@
} else if (RefConfigSection.isValid(name)) {
RefControl rc = pc.controlForRef(name);
- if (rc.isOwner()) {
+ if (rc.isOwner() || metaConfigControl.isVisible()) {
local.add(section);
ownerOf.add(name);
@@ -195,8 +195,9 @@
detail.setLocal(local);
detail.setOwnerOf(ownerOf);
- detail.setConfigVisible(pc.isOwner()
- || pc.controlForRef(GitRepositoryManager.REF_CONFIG).isVisible());
+ detail.setCanUpload(pc.isOwner()
+ || (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
+ detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
return detail;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
new file mode 100644
index 0000000..02b84b0
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -0,0 +1,190 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.project;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class ProjectAccessHandler<T> extends Handler<T> {
+
+ private final ProjectControl.Factory projectControlFactory;
+ protected final GroupBackend groupBackend;
+ private final MetaDataUpdate.User metaDataUpdateFactory;
+
+ protected final Project.NameKey projectName;
+ protected final ObjectId base;
+ private List<AccessSection> sectionList;
+ protected String message;
+ private boolean checkIfOwner;
+
+ protected ProjectAccessHandler(
+ final ProjectControl.Factory projectControlFactory,
+ final GroupBackend groupBackend,
+ final MetaDataUpdate.User metaDataUpdateFactory,
+ final Project.NameKey projectName, final ObjectId base,
+ final List<AccessSection> sectionList, final String message,
+ final boolean checkIfOwner) {
+ this.projectControlFactory = projectControlFactory;
+ this.groupBackend = groupBackend;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+
+ this.projectName = projectName;
+ this.base = base;
+ this.sectionList = sectionList;
+ this.message = message;
+ this.checkIfOwner = checkIfOwner;
+ }
+
+ @Override
+ public final T call() throws NoSuchProjectException, IOException,
+ ConfigInvalidException, InvalidNameException, NoSuchGroupException,
+ OrmException {
+ final ProjectControl projectControl =
+ projectControlFactory.controlFor(projectName);
+
+ final MetaDataUpdate md;
+ try {
+ md = metaDataUpdateFactory.create(projectName);
+ } catch (RepositoryNotFoundException notFound) {
+ throw new NoSuchProjectException(projectName);
+ }
+ try {
+ ProjectConfig config = ProjectConfig.read(md, base);
+ Set<String> toDelete = scanSectionNames(config);
+
+ for (AccessSection section : mergeSections(sectionList)) {
+ String name = section.getName();
+
+ if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
+ if (checkIfOwner && !projectControl.isOwner()) {
+ continue;
+ }
+ replace(config, toDelete, section);
+
+ } else if (AccessSection.isValid(name)) {
+ if (checkIfOwner && !projectControl.controlForRef(name).isOwner()) {
+ continue;
+ }
+
+ RefControl.validateRefPattern(name);
+
+ replace(config, toDelete, section);
+ }
+ }
+
+ for (String name : toDelete) {
+ if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
+ if (!checkIfOwner || projectControl.isOwner()) {
+ config.remove(config.getAccessSection(name));
+ }
+
+ } else if (!checkIfOwner || projectControl.controlForRef(name).isOwner()) {
+ config.remove(config.getAccessSection(name));
+ }
+ }
+
+ if (message != null && !message.isEmpty()) {
+ if (!message.endsWith("\n")) {
+ message += "\n";
+ }
+ md.setMessage(message);
+ } else {
+ md.setMessage("Modify access rules\n");
+ }
+
+ return updateProjectConfig(config, md);
+ } finally {
+ md.close();
+ }
+ }
+
+ protected abstract T updateProjectConfig(ProjectConfig config,
+ MetaDataUpdate md) throws IOException, NoSuchProjectException,
+ ConfigInvalidException, OrmException;
+
+ private void replace(ProjectConfig config, Set<String> toDelete,
+ AccessSection section) throws NoSuchGroupException {
+ for (Permission permission : section.getPermissions()) {
+ for (PermissionRule rule : permission.getRules()) {
+ lookupGroup(rule);
+ }
+ }
+ config.replace(section);
+ toDelete.remove(section.getName());
+ }
+
+ private static List<AccessSection> mergeSections(List<AccessSection> src) {
+ Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
+ for (AccessSection section : src) {
+ if (section.getPermissions().isEmpty()) {
+ continue;
+ }
+
+ AccessSection prior = map.get(section.getName());
+ if (prior != null) {
+ prior.mergeFrom(section);
+ } else {
+ map.put(section.getName(), section);
+ }
+ }
+ return new ArrayList<AccessSection>(map.values());
+ }
+
+ private static Set<String> scanSectionNames(ProjectConfig config) {
+ Set<String> names = new HashSet<String>();
+ for (AccessSection section : config.getAccessSections()) {
+ names.add(section.getName());
+ }
+ return names;
+ }
+
+ private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
+ GroupReference ref = rule.getGroup();
+ if (ref.getUUID() == null) {
+ final GroupReference group =
+ GroupBackends.findBestSuggestion(groupBackend, ref.getName());
+ if (group == null) {
+ throw new NoSuchGroupException(ref.getName());
+ }
+ ref.setUUID(group.getUUID());
+ }
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index a6bb74f..15f167c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -19,8 +19,8 @@
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.ProjectAdminService;
import com.google.gerrit.common.data.ProjectDetail;
-import com.google.gerrit.common.data.ProjectList;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
@@ -34,44 +34,36 @@
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
private final ChangeProjectAccess.Factory changeProjectAccessFactory;
+ private final ReviewProjectAccess.Factory reviewProjectAccessFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
private final DeleteBranches.Factory deleteBranchesFactory;
private final ListBranches.Factory listBranchesFactory;
- private final VisibleProjects.Factory visibleProjectsFactory;
private final VisibleProjectDetails.Factory visibleProjectDetailsFactory;
private final ProjectAccessFactory.Factory projectAccessFactory;
private final CreateProjectHandler.Factory createProjectHandlerFactory;
private final ProjectDetailFactory.Factory projectDetailFactory;
- private final SuggestParentCandidatesHandler.Factory suggestParentCandidatesHandlerFactory;
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
final ChangeProjectAccess.Factory changeProjectAccessFactory,
+ final ReviewProjectAccess.Factory reviewProjectAccessFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
final ListBranches.Factory listBranchesFactory,
- final VisibleProjects.Factory visibleProjectsFactory,
final VisibleProjectDetails.Factory visibleProjectDetailsFactory,
final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectDetailFactory.Factory projectDetailFactory,
- final SuggestParentCandidatesHandler.Factory parentCandidatesFactory,
final CreateProjectHandler.Factory createNewProjectFactory) {
this.addBranchFactory = addBranchFactory;
this.changeProjectAccessFactory = changeProjectAccessFactory;
+ this.reviewProjectAccessFactory = reviewProjectAccessFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
this.deleteBranchesFactory = deleteBranchesFactory;
this.listBranchesFactory = listBranchesFactory;
- this.visibleProjectsFactory = visibleProjectsFactory;
this.visibleProjectDetailsFactory = visibleProjectDetailsFactory;
this.projectAccessFactory = projectAccessFactory;
this.projectDetailFactory = projectDetailFactory;
this.createProjectHandlerFactory = createNewProjectFactory;
- this.suggestParentCandidatesHandlerFactory = parentCandidatesFactory;
- }
-
- @Override
- public void visibleProjects(final AsyncCallback<ProjectList> callback) {
- visibleProjectsFactory.create().to(callback);
}
@Override
@@ -80,11 +72,6 @@
}
@Override
- public void suggestParentCandidates(AsyncCallback<List<Project>> callback) {
- suggestParentCandidatesHandlerFactory.create().to(callback);
- }
-
- @Override
public void projectDetail(final Project.NameKey projectName,
final AsyncCallback<ProjectDetail> callback) {
projectDetailFactory.create(projectName).to(callback);
@@ -116,6 +103,14 @@
}
@Override
+ public void reviewProjectAccess(Project.NameKey projectName,
+ String baseRevision, String msg, List<AccessSection> sections,
+ AsyncCallback<Change.Id> cb) {
+ ObjectId base = ObjectId.fromString(baseRevision);
+ reviewProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
+ }
+
+ @Override
public void listBranches(final Project.NameKey projectName,
final AsyncCallback<ListBranchesResult> callback) {
listBranchesFactory.create(projectName).to(callback);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
index 2eb55b3..e943e3fc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
@@ -30,15 +30,14 @@
protected void configure() {
factory(AddBranch.Factory.class);
factory(ChangeProjectAccess.Factory.class);
+ factory(ReviewProjectAccess.Factory.class);
factory(CreateProjectHandler.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);
factory(ListBranches.Factory.class);
- factory(VisibleProjects.Factory.class);
factory(VisibleProjectDetails.Factory.class);
factory(ProjectAccessFactory.Factory.class);
factory(ProjectDetailFactory.Factory.class);
- factory(SuggestParentCandidatesHandler.Factory.class);
}
});
rpc(ProjectAdminServiceImpl.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
new file mode 100644
index 0000000..c422f6d
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.project;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+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.IdentifiedUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.patch.AddReviewer;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
+ interface Factory {
+ ReviewProjectAccess create(@Assisted Project.NameKey projectName,
+ @Nullable @Assisted ObjectId base,
+ @Assisted List<AccessSection> sectionList,
+ @Nullable @Assisted String message);
+ }
+
+ private final ReviewDb db;
+ private final IdentifiedUser user;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+ private final AddReviewer.Factory addReviewerFactory;
+
+ @Inject
+ ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
+ final GroupBackend groupBackend,
+ final MetaDataUpdate.User metaDataUpdateFactory, final ReviewDb db,
+ final IdentifiedUser user, final PatchSetInfoFactory patchSetInfoFactory,
+ final AddReviewer.Factory addReviewerFactory,
+
+ @Assisted final Project.NameKey projectName,
+ @Nullable @Assisted final ObjectId base,
+ @Assisted List<AccessSection> sectionList,
+ @Nullable @Assisted String message) {
+ super(projectControlFactory, groupBackend, metaDataUpdateFactory,
+ projectName, base, sectionList, message, false);
+ this.db = db;
+ this.user = user;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.addReviewerFactory = addReviewerFactory;
+ }
+
+ @Override
+ protected Change.Id updateProjectConfig(ProjectConfig config, MetaDataUpdate md)
+ throws IOException, NoSuchProjectException, ConfigInvalidException, OrmException {
+ int nextChangeId = db.nextChangeId();
+ PatchSet.Id patchSetId = new PatchSet.Id(new Change.Id(nextChangeId), 1);
+ final PatchSet ps = new PatchSet(patchSetId);
+ RevCommit commit = config.commitToNewRef(md, ps.getRefName());
+ if (commit.getId().equals(base)) {
+ return null;
+ }
+ Change.Key changeKey = new Change.Key("I" + commit.name());
+ final Change change =
+ new Change(changeKey, new Change.Id(nextChangeId), user.getAccountId(),
+ new Branch.NameKey(config.getProject().getNameKey(),
+ GitRepositoryManager.REF_CONFIG));
+ change.nextPatchSetId();
+
+ ps.setCreatedOn(change.getCreatedOn());
+ ps.setUploader(user.getAccountId());
+ ps.setRevision(new RevId(commit.name()));
+
+ db.patchSets().insert(Collections.singleton(ps));
+
+ final PatchSetInfo info = patchSetInfoFactory.get(commit, ps.getId());
+ change.setCurrentPatchSet(info);
+ ChangeUtil.updated(change);
+
+ db.changes().insert(Collections.singleton(change));
+
+ addProjectOwnersAsReviewers(change.getId());
+
+ return change.getId();
+ }
+
+ private void addProjectOwnersAsReviewers(final Change.Id changeId) {
+ final String projectOwners =
+ groupBackend.get(AccountGroup.PROJECT_OWNERS).getName();
+ try {
+ addReviewerFactory.create(changeId, Collections.singleton(projectOwners),
+ false).call();
+ } catch (Exception e) {
+ // one of the owner groups is not visible to the user and this it why it
+ // can't be added as reviewer
+ }
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/SuggestParentCandidatesHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/SuggestParentCandidatesHandler.java
deleted file mode 100644
index ba0e4cd..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/SuggestParentCandidatesHandler.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License
-
-package com.google.gerrit.httpd.rpc.project;
-
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.SuggestParentCandidates;
-import com.google.inject.Inject;
-
-import java.util.List;
-
-public class SuggestParentCandidatesHandler extends Handler<List<Project>> {
- interface Factory {
- SuggestParentCandidatesHandler create();
- }
-
- private final SuggestParentCandidates suggestParentCandidates;
-
- @Inject
- SuggestParentCandidatesHandler(final SuggestParentCandidates suggestParentCandidates) {
- this.suggestParentCandidates = suggestParentCandidates;
- }
-
- @Override
- public List<Project> call() throws Exception {
- return suggestParentCandidates.getProjects();
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/VisibleProjects.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/VisibleProjects.java
deleted file mode 100644
index ba65617..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/VisibleProjects.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.project;
-
-import com.google.gerrit.common.data.ProjectList;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.inject.Inject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-class VisibleProjects extends Handler<ProjectList> {
- interface Factory {
- VisibleProjects create();
- }
-
- private final ProjectControl.Factory projectControlFactory;
- private final ProjectCache projectCache;
- private final CurrentUser user;
-
- @Inject
- VisibleProjects(final ProjectControl.Factory projectControlFactory,
- final ProjectCache projectCache, final CurrentUser user) {
- this.projectControlFactory = projectControlFactory;
- this.projectCache = projectCache;
- this.user = user;
- }
-
- @Override
- public ProjectList call() {
- ProjectList result = new ProjectList();
- result.setProjects(getProjects());
- result.setCanCreateProject(user.getCapabilities().canCreateProject());
- return result;
- }
-
- private List<Project> getProjects() {
- List<Project> result = new ArrayList<Project>();
- for (Project.NameKey p : projectCache.all()) {
- try {
- ProjectControl c = projectControlFactory.controlFor(p);
- if (c.isVisible() || c.isOwner()) {
- result.add(c.getProject());
- }
- } catch (NoSuchProjectException e) {
- continue;
- }
- }
- Collections.sort(result, new Comparator<Project>() {
- public int compare(final Project a, final Project b) {
- return a.getName().compareTo(b.getName());
- }
- });
- return result;
- }
-}
diff --git a/gerrit-launcher/.settings/org.eclipse.core.resources.prefs b/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-main/.settings/org.eclipse.core.resources.prefs b/gerrit-main/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-main/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-main/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-openid/.settings/org.eclipse.core.resources.prefs b/gerrit-openid/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-openid/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-openid/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 0593bce..09a5d10 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.UrlEncoded;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.ConfigUtil;
@@ -416,7 +417,7 @@
lastId.setMaxAge(0);
}
rsp.addCookie(lastId);
- webSession.get().login(arsp, remember);
+ webSession.get().login(arsp, AuthMethod.COOKIE, remember);
if (arsp.isNew() && claimedIdentifier != null) {
final com.google.gerrit.server.account.AuthRequest linkReq =
new com.google.gerrit.server.account.AuthRequest(
@@ -430,7 +431,7 @@
case LINK_IDENTIY: {
arsp = accountManager.link(identifiedUser.get().getAccountId(), areq);
- webSession.get().login(arsp, remember);
+ webSession.get().login(arsp, AuthMethod.COOKIE, remember);
callback(false, req, rsp);
break;
}
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs b/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs b/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-pgm/.settings/org.eclipse.core.resources.prefs b/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
index 9df523e..839d647 100644
--- a/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index c05948d..02b0a1d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -17,7 +17,6 @@
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.gerrit.common.ChangeHookRunner;
-import com.google.gerrit.ehcache.EhcachePoolImpl;
import com.google.gerrit.httpd.CacheBasedWebSession;
import com.google.gerrit.httpd.GitOverHttpModule;
import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
@@ -36,11 +35,14 @@
import com.google.gerrit.pgm.util.RuntimeShutdown;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.contact.HttpContactStoreConnection;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
@@ -49,15 +51,24 @@
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginModule;
+import com.google.gerrit.server.schema.SchemaUpdater;
import com.google.gerrit.server.schema.SchemaVersionCheck;
+import com.google.gerrit.server.schema.UpdateUI;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.gerrit.sshd.commands.SlaveCommandModule;
+import com.google.gwtorm.jdbc.JdbcExecutor;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Provider;
+import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -145,6 +156,7 @@
sysInjector = createSysInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class)
.setCfgInjector(cfgInjector);
+ sysInjector.getInstance(SchemaUpgrade.class).upgradeSchema();
manager.add(dbInjector, cfgInjector, sysInjector);
if (sshd) {
@@ -157,6 +169,7 @@
manager.start();
RuntimeShutdown.add(new Runnable() {
+ @Override
public void run() {
log.info("caught shutdown, cleaning up");
if (runId != null) {
@@ -191,6 +204,74 @@
}
}
+ static class SchemaUpgrade {
+
+ private final Config config;
+ private final SchemaUpdater updater;
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ SchemaUpgrade(@GerritServerConfig Config config, SchemaUpdater updater,
+ SchemaFactory<ReviewDb> schema) {
+ this.config = config;
+ this.updater = updater;
+ this.schema = schema;
+ }
+
+ void upgradeSchema() throws OrmException {
+ SchemaUpgradePolicy policy =
+ config.getEnum("site", null, "upgradeSchemaOnStartup",
+ SchemaUpgradePolicy.OFF);
+ if (policy == SchemaUpgradePolicy.AUTO
+ || policy == SchemaUpgradePolicy.AUTO_NO_PRUNE) {
+ final List<String> pruneList = new ArrayList<String>();
+ updater.update(new UpdateUI() {
+ @Override
+ public void message(String msg) {
+ log.info(msg);
+ }
+
+ @Override
+ public boolean yesno(boolean def, String msg) {
+ return true;
+ }
+
+ @Override
+ public boolean isBatch() {
+ return true;
+ }
+
+ @Override
+ public void pruneSchema(StatementExecutor e, List<String> prune) {
+ for (String p : prune) {
+ if (!pruneList.contains(p)) {
+ pruneList.add(p);
+ }
+ }
+ }
+ });
+
+ if (!pruneList.isEmpty() && policy == SchemaUpgradePolicy.AUTO) {
+ log.info("Pruning: " + pruneList.toString());
+ final JdbcSchema db = (JdbcSchema) schema.open();
+ try {
+ final JdbcExecutor e = new JdbcExecutor(db);
+ try {
+ for (String sql : pruneList) {
+ e.execute(sql);
+ }
+ } finally {
+ e.close();
+ }
+ } finally {
+ db.close();
+ }
+ }
+ }
+ }
+ }
+
+
private String myVersion() {
return com.google.gerrit.common.Version.getVersion();
}
@@ -209,7 +290,7 @@
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
- modules.add(new EhcachePoolImpl.Module());
+ modules.add(new DefaultCacheFactory.Module());
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginModule());
@@ -286,7 +367,8 @@
}
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
- if (authConfig.getAuthType() == AuthType.OPENID) {
+ if (authConfig.getAuthType() == AuthType.OPENID ||
+ authConfig.getAuthType() == AuthType.OPENID_SSO) {
modules.add(new OpenIdModule());
}
modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
index 5f0bc80..525360d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
@@ -17,17 +17,15 @@
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.gerrit.common.data.ApprovalTypes;
-import com.google.gerrit.ehcache.EhcachePoolImpl;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountCacheImpl;
import com.google.gerrit.server.account.GroupCacheImpl;
-import com.google.gerrit.server.cache.CachePool;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
import com.google.gerrit.server.config.ApprovalTypesProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
@@ -36,6 +34,7 @@
import com.google.gerrit.server.git.CreateCodeReviewNotes;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.git.NotesBranchUtil;
import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
@@ -45,7 +44,6 @@
import com.google.inject.Scopes;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
@@ -100,11 +98,12 @@
install(AccountCacheImpl.module());
install(GroupCacheImpl.module());
- install(new EhcachePoolImpl.Module());
+ install(new DefaultCacheFactory.Module());
install(new FactoryModule() {
@Override
protected void configure() {
factory(CreateCodeReviewNotes.Factory.class);
+ factory(NotesBranchUtil.Factory.class);
}
});
install(new LifecycleModule() {
@@ -173,21 +172,8 @@
}
try {
CreateCodeReviewNotes notes = codeReviewNotesFactory.create(db, git);
- try {
- notes.loadBase();
- for (Change change : changes) {
- monitor.update(1);
- PatchSet ps = db.patchSets().get(change.currentPatchSetId());
- if (ps == null) {
- continue;
- }
- notes.add(change, ObjectId.fromString(ps.getRevision().get()));
- }
- notes.commit("Exported prior reviews from Gerrit Code Review\n");
- notes.updateRef();
- } finally {
- notes.release();
- }
+ notes.create(changes, null,
+ "Exported prior reviews from Gerrit Code Review\n", monitor);
} finally {
git.close();
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Gsql.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Gsql.java
index d967969..a5ce908 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Gsql.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Gsql.java
@@ -47,6 +47,7 @@
manager.add(dbInjector);
manager.start();
RuntimeShutdown.add(new Runnable() {
+ @Override
public void run() {
try {
System.in.close();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index f06946f..95b8487f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -141,12 +141,12 @@
}
final StringBuilder buf = new StringBuilder();
- buf.append(why.getMessage());
- why = why.getCause();
while (why != null) {
- buf.append("\n caused by ");
- buf.append(why.toString());
+ buf.append(why.getMessage());
why = why.getCause();
+ if (why != null) {
+ buf.append("\n caused by ");
+ }
}
throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
}
@@ -191,6 +191,11 @@
}
@Override
+ public boolean isBatch() {
+ return ui.isBatch();
+ }
+
+ @Override
public void pruneSchema(StatementExecutor e, List<String> prune) {
for (String p : prune) {
if (!pruneList.contains(p)) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java
index 451ed30..cabdc64 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java
@@ -43,7 +43,7 @@
@Option(name = "--all", usage = "recompile all rules")
private boolean all;
- @Option(name = "--quiet", usage = "supress some messsages")
+ @Option(name = "--quiet", usage = "suppress some messages")
private boolean quiet;
@Argument(index = 0, multiValued = true, metaVar = "PROJECT", usage = "project to compile rules for")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
similarity index 65%
copy from gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
copy to gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
index 204d777..67f5c91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
@@ -12,12 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.pgm;
-public class IncompleteUserInfoException extends Exception {
- private static final long serialVersionUID = 1L;
+/** Policy for auto upgrading schema on server startup */
+public enum SchemaUpgradePolicy {
- public IncompleteUserInfoException(final String userName, final String missingInfo) {
- super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
- }
+ /** Perform schema migration if necessary and prune unused objects */
+ AUTO,
+
+ /** Like AUTO but don't prune unused objects */
+ AUTO_NO_PRUNE,
+
+ /** No automatic schema upgrade */
+ OFF
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index 65e73bd..6f439d2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -30,6 +30,7 @@
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jgit.lib.Config;
import java.io.File;
import java.io.IOException;
@@ -54,7 +55,7 @@
private final AsyncAppender async;
- HttpLog(final SitePaths site) {
+ HttpLog(final SitePaths site, final Config config) {
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
dst.setName(LOG_NAME);
dst.setLayout(new MyLayout());
@@ -69,7 +70,7 @@
async = new AsyncAppender();
async.setBlocking(true);
- async.setBufferSize(64);
+ async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
async.setLocationInfo(false);
async.addAppender(dst);
async.activateOptions();
@@ -93,7 +94,7 @@
private void doLog(Request req, Response rsp, CurrentUser user) {
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
- null, // logger (optional)
+ log, // logger
System.currentTimeMillis(), // when
Level.INFO, // level
"", // message text
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index d85ff20..2e6274c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -117,7 +117,7 @@
Handler app = makeContext(env, cfg);
if (cfg.getBoolean("httpd", "requestlog", !reverseProxy)) {
RequestLogHandler handler = new RequestLogHandler();
- handler.setRequestLog(new HttpLog(site));
+ handler.setRequestLog(new HttpLog(site, cfg));
handler.setHandler(app);
app = handler;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index ee7c794..2d6db63 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -97,7 +97,7 @@
this.userProvider = userProvider;
this.queue = queue;
this.context = context;
- this.maxWait = getTimeUnit(cfg, "httpd", null, "maxwait", 5, MINUTES);
+ this.maxWait = MINUTES.toMillis(getTimeUnit(cfg, "httpd", null, "maxwait", 5, MINUTES));
}
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index 7a3556e..c5732e9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -28,12 +28,14 @@
class InitSendEmail implements InitStep {
private final ConsoleUI ui;
private final Section sendemail;
+ private final SitePaths site;
@Inject
InitSendEmail(final ConsoleUI ui, final SitePaths site,
final Section.Factory sections) {
this.ui = ui;
this.sendemail = sections.get("sendemail");
+ this.site = site;
}
public void run() {
@@ -49,7 +51,9 @@
true);
String username = null;
- if ((enc != null && enc != Encryption.NONE) || !isLocal(hostname)) {
+ if (site.gerrit_config.exists()) {
+ username = sendemail.get("smtpUser");
+ } else if ((enc != null && enc != Encryption.NONE) || !isLocal(hostname)) {
username = username();
}
sendemail.string("SMTP username", "smtpUser", username);
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 5c4ca3449..84f6f7b 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/gerrit-plugin-api/pom.xml
@@ -62,7 +62,6 @@
<excludes>
<exclude>gwtexpui:gwtexpui</exclude>
<exclude>gwtjsonrpc:gwtjsonrpc</exclude>
- <exclude>com.google.gerrit:gerrit-ehcache</exclude>
<exclude>com.google.gerrit:gerrit-prettify</exclude>
<exclude>com.google.gerrit:gerrit-patch-commonsnet</exclude>
<exclude>com.google.gerrit:gerrit-patch-jgit</exclude>
@@ -82,7 +81,6 @@
<exclude>asm:asm</exclude>
<exclude>eu.medsea.mimeutil:mime-util</exclude>
- <exclude>net.sf.ehcache:ehcache-core</exclude>
<exclude>org.antlr:antlr</exclude>
<exclude>org.antlr:antlr-runtime</exclude>
<exclude>org.apache.mina:mina-core</exclude>
diff --git a/gerrit-ehcache/.gitignore b/gerrit-plugin-archetype/.gitignore
similarity index 84%
rename from gerrit-ehcache/.gitignore
rename to gerrit-plugin-archetype/.gitignore
index fe190c9..80d6257 100644
--- a/gerrit-ehcache/.gitignore
+++ b/gerrit-plugin-archetype/.gitignore
@@ -1,6 +1,5 @@
/target
/.classpath
/.project
-/.settings/org.eclipse.m2e.core.prefs
/.settings/org.maven.ide.eclipse.prefs
-/gerrit-ehcache.iml
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
similarity index 70%
copy from gerrit-ehcache/.settings/org.eclipse.core.resources.prefs
copy to gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
index 97e731b..abdea9ac 100644
--- a/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,4 @@
-#Tue May 15 09:21:09 PDT 2012
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
similarity index 99%
copy from gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs
copy to gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
index e89c048..470942d 100644
--- a/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs
+++ b/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
@@ -1,4 +1,4 @@
-#Thu Jan 19 12:55:44 PST 2012
+#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
new file mode 100644
index 0000000..dd1794b
--- /dev/null
+++ b/gerrit-plugin-archetype/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-parent</artifactId>
+ <version>2.5-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>gerrit-plugin-archetype</artifactId>
+ <name>Gerrit Code Review - Plugin Archetype</name>
+
+ <properties>
+ <defaultGerritApiVersion>${project.version}</defaultGerritApiVersion>
+ </properties>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>META-INF/maven/archetype-metadata.xml</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>false</filtering>
+ <excludes>
+ <exclude>META-INF/maven/archetype-metadata.xml</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ </build>
+
+</project>
diff --git a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
new file mode 100644
index 0000000..88328be
--- /dev/null
+++ b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<archetype-descriptor name="com.sap.ldi.qi.itests">
+ <requiredProperties>
+ <requiredProperty key="pluginName"/>
+
+ <requiredProperty key="Gerrit-Module">
+ <defaultValue>Y</defaultValue>
+ </requiredProperty>
+ <requiredProperty key="Gerrit-SshModule">
+ <defaultValue>Y</defaultValue>
+ </requiredProperty>
+ <requiredProperty key="Gerrit-HttpModule">
+ <defaultValue>Y</defaultValue>
+ </requiredProperty>
+
+ <requiredProperty key="Implementation-Vendor"/>
+ <requiredProperty key="Implementation-Url"/>
+
+ <requiredProperty key="gerritApiType">
+ <defaultValue>plugin</defaultValue>
+ </requiredProperty>
+ <requiredProperty key="gerritApiVersion">
+ <defaultValue>${defaultGerritApiVersion}</defaultValue>
+ </requiredProperty>
+ </requiredProperties>
+
+ <fileSets>
+ <fileSet filtered="true" packaged="true">
+ <directory>src/main/java</directory>
+ <includes>
+ <include>**/*.java</include>
+ </includes>
+ </fileSet>
+
+ <fileSet filtered="true">
+ <directory>src/main/resources/Documentation</directory>
+ <includes>
+ <include>**/*.md</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory></directory>
+ <includes>
+ <include>.gitignore</include>
+ <include>LICENSE</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+</archetype-descriptor>
diff --git a/gerrit-ehcache/.gitignore b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.gitignore
similarity index 84%
copy from gerrit-ehcache/.gitignore
copy to gerrit-plugin-archetype/src/main/resources/archetype-resources/.gitignore
index fe190c9..80d6257 100644
--- a/gerrit-ehcache/.gitignore
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.gitignore
@@ -1,6 +1,5 @@
/target
/.classpath
/.project
-/.settings/org.eclipse.m2e.core.prefs
/.settings/org.maven.ide.eclipse.prefs
-/gerrit-ehcache.iml
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/LICENSE b/gerrit-plugin-archetype/src/main/resources/archetype-resources/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 0000000..92099fa
--- /dev/null
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,103 @@
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>${groupId}</groupId>
+ <artifactId>${artifactId}</artifactId>
+ <packaging>jar</packaging>
+ <version>${version}</version>
+ <name>${pluginName}</name>
+
+ <properties>
+ <Gerrit-ApiType>${gerritApiType}</Gerrit-ApiType>
+ <Gerrit-ApiVersion>${gerritApiVersion}</Gerrit-ApiVersion>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+#if ($Gerrit-Module.equalsIgnoreCase("Y"))
+ <Gerrit-Module>${package}.Module</Gerrit-Module>
+#end
+#if ($Gerrit-SshModule.equalsIgnoreCase("Y"))
+ <Gerrit-SshModule>${package}.SshModule</Gerrit-SshModule>
+#end
+#if ($Gerrit-HttpModule.equalsIgnoreCase("Y"))
+ <Gerrit-HttpModule>${package}.HttpModule</Gerrit-HttpModule>
+#end
+
+ <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
+ <Implementation-URL>${Implementation-Url}</Implementation-URL>
+
+ <Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+
+ <Gerrit-ApiType>${Gerrit-ApiType}</Gerrit-ApiType>
+ <Gerrit-ApiVersion>${Gerrit-ApiVersion}</Gerrit-ApiVersion>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-${Gerrit-ApiType}-api</artifactId>
+ <version>${Gerrit-ApiVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>gerrit-api-repository</id>
+#if ($gerritApiVersion.endsWith("SNAPSHOT"))
+ <url>https://gerrit-api.commondatastorage.googleapis.com/snapshot/</url>
+#else
+ <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
+#end
+ </repository>
+ </repositories>
+</project>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
similarity index 70%
copy from gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
copy to gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
index 3370b08..2840112 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package ${package};
-public interface CachePool {
- public <K, V> ProxyCache<K, V> register(CacheProvider<K, V> provider);
+import com.google.inject.servlet.ServletModule;
+
+class HttpModule extends ServletModule {
+ @Override
+ protected void configureServlets() {
+ // TODO
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
similarity index 71%
rename from gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
rename to gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
index 3370b08..0d28349 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package ${package};
-public interface CachePool {
- public <K, V> ProxyCache<K, V> register(CacheProvider<K, V> provider);
+import com.google.inject.AbstractModule;
+
+class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ // TODO
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java
similarity index 66%
copy from gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
copy to gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java
index 3370b08..aa15ca5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package ${package};
-public interface CachePool {
- public <K, V> ProxyCache<K, V> register(CacheProvider<K, V> provider);
+import com.google.gerrit.sshd.PluginCommandModule;
+
+class SshModule extends PluginCommandModule {
+ @Override
+ protected void configureCommands() {
+ // command("my-command").to(MyCommand.class);
+ }
}
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/cmd-start.md b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/cmd-start.md
new file mode 100644
index 0000000..beecb90
--- /dev/null
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/cmd-start.md
@@ -0,0 +1 @@
+TODO: command documentation
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/config.md b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..bde3084
--- /dev/null
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/config.md
@@ -0,0 +1 @@
+TODO: config documentation
diff --git a/gerrit-prettify/.settings/org.eclipse.core.resources.prefs b/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
index e7d6680..abdea9ac 100644
--- a/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs b/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
index e7d6680..abdea9ac 100644
--- a/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
index 1f244bb..061ef3e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
@@ -79,30 +79,10 @@
}
}
- /** Distinguished name, within organization directory server. */
- public static class ExternalNameKey extends
- StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- @Column(id = 1)
- protected String name;
-
- protected ExternalNameKey() {
- }
-
- public ExternalNameKey(final String n) {
- name = n;
- }
-
- @Override
- public String get() {
- return name;
- }
-
- @Override
- protected void set(String newValue) {
- name = newValue;
- }
+ /** @return true if the UUID is for a group managed within Gerrit. */
+ public static boolean isInternalGroup(AccountGroup.UUID uuid) {
+ return uuid.get().startsWith("global:")
+ || uuid.get().matches("^[0-9a-f]{40}$");
}
/** Synthetic key to link to within the database */
@@ -157,20 +137,7 @@
* who is a member of the owner group. These groups are not treated special
* in the code.
*/
- INTERNAL,
-
- /**
- * Group defined by external LDAP database.
- * <p>
- * A group whose membership is determined by the LDAP directory that we
- * connect to for user and group information. In UI contexts the membership
- * of the group is not displayed, as it may be exceedingly large, or might
- * contain users who have never logged into this server before (and thus
- * have no matching account record). Adding or removing users from an LDAP
- * group requires making edits through the LDAP directory, and cannot be
- * done through our UI.
- */
- LDAP;
+ INTERNAL;
}
/** Common UUID assigned to the "Project Owners" placeholder group. */
@@ -201,10 +168,6 @@
@Column(id = 5, length = 8)
protected String groupType;
- /** Distinguished name in the directory server. */
- @Column(id = 6, notNull = false)
- protected ExternalNameKey externalName;
-
@Column(id = 7)
protected boolean visibleToAll;
@@ -273,14 +236,6 @@
groupType = t.name();
}
- public ExternalNameKey getExternalNameKey() {
- return externalName;
- }
-
- public void setExternalNameKey(final ExternalNameKey k) {
- externalName = k;
- }
-
public void setVisibleToAll(final boolean visibleToAll) {
this.visibleToAll = visibleToAll;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
index 962426b..b615fc5 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
@@ -18,6 +18,9 @@
/** Login relies upon the OpenID standard: {@link "http://openid.net/"} */
OPENID,
+ /** Login relies upon the OpenID standard: {@link "http://openid.net/"} in Single Sign On mode */
+ OPENID_SSO,
+
/**
* Login relies upon the container/web server security.
* <p>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
index 9e88244..1de80f3 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
@@ -29,10 +29,6 @@
@Query("WHERE groupUUID = ?")
ResultSet<AccountGroup> byUUID(AccountGroup.UUID uuid) throws OrmException;
- @Query("WHERE externalName = ?")
- ResultSet<AccountGroup> byExternalName(AccountGroup.ExternalNameKey name)
- throws OrmException;
-
@Query
ResultSet<AccountGroup> all() throws OrmException;
}
diff --git a/gerrit-server/.settings/org.eclipse.core.resources.prefs b/gerrit-server/.settings/org.eclipse.core.resources.prefs
index 7d5f965..29abf99 100644
--- a/gerrit-server/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-server/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index ceb3c55..af18173 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -110,6 +110,11 @@
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-antlr</artifactId>
<version>${project.version}</version>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 832bd23..935a707 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -35,8 +35,9 @@
import com.google.gerrit.server.events.ChangeAbandonedEvent;
import com.google.gerrit.server.events.ChangeEvent;
import com.google.gerrit.server.events.ChangeMergedEvent;
-import com.google.gerrit.server.events.ChangeRestoreEvent;
+import com.google.gerrit.server.events.ChangeRestoredEvent;
import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.DraftPublishedEvent;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.PatchSetCreatedEvent;
import com.google.gerrit.server.events.RefUpdatedEvent;
@@ -98,6 +99,9 @@
/** Filename of the new patchset hook. */
private final File patchsetCreatedHook;
+ /** Filename of the draft published hook. */
+ private final File draftPublishedHook;
+
/** Filename of the new comments hook. */
private final File commentAddedHook;
@@ -107,7 +111,7 @@
/** Filename of the change abandoned hook. */
private final File changeAbandonedHook;
- /** Filename of the change abandoned hook. */
+ /** Filename of the change restored hook. */
private final File changeRestoredHook;
/** Filename of the ref updated hook. */
@@ -163,6 +167,7 @@
final File hooksPath = sitePath.resolve(getValue(config, "hooks", "path", sitePath.hooks_dir.getAbsolutePath()));
patchsetCreatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "patchsetCreatedHook", "patchset-created")).getPath());
+ draftPublishedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "draftPublishedHook", "draft-published")).getPath());
commentAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "commentAddedHook", "comment-added")).getPath());
changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath());
changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath());
@@ -237,6 +242,28 @@
runHook(change.getProject(), patchsetCreatedHook, args);
}
+ public void doDraftPublishedHook(final Change change, final PatchSet patchSet,
+ final ReviewDb db) throws OrmException {
+ final DraftPublishedEvent event = new DraftPublishedEvent();
+ final AccountState uploader = accountCache.get(patchSet.getUploader());
+
+ event.change = eventFactory.asChangeAttribute(change);
+ event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+ event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
+ fireEvent(change, event, db);
+
+ final List<String> args = new ArrayList<String>();
+ addArg(args, "--change", event.change.id);
+ addArg(args, "--change-url", event.change.url);
+ addArg(args, "--project", event.change.project);
+ addArg(args, "--branch", event.change.branch);
+ addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
+ addArg(args, "--commit", event.patchSet.revision);
+ addArg(args, "--patchset", event.patchSet.number);
+
+ runHook(change.getProject(), draftPublishedHook, args);
+ }
+
public void doCommentAddedHook(final Change change, final Account account,
final PatchSet patchSet, final String comment, final Map<ApprovalCategory.Id,
ApprovalCategoryValue.Id> approvals, final ReviewDb db) throws OrmException {
@@ -312,9 +339,9 @@
runHook(change.getProject(), changeAbandonedHook, args);
}
- public void doChangeRestoreHook(final Change change, final Account account,
+ public void doChangeRestoredHook(final Change change, final Account account,
final String reason, final ReviewDb db) throws OrmException {
- final ChangeRestoreEvent event = new ChangeRestoreEvent();
+ final ChangeRestoredEvent event = new ChangeRestoredEvent();
event.change = eventFactory.asChangeAttribute(change);
event.restorer = eventFactory.asAccountAttribute(account);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
index dc258ca..134057d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -47,11 +47,21 @@
ReviewDb db) throws OrmException;
/**
+ * Fire the Draft Published Hook.
+ *
+ * @param change The change itself.
+ * @param patchSet The Patchset that was created.
+ * @throws OrmException
+ */
+ public void doDraftPublishedHook(Change change, PatchSet patchSet,
+ ReviewDb db) throws OrmException;
+
+ /**
* Fire the Comment Added Hook.
*
* @param change The change itself.
* @param patchSet The patchset this comment is related to.
- * @param account The gerrit user who commited the change.
+ * @param account The gerrit user who added the comment.
* @param comment The comment given.
* @param approvals Map of Approval Categories and Scores
* @throws OrmException
@@ -65,7 +75,7 @@
* Fire the Change Merged Hook.
*
* @param change The change itself.
- * @param account The gerrit user who commited the change.
+ * @param account The gerrit user who submitted the change.
* @param patchSet The patchset that was merged.
* @throws OrmException
*/
@@ -91,7 +101,7 @@
* @param reason Reason for restoring the change.
* @throws OrmException
*/
- public void doChangeRestoreHook(Change change, Account account,
+ public void doChangeRestoredHook(Change change, Account account,
String reason, ReviewDb db) throws OrmException;
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index 496a273..f30f5ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -46,7 +46,7 @@
}
@Override
- public void doChangeRestoreHook(Change change, Account account,
+ public void doChangeRestoredHook(Change change, Account account,
String reason, ReviewDb db) {
}
@@ -66,6 +66,11 @@
}
@Override
+ public void doDraftPublishedHook(Change change, PatchSet patchSet,
+ ReviewDb db) {
+ }
+
+ @Override
public void doRefUpdatedHook(NameKey refName, RefUpdate refUpdate,
Account account) {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index 2ba1304..1185fd3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
@@ -80,8 +81,10 @@
ObjectId b = ObjectId.fromString(psInfo.getRevId());
Whitespace ws = Whitespace.IGNORE_NONE;
PatchListKey plKey = new PatchListKey(projectKey, a, b, ws);
- PatchList patchList = plCache.get(plKey);
- if (patchList == null) {
+ PatchList patchList;
+ try {
+ patchList = plCache.get(plKey);
+ } catch (PatchListNotAvailableException e) {
throw new SystemException("Cannot create " + plKey);
}
return patchList;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 556ae82..a295c49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -124,14 +124,14 @@
Account.Id authorId = info.getAuthor() != null
? info.getAuthor().getAccount()
: null;
- if (authorId != null) {
+ if (authorId != null && !ps.isDraft()) {
need.add(authorId);
}
Account.Id committerId = info.getCommitter() != null
? info.getCommitter().getAccount()
: null;
- if (committerId != null) {
+ if (committerId != null && !ps.isDraft()) {
need.add(committerId);
}
need.remove(change.getOwner());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 4b98447..a0814f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -63,6 +63,8 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.Base64;
import org.eclipse.jgit.util.NB;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -74,6 +76,9 @@
import java.util.regex.Matcher;
public class ChangeUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(ChangeUtil.class);
+
private static int uuidPrefix;
private static int uuidSeq;
@@ -585,21 +590,21 @@
public static <T extends ReplyToChangeSender> void updatedChange(
final ReviewDb db, final IdentifiedUser user, final Change change,
- final ChangeMessage cmsg, ReplyToChangeSender.Factory<T> senderFactory,
- final String err) throws NoSuchChangeException,
- InvalidChangeOperationException, EmailException, OrmException {
- if (change == null) {
- throw new InvalidChangeOperationException(err);
- }
+ final ChangeMessage cmsg, ReplyToChangeSender.Factory<T> senderFactory)
+ throws NoSuchChangeException, EmailException, OrmException {
db.changeMessages().insert(Collections.singleton(cmsg));
new ApprovalsUtil(db, null).syncChangeStatus(change);
// Email the reviewers
- final ReplyToChangeSender cm = senderFactory.create(change);
- cm.setFrom(user.getAccountId());
- cm.setChangeMessage(cmsg);
- cm.send();
+ try {
+ final ReplyToChangeSender cm = senderFactory.create(change);
+ cm.setFrom(user.getAccountId());
+ cm.setChangeMessage(cmsg);
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot email update for change " + change.getChangeId(), e);
+ }
}
public static String sortKey(long lastUpdated, int id){
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
new file mode 100644
index 0000000..e64533c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.args4j.AccountGroupIdHandler;
+import com.google.gerrit.server.args4j.AccountGroupUUIDHandler;
+import com.google.gerrit.server.args4j.AccountIdHandler;
+import com.google.gerrit.server.args4j.ChangeIdHandler;
+import com.google.gerrit.server.args4j.ObjectIdHandler;
+import com.google.gerrit.server.args4j.PatchSetIdHandler;
+import com.google.gerrit.server.args4j.ProjectControlHandler;
+import com.google.gerrit.server.args4j.SocketAddressHandler;
+import com.google.gerrit.util.cli.CmdLineParser;
+import com.google.gerrit.util.cli.OptionHandlerUtil;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import org.kohsuke.args4j.spi.OptionHandler;
+
+import java.net.SocketAddress;
+
+public class CmdLineParserModule extends FactoryModule {
+ public CmdLineParserModule() {
+ }
+
+ @Override
+ protected void configure() {
+ factory(CmdLineParser.Factory.class);
+
+ registerOptionHandler(Account.Id.class, AccountIdHandler.class);
+ registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class);
+ registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class);
+ registerOptionHandler(Change.Id.class, ChangeIdHandler.class);
+ registerOptionHandler(ObjectId.class, ObjectIdHandler.class);
+ registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
+ registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
+ registerOptionHandler(SocketAddress.class, SocketAddressHandler.class);
+ }
+
+ private <T> void registerOptionHandler(Class<T> type,
+ Class<? extends OptionHandler<T>> impl) {
+ install(OptionHandlerUtil.moduleFor(type, impl));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 6e519c4..89cbac1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -24,8 +26,9 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
-import com.google.gerrit.server.account.MaterializedGroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AuthConfig;
@@ -46,13 +49,10 @@
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.URL;
-import java.util.AbstractSet;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
@@ -70,7 +70,7 @@
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
- private final MaterializedGroupMembership.Factory groupMembershipFactory;
+ private final GroupBackend groupBackend;
@Inject
GenericFactory(
@@ -79,14 +79,14 @@
final @AnonymousCowardName String anonymousCowardName,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
- final MaterializedGroupMembership.Factory groupMembershipFactory) {
+ final GroupBackend groupBackend) {
this.capabilityControlFactory = capabilityControlFactory;
this.authConfig = authConfig;
this.anonymousCowardName = anonymousCowardName;
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
- this.groupMembershipFactory = groupMembershipFactory;
+ this.groupBackend = groupBackend;
}
public IdentifiedUser create(final Account.Id id) {
@@ -96,14 +96,14 @@
public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, AccessPath.UNKNOWN,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
- groupMembershipFactory, null, db, id);
+ groupBackend, null, db, id);
}
public IdentifiedUser create(AccessPath accessPath,
Provider<SocketAddress> remotePeerProvider, Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, accessPath,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
- groupMembershipFactory, remotePeerProvider, null, id);
+ groupBackend, remotePeerProvider, null, id);
}
}
@@ -121,7 +121,7 @@
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
- private final MaterializedGroupMembership.Factory groupMembershipFactory;
+ private final GroupBackend groupBackend;
private final Provider<SocketAddress> remotePeerProvider;
private final Provider<ReviewDb> dbProvider;
@@ -133,7 +133,7 @@
final @AnonymousCowardName String anonymousCowardName,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
- final MaterializedGroupMembership.Factory groupMembershipFactory,
+ final GroupBackend groupBackend,
final @RemotePeer Provider<SocketAddress> remotePeerProvider,
final Provider<ReviewDb> dbProvider) {
@@ -143,7 +143,7 @@
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
- this.groupMembershipFactory = groupMembershipFactory;
+ this.groupBackend = groupBackend;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
@@ -153,40 +153,22 @@
final Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, accessPath,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
- groupMembershipFactory, remotePeerProvider, dbProvider, id);
+ groupBackend, remotePeerProvider, dbProvider, id);
}
}
private static final Logger log =
LoggerFactory.getLogger(IdentifiedUser.class);
- private static final Set<AccountGroup.UUID> registeredGroups =
- new AbstractSet<AccountGroup.UUID>() {
- private final List<AccountGroup.UUID> groups =
- Collections.unmodifiableList(Arrays.asList(new AccountGroup.UUID[] {
- AccountGroup.ANONYMOUS_USERS, AccountGroup.REGISTERED_USERS}));
-
- @Override
- public boolean contains(Object o) {
- return groups.contains(o);
- }
-
- @Override
- public Iterator<AccountGroup.UUID> iterator() {
- return groups.iterator();
- }
-
- @Override
- public int size() {
- return groups.size();
- }
- };
+ private static final GroupMembership registeredGroups =
+ new ListGroupMembership(ImmutableSet.of(
+ AccountGroup.ANONYMOUS_USERS,
+ AccountGroup.REGISTERED_USERS));
private final Provider<String> canonicalUrl;
- private final Realm realm;
private final AccountCache accountCache;
- private final MaterializedGroupMembership.Factory groupMembershipFactory;
private final AuthConfig authConfig;
+ private final GroupBackend groupBackend;
private final String anonymousCowardName;
@Nullable
@@ -210,14 +192,13 @@
final String anonymousCowardName,
final Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
- final MaterializedGroupMembership.Factory groupMembershipFactory,
+ final GroupBackend groupBackend,
@Nullable final Provider<SocketAddress> remotePeerProvider,
@Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
super(capabilityControlFactory, accessPath);
this.canonicalUrl = canonicalUrl;
- this.realm = realm;
this.accountCache = accountCache;
- this.groupMembershipFactory = groupMembershipFactory;
+ this.groupBackend = groupBackend;
this.authConfig = authConfig;
this.anonymousCowardName = anonymousCowardName;
this.remotePeerProvider = remotePeerProvider;
@@ -225,7 +206,8 @@
this.accountId = id;
}
- private AccountState state() {
+ // TODO(cranger): maybe get the state through the accountCache instead.
+ public AccountState state() {
if (state == null) {
state = accountCache.get(getAccountId());
}
@@ -268,16 +250,23 @@
return emailAddresses;
}
+ public String getName() {
+ return new AccountInfo(getAccount()).getName(anonymousCowardName);
+ }
+
+ public String getNameEmail() {
+ return new AccountInfo(getAccount()).getNameEmail(anonymousCowardName);
+ }
+
@Override
public GroupMembership getEffectiveGroups() {
if (effectiveGroups == null) {
if (authConfig.isIdentityTrustable(state().getExternalIds())) {
- effectiveGroups = realm.groups(state());
+ effectiveGroups = groupBackend.membershipsOf(this);
} else {
- effectiveGroups = groupMembershipFactory.create(registeredGroups);
+ effectiveGroups = registeredGroups;
}
}
-
return effectiveGroups;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java
index 8847f96..ea1f1d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java
@@ -29,7 +29,8 @@
*
* @param repoManager Git repository manager to open the git repository
* @param branch the branch for which it should be checked if it exists
- * @return <code>true</code> if the specified branch exists, otherwise
+ * @return <code>true</code> if the specified branch exists or if
+ * <code>HEAD</code> points to this branch, otherwise
* <code>false</code>
* @throws RepositoryNotFoundException the repository of the branch's project
* does not exist.
@@ -40,7 +41,11 @@
IOException {
final Repository repo = repoManager.openRepository(branch.getParentKey());
try {
- return repo.getRef(branch.get()) != null;
+ boolean exists = repo.getRef(branch.get()) != null;
+ if (!exists) {
+ exists = repo.getFullBranch().equals(branch.get());
+ }
+ return exists;
} finally {
repo.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
new file mode 100644
index 0000000..fe1072d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+public class StringUtil {
+ /**
+ * An array of the string representations that should be used in place
+ * of the non-printable characters in the beginning of the ASCII table
+ * when escaping a string. The index of each element in the array
+ * corresponds to its ASCII value, i.e. the string representation of
+ * ASCII 0 is found in the first element of this array.
+ */
+ static String[] NON_PRINTABLE_CHARS =
+ { "\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
+ "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
+ "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+ "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d", "\\x1e", "\\x1f" };
+
+ /**
+ * Escapes the input string so that all non-printable characters
+ * (0x00-0x1f) are represented as a hex escape (\x00, \x01, ...)
+ * or as a C-style escape sequence (\a, \b, \t, \n, \v, \f, or \r).
+ * Backslashes in the input string are doubled (\\).
+ */
+ public static String escapeString(final String str) {
+ // Allocate a buffer big enough to cover the case with a string needed
+ // very excessive escaping without having to reallocate the buffer.
+ final StringBuilder result = new StringBuilder(3 * str.length());
+
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c < NON_PRINTABLE_CHARS.length) {
+ result.append(NON_PRINTABLE_CHARS[c]);
+ } else if (c == '\\') {
+ result.append("\\\\");
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
index 72fb2e8..4827ed5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
@@ -14,12 +14,14 @@
package com.google.gerrit.server.account;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -27,45 +29,58 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.Collections;
-import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
/** Translates an email address to a set of matching accounts. */
@Singleton
public class AccountByEmailCacheImpl implements AccountByEmailCache {
+ private static final Logger log = LoggerFactory
+ .getLogger(AccountByEmailCacheImpl.class);
private static final String CACHE_NAME = "accounts_byemail";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<String, Set<Account.Id>>> type =
- new TypeLiteral<Cache<String, Set<Account.Id>>>() {};
- core(type, CACHE_NAME).populateWith(Loader.class);
+ cache(CACHE_NAME,
+ String.class,
+ new TypeLiteral<Set<Account.Id>>() {})
+ .loader(Loader.class);
bind(AccountByEmailCacheImpl.class);
bind(AccountByEmailCache.class).to(AccountByEmailCacheImpl.class);
}
};
}
- private final Cache<String, Set<Account.Id>> cache;
+ private final LoadingCache<String, Set<Account.Id>> cache;
@Inject
AccountByEmailCacheImpl(
- @Named(CACHE_NAME) final Cache<String, Set<Account.Id>> cache) {
+ @Named(CACHE_NAME) LoadingCache<String, Set<Account.Id>> cache) {
this.cache = cache;
}
public Set<Account.Id> get(final String email) {
- return cache.get(email);
+ try {
+ return cache.get(email);
+ } catch (ExecutionException e) {
+ log.warn("Cannot resolve accounts by email", e);
+ return Collections.emptySet();
+ }
}
public void evict(final String email) {
- cache.remove(email);
+ if (email != null) {
+ cache.invalidate(email);
+ }
}
- static class Loader extends EntryCreator<String, Set<Account.Id>> {
+ static class Loader extends CacheLoader<String, Set<Account.Id>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -74,10 +89,10 @@
}
@Override
- public Set<Account.Id> createEntry(final String email) throws Exception {
+ public Set<Account.Id> load(String email) throws Exception {
final ReviewDb db = schema.open();
try {
- final HashSet<Account.Id> r = new HashSet<Account.Id>();
+ Set<Account.Id> r = Sets.newHashSet();
for (Account a : db.accounts().byPreferredEmail(email)) {
r.add(a.getId());
}
@@ -85,30 +100,10 @@
.byEmailAddress(email)) {
r.add(a.getAccountId());
}
- return pack(r);
+ return ImmutableSet.copyOf(r);
} finally {
db.close();
}
}
-
- @Override
- public Set<Account.Id> missing(final String key) {
- return Collections.emptySet();
- }
-
- private static Set<Account.Id> pack(final Set<Account.Id> c) {
- switch (c.size()) {
- case 0:
- return Collections.emptySet();
- case 1:
- return one(c);
- default:
- return Collections.unmodifiableSet(new HashSet<Account.Id>(c));
- }
- }
-
- private static <T> Set<T> one(final Set<T> c) {
- return Collections.singleton(c.iterator().next());
- }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 819ec31..4217f9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -14,14 +14,16 @@
package com.google.gerrit.server.account;
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -30,14 +32,21 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
/** Caches important (but small) account state to avoid database hits. */
@Singleton
public class AccountCacheImpl implements AccountCache {
+ private static final Logger log = LoggerFactory
+ .getLogger(AccountCacheImpl.class);
+
private static final String BYID_NAME = "accounts";
private static final String BYUSER_NAME = "accounts_byname";
@@ -45,13 +54,13 @@
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<Account.Id, AccountState>> byIdType =
- new TypeLiteral<Cache<Account.Id, AccountState>>() {};
- core(byIdType, BYID_NAME).populateWith(ByIdLoader.class);
+ cache(BYID_NAME, Account.Id.class, AccountState.class)
+ .loader(ByIdLoader.class);
- final TypeLiteral<Cache<String, Account.Id>> byUsernameType =
- new TypeLiteral<Cache<String, Account.Id>>() {};
- core(byUsernameType, BYUSER_NAME).populateWith(ByNameLoader.class);
+ cache(BYUSER_NAME,
+ String.class,
+ new TypeLiteral<Optional<Account.Id>>() {})
+ .loader(ByNameLoader.class);
bind(AccountCacheImpl.class);
bind(AccountCache.class).to(AccountCacheImpl.class);
@@ -59,54 +68,76 @@
};
}
- private final Cache<Account.Id, AccountState> byId;
- private final Cache<String, Account.Id> byName;
+ private final LoadingCache<Account.Id, AccountState> byId;
+ private final LoadingCache<String, Optional<Account.Id>> byName;
@Inject
- AccountCacheImpl(@Named(BYID_NAME) Cache<Account.Id, AccountState> byId,
- @Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
+ AccountCacheImpl(@Named(BYID_NAME) LoadingCache<Account.Id, AccountState> byId,
+ @Named(BYUSER_NAME) LoadingCache<String, Optional<Account.Id>> byUsername) {
this.byId = byId;
this.byName = byUsername;
}
- public AccountState get(final Account.Id accountId) {
- return byId.get(accountId);
+ public AccountState get(Account.Id accountId) {
+ try {
+ return byId.get(accountId);
+ } catch (ExecutionException e) {
+ log.warn("Cannot load AccountState for " + accountId, e);
+ return missing(accountId);
+ }
}
@Override
public AccountState getByUsername(String username) {
- Account.Id id = byName.get(username);
- return id != null ? byId.get(id) : null;
+ try {
+ Optional<Account.Id> id = byName.get(username);
+ return id != null && id.isPresent() ? byId.get(id.get()) : null;
+ } catch (ExecutionException e) {
+ log.warn("Cannot load AccountState for " + username, e);
+ return null;
+ }
}
- public void evict(final Account.Id accountId) {
- byId.remove(accountId);
+ public void evict(Account.Id accountId) {
+ if (accountId != null) {
+ byId.invalidate(accountId);
+ }
}
public void evictByUsername(String username) {
- byName.remove(username);
+ if (username != null) {
+ byName.invalidate(username);
+ }
}
- static class ByIdLoader extends EntryCreator<Account.Id, AccountState> {
+ private static AccountState missing(Account.Id accountId) {
+ Account account = new Account(accountId);
+ Collection<AccountExternalId> ids = Collections.emptySet();
+ Set<AccountGroup.UUID> anon = ImmutableSet.of(AccountGroup.ANONYMOUS_USERS);
+ return new AccountState(account, anon, ids);
+ }
+
+ static class ByIdLoader extends CacheLoader<Account.Id, AccountState> {
private final SchemaFactory<ReviewDb> schema;
private final GroupCache groupCache;
- private final Cache<String, Account.Id> byName;
+ private final LoadingCache<String, Optional<Account.Id>> byName;
@Inject
ByIdLoader(SchemaFactory<ReviewDb> sf, GroupCache groupCache,
- @Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
+ @Named(BYUSER_NAME) LoadingCache<String, Optional<Account.Id>> byUsername) {
this.schema = sf;
this.groupCache = groupCache;
this.byName = byUsername;
}
@Override
- public AccountState createEntry(final Account.Id key) throws Exception {
+ public AccountState load(Account.Id key) throws Exception {
final ReviewDb db = schema.open();
try {
final AccountState state = load(db, key);
- if (state.getUserName() != null) {
- byName.put(state.getUserName(), state.getAccount().getId());
+ String user = state.getUserName();
+ if (user != null) {
+ byName.put(user, Optional.of(state.getAccount().getId()));
}
return state;
} finally {
@@ -142,18 +173,9 @@
return new AccountState(account, internalGroups, externalIds);
}
-
- @Override
- public AccountState missing(final Account.Id accountId) {
- final Account account = new Account(accountId);
- final Collection<AccountExternalId> ids = Collections.emptySet();
- final Set<AccountGroup.UUID> anonymous =
- Collections.singleton(AccountGroup.ANONYMOUS_USERS);
- return new AccountState(account, anonymous, ids);
- }
}
- static class ByNameLoader extends EntryCreator<String, Account.Id> {
+ static class ByNameLoader extends CacheLoader<String, Optional<Account.Id>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -162,14 +184,17 @@
}
@Override
- public Account.Id createEntry(final String username) throws Exception {
+ public Optional<Account.Id> load(String username) throws Exception {
final ReviewDb db = schema.open();
try {
final AccountExternalId.Key key = new AccountExternalId.Key( //
AccountExternalId.SCHEME_USERNAME, //
username);
final AccountExternalId id = db.accountExternalIds().get(key);
- return id != null ? id.getAccountId() : null;
+ if (id != null) {
+ return Optional.of(id.getAccountId());
+ }
+ return Optional.absent();
} finally {
db.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
index e297ad7..32b4e2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
@@ -83,10 +83,21 @@
* effective groups.
*/
public boolean canSee(final Account otherUser) {
+ return canSee(otherUser.getId());
+ }
+
+ /**
+ * Returns true if the otherUser is allowed to see the current user, based
+ * on the account visibility policy. Depending on the group membership
+ * realms supported, this may not be able to determine SAME_GROUP or
+ * VISIBLE_GROUP correctly (defaulting to not being visible). This is because
+ * {@link GroupMembership#getKnownGroups()} may only return a subset of the
+ * effective groups.
+ */
+ public boolean canSee(final Account.Id otherUser) {
// Special case: I can always see myself.
if (currentUser instanceof IdentifiedUser
- && ((IdentifiedUser) currentUser).getAccountId()
- .equals(otherUser.getId())) {
+ && ((IdentifiedUser) currentUser).getAccountId().equals(otherUser)) {
return true;
}
@@ -131,7 +142,7 @@
return false;
}
- private Set<AccountGroup.UUID> groupsOf(Account account) {
- return userFactory.create(account.getId()).getEffectiveGroups().getKnownGroups();
+ private Set<AccountGroup.UUID> groupsOf(Account.Id account) {
+ return userFactory.create(account).getEffectiveGroups().getKnownGroups();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
index 3a2ac56..abdf29e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -115,7 +116,21 @@
final int lt = nameOrEmail.indexOf('<');
final int gt = nameOrEmail.indexOf('>');
if (lt >= 0 && gt > lt && nameOrEmail.contains("@")) {
- return byEmail.get(nameOrEmail.substring(lt + 1, gt));
+ Set<Account.Id> ids = byEmail.get(nameOrEmail.substring(lt + 1, gt));
+ if (ids.isEmpty() || ids.size() == 1) {
+ return ids;
+ }
+
+ // more than one match, try to return the best one
+ String name = nameOrEmail.substring(0, lt - 1);
+ Set<Account.Id> nameMatches = Sets.newHashSet();
+ for (Account.Id id : ids) {
+ Account a = byId.get(id).getAccount();
+ if (name.equals(a.getFullName())) {
+ nameMatches.add(id);
+ }
+ }
+ return nameMatches.isEmpty() ? ids : nameMatches;
}
if (nameOrEmail.contains("@")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthMethod.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthMethod.java
new file mode 100644
index 0000000..fdaabd2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthMethod.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+/** Method by which a user has authenticated for a given request. */
+public enum AuthMethod {
+ /** The user is not authenticated */
+ NONE,
+
+ /** The user is authenticated via a cookie. */
+ COOKIE,
+
+ /** The user authenticated with a password for this request. */
+ PASSWORD,
+
+ /** The user has used a credentialess development feature to login. */
+ BACKDOOR;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index eb42921..1524185 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -83,7 +83,7 @@
|| canAdministrateServer();
}
- /** @return true if the user can create a group. */
+ /** @return true if the user can create a project. */
public boolean canCreateProject() {
return canPerform(GlobalCapability.CREATE_PROJECT)
|| canAdministrateServer();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 844e604..c90f3e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -15,25 +15,20 @@
package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.inject.Inject;
-import java.util.Collections;
import java.util.Set;
public class DefaultRealm implements Realm {
private final EmailExpander emailExpander;
private final AccountByEmailCache byEmail;
- private final MaterializedGroupMembership.Factory groupMembershipFactory;
@Inject
DefaultRealm(final EmailExpander emailExpander,
- final AccountByEmailCache byEmail,
- final MaterializedGroupMembership.Factory groupMembershipFactory) {
+ final AccountByEmailCache byEmail) {
this.emailExpander = emailExpander;
this.byEmail = byEmail;
- this.groupMembershipFactory = groupMembershipFactory;
}
@Override
@@ -65,11 +60,6 @@
}
@Override
- public GroupMembership groups(final AccountState who) {
- return groupMembershipFactory.create(who.getInternalGroups());
- }
-
- @Override
public Account.Id lookup(final String accountName) {
if (emailExpander.canExpand(accountName)) {
final Set<Account.Id> c = byEmail.get(emailExpander.expand(accountName));
@@ -79,9 +69,4 @@
}
return null;
}
-
- @Override
- public Set<AccountGroup.ExternalNameKey> lookupGroups(String name) {
- return Collections.emptySet();
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java
new file mode 100644
index 0000000..b4e770f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.IdentifiedUser;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+/**
+ * Implementations of GroupBackend provide lookup and membership accessors
+ * to a group system.
+ */
+@ExtensionPoint
+public interface GroupBackend {
+ /** @return {@code true} if the backend can operate on the UUID. */
+ boolean handles(AccountGroup.UUID uuid);
+
+ /**
+ * Looks up a group in the backend. If the group does not exist, null is
+ * returned.
+ *
+ * @param uuid the group identifier
+ * @return the group
+ */
+ @Nullable
+ GroupDescription.Basic get(AccountGroup.UUID uuid);
+
+ /** @return suggestions for the group name sorted by name. */
+ Collection<GroupReference> suggest(String name);
+
+ /** @return the group membership checker for the backend. */
+ GroupMembership membershipsOf(IdentifiedUser user);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackends.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackends.java
new file mode 100644
index 0000000..cdbb0e4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackends.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.common.data.GroupReference;
+
+import java.util.Collection;
+import java.util.Comparator;
+
+import javax.annotation.Nullable;
+
+/**
+ * Utility class for dealing with a GroupBackend.
+ */
+public class GroupBackends {
+
+ public static final Comparator<GroupReference> GROUP_REF_NAME_COMPARATOR =
+ new Comparator<GroupReference>() {
+ @Override
+ public int compare(GroupReference a, GroupReference b) {
+ return a.getName().compareTo(b.getName());
+ }
+ };
+
+ /**
+ * Runs {@link GroupBackend#suggest(String)} and filters the result to return
+ * the best suggestion, or null if one does not exist.
+ *
+ * @param groupBackend the group backend
+ * @param name the name for which to suggest groups
+ * @return the best single GroupReference suggestion
+ */
+ @Nullable
+ public static GroupReference findBestSuggestion(
+ GroupBackend groupBackend, String name) {
+ Collection<GroupReference> refs = groupBackend.suggest(name);
+ if (refs.size() == 1) {
+ return Iterables.getOnlyElement(refs);
+ }
+
+ for (GroupReference ref : refs) {
+ if (isExactSuggestion(ref, name)) {
+ return ref;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Runs {@link GroupBackend#suggest(String)} and filters the result to return
+ * the exact suggestion, or null if one does not exist.
+ *
+ * @param groupBackend the group backend
+ * @param name the name for which to suggest groups
+ * @return the exact single GroupReference suggestion
+ */
+ @Nullable
+ public static GroupReference findExactSuggestion(
+ GroupBackend groupBackend, String name) {
+ Collection<GroupReference> refs = groupBackend.suggest(name);
+ for (GroupReference ref : refs) {
+ if (isExactSuggestion(ref, name)) {
+ return ref;
+ }
+ }
+ return null;
+ }
+
+ /** Returns whether the GroupReference is an exact suggestion for the name. */
+ public static boolean isExactSuggestion(GroupReference ref, String name) {
+ return ref.getName().equalsIgnoreCase(name) || ref.getUUID().get().equals(name);
+ }
+
+ private GroupBackends() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
index b092ac4..3b9e85f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
@@ -16,8 +16,6 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
-import java.util.Collection;
-
import javax.annotation.Nullable;
/** Tracks group objects in memory for efficient access. */
@@ -34,8 +32,6 @@
@Nullable
public AccountGroup get(AccountGroup.UUID uuid);
- public Collection<AccountGroup> get(AccountGroup.ExternalNameKey externalName);
-
/** @return sorted iteration of groups. */
public abstract Iterable<AccountGroup> all();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
index d29a5e5..b301839 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -14,12 +14,15 @@
package com.google.gerrit.server.account;
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -27,48 +30,41 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
-import java.util.ArrayList;
-import java.util.Collection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.Collections;
import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ExecutionException;
/** Tracks group objects in memory for efficient access. */
@Singleton
public class GroupCacheImpl implements GroupCache {
+ private static final Logger log = LoggerFactory
+ .getLogger(GroupCacheImpl.class);
+
private static final String BYID_NAME = "groups";
private static final String BYNAME_NAME = "groups_byname";
private static final String BYUUID_NAME = "groups_byuuid";
- private static final String BYEXT_NAME = "groups_byext";
- private static final String BYNAME_LIST = "groups_byname_list";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> byId =
- new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {};
- core(byId, BYID_NAME).populateWith(ByIdLoader.class);
+ cache(BYID_NAME,
+ AccountGroup.Id.class,
+ new TypeLiteral<Optional<AccountGroup>>() {})
+ .loader(ByIdLoader.class);
- final TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>> byName =
- new TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>>() {};
- core(byName, BYNAME_NAME).populateWith(ByNameLoader.class);
+ cache(BYNAME_NAME,
+ String.class,
+ new TypeLiteral<Optional<AccountGroup>>() {})
+ .loader(ByNameLoader.class);
- final TypeLiteral<Cache<AccountGroup.UUID, AccountGroup>> byUUID =
- new TypeLiteral<Cache<AccountGroup.UUID, AccountGroup>>() {};
- core(byUUID, BYUUID_NAME).populateWith(ByUUIDLoader.class);
-
- final TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>> byExternalName =
- new TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>>() {};
- core(byExternalName, BYEXT_NAME) //
- .populateWith(ByExternalNameLoader.class);
-
- final TypeLiteral<Cache<ListKey, SortedSet<AccountGroup.NameKey>>> listType =
- new TypeLiteral<Cache<ListKey, SortedSet<AccountGroup.NameKey>>>() {};
- core(listType, BYNAME_LIST).populateWith(Lister.class);
+ cache(BYUUID_NAME,
+ String.class,
+ new TypeLiteral<Optional<AccountGroup>>() {})
+ .loader(ByUUIDLoader.class);
bind(GroupCacheImpl.class);
bind(GroupCache.class).to(GroupCacheImpl.class);
@@ -76,94 +72,113 @@
};
}
- private final Cache<AccountGroup.Id, AccountGroup> byId;
- private final Cache<AccountGroup.NameKey, AccountGroup> byName;
- private final Cache<AccountGroup.UUID, AccountGroup> byUUID;
- private final Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName;
- private final Cache<ListKey,SortedSet<AccountGroup.NameKey>> list;
- private final Lock listLock;
+ private final LoadingCache<AccountGroup.Id, Optional<AccountGroup>> byId;
+ private final LoadingCache<String, Optional<AccountGroup>> byName;
+ private final LoadingCache<String, Optional<AccountGroup>> byUUID;
+ private final SchemaFactory<ReviewDb> schema;
@Inject
GroupCacheImpl(
- @Named(BYID_NAME) Cache<AccountGroup.Id, AccountGroup> byId,
- @Named(BYNAME_NAME) Cache<AccountGroup.NameKey, AccountGroup> byName,
- @Named(BYUUID_NAME) Cache<AccountGroup.UUID, AccountGroup> byUUID,
- @Named(BYEXT_NAME) Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName,
- @Named(BYNAME_LIST) final Cache<ListKey, SortedSet<AccountGroup.NameKey>> list) {
+ @Named(BYID_NAME) LoadingCache<AccountGroup.Id, Optional<AccountGroup>> byId,
+ @Named(BYNAME_NAME) LoadingCache<String, Optional<AccountGroup>> byName,
+ @Named(BYUUID_NAME) LoadingCache<String, Optional<AccountGroup>> byUUID,
+ SchemaFactory<ReviewDb> schema) {
this.byId = byId;
this.byName = byName;
this.byUUID = byUUID;
- this.byExternalName = byExternalName;
- this.list = list;
- this.listLock = new ReentrantLock(true /* fair */);
+ this.schema = schema;
}
+ @Override
public AccountGroup get(final AccountGroup.Id groupId) {
- return byId.get(groupId);
+ try {
+ Optional<AccountGroup> g = byId.get(groupId);
+ return g.isPresent() ? g.get() : missing(groupId);
+ } catch (ExecutionException e) {
+ log.warn("Cannot load group "+groupId, e);
+ return missing(groupId);
+ }
}
+ @Override
public void evict(final AccountGroup group) {
- byId.remove(group.getId());
- byName.remove(group.getNameKey());
- byUUID.remove(group.getGroupUUID());
- byExternalName.remove(group.getExternalNameKey());
+ if (group.getId() != null) {
+ byId.invalidate(group.getId());
+ }
+ if (group.getNameKey() != null) {
+ byName.invalidate(group.getNameKey().get());
+ }
+ if (group.getGroupUUID() != null) {
+ byUUID.invalidate(group.getGroupUUID().get());
+ }
}
+ @Override
public void evictAfterRename(final AccountGroup.NameKey oldName,
final AccountGroup.NameKey newName) {
- byName.remove(oldName);
- updateGroupList(oldName, newName);
+ if (oldName != null) {
+ byName.invalidate(oldName.get());
+ }
+ if (newName != null) {
+ byName.invalidate(newName.get());
+ }
}
- public AccountGroup get(final AccountGroup.NameKey name) {
- return byName.get(name);
+ @Override
+ public AccountGroup get(AccountGroup.NameKey name) {
+ if (name == null) {
+ return null;
+ }
+ try {
+ return byName.get(name.get()).orNull();
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot lookup group %s by name", name.get()), e);
+ return null;
+ }
}
- public AccountGroup get(final AccountGroup.UUID uuid) {
- return byUUID.get(uuid);
- }
-
- public Collection<AccountGroup> get(
- final AccountGroup.ExternalNameKey externalName) {
- return byExternalName.get(externalName);
+ @Override
+ public AccountGroup get(AccountGroup.UUID uuid) {
+ if (uuid == null) {
+ return null;
+ }
+ try {
+ return byUUID.get(uuid.get()).orNull();
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot lookup group %s by name", uuid.get()), e);
+ return null;
+ }
}
@Override
public Iterable<AccountGroup> all() {
- final List<AccountGroup> groups = new ArrayList<AccountGroup>();
- for (final AccountGroup.NameKey groupName : list.get(ListKey.ALL)) {
- final AccountGroup group = get(groupName);
- if (group != null) {
- groups.add(group);
+ try {
+ ReviewDb db = schema.open();
+ try {
+ return Collections.unmodifiableList(db.accountGroups().all().toList());
+ } finally {
+ db.close();
}
+ } catch (OrmException e) {
+ log.warn("Cannot list internal groups", e);
+ return Collections.emptyList();
}
- return Collections.unmodifiableList(groups);
}
@Override
- public void onCreateGroup(final AccountGroup.NameKey newGroupName) {
- updateGroupList(null, newGroupName);
+ public void onCreateGroup(AccountGroup.NameKey newGroupName) {
+ byName.invalidate(newGroupName.get());
}
- private void updateGroupList(final AccountGroup.NameKey nameToRemove,
- final AccountGroup.NameKey nameToAdd) {
- listLock.lock();
- try {
- SortedSet<AccountGroup.NameKey> n = list.get(ListKey.ALL);
- n = new TreeSet<AccountGroup.NameKey>(n);
- if (nameToRemove != null) {
- n.remove(nameToRemove);
- }
- if (nameToAdd != null) {
- n.add(nameToAdd);
- }
- list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
- } finally {
- listLock.unlock();
- }
+ private static AccountGroup missing(AccountGroup.Id key) {
+ AccountGroup.NameKey name = new AccountGroup.NameKey("Deleted Group" + key);
+ AccountGroup g = new AccountGroup(name, key, null);
+ g.setType(AccountGroup.Type.SYSTEM);
+ return g;
}
- static class ByIdLoader extends EntryCreator<AccountGroup.Id, AccountGroup> {
+ static class ByIdLoader extends
+ CacheLoader<AccountGroup.Id, Optional<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -172,32 +187,18 @@
}
@Override
- public AccountGroup createEntry(final AccountGroup.Id key) throws Exception {
+ public Optional<AccountGroup> load(final AccountGroup.Id key)
+ throws Exception {
final ReviewDb db = schema.open();
try {
- final AccountGroup group = db.accountGroups().get(key);
- if (group != null) {
- return group;
- } else {
- return missing(key);
- }
+ return Optional.fromNullable(db.accountGroups().get(key));
} finally {
db.close();
}
}
-
- @Override
- public AccountGroup missing(final AccountGroup.Id key) {
- final AccountGroup.NameKey name =
- new AccountGroup.NameKey("Deleted Group" + key.toString());
- final AccountGroup g = new AccountGroup(name, key, null);
- g.setType(AccountGroup.Type.SYSTEM);
- return g;
- }
}
- static class ByNameLoader extends
- EntryCreator<AccountGroup.NameKey, AccountGroup> {
+ static class ByNameLoader extends CacheLoader<String, Optional<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -206,25 +207,23 @@
}
@Override
- public AccountGroup createEntry(final AccountGroup.NameKey key)
+ public Optional<AccountGroup> load(String name)
throws Exception {
- final AccountGroupName r;
final ReviewDb db = schema.open();
try {
- r = db.accountGroupNames().get(key);
+ AccountGroup.NameKey key = new AccountGroup.NameKey(name);
+ AccountGroupName r = db.accountGroupNames().get(key);
if (r != null) {
- return db.accountGroups().get(r.getId());
- } else {
- return null;
+ return Optional.fromNullable(db.accountGroups().get(r.getId()));
}
+ return Optional.absent();
} finally {
db.close();
}
}
}
- static class ByUUIDLoader extends
- EntryCreator<AccountGroup.UUID, AccountGroup> {
+ static class ByUUIDLoader extends CacheLoader<String, Optional<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -233,74 +232,23 @@
}
@Override
- public AccountGroup createEntry(final AccountGroup.UUID uuid)
+ public Optional<AccountGroup> load(String uuid)
throws Exception {
final ReviewDb db = schema.open();
try {
- List<AccountGroup> r = db.accountGroups().byUUID(uuid).toList();
+ List<AccountGroup> r;
+
+ r = db.accountGroups().byUUID(new AccountGroup.UUID(uuid)).toList();
if (r.size() == 1) {
- return r.get(0);
+ return Optional.of(r.get(0));
+ } else if (r.size() == 0) {
+ return Optional.absent();
} else {
- return null;
+ throw new OrmDuplicateKeyException("Duplicate group UUID " + uuid);
}
} finally {
db.close();
}
}
}
-
- static class ByExternalNameLoader extends
- EntryCreator<AccountGroup.ExternalNameKey, Collection<AccountGroup>> {
- private final SchemaFactory<ReviewDb> schema;
-
- @Inject
- ByExternalNameLoader(final SchemaFactory<ReviewDb> sf) {
- schema = sf;
- }
-
- @Override
- public Collection<AccountGroup> createEntry(
- final AccountGroup.ExternalNameKey key) throws Exception {
- final ReviewDb db = schema.open();
- try {
- return db.accountGroups().byExternalName(key).toList();
- } finally {
- db.close();
- }
- }
- }
-
- static class ListKey {
- static final ListKey ALL = new ListKey();
-
- private ListKey() {
- }
- }
-
- static class Lister extends EntryCreator<ListKey, SortedSet<AccountGroup.NameKey>> {
- private final SchemaFactory<ReviewDb> schema;
-
- @Inject
- Lister(final SchemaFactory<ReviewDb> sf) {
- schema = sf;
- }
-
- @Override
- public SortedSet<AccountGroup.NameKey> createEntry(ListKey key)
- throws Exception {
- final ReviewDb db = schema.open();
- try {
- final List<AccountGroupName> groupNames =
- db.accountGroupNames().all().toList();
- final SortedSet<AccountGroup.NameKey> groups =
- new TreeSet<AccountGroup.NameKey>();
- for (final AccountGroupName groupName : groupNames) {
- groups.add(groupName.getNameKey());
- }
- return Collections.unmodifiableSortedSet(groups);
- } finally {
- db.close();
- }
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
index ea388c7..d9b12ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -27,11 +29,14 @@
public static class Factory {
private final GroupCache groupCache;
private final Provider<CurrentUser> user;
+ private final GroupBackend groupBackend;
@Inject
- Factory(final GroupCache gc, final Provider<CurrentUser> cu) {
+ Factory(final GroupCache gc, final Provider<CurrentUser> cu,
+ final GroupBackend gb) {
groupCache = gc;
user = cu;
+ groupBackend = gb;
}
public GroupControl controlFor(final AccountGroup.Id groupId)
@@ -45,7 +50,7 @@
public GroupControl controlFor(final AccountGroup.UUID groupId)
throws NoSuchGroupException {
- final AccountGroup group = groupCache.get(groupId);
+ final GroupDescription.Basic group = groupBackend.get(groupId);
if (group == null) {
throw new NoSuchGroupException(groupId);
}
@@ -67,22 +72,22 @@
}
private final CurrentUser user;
- private final AccountGroup group;
+ private final GroupDescription.Basic group;
private Boolean isOwner;
- GroupControl(CurrentUser who, AccountGroup gc) {
+ GroupControl(CurrentUser who, GroupDescription.Basic gd) {
user = who;
- group = gc;
+ group = gd;
+ }
+
+ GroupControl(CurrentUser who, AccountGroup ag) {
+ this(who, GroupDescriptions.forAccountGroup(ag));
}
public CurrentUser getCurrentUser() {
return user;
}
- public AccountGroup getAccountGroup() {
- return group;
- }
-
/** Can this user see this group exists? */
public boolean isVisible() {
return group.isVisibleToAll()
@@ -91,8 +96,11 @@
}
public boolean isOwner() {
- if (isOwner == null) {
- AccountGroup.UUID ownerUUID = group.getOwnerGroupUUID();
+ AccountGroup accountGroup = GroupDescriptions.toAccountGroup(group);
+ if (accountGroup == null) {
+ isOwner = false;
+ } else if (isOwner == null) {
+ AccountGroup.UUID ownerUUID = accountGroup.getOwnerGroupUUID();
isOwner = getCurrentUser().getEffectiveGroups().contains(ownerUUID)
|| getCurrentUser().getCapabilities().canAdministrateServer();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
index 2d455e9..2e500bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
@@ -40,6 +41,7 @@
private final ReviewDb db;
private final GroupControl.Factory groupControl;
private final GroupCache groupCache;
+ private final GroupBackend groupBackend;
private final AccountInfoCacheFactory aic;
private final GroupInfoCacheFactory gic;
@@ -49,12 +51,14 @@
@Inject
GroupDetailFactory(final ReviewDb db,
final GroupControl.Factory groupControl, final GroupCache groupCache,
+ final GroupBackend groupBackend,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final GroupInfoCacheFactory.Factory groupInfoCacheFactory,
@Assisted final AccountGroup.Id groupId) {
this.db = db;
this.groupControl = groupControl;
this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
this.aic = accountInfoCacheFactory.create();
this.gic = groupInfoCacheFactory.create();
@@ -64,10 +68,10 @@
@Override
public GroupDetail call() throws OrmException, NoSuchGroupException {
control = groupControl.validateFor(groupId);
- final AccountGroup group = control.getAccountGroup();
+ final AccountGroup group = groupCache.get(groupId);
final GroupDetail detail = new GroupDetail();
detail.setGroup(group);
- AccountGroup ownerGroup = groupCache.get(group.getOwnerGroupUUID());
+ GroupDescription.Basic ownerGroup = groupBackend.get(group.getOwnerGroupUUID());
if (ownerGroup != null) {
detail.setOwnerGroup(GroupReference.forGroup(ownerGroup));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
index 791d0f5..7fbba45 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
@@ -14,12 +14,14 @@
package com.google.gerrit.server.account;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -27,24 +29,30 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
/** Tracks group inclusions in memory for efficient access. */
@Singleton
public class GroupIncludeCacheImpl implements GroupIncludeCache {
+ private static final Logger log = LoggerFactory
+ .getLogger(GroupIncludeCacheImpl.class);
private static final String BYINCLUDE_NAME = "groups_byinclude";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>>> byInclude =
- new TypeLiteral<Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>>>() {};
- core(byInclude, BYINCLUDE_NAME).populateWith(ByIncludeLoader.class);
+ cache(BYINCLUDE_NAME,
+ AccountGroup.UUID.class,
+ new TypeLiteral<Set<AccountGroup.UUID>>() {})
+ .loader(ByIncludeLoader.class);
bind(GroupIncludeCacheImpl.class);
bind(GroupIncludeCache.class).to(GroupIncludeCacheImpl.class);
@@ -52,24 +60,31 @@
};
}
- private final Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>> byInclude;
+ private final LoadingCache<AccountGroup.UUID, Set<AccountGroup.UUID>> byInclude;
@Inject
GroupIncludeCacheImpl(
- @Named(BYINCLUDE_NAME) Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>> byInclude) {
+ @Named(BYINCLUDE_NAME) LoadingCache<AccountGroup.UUID, Set<AccountGroup.UUID>> byInclude) {
this.byInclude = byInclude;
}
public Collection<AccountGroup.UUID> getByInclude(AccountGroup.UUID groupId) {
- return byInclude.get(groupId);
+ try {
+ return byInclude.get(groupId);
+ } catch (ExecutionException e) {
+ log.warn("Cannot load included groups", e);
+ return Collections.emptySet();
+ }
}
public void evictInclude(AccountGroup.UUID groupId) {
- byInclude.remove(groupId);
+ if (groupId != null) {
+ byInclude.invalidate(groupId);
+ }
}
static class ByIncludeLoader extends
- EntryCreator<AccountGroup.UUID, Collection<AccountGroup.UUID>> {
+ CacheLoader<AccountGroup.UUID, Set<AccountGroup.UUID>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -78,32 +93,28 @@
}
@Override
- public Collection<AccountGroup.UUID> createEntry(final AccountGroup.UUID key) throws Exception {
+ public Set<AccountGroup.UUID> load(AccountGroup.UUID key) throws Exception {
final ReviewDb db = schema.open();
try {
List<AccountGroup> group = db.accountGroups().byUUID(key).toList();
if (group.size() != 1) {
- return Collections.emptyList();
+ return Collections.emptySet();
}
- Set<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
- for (AccountGroupInclude agi : db.accountGroupIncludes().byInclude(group.get(0).getId())) {
+ Set<AccountGroup.Id> ids = Sets.newHashSet();
+ for (AccountGroupInclude agi : db.accountGroupIncludes()
+ .byInclude(group.get(0).getId())) {
ids.add(agi.getGroupId());
}
- Set<AccountGroup.UUID> groupArray = new HashSet<AccountGroup.UUID> ();
+ Set<AccountGroup.UUID> groupArray = Sets.newHashSet();
for (AccountGroup g : db.accountGroups().get(ids)) {
groupArray.add(g.getGroupUUID());
}
- return Collections.unmodifiableCollection(groupArray);
+ return ImmutableSet.copyOf(groupArray);
} finally {
db.close();
}
}
-
- @Override
- public Collection<AccountGroup.UUID> missing(final AccountGroup.UUID key) {
- return Collections.emptyList();
- }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
index 9bb571e..d536c09 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
@@ -24,7 +24,6 @@
* the presence of a user in a particular group.
*/
public interface GroupMembership {
-
public static final GroupMembership EMPTY =
new ListGroupMembership(Collections.<AccountGroup.UUID>emptySet());
@@ -45,7 +44,7 @@
* This may not return all groups the {@link #contains(AccountGroup.UUID)}
* would return {@code true} for, but will at least contain all top level
* groups. This restriction stems from the API of some group systems, which
- * make it expensive to enumate the members of a group.
+ * make it expensive to enumerate the members of a group.
*/
Set<AccountGroup.UUID> getKnownGroups();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/MaterializedGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
similarity index 89%
rename from gerrit-server/src/main/java/com/google/gerrit/server/account/MaterializedGroupMembership.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index 81ff656..d448fff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/MaterializedGroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -25,11 +25,12 @@
import java.util.Set;
/**
- * Creates a GroupMembership object from materialized collection of groups.
+ * Creates a GroupMembership checker for the internal group system, which
+ * starts with the seed groups and includes all child groups.
*/
-public class MaterializedGroupMembership implements GroupMembership {
+public class IncludingGroupMembership implements GroupMembership {
public interface Factory {
- MaterializedGroupMembership create(Iterable<AccountGroup.UUID> groupIds);
+ IncludingGroupMembership create(Iterable<AccountGroup.UUID> groupIds);
}
private final GroupIncludeCache groupIncludeCache;
@@ -37,7 +38,7 @@
private final Queue<AccountGroup.UUID> groupQueue;
@Inject
- MaterializedGroupMembership(
+ IncludingGroupMembership(
GroupIncludeCache groupIncludeCache,
@Assisted Iterable<AccountGroup.UUID> seedGroups) {
this.groupIncludeCache = groupIncludeCache;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
new file mode 100644
index 0000000..ad65499
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -0,0 +1,94 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupDescriptions;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.Collection;
+
+/**
+ * Implementation of GroupBackend for the internal group system.
+ */
+@Singleton
+public class InternalGroupBackend implements GroupBackend {
+ private static final Function<AccountGroup, GroupReference> ACT_GROUP_TO_GROUP_REF =
+ new Function<AccountGroup, GroupReference>() {
+ @Override
+ public GroupReference apply(AccountGroup group) {
+ return GroupReference.forGroup(group);
+ }
+ };
+
+ private final GroupControl.Factory groupControlFactory;
+ private final GroupCache groupCache;
+ private final IncludingGroupMembership.Factory groupMembershipFactory;
+
+
+ @Inject
+ InternalGroupBackend(GroupControl.Factory groupControlFactory,
+ GroupCache groupCache,
+ IncludingGroupMembership.Factory groupMembershipFactory) {
+ this.groupControlFactory = groupControlFactory;
+ this.groupCache = groupCache;
+ this.groupMembershipFactory = groupMembershipFactory;
+ }
+
+ @Override
+ public boolean handles(AccountGroup.UUID uuid) {
+ return AccountGroup.isInternalGroup(uuid);
+ }
+
+ @Override
+ public GroupDescription.Internal get(AccountGroup.UUID uuid) {
+ if (!handles(uuid)) {
+ return null;
+ }
+
+ AccountGroup g = groupCache.get(uuid);
+ if (g == null) {
+ return null;
+ }
+ return GroupDescriptions.forAccountGroup(g);
+ }
+
+ @Override
+ public Collection<GroupReference> suggest(final String name) {
+ Iterable<AccountGroup> filtered = Iterables.filter(groupCache.all(),
+ new Predicate<AccountGroup>() {
+ @Override
+ public boolean apply(AccountGroup group) {
+ // startsWithIgnoreCase && isVisible
+ return group.getName().regionMatches(true, 0, name, 0, name.length())
+ && groupControlFactory.controlFor(group).isVisible();
+ }
+ });
+ return Lists.newArrayList(Iterables.transform(filtered, ACT_GROUP_TO_GROUP_REF));
+ }
+
+ @Override
+ public GroupMembership membershipsOf(IdentifiedUser user) {
+ return groupMembershipFactory.create(user.state().getInternalGroups());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
index 2ebd0e5..e44d46e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -15,11 +15,8 @@
package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import java.util.Set;
-
public interface Realm {
/** Can the end-user modify this field of their own account? */
public boolean allowsEdit(Account.FieldName field);
@@ -34,8 +31,6 @@
public void onCreateAccount(AuthRequest who, Account account);
- public GroupMembership groups(AccountState who);
-
/**
* Locate an account whose local username is the given account name.
* <p>
@@ -45,9 +40,4 @@
* user by that email address.
*/
public Account.Id lookup(String accountName);
-
- /**
- * Search for matching external groups.
- */
- public Set<AccountGroup.ExternalNameKey> lookupGroups(String name);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
new file mode 100644
index 0000000..1974961
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -0,0 +1,161 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Universal implementation of the GroupBackend that works with the injected
+ * set of GroupBackends.
+ */
+@Singleton
+public class UniversalGroupBackend implements GroupBackend {
+ private static final Logger log =
+ LoggerFactory.getLogger(UniversalGroupBackend.class);
+
+ private final DynamicSet<GroupBackend> backends;
+
+ @Inject
+ UniversalGroupBackend(DynamicSet<GroupBackend> backends) {
+ this.backends = backends;
+ }
+
+ @Nullable
+ private GroupBackend backend(AccountGroup.UUID uuid) {
+ if (uuid != null) {
+ for (GroupBackend g : backends) {
+ if (g.handles(uuid)) {
+ return g;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean handles(AccountGroup.UUID uuid) {
+ return backend(uuid) != null;
+ }
+
+ @Override
+ public GroupDescription.Basic get(AccountGroup.UUID uuid) {
+ GroupBackend b = backend(uuid);
+ if (b == null) {
+ log.warn("Unknown GroupBackend for UUID: " + uuid);
+ return null;
+ }
+ return b.get(uuid);
+ }
+
+ @Override
+ public Collection<GroupReference> suggest(String name) {
+ Set<GroupReference> groups = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
+ for (GroupBackend g : backends) {
+ groups.addAll(g.suggest(name));
+ }
+ return groups;
+ }
+
+ @Override
+ public GroupMembership membershipsOf(IdentifiedUser user) {
+ return new UniversalGroupMembership(user);
+ }
+
+ private class UniversalGroupMembership implements GroupMembership {
+ private final Map<GroupBackend, GroupMembership> memberships;
+
+ private UniversalGroupMembership(IdentifiedUser user) {
+ ImmutableMap.Builder<GroupBackend, GroupMembership> builder =
+ ImmutableMap.builder();
+ for (GroupBackend g : backends) {
+ builder.put(g, g.membershipsOf(user));
+ }
+ this.memberships = builder.build();
+ }
+
+ @Nullable
+ private GroupMembership membership(AccountGroup.UUID uuid) {
+ if (uuid != null) {
+ for (Map.Entry<GroupBackend, GroupMembership> m : memberships.entrySet()) {
+ if (m.getKey().handles(uuid)) {
+ return m.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean contains(AccountGroup.UUID uuid) {
+ GroupMembership m = membership(uuid);
+ if (m == null) {
+ log.warn("Unknown GroupMembership for UUID: " + uuid);
+ return false;
+ }
+ return m.contains(uuid);
+ }
+
+ @Override
+ public boolean containsAnyOf(Iterable<AccountGroup.UUID> uuids) {
+ Multimap<GroupMembership, AccountGroup.UUID> lookups =
+ ArrayListMultimap.create();
+ for (AccountGroup.UUID uuid : uuids) {
+ GroupMembership m = membership(uuid);
+ if (m == null) {
+ log.warn("Unknown GroupMembership for UUID: " + uuid);
+ continue;
+ }
+ lookups.put(m, uuid);
+ }
+ for (Map.Entry<GroupMembership, Collection<AccountGroup.UUID>> entry :
+ lookups.asMap().entrySet()) {
+ if (entry.getKey().containsAnyOf(entry.getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Set<AccountGroup.UUID> getKnownGroups() {
+ Set<AccountGroup.UUID> groups = Sets.newHashSet();
+ for (GroupMembership m : memberships.values()) {
+ groups.addAll(m.getKnownGroups());
+ }
+ return groups;
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java
similarity index 97%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java
index 307a10a..bf74a4a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupUUIDHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java
similarity index 77%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupUUIDHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java
index 49bf695..406ca58 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupUUIDHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java
@@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
+import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -27,25 +29,25 @@
import org.kohsuke.args4j.spi.Setter;
public class AccountGroupUUIDHandler extends OptionHandler<AccountGroup.UUID> {
- private final GroupCache groupCache;
+ private final GroupBackend groupBackend;
@Inject
- public AccountGroupUUIDHandler(final GroupCache groupCache,
+ public AccountGroupUUIDHandler(final GroupBackend groupBackend,
@Assisted final CmdLineParser parser, @Assisted final OptionDef option,
@Assisted final Setter<AccountGroup.UUID> setter) {
super(parser, option, setter);
- this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
}
@Override
public final int parseArguments(final Parameters params)
throws CmdLineException {
final String n = params.getParameter(0);
- final AccountGroup group = groupCache.get(new AccountGroup.NameKey(n));
+ final GroupReference group = GroupBackends.findBestSuggestion(groupBackend, n);
if (group == null) {
throw new CmdLineException(owner, "Group \"" + n + "\" does not exist");
}
- setter.addValue(group.getGroupUUID());
+ setter.addValue(group.getUUID());
return 1;
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
similarity index 98%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
index d54ae34..8e71b88 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AuthType;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ChangeIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
similarity index 94%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ChangeIdHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
index 0194b91..9c3d052 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ChangeIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -56,7 +56,8 @@
try {
final Change.Key key = Change.Key.parse(tokens[2]);
final Project.NameKey project = new Project.NameKey(tokens[0]);
- final Branch.NameKey branch = new Branch.NameKey(project, tokens[1]);
+ final Branch.NameKey branch =
+ new Branch.NameKey(project, "refs/heads/" + tokens[1]);
for (final Change change : db.changes().byBranchKey(branch, key)) {
setter.addValue(change.getId());
return 1;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ObjectIdHandler.java
similarity index 96%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/ObjectIdHandler.java
index adb5ad6..b7f2fb9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ObjectIdHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/PatchSetIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
similarity index 97%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/PatchSetIdHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
index 2d6a4df..a48568f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/PatchSetIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
similarity index 94%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
index e0f7c4c..da033e7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -71,7 +71,7 @@
final ProjectControl control;
try {
Project.NameKey nameKey = new Project.NameKey(projectName);
- control = projectControlFactory.validateFor(nameKey);
+ control = projectControlFactory.validateFor(nameKey, ProjectControl.OWNER | ProjectControl.VISIBLE);
} catch (NoSuchProjectException e) {
throw new CmdLineException(owner, "'" + token + "': not a Gerrit project");
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SocketAddressHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/SocketAddressHandler.java
similarity index 97%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SocketAddressHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/SocketAddressHandler.java
index 454a084..0c20b2d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SocketAddressHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/SocketAddressHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SubcommandHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/SubcommandHandler.java
similarity index 96%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SubcommandHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/SubcommandHandler.java
index 3df73a8..619ec1f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SubcommandHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/SubcommandHandler.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.args4j;
+package com.google.gerrit.server.args4j;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index e81bfc2..2265bc2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -17,7 +17,6 @@
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountException;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
@@ -47,7 +46,8 @@
import javax.net.ssl.SSLSocketFactory;
@Singleton class Helper {
- private final GroupCache groupCache;
+ static final String LDAP_UUID = "ldap:";
+
private final Config config;
private final String server;
private final String username;
@@ -58,8 +58,7 @@
private final String readTimeOutMillis;
@Inject
- Helper(@GerritServerConfig final Config config, final GroupCache groupCache) {
- this.groupCache = groupCache;
+ Helper(@GerritServerConfig final Config config) {
this.config = config;
this.server = LdapRealm.required(config, "server");
this.username = LdapRealm.optional(config, "username");
@@ -195,12 +194,7 @@
final Set<AccountGroup.UUID> actual = new HashSet<AccountGroup.UUID>();
for (String dn : groupDNs) {
- for (AccountGroup group : groupCache
- .get(new AccountGroup.ExternalNameKey(dn))) {
- if (group.getType() == AccountGroup.Type.LDAP) {
- actual.add(group.getGroupUUID());
- }
- }
+ actual.add(new AccountGroup.UUID(LDAP_UUID + dn));
}
if (actual.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
new file mode 100644
index 0000000..5c30e5c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -0,0 +1,227 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.auth.ldap;
+
+import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR;
+import static com.google.gerrit.server.auth.ldap.Helper.LDAP_UUID;
+import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_CACHE;
+import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_EXIST_CACHE;
+
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/**
+ * Implementation of GroupBackend for the LDAP group system.
+ */
+public class LdapGroupBackend implements GroupBackend {
+ private static final Logger log = LoggerFactory.getLogger(LdapGroupBackend.class);
+
+ private static final String LDAP_NAME = "ldap/";
+ private static final String GROUPNAME = "groupname";
+
+ private final Helper helper;
+ private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
+ private final LoadingCache<String, Boolean> existsCache;
+ private final Provider<CurrentUser> userProvider;
+
+ @Inject
+ LdapGroupBackend(
+ Helper helper,
+ @Named(GROUP_CACHE) LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
+ @Named(GROUP_EXIST_CACHE) LoadingCache<String, Boolean> existsCache,
+ Provider<CurrentUser> userProvider) {
+ this.helper = helper;
+ this.membershipCache = membershipCache;
+ this.existsCache = existsCache;
+ this.userProvider = userProvider;
+ }
+
+ private static boolean isLdapUUID(AccountGroup.UUID uuid) {
+ return uuid.get().startsWith(LDAP_UUID);
+ }
+
+ private static GroupReference groupReference(LdapQuery.Result res)
+ throws NamingException {
+ return new GroupReference(
+ new AccountGroup.UUID(LDAP_UUID + res.getDN()),
+ LDAP_NAME + cnFor(res.getDN()));
+ }
+
+ private static String cnFor(String dn) {
+ try {
+ LdapName name = new LdapName(dn);
+ if (!name.isEmpty()) {
+ String cn = name.get(name.size() - 1);
+ int index = cn.indexOf('=');
+ if (index >= 0) {
+ cn = cn.substring(index + 1);
+ }
+ return cn;
+ }
+ } catch (InvalidNameException e) {
+ log.warn("Cannot parse LDAP dn for cn", e);
+ }
+ return dn;
+ }
+
+ @Override
+ public boolean handles(AccountGroup.UUID uuid) {
+ return isLdapUUID(uuid);
+ }
+
+ @Override
+ public GroupDescription.Basic get(final AccountGroup.UUID uuid) {
+ if (!handles(uuid)) {
+ return null;
+ }
+
+ String groupDn = uuid.get().substring(LDAP_UUID.length());
+ CurrentUser user = userProvider.get();
+ if (!(user instanceof IdentifiedUser)
+ || !membershipsOf((IdentifiedUser) user).contains(uuid)) {
+ try {
+ if (!existsCache.get(groupDn)) {
+ return null;
+ }
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot lookup group %s in LDAP", groupDn), e);
+ return null;
+ }
+ }
+
+ final String name = LDAP_NAME + cnFor(groupDn);
+ return new GroupDescription.Basic() {
+ @Override
+ public AccountGroup.UUID getGroupUUID() {
+ return uuid;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isVisibleToAll() {
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public Collection<GroupReference> suggest(String name) {
+ AccountGroup.UUID uuid = new AccountGroup.UUID(name);
+ if (isLdapUUID(uuid)) {
+ GroupDescription.Basic g = get(uuid);
+ if (g == null) {
+ return Collections.emptySet();
+ }
+ return Collections.singleton(GroupReference.forGroup(g));
+ } else if (name.startsWith(LDAP_NAME)) {
+ return suggestLdap(name.substring(LDAP_NAME.length()));
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public GroupMembership membershipsOf(IdentifiedUser user) {
+ String id = findId(user.state().getExternalIds());
+ if (id == null) {
+ return GroupMembership.EMPTY;
+ }
+
+ try {
+ return new ListGroupMembership(membershipCache.get(id));
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot lookup membershipsOf %s in LDAP", id), e);
+ return GroupMembership.EMPTY;
+ }
+ }
+
+ private static String findId(final Collection<AccountExternalId> ids) {
+ for (final AccountExternalId i : ids) {
+ if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
+ return i.getSchemeRest();
+ }
+ }
+ return null;
+ }
+
+
+ private Set<GroupReference> suggestLdap(String name) {
+ if (name.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set<GroupReference> out = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
+ try {
+ DirContext ctx = helper.open();
+ try {
+ // Do exact lookups until there are at least 3 characters.
+ name = Rdn.escapeValue(name) + ((name.length() >= 3) ? "*" : "");
+ LdapSchema schema = helper.getSchema(ctx);
+ ParameterizedString filter = ParameterizedString.asis(
+ schema.groupPattern.replace(GROUPNAME, name).toString());
+ Set<String> returnAttrs = Collections.<String>emptySet();
+ Map<String, String> params = Collections.emptyMap();
+ for (String groupBase : schema.groupBases) {
+ LdapQuery query = new LdapQuery(
+ groupBase, schema.groupScope, filter, returnAttrs);
+ for (LdapQuery.Result res : query.query(ctx, params)) {
+ out.add(groupReference(res));
+ }
+ }
+ } finally {
+ try {
+ ctx.close();
+ } catch (NamingException e) {
+ log.warn("Cannot close LDAP query handle", e);
+ }
+ }
+ } catch (NamingException e) {
+ log.warn("Cannot query LDAP for groups matching requested name", e);
+ }
+ return out;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
index 6eb2f54..29533b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
@@ -16,10 +16,12 @@
import static java.util.concurrent.TimeUnit.HOURS;
+import com.google.common.base.Optional;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
@@ -29,20 +31,31 @@
public class LdapModule extends CacheModule {
static final String USERNAME_CACHE = "ldap_usernames";
static final String GROUP_CACHE = "ldap_groups";
+ static final String GROUP_EXIST_CACHE = "ldap_group_existence";
+
@Override
protected void configure() {
- final TypeLiteral<Cache<String, Set<AccountGroup.UUID>>> groups =
- new TypeLiteral<Cache<String, Set<AccountGroup.UUID>>>() {};
- core(groups, GROUP_CACHE).maxAge(1, HOURS) //
- .populateWith(LdapRealm.MemberLoader.class);
+ cache(GROUP_CACHE,
+ String.class,
+ new TypeLiteral<Set<AccountGroup.UUID>>() {})
+ .expireAfterWrite(1, HOURS)
+ .loader(LdapRealm.MemberLoader.class);
- final TypeLiteral<Cache<String, Account.Id>> usernames =
- new TypeLiteral<Cache<String, Account.Id>>() {};
- core(usernames, USERNAME_CACHE) //
- .populateWith(LdapRealm.UserLoader.class);
+ cache(USERNAME_CACHE,
+ String.class,
+ new TypeLiteral<Optional<Account.Id>>() {})
+ .loader(LdapRealm.UserLoader.class);
+
+ cache(GROUP_EXIST_CACHE,
+ String.class,
+ new TypeLiteral<Boolean>() {})
+ .expireAfterWrite(1, HOURS)
+ .loader(LdapRealm.ExistenceLoader.class);
bind(Realm.class).to(LdapRealm.class).in(Scopes.SINGLETON);
bind(Helper.class);
+
+ DynamicSet.bind(binder(), GroupBackend.class).to(LdapGroupBackend.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 910bf06..72eb7ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -16,7 +16,10 @@
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GERRIT;
-import com.google.common.collect.Iterables;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -24,20 +27,13 @@
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountException;
-import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.EmailExpander;
-import com.google.gerrit.server.account.GroupMembership;
-import com.google.gerrit.server.account.MaterializedGroupMembership;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
-import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
-import com.google.gerrit.server.cache.Cache;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -48,15 +44,16 @@
import org.slf4j.LoggerFactory;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import javax.naming.CompositeName;
+import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
@@ -65,34 +62,30 @@
static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
static final String LDAP = "com.sun.jndi.ldap.LdapCtxFactory";
static final String USERNAME = "username";
- private static final String GROUPNAME = "groupname";
private final Helper helper;
private final AuthConfig authConfig;
private final EmailExpander emailExpander;
- private final Cache<String, Account.Id> usernameCache;
+ private final LoadingCache<String, Optional<Account.Id>> usernameCache;
private final Set<Account.FieldName> readOnlyAccountFields;
private final Config config;
- private final Cache<String, Set<AccountGroup.UUID>> membershipCache;
- private final MaterializedGroupMembership.Factory groupMembershipFactory;
+ private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
@Inject
LdapRealm(
final Helper helper,
final AuthConfig authConfig,
final EmailExpander emailExpander,
- @Named(LdapModule.GROUP_CACHE) final Cache<String, Set<AccountGroup.UUID>> membershipCache,
- @Named(LdapModule.USERNAME_CACHE) final Cache<String, Account.Id> usernameCache,
- @GerritServerConfig final Config config,
- final MaterializedGroupMembership.Factory groupMembershipFactory) {
+ @Named(LdapModule.GROUP_CACHE) final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
+ @Named(LdapModule.USERNAME_CACHE) final LoadingCache<String, Optional<Account.Id>> usernameCache,
+ @GerritServerConfig final Config config) {
this.helper = helper;
this.authConfig = authConfig;
this.emailExpander = emailExpander;
this.usernameCache = usernameCache;
this.membershipCache = membershipCache;
this.config = config;
- this.groupMembershipFactory = groupMembershipFactory;
this.readOnlyAccountFields = new HashSet<Account.FieldName>();
@@ -189,6 +182,7 @@
return r.isEmpty() ? null : r;
}
+ @Override
public AuthRequest authenticate(final AuthRequest who)
throws AccountException {
if (config.getBoolean("ldap", "localUsernameToLowerCase", false)) {
@@ -261,65 +255,24 @@
@Override
public void onCreateAccount(final AuthRequest who, final Account account) {
- usernameCache.put(who.getLocalUser(), account.getId());
+ usernameCache.put(who.getLocalUser(), Optional.of(account.getId()));
}
@Override
- public GroupMembership groups(final AccountState who) {
- return groupMembershipFactory.create(Iterables.concat(
- membershipCache.get(findId(who.getExternalIds())),
- who.getInternalGroups()));
- }
-
- private static String findId(final Collection<AccountExternalId> ids) {
- for (final AccountExternalId i : ids) {
- if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
- return i.getSchemeRest();
- }
+ public Account.Id lookup(String accountName) {
+ if (Strings.isNullOrEmpty(accountName)) {
+ return null;
}
- return null;
- }
-
- @Override
- public Account.Id lookup(final String accountName) {
- return usernameCache.get(accountName);
- }
-
- @Override
- public Set<AccountGroup.ExternalNameKey> lookupGroups(String name) {
- final Set<AccountGroup.ExternalNameKey> out;
- final Map<String, String> params = Collections.<String, String> emptyMap();
-
- out = new HashSet<AccountGroup.ExternalNameKey>();
try {
- final DirContext ctx = helper.open();
- try {
- final LdapSchema schema = helper.getSchema(ctx);
- final ParameterizedString filter =
- ParameterizedString.asis(schema.groupPattern
- .replace(GROUPNAME, name).toString());
- for (String groupBase : schema.groupBases) {
- final LdapQuery query =
- new LdapQuery(groupBase, schema.groupScope, filter, Collections
- .<String> emptySet());
- for (LdapQuery.Result res : query.query(ctx, params)) {
- out.add(new AccountGroup.ExternalNameKey(res.getDN()));
- }
- }
- } finally {
- try {
- ctx.close();
- } catch (NamingException e) {
- log.warn("Cannot close LDAP query handle", e);
- }
- }
- } catch (NamingException e) {
- log.warn("Cannot query LDAP for groups matching requested name", e);
+ Optional<Account.Id> id = usernameCache.get(accountName);
+ return id != null ? id.orNull() : null;
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot lookup account %s in LDAP", accountName), e);
+ return null;
}
- return out;
}
- static class UserLoader extends EntryCreator<String, Account.Id> {
+ static class UserLoader extends CacheLoader<String, Optional<Account.Id>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -328,25 +281,23 @@
}
@Override
- public Account.Id createEntry(final String username) throws Exception {
+ public Optional<Account.Id> load(String username) throws Exception {
+ final ReviewDb db = schema.open();
try {
- final ReviewDb db = schema.open();
- try {
- final AccountExternalId extId =
- db.accountExternalIds().get(
- new AccountExternalId.Key(SCHEME_GERRIT, username));
- return extId != null ? extId.getAccountId() : null;
- } finally {
- db.close();
+ final AccountExternalId extId =
+ db.accountExternalIds().get(
+ new AccountExternalId.Key(SCHEME_GERRIT, username));
+ if (extId != null) {
+ return Optional.of(extId.getAccountId());
}
- } catch (OrmException e) {
- log.warn("Cannot query for username in database", e);
- return null;
+ return Optional.absent();
+ } finally {
+ db.close();
}
}
}
- static class MemberLoader extends EntryCreator<String, Set<AccountGroup.UUID>> {
+ static class MemberLoader extends CacheLoader<String, Set<AccountGroup.UUID>> {
private final Helper helper;
@Inject
@@ -355,8 +306,7 @@
}
@Override
- public Set<AccountGroup.UUID> createEntry(final String username)
- throws Exception {
+ public Set<AccountGroup.UUID> load(String username) throws Exception {
final DirContext ctx = helper.open();
try {
return helper.queryForGroups(ctx, username, null);
@@ -368,10 +318,34 @@
}
}
}
+ }
+
+ static class ExistenceLoader extends CacheLoader<String, Boolean> {
+ private final Helper helper;
+
+ @Inject
+ ExistenceLoader(final Helper helper) {
+ this.helper = helper;
+ }
@Override
- public Set<AccountGroup.UUID> missing(final String key) {
- return Collections.emptySet();
+ public Boolean load(final String groupDn) throws Exception {
+ final DirContext ctx = helper.open();
+ try {
+ Name compositeGroupName = new CompositeName().add(groupDn);
+ try {
+ ctx.getAttributes(compositeGroupName);
+ return true;
+ } catch (NamingException e) {
+ return false;
+ }
+ } finally {
+ try {
+ ctx.close();
+ } catch (NamingException e) {
+ log.warn("Cannot close LDAP query handle", e);
+ }
+ }
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
deleted file mode 100644
index 7892ea1..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-/**
- * A fast in-memory and/or on-disk based cache.
- *
- * @type <K> type of key used to lookup entries in the cache.
- * @type <V> type of value stored within each cache entry.
- */
-public interface Cache<K, V> {
- /** Get the element from the cache, or null if not stored in the cache. */
- public V get(K key);
-
- /** Put one element into the cache, replacing any existing value. */
- public void put(K key, V value);
-
- /** Remove any existing value from the cache, no-op if not present. */
- public void remove(K key);
-
- /** Remove all cached items. */
- public void removeAll();
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheBinding.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheBinding.java
new file mode 100644
index 0000000..625bd14
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheBinding.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.Weigher;
+import com.google.inject.TypeLiteral;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+
+/** Configure a cache declared within a {@link CacheModule} instance. */
+public interface CacheBinding<K, V> {
+ /** Set the total size of the cache. */
+ CacheBinding<K, V> maximumWeight(long weight);
+
+ /** Set the time an element lives before being expired. */
+ CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit durationUnits);
+
+ /** Populate the cache with items from the CacheLoader. */
+ CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz);
+
+ /** Algorithm to weigh an object with a method other than the unit weight 1. */
+ CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz);
+
+ String name();
+ TypeLiteral<K> keyType();
+ TypeLiteral<V> valueType();
+ long maximumWeight();
+ @Nullable Long expireAfterWrite(TimeUnit unit);
+ @Nullable Weigher<K, V> weigher();
+ @Nullable CacheLoader<K, V> loader();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
index 7fb3b3b..c1e92da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
@@ -14,33 +14,41 @@
package com.google.gerrit.server.cache;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.extensions.annotations.Exports;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
-import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.name.Names;
+import com.google.inject.util.Types;
import java.io.Serializable;
+import java.lang.reflect.Type;
/**
* Miniature DSL to support binding {@link Cache} instances in Guice.
*/
public abstract class CacheModule extends AbstractModule {
+ private static final TypeLiteral<Cache<?, ?>> ANY_CACHE =
+ new TypeLiteral<Cache<?, ?>>() {};
+
/**
- * Declare an unnamed in-memory cache.
+ * Declare a named in-memory cache.
*
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
- * @param type type literal for the cache, this literal will be used to match
- * injection sites.
- * @return binding to describe the cache. Caller must set at least the name on
- * the returned binding.
+ * @return binding to describe the cache.
*/
- protected <K, V> UnnamedCacheBinding<K, V> core(
- final TypeLiteral<Cache<K, V>> type) {
- return core(Key.get(type));
+ protected <K, V> CacheBinding<K, V> cache(
+ String name,
+ Class<K> keyType,
+ Class<V> valType) {
+ return cache(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
}
/**
@@ -48,74 +56,127 @@
*
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
- * @param type type literal for the cache, this literal will be used to match
- * injection sites. Injection sites are matched by this type literal
- * and with {@code @Named} annotations.
* @return binding to describe the cache.
*/
- protected <K, V> NamedCacheBinding<K, V> core(
- final TypeLiteral<Cache<K, V>> type, final String name) {
- return core(Key.get(type, Names.named(name))).name(name);
- }
-
- private <K, V> UnnamedCacheBinding<K, V> core(final Key<Cache<K, V>> key) {
- final boolean disk = false;
- final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
- bind(key).toProvider(b).in(Scopes.SINGLETON);
- return b;
+ protected <K, V> CacheBinding<K, V> cache(
+ String name,
+ Class<K> keyType,
+ TypeLiteral<V> valType) {
+ return cache(name, TypeLiteral.get(keyType), valType);
}
/**
- * Declare an unnamed in-memory/on-disk cache.
+ * Declare a named in-memory cache.
*
- * @param <K> type of key used to find entries, must be {@link Serializable}.
- * @param <V> type of value stored by the cache, must be {@link Serializable}.
- * @param type type literal for the cache, this literal will be used to match
- * injection sites. Injection sites are matched by this type literal
- * and with {@code @Named} annotations.
- * @return binding to describe the cache. Caller must set at least the name on
- * the returned binding.
+ * @param <K> type of key used to lookup entries.
+ * @param <V> type of value stored by the cache.
+ * @return binding to describe the cache.
*/
- protected <K extends Serializable, V extends Serializable> UnnamedCacheBinding<K, V> disk(
- final TypeLiteral<Cache<K, V>> type) {
- return disk(Key.get(type));
+ protected <K, V> CacheBinding<K, V> cache(
+ String name,
+ TypeLiteral<K> keyType,
+ TypeLiteral<V> valType) {
+ Type type = Types.newParameterizedType(
+ Cache.class,
+ keyType.getType(), valType.getType());
+
+ @SuppressWarnings("unchecked")
+ Key<Cache<K, V>> key = (Key<Cache<K, V>>) Key.get(type, Names.named(name));
+
+ CacheProvider<K, V> m =
+ new CacheProvider<K, V>(this, name, keyType, valType);
+ bind(key).toProvider(m).in(Scopes.SINGLETON);
+ bind(ANY_CACHE).annotatedWith(Exports.named(name)).to(key);
+ return m.maximumWeight(1024);
+ }
+
+ <K,V> Provider<CacheLoader<K,V>> bindCacheLoader(
+ CacheProvider<K, V> m,
+ Class<? extends CacheLoader<K,V>> impl) {
+ Type type = Types.newParameterizedType(
+ Cache.class,
+ m.keyType().getType(), m.valueType().getType());
+
+ Type loadingType = Types.newParameterizedType(
+ LoadingCache.class,
+ m.keyType().getType(), m.valueType().getType());
+
+ Type loaderType = Types.newParameterizedType(
+ CacheLoader.class,
+ m.keyType().getType(), m.valueType().getType());
+
+ @SuppressWarnings("unchecked")
+ Key<LoadingCache<K, V>> key =
+ (Key<LoadingCache<K, V>>) Key.get(type, Names.named(m.name));
+
+ @SuppressWarnings("unchecked")
+ Key<LoadingCache<K, V>> loadingKey =
+ (Key<LoadingCache<K, V>>) Key.get(loadingType, Names.named(m.name));
+
+ @SuppressWarnings("unchecked")
+ Key<CacheLoader<K, V>> loaderKey =
+ (Key<CacheLoader<K, V>>) Key.get(loaderType, Names.named(m.name));
+
+ bind(loaderKey).to(impl).in(Scopes.SINGLETON);
+ bind(loadingKey).to(key);
+ return getProvider(loaderKey);
+ }
+
+ <K,V> Provider<Weigher<K,V>> bindWeigher(
+ CacheProvider<K, V> m,
+ Class<? extends Weigher<K,V>> impl) {
+ Type weigherType = Types.newParameterizedType(
+ Weigher.class,
+ m.keyType().getType(), m.valueType().getType());
+
+ @SuppressWarnings("unchecked")
+ Key<Weigher<K, V>> key =
+ (Key<Weigher<K, V>>) Key.get(weigherType, Names.named(m.name));
+
+ bind(key).to(impl).in(Scopes.SINGLETON);
+ return getProvider(key);
}
/**
* Declare a named in-memory/on-disk cache.
*
- * @param <K> type of key used to find entries, must be {@link Serializable}.
- * @param <V> type of value stored by the cache, must be {@link Serializable}.
- * @param type type literal for the cache, this literal will be used to match
- * injection sites. Injection sites are matched by this type literal
- * and with {@code @Named} annotations.
+ * @param <K> type of key used to lookup entries.
+ * @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
- protected <K extends Serializable, V extends Serializable> NamedCacheBinding<K, V> disk(
- final TypeLiteral<Cache<K, V>> type, final String name) {
- return disk(Key.get(type, Names.named(name))).name(name);
+ protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
+ String name,
+ Class<K> keyType,
+ Class<V> valType) {
+ return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
}
- private <K, V> UnnamedCacheBinding<K, V> disk(final Key<Cache<K, V>> key) {
- final boolean disk = true;
- final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
- bind(key).toProvider(b).in(Scopes.SINGLETON);
- return b;
+ /**
+ * Declare a named in-memory/on-disk cache.
+ *
+ * @param <K> type of key used to lookup entries.
+ * @param <V> type of value stored by the cache.
+ * @return binding to describe the cache.
+ */
+ protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
+ String name,
+ Class<K> keyType,
+ TypeLiteral<V> valType) {
+ return persist(name, TypeLiteral.get(keyType), valType);
}
- <K, V> Provider<EntryCreator<K, V>> getEntryCreator(CacheProvider<K, V> cp,
- Class<? extends EntryCreator<K, V>> type) {
- Key<EntryCreator<K, V>> key = newKey();
- bind(key).to(type).in(Scopes.SINGLETON);
- return getProvider(key);
- }
-
- @SuppressWarnings("unchecked")
- private static <K, V> Key<EntryCreator<K, V>> newKey() {
- return (Key<EntryCreator<K, V>>) newKeyImpl();
- }
-
- private static Key<?> newKeyImpl() {
- return Key.get(EntryCreator.class, UniqueAnnotations.create());
+ /**
+ * Declare a named in-memory/on-disk cache.
+ *
+ * @param <K> type of key used to lookup entries.
+ * @param <V> type of value stored by the cache.
+ * @return binding to describe the cache.
+ */
+ protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
+ String name,
+ TypeLiteral<K> keyType,
+ TypeLiteral<V> valType) {
+ return ((CacheProvider<K, V>) cache(name, keyType, valType))
+ .persist(true);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
index 1fa047b..1b8eea5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,130 +14,156 @@
package com.google.gerrit.server.cache;
-import static com.google.gerrit.server.cache.EvictionPolicy.LFU;
-import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
+import com.google.inject.TypeLiteral;
import java.util.concurrent.TimeUnit;
-public final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
- NamedCacheBinding<K, V>, UnnamedCacheBinding<K, V> {
+import javax.annotation.Nullable;
+
+class CacheProvider<K, V>
+ implements Provider<Cache<K, V>>,
+ CacheBinding<K, V> {
private final CacheModule module;
- private final boolean disk;
- private int memoryLimit;
- private int diskLimit;
- private long maxAge;
- private EvictionPolicy evictionPolicy;
- private String cacheName;
- private ProxyCache<K, V> cache;
- private Provider<EntryCreator<K, V>> entryCreator;
+ final String name;
+ private final TypeLiteral<K> keyType;
+ private final TypeLiteral<V> valType;
+ private boolean persist;
+ private long maximumWeight;
+ private Long expireAfterWrite;
+ private Provider<CacheLoader<K, V>> loader;
+ private Provider<Weigher<K, V>> weigher;
- CacheProvider(final boolean disk, CacheModule module) {
- this.disk = disk;
+ private String plugin;
+ private MemoryCacheFactory memoryCacheFactory;
+ private PersistentCacheFactory persistentCacheFactory;
+ private boolean frozen;
+
+ CacheProvider(CacheModule module,
+ String name,
+ TypeLiteral<K> keyType,
+ TypeLiteral<V> valType) {
this.module = module;
+ this.name = name;
+ this.keyType = keyType;
+ this.valType = valType;
+ }
- memoryLimit(1024);
- maxAge(90, DAYS);
- evictionPolicy(LFU);
-
- if (disk) {
- diskLimit(16384);
- }
+ @Inject(optional = true)
+ void setPluginName(@PluginName String pluginName) {
+ this.plugin = pluginName;
}
@Inject
- void setCachePool(final CachePool pool) {
- this.cache = pool.register(this);
+ void setMemoryCacheFactory(MemoryCacheFactory factory) {
+ this.memoryCacheFactory = factory;
}
- public void bind(Cache<K, V> impl) {
- if (cache == null) {
- throw new ProvisionException("Cache was never registered");
- }
- cache.bind(impl);
+ @Inject(optional = true)
+ void setPersistentCacheFactory(@Nullable PersistentCacheFactory factory) {
+ this.persistentCacheFactory = factory;
}
- public EntryCreator<K, V> getEntryCreator() {
- return entryCreator != null ? entryCreator.get() : null;
- }
-
- public String getName() {
- if (cacheName == null) {
- throw new ProvisionException("Cache has no name");
- }
- return cacheName;
- }
-
- public boolean disk() {
- return disk;
- }
-
- public int memoryLimit() {
- return memoryLimit;
- }
-
- public int diskLimit() {
- return diskLimit;
- }
-
- public long maxAge() {
- return maxAge;
- }
-
- public EvictionPolicy evictionPolicy() {
- return evictionPolicy;
- }
-
- public NamedCacheBinding<K, V> name(final String name) {
- if (cacheName != null) {
- throw new IllegalStateException("Cache name already set");
- }
- cacheName = name;
- return this;
- }
-
- public NamedCacheBinding<K, V> memoryLimit(final int objects) {
- memoryLimit = objects;
- return this;
- }
-
- public NamedCacheBinding<K, V> diskLimit(final int objects) {
- if (!disk) {
- // TODO This should really be a compile time type error, but I'm
- // too lazy to create the mess of permutations required to setup
- // type safe returns for bindings in our little DSL.
- //
- throw new IllegalStateException("Cache is not disk based");
- }
- diskLimit = objects;
- return this;
- }
-
- public NamedCacheBinding<K, V> maxAge(final long duration, final TimeUnit unit) {
- maxAge = SECONDS.convert(duration, unit);
+ CacheBinding<K, V> persist(boolean p) {
+ Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ persist = p;
return this;
}
@Override
- public NamedCacheBinding<K, V> evictionPolicy(final EvictionPolicy policy) {
- evictionPolicy = policy;
+ public CacheBinding<K, V> maximumWeight(long weight) {
+ Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ maximumWeight = weight;
return this;
}
- public NamedCacheBinding<K, V> populateWith(
- Class<? extends EntryCreator<K, V>> creator) {
- entryCreator = module.getEntryCreator(this, creator);
+ @Override
+ public CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit unit) {
+ Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ expireAfterWrite = SECONDS.convert(duration, unit);
return this;
}
- public Cache<K, V> get() {
- if (cache == null) {
- throw new ProvisionException("Cache \"" + cacheName + "\" not available");
+ @Override
+ public CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> impl) {
+ Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ loader = module.bindCacheLoader(this, impl);
+ return this;
+ }
+
+ @Override
+ public CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> impl) {
+ Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ weigher = module.bindWeigher(this, impl);
+ return this;
+ }
+
+ @Override
+ public String name() {
+ if (!Strings.isNullOrEmpty(plugin)) {
+ return plugin + "." + name;
}
- return cache;
+ return name;
+ }
+
+ @Override
+ public TypeLiteral<K> keyType() {
+ return keyType;
+ }
+
+ @Override
+ public TypeLiteral<V> valueType() {
+ return valType;
+ }
+
+ @Override
+ public long maximumWeight() {
+ return maximumWeight;
+ }
+
+ @Override
+ @Nullable
+ public Long expireAfterWrite(TimeUnit unit) {
+ return expireAfterWrite != null
+ ? unit.convert(expireAfterWrite, SECONDS)
+ : null;
+ }
+
+ @Override
+ @Nullable
+ public Weigher<K, V> weigher() {
+ return weigher != null ? weigher.get() : null;
+ }
+
+ @Override
+ @Nullable
+ public CacheLoader<K, V> loader() {
+ return loader != null ? loader.get() : null;
+ }
+
+ @Override
+ public Cache<K, V> get() {
+ frozen = true;
+
+ if (loader != null) {
+ CacheLoader<K, V> ldr = loader.get();
+ if (persist && persistentCacheFactory != null) {
+ return persistentCacheFactory.build(this, ldr);
+ }
+ return memoryCacheFactory.build(this, ldr);
+ } else if (persist && persistentCacheFactory != null) {
+ return persistentCacheFactory.build(this);
+ } else {
+ return memoryCacheFactory.build(this);
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ConcurrentHashMapCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ConcurrentHashMapCache.java
deleted file mode 100644
index bafdc49..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ConcurrentHashMapCache.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.package com.google.gerrit.server.git;
-
-package com.google.gerrit.server.cache;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * An infinitely sized cache backed by java.util.ConcurrentHashMap.
- * <p>
- * This cache type is only suitable for unit tests, as it has no upper limit on
- * number of items held in the cache. No upper limit can result in memory leaks
- * in production servers.
- */
-public class ConcurrentHashMapCache<K, V> implements Cache<K, V> {
- private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();
-
- @Override
- public V get(K key) {
- return map.get(key);
- }
-
- @Override
- public void put(K key, V value) {
- map.put(key, value);
- }
-
- @Override
- public void remove(K key) {
- map.remove(key);
- }
-
- @Override
- public void removeAll() {
- map.clear();
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java
deleted file mode 100644
index af07e08..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/EntryCreator.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-/**
- * Creates a cache entry on demand when its not found.
- *
- * @param <K> type of the cache's key.
- * @param <V> type of the cache's value element.
- */
-public abstract class EntryCreator<K, V> {
- /**
- * Invoked on a cache miss, to compute the cache entry.
- *
- * @param key entry whose content needs to be obtained.
- * @return new cache content. The caller will automatically put this object
- * into the cache.
- * @throws Exception the cache content cannot be computed. No entry will be
- * stored in the cache, and {@link #missing(Object)} will be invoked
- * instead. Future requests for the same key will retry this method.
- */
- public abstract V createEntry(K key) throws Exception;
-
- /** Invoked when {@link #createEntry(Object)} fails, by default return null. */
- public V missing(K key) {
- return null;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/EvictionPolicy.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/EvictionPolicy.java
deleted file mode 100644
index cff4f11..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/EvictionPolicy.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-/** How entries should be evicted from the cache. */
-public enum EvictionPolicy {
- /** Least recently used is evicted first. */
- LRU,
-
- /** Least frequently used is evicted first. */
- LFU;
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
similarity index 62%
copy from gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
index 204d777..6b8b489 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
@@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.cache;
-public class IncompleteUserInfoException extends Exception {
- private static final long serialVersionUID = 1L;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
- public IncompleteUserInfoException(final String userName, final String missingInfo) {
- super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
- }
+public interface MemoryCacheFactory {
+ <K, V> Cache<K, V> build(CacheBinding<K, V> def);
+
+ <K, V> LoadingCache<K, V> build(
+ CacheBinding<K, V> def,
+ CacheLoader<K, V> loader);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
deleted file mode 100644
index 3394c71..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-import java.util.concurrent.TimeUnit;
-
-/** Configure a cache declared within a {@link CacheModule} instance. */
-public interface NamedCacheBinding<K, V> {
- /** Set the number of objects to cache in memory. */
- public NamedCacheBinding<K, V> memoryLimit(int objects);
-
- /** Set the number of objects to cache in memory. */
- public NamedCacheBinding<K, V> diskLimit(int objects);
-
- /** Set the time an element lives before being expired. */
- public NamedCacheBinding<K, V> maxAge(long duration, TimeUnit durationUnits);
-
- /** Set the eviction policy for elements when the cache is full. */
- public NamedCacheBinding<K, V> evictionPolicy(EvictionPolicy policy);
-
- /** Populate the cache with items from the EntryCreator. */
- public NamedCacheBinding<K, V> populateWith(Class<? extends EntryCreator<K, V>> creator);
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
similarity index 62%
copy from gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
index 204d777..983e956 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
@@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.cache;
-public class IncompleteUserInfoException extends Exception {
- private static final long serialVersionUID = 1L;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
- public IncompleteUserInfoException(final String userName, final String missingInfo) {
- super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
- }
+public interface PersistentCacheFactory {
+ <K, V> Cache<K, V> build(CacheBinding<K, V> def);
+
+ <K, V> LoadingCache<K, V> build(
+ CacheBinding<K, V> def,
+ CacheLoader<K, V> loader);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyCache.java
deleted file mode 100644
index c1b0292..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyCache.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-/** Proxy around a cache which has not yet been created. */
-public final class ProxyCache<K, V> implements Cache<K, V> {
- private volatile Cache<K, V> self;
-
- public void bind(Cache<K, V> self) {
- this.self = self;
- }
-
- public V get(K key) {
- return self.get(key);
- }
-
- public void put(K key, V value) {
- self.put(key, value);
- }
-
- public void remove(K key) {
- self.remove(key);
- }
-
- public void removeAll() {
- self.removeAll();
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
deleted file mode 100644
index 43039e1..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-
-/** Configure a cache declared within a {@link CacheModule} instance. */
-public interface UnnamedCacheBinding<K, V> {
- /** Set the name of the cache. */
- public NamedCacheBinding<K, V> name(String cacheName);
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
index 83fa671..dca4a83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
@@ -31,46 +31,56 @@
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
import java.util.concurrent.Callable;
-import javax.annotation.Nullable;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
public class AbandonChange implements Callable<ReviewResult> {
- public interface Factory {
- AbandonChange create(Change.Id changeId, String changeComment);
- }
-
private final AbandonedSender.Factory abandonedSenderFactory;
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
private final IdentifiedUser currentUser;
private final ChangeHooks hooks;
- private final Change.Id changeId;
- private final String changeComment;
+ @Argument(index = 0, required = true, multiValued = false, usage = "change to abandon")
+ private Change.Id changeId;
+
+ public void setChangeId(final Change.Id changeId) {
+ this.changeId = changeId;
+ }
+
+ @Option(name = "--message", aliases = {"-m"},
+ usage = "optional message to append to change")
+ private String message;
+
+ public void setMessage(final String message) {
+ this.message = message;
+ }
@Inject
AbandonChange(final AbandonedSender.Factory abandonedSenderFactory,
final ChangeControl.Factory changeControlFactory, final ReviewDb db,
- final IdentifiedUser currentUser, final ChangeHooks hooks,
- @Assisted final Change.Id changeId,
- @Assisted @Nullable final String changeComment) {
+ final IdentifiedUser currentUser, final ChangeHooks hooks) {
this.abandonedSenderFactory = abandonedSenderFactory;
this.changeControlFactory = changeControlFactory;
this.db = db;
this.currentUser = currentUser;
this.hooks = hooks;
- this.changeId = changeId;
- this.changeComment = changeComment;
+ changeId = null;
+ message = null;
}
@Override
public ReviewResult call() throws EmailException,
InvalidChangeOperationException, NoSuchChangeException, OrmException {
+ if (changeId == null) {
+ throw new InvalidChangeOperationException("changeId is required");
+ }
+
final ReviewResult result = new ReviewResult();
result.setChangeId(changeId);
@@ -91,9 +101,9 @@
currentUser.getAccountId(), patchSetId);
final StringBuilder msgBuf =
new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
- if (changeComment != null && changeComment.length() > 0) {
+ if (message != null && message.length() > 0) {
msgBuf.append("\n\n");
- msgBuf.append(changeComment);
+ msgBuf.append(message);
}
cmsg.setMessage(msgBuf.toString());
@@ -102,8 +112,7 @@
new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
- if (change.getStatus().isOpen()
- && change.currentPatchSetId().equals(patchSetId)) {
+ if (change.getStatus().isOpen()) {
change.setStatus(Change.Status.ABANDONED);
ChangeUtil.updated(change);
return change;
@@ -112,11 +121,17 @@
}
}
});
- ChangeUtil.updatedChange(
- db, currentUser, updatedChange, cmsg, abandonedSenderFactory,
- "Change is no longer open or patchset is not latest");
+
+ if (updatedChange == null) {
+ result.addError(new ReviewResult.Error(
+ ReviewResult.Error.Type.CHANGE_IS_CLOSED));
+ return result;
+ }
+
+ ChangeUtil.updatedChange(db, currentUser, updatedChange, cmsg,
+ abandonedSenderFactory);
hooks.doChangeAbandonedHook(updatedChange, currentUser.getAccount(),
- changeComment, db);
+ message, db);
}
return result;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
index 028feac..29e5dbb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.changedetail;
+import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.ReviewResult;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -37,14 +38,17 @@
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
+ private final ChangeHooks hooks;
private final PatchSet.Id patchSetId;
@Inject
PublishDraft(ChangeControl.Factory changeControlFactory,
- ReviewDb db, @Assisted final PatchSet.Id patchSetId) {
+ ReviewDb db, @Assisted final PatchSet.Id patchSetId,
+ final ChangeHooks hooks) {
this.changeControlFactory = changeControlFactory;
this.db = db;
+ this.hooks = hooks;
this.patchSetId = patchSetId;
}
@@ -70,19 +74,26 @@
result.addError(new ReviewResult.Error(
ReviewResult.Error.Type.PUBLISH_NOT_PERMITTED));
} else {
- db.patchSets().atomicUpdate(patchSetId, new AtomicUpdate<PatchSet>() {
+ boolean published = false;
+ final PatchSet updatedPatch = db.patchSets().atomicUpdate(patchSetId,
+ new AtomicUpdate<PatchSet>() {
@Override
public PatchSet update(PatchSet patchset) {
if (patchset.isDraft()) {
patchset.setDraft(false);
+ return patchset;
}
return null;
}
});
+ if ((updatedPatch != null) && (!updatedPatch.isDraft())) {
+ published = true;
+ }
+
final Change change = db.changes().get(changeId);
if (change.getStatus() == Change.Status.DRAFT) {
- db.changes().atomicUpdate(changeId,
+ final Change updatedChange = db.changes().atomicUpdate(changeId,
new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
@@ -95,6 +106,15 @@
}
}
});
+
+ if ((updatedChange != null) &&
+ (updatedChange.getStatus() == Change.Status.NEW)) {
+ published = true;
+ }
+ }
+
+ if (published) {
+ hooks.doDraftPublishedHook(change, patch, db);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
index 966efce..53da2b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
@@ -34,21 +34,17 @@
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import java.io.IOException;
import java.util.concurrent.Callable;
-import javax.annotation.Nullable;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
public class RestoreChange implements Callable<ReviewResult> {
- public interface Factory {
- RestoreChange create(Change.Id changeId, String changeComment);
- }
-
private final RestoredSender.Factory restoredSenderFactory;
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
@@ -56,15 +52,25 @@
private final IdentifiedUser currentUser;
private final ChangeHooks hooks;
- private final Change.Id changeId;
- private final String changeComment;
+ @Argument(index = 0, required = true, multiValued = false,
+ usage = "change to restore", metaVar = "CHANGE")
+ private Change.Id changeId;
+ public void setChangeId(final Change.Id changeId) {
+ this.changeId = changeId;
+ }
+
+ @Option(name = "--message", aliases = {"-m"},
+ usage = "optional message to append to change")
+ private String message;
+ public void setMessage(final String message) {
+ this.message = message;
+ }
@Inject
RestoreChange(final RestoredSender.Factory restoredSenderFactory,
final ChangeControl.Factory changeControlFactory, final ReviewDb db,
final GitRepositoryManager repoManager, final IdentifiedUser currentUser,
- final ChangeHooks hooks, @Assisted final Change.Id changeId,
- @Assisted @Nullable final String changeComment) {
+ final ChangeHooks hooks) {
this.restoredSenderFactory = restoredSenderFactory;
this.changeControlFactory = changeControlFactory;
this.db = db;
@@ -72,14 +78,18 @@
this.currentUser = currentUser;
this.hooks = hooks;
- this.changeId = changeId;
- this.changeComment = changeComment;
+ changeId = null;
+ message = null;
}
@Override
- public ReviewResult call() throws EmailException,
- InvalidChangeOperationException, NoSuchChangeException, OrmException,
+ public ReviewResult call() throws EmailException, NoSuchChangeException,
+ InvalidChangeOperationException, OrmException,
RepositoryNotFoundException, IOException {
+ if (changeId == null) {
+ throw new InvalidChangeOperationException("changeId is required");
+ }
+
final ReviewResult result = new ReviewResult();
result.setChangeId(changeId);
@@ -110,9 +120,9 @@
.messageUUID(db)), currentUser.getAccountId(), patchSetId);
final StringBuilder msgBuf =
new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
- if (changeComment != null && changeComment.length() > 0) {
+ if (message != null && message.length() > 0) {
msgBuf.append("\n\n");
- msgBuf.append(changeComment);
+ msgBuf.append(message);
}
cmsg.setMessage(msgBuf.toString());
@@ -121,8 +131,7 @@
new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
- if (change.getStatus() == Change.Status.ABANDONED
- && change.currentPatchSetId().equals(patchSetId)) {
+ if (change.getStatus() == Change.Status.ABANDONED) {
change.setStatus(Change.Status.NEW);
ChangeUtil.updated(change);
return change;
@@ -132,12 +141,16 @@
}
});
- ChangeUtil.updatedChange(
- db, currentUser, updatedChange, cmsg, restoredSenderFactory,
- "Change is not abandoned or patchset is not latest");
+ if (updatedChange == null) {
+ result.addError(new ReviewResult.Error(
+ ReviewResult.Error.Type.CHANGE_NOT_ABANDONED));
+ return result;
+ }
- hooks.doChangeRestoreHook(updatedChange, currentUser.getAccount(),
- changeComment, db);
+ ChangeUtil.updatedChange(db, currentUser, updatedChange, cmsg,
+ restoredSenderFactory);
+ hooks.doChangeRestoredHook(updatedChange, currentUser.getAccount(),
+ message, db);
return result;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
index 6648c7b..3287aa1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
@@ -24,6 +24,8 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.ProjectUtil;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.project.ChangeControl;
@@ -34,6 +36,7 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -49,6 +52,7 @@
private final MergeOp.Factory opFactory;
private final MergeQueue merger;
private final ReviewDb db;
+ private final GitRepositoryManager repoManager;
private final IdentifiedUser currentUser;
private final PatchSet.Id patchSetId;
@@ -56,12 +60,13 @@
@Inject
Submit(final ChangeControl.Factory changeControlFactory,
final MergeOp.Factory opFactory, final MergeQueue merger,
- final ReviewDb db, final IdentifiedUser currentUser,
- @Assisted final PatchSet.Id patchSetId) {
+ final ReviewDb db, final GitRepositoryManager repoManager,
+ final IdentifiedUser currentUser, @Assisted final PatchSet.Id patchSetId) {
this.changeControlFactory = changeControlFactory;
this.opFactory = opFactory;
this.merger = merger;
this.db = db;
+ this.repoManager = repoManager;
this.currentUser = currentUser;
this.patchSetId = patchSetId;
@@ -69,7 +74,8 @@
@Override
public ReviewResult call() throws IllegalStateException,
- InvalidChangeOperationException, NoSuchChangeException, OrmException {
+ InvalidChangeOperationException, NoSuchChangeException, OrmException,
+ IOException {
final ReviewResult result = new ReviewResult();
final PatchSet patch = db.patchSets().get(patchSetId);
@@ -113,6 +119,10 @@
errMsg.append("change " + changeId + ": needs " + lbl.label);
break;
+ case MAY:
+ // The MAY label didn't cause the NOT_READY status
+ break;
+
case IMPOSSIBLE:
if (errMsg.length() > 0) errMsg.append("; ");
errMsg.append("change " + changeId + ": needs " + lbl.label
@@ -147,6 +157,14 @@
}
}
+ if (!ProjectUtil.branchExists(repoManager, control.getChange().getDest())) {
+ result.addError(new ReviewResult.Error(
+ ReviewResult.Error.Type.DEST_BRANCH_NOT_FOUND,
+ "Destination branch \"" + control.getChange().getDest().get()
+ + "\" not found."));
+ return result;
+ }
+
// Submit the change if we can
if (result.getErrors().isEmpty()) {
final List<PatchSetApproval> allApprovals =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index a0f0d36..dc36988 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -36,7 +36,10 @@
private final AuthType authType;
private final String httpHeader;
private final boolean trustContainerAuth;
+ private final boolean userNameToLowerCase;
+ private final boolean gitBasicAuth;
private final String logoutUrl;
+ private final String openIdSsoUrl;
private final List<OpenIdProviderPattern> trustedOpenIDs;
private final List<OpenIdProviderPattern> allowedOpenIDs;
private final String cookiePath;
@@ -51,11 +54,15 @@
authType = toType(cfg);
httpHeader = cfg.getString("auth", null, "httpheader");
logoutUrl = cfg.getString("auth", null, "logouturl");
+ openIdSsoUrl = cfg.getString("auth", null, "openidssourl");
trustedOpenIDs = toPatterns(cfg, "trustedOpenID");
allowedOpenIDs = toPatterns(cfg, "allowedOpenID");
cookiePath = cfg.getString("auth", null, "cookiepath");
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
+ gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
+ userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
+
String key = cfg.getString("auth", null, "registerEmailPrivateKey");
if (key != null && !key.isEmpty()) {
@@ -106,6 +113,10 @@
return logoutUrl;
}
+ public String getOpenIdSsoUrl() {
+ return openIdSsoUrl;
+ }
+
public String getCookiePath() {
return cookiePath;
}
@@ -132,6 +143,16 @@
return trustContainerAuth;
}
+ /** Whether user name should be converted to lower-case before validation */
+ public boolean isUserNameToLowerCase() {
+ return userNameToLowerCase;
+ }
+
+ /** Whether git-over-http should use Gerrit basic authentication scheme. */
+ public boolean isGitBasichAuth() {
+ return gitBasicAuth;
+ }
+
public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) {
switch (getAuthType()) {
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
@@ -146,6 +167,10 @@
//
return true;
+ case OPENID_SSO:
+ // There's only one provider in SSO mode, so it must be okay.
+ return true;
+
case OPENID:
// All identities must be trusted in order to trust the account.
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 2875920..9e3aeca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -16,9 +16,11 @@
import static com.google.inject.Scopes.SINGLETON;
+import com.google.common.cache.Cache;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.rules.PrologModule;
@@ -36,11 +38,15 @@
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.DefaultRealm;
import com.google.gerrit.server.account.EmailExpander;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCacheImpl;
+import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
import com.google.gerrit.server.account.GroupInfoCacheFactory;
-import com.google.gerrit.server.account.MaterializedGroupMembership;
+import com.google.gerrit.server.account.IncludingGroupMembership;
+import com.google.gerrit.server.account.InternalGroupBackend;
import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.account.UniversalGroupBackend;
import com.google.gerrit.server.auth.ldap.LdapModule;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -68,7 +74,7 @@
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.inject.Inject;
-import com.google.inject.servlet.RequestScoped;
+import com.google.inject.TypeLiteral;
import org.apache.velocity.runtime.RuntimeInstance;
import org.eclipse.jgit.lib.Config;
@@ -128,12 +134,18 @@
factory(InternalUser.Factory.class);
factory(ProjectNode.Factory.class);
factory(ProjectState.Factory.class);
- factory(MaterializedGroupMembership.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class)
.toProvider(AccountVisibilityProvider.class)
.in(SINGLETON);
+ bind(GroupControl.Factory.class).in(SINGLETON);
+ factory(IncludingGroupMembership.Factory.class);
+ bind(InternalGroupBackend.class).in(SINGLETON);
+ bind(GroupBackend.class).to(UniversalGroupBackend.class).in(SINGLETON);
+ DynamicSet.setOf(binder(), GroupBackend.class);
+ DynamicSet.bind(binder(), GroupBackend.class).to(InternalGroupBackend.class);
+
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(ToolsCatalog.class);
bind(EventFactory.class);
@@ -156,6 +168,7 @@
factory(FunctionState.Factory.class);
bind(GitReferenceUpdated.class);
+ DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 5d6ecc3..ba54c56 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -17,28 +17,25 @@
import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.server.account.PerformRenameGroup;
import com.google.gerrit.server.account.VisibleGroups;
-import com.google.gerrit.server.changedetail.AbandonChange;
import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
import com.google.gerrit.server.changedetail.PublishDraft;
-import com.google.gerrit.server.changedetail.RestoreChange;
import com.google.gerrit.server.changedetail.Submit;
import com.google.gerrit.server.git.AsyncReceiveCommits;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.CreateCodeReviewNotes;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.NotesBranchUtil;
import com.google.gerrit.server.git.SubmoduleOp;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender;
@@ -79,7 +76,6 @@
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
bind(ChangeControl.Factory.class).in(SINGLETON);
- bind(GroupControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON);
bind(AccountControl.Factory.class).in(SINGLETON);
@@ -87,12 +83,12 @@
factory(SubmoduleOp.Factory.class);
factory(MergeOp.Factory.class);
factory(CreateCodeReviewNotes.Factory.class);
+ factory(NotesBranchUtil.Factory.class);
install(new AsyncReceiveCommits.Module());
// Not really per-request, but dammit, I don't know where else to
// easily park this stuff.
//
- factory(AbandonChange.Factory.class);
factory(AddReviewer.Factory.class);
factory(AddReviewerSender.Factory.class);
factory(CreateChangeSender.Factory.class);
@@ -103,7 +99,6 @@
factory(RebasedPatchSetSender.Factory.class);
factory(AbandonedSender.Factory.class);
factory(RemoveReviewer.Factory.class);
- factory(RestoreChange.Factory.class);
factory(RestoredSender.Factory.class);
factory(RevertedSender.Factory.class);
factory(CommentSender.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
index c89f025..8b517a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -24,9 +24,9 @@
public class GitReceivePackGroupsProvider extends GroupSetProvider {
@Inject
- public GitReceivePackGroupsProvider(GroupCache gc,
+ public GitReceivePackGroupsProvider(GroupBackend gb,
@GerritServerConfig Config config) {
- super(gc, config, "receive", null, "allowGroup");
+ super(gb, config, "receive", null, "allowGroup");
// If no group was set, default to "registered users"
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
index b5de742..c519902 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -25,9 +25,9 @@
public class GitUploadPackGroupsProvider extends GroupSetProvider {
@Inject
- public GitUploadPackGroupsProvider(GroupCache gc,
+ public GitUploadPackGroupsProvider(GroupBackend gb,
@GerritServerConfig Config config) {
- super(gc, config, "upload", null, "allowGroup");
+ super(gb, config, "upload", null, "allowGroup");
// If no group was set, default to "registered users" and "anonymous"
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
index 3619cda..5fa243b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -15,8 +15,10 @@
package com.google.gerrit.server.config;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -34,17 +36,17 @@
protected Set<AccountGroup.UUID> groupIds;
@Inject
- protected GroupSetProvider(GroupCache groupCache,
+ protected GroupSetProvider(GroupBackend groupBackend,
@GerritServerConfig Config config, String section,
String subsection, String name) {
String[] groupNames = config.getStringList(section, subsection, name);
ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
for (String n : groupNames) {
- AccountGroup g = groupCache.get(new AccountGroup.NameKey(n));
- if (g != null) {
- builder.add(g.getGroupUUID());
- } else {
+ GroupReference g = GroupBackends.findBestSuggestion(groupBackend, n);
+ if (g == null) {
log.warn("Group \"{0}\" not in database, skipping.", n);
+ } else {
+ builder.add(g.getUUID());
}
}
groupIds = builder.build();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
index 7172b6f..6622b0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -32,8 +32,8 @@
*/
public class ProjectOwnerGroupsProvider extends GroupSetProvider {
@Inject
- public ProjectOwnerGroupsProvider(GroupCache gc,
+ public ProjectOwnerGroupsProvider(GroupBackend gb,
@GerritServerConfig final Config config) {
- super(gc, config, "repository", "*", "ownerGroup");
+ super(gb, config, "repository", "*", "ownerGroup");
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
index 2ad7ffe..2d88b83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
@@ -17,4 +17,5 @@
public class AccountAttribute {
public String name;
public String email;
+ public String username;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
index 1a2922b..717e23c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.events;
-public class ChangeRestoreEvent extends ChangeEvent {
+public class ChangeRestoredEvent extends ChangeEvent {
public final String type = "change-restored";
public ChangeAttribute change;
public PatchSetAttribute patchSet;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
similarity index 75%
copy from gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
index 1a2922b..c90ac90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeRestoreEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
package com.google.gerrit.server.events;
-public class ChangeRestoreEvent extends ChangeEvent {
- public final String type = "change-restored";
+public class DraftPublishedEvent extends ChangeEvent {
+ public final String type = "draft-published";
public ChangeAttribute change;
public PatchSetAttribute patchSet;
- public AccountAttribute restorer;
- public String reason;
+ public AccountAttribute uploader;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index c538aa6..41cac4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -232,16 +233,19 @@
public void addPatchSetFileNames(PatchSetAttribute patchSetAttribute,
Change change, PatchSet patchSet) {
- PatchList patchList = patchListCache.get(change, patchSet);
- for (PatchListEntry patch : patchList.getPatches()) {
- if (patchSetAttribute.files == null) {
- patchSetAttribute.files = new ArrayList<PatchAttribute>();
- }
+ try {
+ PatchList patchList = patchListCache.get(change, patchSet);
+ for (PatchListEntry patch : patchList.getPatches()) {
+ if (patchSetAttribute.files == null) {
+ patchSetAttribute.files = new ArrayList<PatchAttribute>();
+ }
- PatchAttribute p = new PatchAttribute();
- p.file = patch.getNewName();
- p.type = patch.getChangeType();
- patchSetAttribute.files.add(p);
+ PatchAttribute p = new PatchAttribute();
+ p.file = patch.getNewName();
+ p.type = patch.getChangeType();
+ patchSetAttribute.files.add(p);
+ }
+ } catch (PatchListNotAvailableException e) {
}
}
@@ -338,6 +342,7 @@
AccountAttribute who = new AccountAttribute();
who.name = account.getFullName();
who.email = account.getPreferredEmail();
+ who.username = account.getUserName();
return who;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
index c9c9753..da38573 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -16,93 +16,71 @@
import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
+import com.google.common.base.Strings;
import com.google.gerrit.common.errors.PermissionDeniedException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.notes.NoteMapMerger;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
import java.util.List;
+import java.util.TimeZone;
public class BanCommit {
-
- private static final int MAX_LOCK_FAILURE_CALLS = 10;
- private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
-
public interface Factory {
BanCommit create();
}
- private final Provider<CurrentUser> currentUser;
+ private final Provider<IdentifiedUser> currentUser;
private final GitRepositoryManager repoManager;
- private final AccountCache accountCache;
private final PersonIdent gerritIdent;
+ private NotesBranchUtil.Factory notesBranchUtilFactory;
@Inject
- BanCommit(final Provider<CurrentUser> currentUser,
- final GitRepositoryManager repoManager, final AccountCache accountCache,
- @GerritPersonIdent final PersonIdent gerritIdent) {
+ BanCommit(final Provider<IdentifiedUser> currentUser,
+ final GitRepositoryManager repoManager,
+ @GerritPersonIdent final PersonIdent gerritIdent,
+ final NotesBranchUtil.Factory notesBranchUtilFactory) {
this.currentUser = currentUser;
this.repoManager = repoManager;
- this.accountCache = accountCache;
this.gerritIdent = gerritIdent;
+ this.notesBranchUtilFactory = notesBranchUtilFactory;
}
public BanCommitResult ban(final ProjectControl projectControl,
final List<ObjectId> commitsToBan, final String reason)
throws PermissionDeniedException, IOException,
- IncompleteUserInfoException, InterruptedException, MergeException {
+ InterruptedException, MergeException, ConcurrentRefUpdateException {
if (!projectControl.isOwner()) {
throw new PermissionDeniedException(
"No project owner: not permitted to ban commits");
}
final BanCommitResult result = new BanCommitResult();
-
- final PersonIdent currentUserIdent = createPersonIdent();
+ NoteMap banCommitNotes = NoteMap.newEmptyMap();
+ // add a note for each banned commit to notes
final Repository repo =
repoManager.openRepository(projectControl.getProject().getNameKey());
try {
final RevWalk revWalk = new RevWalk(repo);
final ObjectInserter inserter = repo.newObjectInserter();
try {
- NoteMap baseNoteMap = null;
- RevCommit baseCommit = null;
- final Ref notesBranch = repo.getRef(REF_REJECT_COMMITS);
- if (notesBranch != null) {
- baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
- baseNoteMap = NoteMap.read(revWalk.getObjectReader(), baseCommit);
- }
-
- final NoteMap ourNoteMap;
- if (baseCommit != null) {
- ourNoteMap = NoteMap.read(repo.newObjectReader(), baseCommit);
- } else {
- ourNoteMap = NoteMap.newEmptyMap();
- }
-
for (final ObjectId commitToBan : commitsToBan) {
try {
revWalk.parseCommit(commitToBan);
@@ -112,31 +90,22 @@
result.notACommit(commitToBan, e.getMessage());
continue;
}
+ banCommitNotes.set(commitToBan, createNoteContent(reason, inserter));
+ }
+ inserter.flush();
+ NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(repo);
+ NoteMap newlyCreated =
+ notesBranchUtil.commitNewNotes(banCommitNotes, REF_REJECT_COMMITS,
+ createPersonIdent(), buildCommitMessage(commitsToBan, reason));
- final Note note = ourNoteMap.getNote(commitToBan);
- if (note != null) {
- result.commitAlreadyBanned(commitToBan);
- continue;
+ for (Note n : banCommitNotes) {
+ if (newlyCreated.contains(n)) {
+ result.commitBanned(n);
+ } else {
+ result.commitAlreadyBanned(n);
}
-
- final String noteContent = reason != null ? reason : "";
- final ObjectId noteContentId =
- inserter
- .insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
- ourNoteMap.set(commitToBan, noteContentId);
- result.commitBanned(commitToBan);
}
-
- if (result.getNewlyBannedCommits().isEmpty()) {
- return result;
- }
-
- final ObjectId ourCommit =
- commit(ourNoteMap, inserter, currentUserIdent, baseCommit, result,
- reason);
-
- updateRef(repo, revWalk, inserter, ourNoteMap, ourCommit, baseNoteMap,
- baseCommit);
+ return result;
} finally {
revWalk.release();
inserter.release();
@@ -144,49 +113,21 @@
} finally {
repo.close();
}
-
- return result;
}
- private PersonIdent createPersonIdent() throws IncompleteUserInfoException {
- final String userName = currentUser.get().getUserName();
- final Account account = accountCache.getByUsername(userName).getAccount();
- if (account.getFullName() == null) {
- throw new IncompleteUserInfoException(userName, "full name");
+ private ObjectId createNoteContent(String reason, ObjectInserter inserter)
+ throws UnsupportedEncodingException, IOException {
+ String noteContent = reason != null ? reason : "";
+ if (noteContent.length() > 0 && !noteContent.endsWith("\n")) {
+ noteContent = noteContent + "\n";
}
- if (account.getPreferredEmail() == null) {
- throw new IncompleteUserInfoException(userName, "preferred email");
- }
- return new PersonIdent(account.getFullName(), account.getPreferredEmail());
+ return inserter.insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
}
- private static ObjectId commit(final NoteMap noteMap,
- final ObjectInserter inserter, final PersonIdent personIdent,
- final ObjectId baseCommit, final BanCommitResult result,
- final String reason) throws IOException {
- final String commitMsg =
- buildCommitMessage(result.getNewlyBannedCommits(), reason);
- if (baseCommit != null) {
- return createCommit(noteMap, inserter, personIdent, commitMsg, baseCommit);
- } else {
- return createCommit(noteMap, inserter, personIdent, commitMsg);
- }
- }
-
- private static ObjectId createCommit(final NoteMap noteMap,
- final ObjectInserter inserter, final PersonIdent personIdent,
- final String message, final ObjectId... parents) throws IOException {
- final CommitBuilder b = new CommitBuilder();
- b.setTreeId(noteMap.writeTree(inserter));
- b.setAuthor(personIdent);
- b.setCommitter(personIdent);
- if (parents.length > 0) {
- b.setParentIds(parents);
- }
- b.setMessage(message);
- final ObjectId commitId = inserter.insert(b);
- inserter.flush();
- return commitId;
+ private PersonIdent createPersonIdent() {
+ Date now = new Date();
+ TimeZone tz = gerritIdent.getTimeZone();
+ return currentUser.get().newCommitterIdent(now, tz);
}
private static String buildCommitMessage(final List<ObjectId> bannedCommits,
@@ -213,61 +154,4 @@
commitMsg.append(commitList);
return commitMsg.toString();
}
-
- public void updateRef(final Repository repo, final RevWalk revWalk,
- final ObjectInserter inserter, final NoteMap ourNoteMap,
- final ObjectId oursCommit, final NoteMap baseNoteMap,
- final ObjectId baseCommit) throws IOException, InterruptedException,
- MissingObjectException, IncorrectObjectTypeException,
- CorruptObjectException, MergeException {
-
- int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
- RefUpdate refUpdate = createRefUpdate(repo, oursCommit, baseCommit);
-
- for (;;) {
- final Result result = refUpdate.update();
-
- if (result == Result.LOCK_FAILURE) {
- if (--remainingLockFailureCalls > 0) {
- Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
- } else {
- throw new MergeException("Failed to lock the ref: "
- + REF_REJECT_COMMITS);
- }
-
- } else if (result == Result.REJECTED) {
- final RevCommit theirsCommit =
- revWalk.parseCommit(refUpdate.getOldObjectId());
- final NoteMap theirNoteMap =
- NoteMap.read(revWalk.getObjectReader(), theirsCommit);
- final NoteMapMerger merger = new NoteMapMerger(repo);
- final NoteMap merged =
- merger.merge(baseNoteMap, ourNoteMap, theirNoteMap);
- final ObjectId mergeCommit =
- createCommit(merged, inserter, gerritIdent,
- "Merged note commits\n", oursCommit, theirsCommit);
- refUpdate = createRefUpdate(repo, mergeCommit, theirsCommit);
- remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-
- } else if (result == Result.IO_FAILURE) {
- throw new IOException(
- "Couldn't create commit reject notes because of IO_FAILURE");
- } else {
- break;
- }
- }
- }
-
- private static RefUpdate createRefUpdate(final Repository repo,
- final ObjectId newObjectId, final ObjectId expectedOldObjectId)
- throws IOException {
- RefUpdate refUpdate = repo.updateRef(REF_REJECT_COMMITS);
- refUpdate.setNewObjectId(newObjectId);
- if (expectedOldObjectId == null) {
- refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
- } else {
- refUpdate.setExpectedOldObjectId(expectedOldObjectId);
- }
- return refUpdate;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
index 6fea8f1..b067a49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
@@ -20,6 +20,7 @@
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@@ -31,23 +32,15 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.notes.NoteMapMerger;
-import org.eclipse.jgit.notes.NoteMerger;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -68,30 +61,22 @@
CreateCodeReviewNotes create(ReviewDb reviewDb, Repository db);
}
- private static final int MAX_LOCK_FAILURE_CALLS = 10;
- private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
- private final ReviewDb schema;
- private final PersonIdent gerritIdent;
private final AccountCache accountCache;
private final ApprovalTypes approvalTypes;
private final String canonicalWebUrl;
private final String anonymousCowardName;
+ private final ReviewDb schema;
private final Repository db;
- private final RevWalk revWalk;
- private final ObjectInserter inserter;
- private final ObjectReader reader;
- private RevCommit baseCommit;
- private NoteMap base;
-
- private RevCommit oursCommit;
- private NoteMap ours;
-
- private List<CodeReviewCommit> commits;
private PersonIdent author;
+ private RevWalk revWalk;
+ private ObjectInserter inserter;
+
+ private final NotesBranchUtil.Factory notesBranchUtilFactory;
+
@Inject
CreateCodeReviewNotes(
@GerritPersonIdent final PersonIdent gerritIdent,
@@ -99,90 +84,87 @@
final ApprovalTypes approvalTypes,
final @Nullable @CanonicalWebUrl String canonicalWebUrl,
final @AnonymousCowardName String anonymousCowardName,
+ final NotesBranchUtil.Factory notesBranchUtilFactory,
final @Assisted ReviewDb reviewDb,
final @Assisted Repository db) {
- schema = reviewDb;
this.author = gerritIdent;
- this.gerritIdent = gerritIdent;
this.accountCache = accountCache;
this.approvalTypes = approvalTypes;
this.canonicalWebUrl = canonicalWebUrl;
this.anonymousCowardName = anonymousCowardName;
+ this.notesBranchUtilFactory = notesBranchUtilFactory;
+ schema = reviewDb;
this.db = db;
-
- revWalk = new RevWalk(db);
- inserter = db.newObjectInserter();
- reader = db.newObjectReader();
}
public void create(List<CodeReviewCommit> commits, PersonIdent author)
throws CodeReviewNoteCreationException {
try {
- this.commits = commits;
- this.author = author;
- loadBase();
- applyNotes();
- updateRef();
+ revWalk = new RevWalk(db);
+ inserter = db.newObjectInserter();
+ if (author != null) {
+ this.author = author;
+ }
+
+ NoteMap notes = NoteMap.newEmptyMap();
+ StringBuilder message =
+ new StringBuilder("Update notes for submitted changes\n\n");
+ for (CodeReviewCommit c : commits) {
+ notes.set(c, createNoteContent(c.change, c));
+ message.append("* ").append(c.getShortMessage()).append("\n");
+ }
+
+ NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+ notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
+ message.toString());
} catch (IOException e) {
throw new CodeReviewNoteCreationException(e);
- } catch (InterruptedException e) {
+ } catch (ConcurrentRefUpdateException e) {
throw new CodeReviewNoteCreationException(e);
} finally {
- release();
+ revWalk.release();
+ inserter.release();
}
}
- public void loadBase() throws IOException {
- Ref notesBranch = db.getRef(REFS_NOTES_REVIEW);
- if (notesBranch != null) {
- baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
- base = NoteMap.read(revWalk.getObjectReader(), baseCommit);
- }
- if (baseCommit != null) {
- ours = NoteMap.read(db.newObjectReader(), baseCommit);
- } else {
- ours = NoteMap.newEmptyMap();
+ public void create(List<Change> changes, PersonIdent author,
+ String commitMessage, ProgressMonitor monitor) throws OrmException,
+ IOException, CodeReviewNoteCreationException {
+ try {
+ revWalk = new RevWalk(db);
+ inserter = db.newObjectInserter();
+ if (author != null) {
+ this.author = author;
+ }
+ if (monitor == null) {
+ monitor = NullProgressMonitor.INSTANCE;
+ }
+
+ NoteMap notes = NoteMap.newEmptyMap();
+ for (Change c : changes) {
+ monitor.update(1);
+ PatchSet ps = schema.patchSets().get(c.currentPatchSetId());
+ ObjectId commitId = ObjectId.fromString(ps.getRevision().get());
+ notes.set(commitId, createNoteContent(c, commitId));
+ }
+
+ NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+ notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
+ commitMessage);
+ } catch (ConcurrentRefUpdateException e) {
+ throw new CodeReviewNoteCreationException(e);
+ } finally {
+ revWalk.release();
+ inserter.release();
}
}
- private void applyNotes() throws IOException, CodeReviewNoteCreationException {
- StringBuilder message =
- new StringBuilder("Update notes for submitted changes\n\n");
- for (CodeReviewCommit c : commits) {
- add(c.change, c);
- message.append("* ").append(c.getShortMessage()).append("\n");
- }
- commit(message.toString());
- }
-
- public void commit(String message) throws IOException {
- if (baseCommit != null) {
- oursCommit = createCommit(ours, author, message, baseCommit);
- } else {
- oursCommit = createCommit(ours, author, message);
- }
- }
-
- public void add(Change change, ObjectId commit)
- throws MissingObjectException, IncorrectObjectTypeException, IOException,
- CodeReviewNoteCreationException {
+ private ObjectId createNoteContent(Change change, ObjectId commit)
+ throws CodeReviewNoteCreationException, IOException {
if (!(commit instanceof RevCommit)) {
commit = revWalk.parseCommit(commit);
}
-
- RevCommit c = (RevCommit) commit;
- ObjectId noteContent = createNoteContent(change, c);
- if (ours.contains(c)) {
- // merge the existing and the new note as if they are both new
- // means: base == null
- // there is not really a common ancestry for these two note revisions
- // use the same NoteMerger that is used from the NoteMapMerger
- NoteMerger noteMerger = new ReviewNoteMerger();
- Note newNote = new Note(c, noteContent);
- noteContent = noteMerger.merge(null, newNote, ours.getNote(c),
- reader, inserter).getData();
- }
- ours.set(c, noteContent);
+ return createNoteContent(change, (RevCommit) commit);
}
private ObjectId createNoteContent(Change change, RevCommit commit)
@@ -227,83 +209,4 @@
throw new CodeReviewNoteCreationException(commit, e);
}
}
-
- public void updateRef() throws IOException, InterruptedException,
- CodeReviewNoteCreationException, MissingObjectException,
- IncorrectObjectTypeException, CorruptObjectException {
- if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) {
- // If the trees are identical, there is no change in the notes.
- // Avoid saving this commit as it has no new information.
- return;
- }
-
- int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
- RefUpdate refUpdate = createRefUpdate(oursCommit, baseCommit);
-
- for (;;) {
- Result result = refUpdate.update();
-
- if (result == Result.LOCK_FAILURE) {
- if (--remainingLockFailureCalls > 0) {
- Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
- } else {
- throw new CodeReviewNoteCreationException(
- "Failed to lock the ref: " + REFS_NOTES_REVIEW);
- }
-
- } else if (result == Result.REJECTED) {
- RevCommit theirsCommit =
- revWalk.parseCommit(refUpdate.getOldObjectId());
- NoteMap theirs =
- NoteMap.read(revWalk.getObjectReader(), theirsCommit);
- NoteMapMerger merger = new NoteMapMerger(db);
- NoteMap merged = merger.merge(base, ours, theirs);
- RevCommit mergeCommit =
- createCommit(merged, gerritIdent, "Merged note commits\n",
- theirsCommit, oursCommit);
- refUpdate = createRefUpdate(mergeCommit, theirsCommit);
- remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-
- } else if (result == Result.IO_FAILURE) {
- throw new CodeReviewNoteCreationException(
- "Couldn't create code review notes because of IO_FAILURE");
- } else {
- break;
- }
- }
- }
-
- public void release() {
- reader.release();
- inserter.release();
- revWalk.release();
- }
-
- private RevCommit createCommit(NoteMap map, PersonIdent author,
- String message, RevCommit... parents) throws IOException {
- CommitBuilder b = new CommitBuilder();
- b.setTreeId(map.writeTree(inserter));
- b.setAuthor(author != null ? author : gerritIdent);
- b.setCommitter(gerritIdent);
- if (parents.length > 0) {
- b.setParentIds(parents);
- }
- b.setMessage(message);
- ObjectId commitId = inserter.insert(b);
- inserter.flush();
- return revWalk.parseCommit(commitId);
- }
-
-
- private RefUpdate createRefUpdate(ObjectId newObjectId,
- ObjectId expectedOldObjectId) throws IOException {
- RefUpdate refUpdate = db.updateRef(REFS_NOTES_REVIEW);
- refUpdate.setNewObjectId(newObjectId);
- if (expectedOldObjectId == null) {
- refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
- } else {
- refUpdate.setExpectedOldObjectId(expectedOldObjectId);
- }
- return refUpdate;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LargeObjectException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LargeObjectException.java
new file mode 100644
index 0000000..d08b8768
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LargeObjectException.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+/**
+ * Wrapper for {@link org.eclipse.jgit.errors.LargeObjectException}. Since
+ * org.eclipse.jgit.errors.LargeObjectException is a {@link RuntimeException}
+ * the GerritJsonServlet would treat it as internal failure and as result the
+ * web ui would just show 'Internal Server Error'. Wrapping
+ * org.eclipse.jgit.errors.LargeObjectException into a normal {@link Exception}
+ * allows to display a proper error message.
+ */
+public class LargeObjectException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public LargeObjectException(final String message,
+ final org.eclipse.jgit.errors.LargeObjectException cause) {
+ super(message, cause);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 6ddcb39..fa47a2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -159,6 +159,7 @@
private CodeReviewCommit mergeTip;
private Set<RevCommit> alreadyAccepted;
private RefUpdate branchUpdate;
+ private ObjectInserter inserter;
private final ChangeHooks hooks;
private final AccountCache accountCache;
@@ -248,10 +249,12 @@
log.error("Test merge attempt for change: " + change.getId()
+ " failed", e);
} finally {
+ if (repo != null) {
+ repo.close();
+ }
if (db != null) {
db.close();
}
- db = null;
}
}
@@ -282,14 +285,18 @@
} catch (OrmException e) {
throw new MergeException("Cannot query the database", e);
} finally {
+ if (inserter != null) {
+ inserter.release();
+ }
if (rw != null) {
rw.release();
}
if (repo != null) {
repo.close();
}
- db.close();
- db = null;
+ if (db != null) {
+ db.close();
+ }
}
}
@@ -334,6 +341,8 @@
rw.sort(RevSort.TOPO);
rw.sort(RevSort.COMMIT_TIME_DESC, true);
CAN_MERGE = rw.newFlag("CAN_MERGE");
+
+ inserter = repo.newObjectInserter();
}
private void openBranch() throws MergeException {
@@ -349,6 +358,21 @@
branchTip = null;
}
+ try {
+ final Ref destRef = repo.getRef(destBranch.get());
+ if (destRef != null) {
+ branchUpdate.setExpectedOldObjectId(destRef.getObjectId());
+ } else if (repo.getFullBranch().equals(destBranch.get())) {
+ branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+ } else {
+ throw new MergeException("Destination branch \""
+ + branchUpdate.getRef().getName() + "\" does not exist");
+ }
+ } catch (IOException e) {
+ throw new MergeException(
+ "Failed to check existence of destination branch", e);
+ }
+
for (final Ref r : repo.getAllRefs().values()) {
if (r.getName().startsWith(Constants.R_HEADS)
|| r.getName().startsWith(Constants.R_TAGS)) {
@@ -505,21 +529,10 @@
}
private void mergeOneCommit(final CodeReviewCommit n) throws MergeException {
- final ThreeWayMerger m;
- if (destProject.isUseContentMerge()) {
- // Settings for this project allow us to try and
- // automatically resolve conflicts within files if needed.
- // Use ResolveMerge and instruct to operate in core.
- m = MergeStrategy.RESOLVE.newMerger(repo, true);
- } else {
- // No auto conflict resolving allowed. If any of the
- // affected files was modified, merge will fail.
- m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
- }
-
+ final ThreeWayMerger m = newThreeWayMerger();
try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
- writeMergeCommit(m, n);
+ writeMergeCommit(m.getResultTreeId(), n);
} else {
failed(n, CommitMergeStatus.PATH_CONFLICT);
@@ -537,6 +550,35 @@
}
}
+ private ThreeWayMerger newThreeWayMerger() {
+ ThreeWayMerger m;
+ if (destProject.isUseContentMerge()) {
+ // Settings for this project allow us to try and
+ // automatically resolve conflicts within files if needed.
+ // Use ResolveMerge and instruct to operate in core.
+ m = MergeStrategy.RESOLVE.newMerger(repo, true);
+ } else {
+ // No auto conflict resolving allowed. If any of the
+ // affected files was modified, merge will fail.
+ m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
+ }
+ m.setObjectInserter(new ObjectInserter.Filter() {
+ @Override
+ protected ObjectInserter delegate() {
+ return inserter;
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void release() {
+ }
+ });
+ return m;
+ }
+
private CodeReviewCommit failed(final CodeReviewCommit n,
final CommitMergeStatus failure) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -550,7 +592,7 @@
return failed;
}
- private void writeMergeCommit(final Merger m, final CodeReviewCommit n)
+ private void writeMergeCommit(ObjectId treeId, CodeReviewCommit n)
throws IOException, MissingObjectException, IncorrectObjectTypeException {
final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
rw.reset();
@@ -599,13 +641,13 @@
PersonIdent authorIdent = computeAuthor(merged);
final CommitBuilder mergeCommit = new CommitBuilder();
- mergeCommit.setTreeId(m.getResultTreeId());
+ mergeCommit.setTreeId(treeId);
mergeCommit.setParentIds(mergeTip, n);
mergeCommit.setAuthor(authorIdent);
mergeCommit.setCommitter(myIdent);
mergeCommit.setMessage(msgbuf.toString());
- mergeTip = (CodeReviewCommit) rw.parseCommit(commit(m, mergeCommit));
+ mergeTip = (CodeReviewCommit) rw.parseCommit(commit(mergeCommit));
}
private PersonIdent computeAuthor(
@@ -691,19 +733,7 @@
private void cherryPickChanges() throws MergeException, OrmException {
while (!toMerge.isEmpty()) {
final CodeReviewCommit n = toMerge.remove(0);
- final ThreeWayMerger m;
-
- if (destProject.isUseContentMerge()) {
- // Settings for this project allow us to try and
- // automatically resolve conflicts within files if needed.
- // Use ResolveMerge and instruct to operate in core.
- m = MergeStrategy.RESOLVE.newMerger(repo, true);
- } else {
- // No auto conflict resolving allowed. If any of the
- // affected files was modified, merge will fail.
- m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
- }
-
+ final ThreeWayMerger m = newThreeWayMerger();
try {
if (mergeTip == null) {
// The branch is unborn. Take a fast-forward resolution to
@@ -898,8 +928,9 @@
mergeCommit.setCommitter(toCommitterIdent(submitAudit));
mergeCommit.setMessage(msgbuf.toString());
- final ObjectId id = commit(m, mergeCommit);
+ final ObjectId id = commit(mergeCommit);
final CodeReviewCommit newCommit = (CodeReviewCommit) rw.parseCommit(id);
+ final Change oldChange = n.change;
n.change =
db.changes().atomicUpdate(n.change.getId(),
@@ -929,6 +960,9 @@
}
});
+ this.submitted.remove(oldChange);
+ this.submitted.add(n.change);
+
if (approvalList != null) {
for (PatchSetApproval a : approvalList) {
db.patchSetApprovals().insert(
@@ -957,16 +991,11 @@
db.patchSetAncestors().insert(toInsert);
}
- private ObjectId commit(final Merger m, final CommitBuilder mergeCommit)
+ private ObjectId commit(CommitBuilder mergeCommit)
throws IOException, UnsupportedEncodingException {
- ObjectInserter oi = m.getObjectInserter();
- try {
- ObjectId id = oi.insert(mergeCommit);
- oi.flush();
- return id;
- } finally {
- oi.release();
- }
+ ObjectId id = inserter.insert(mergeCommit);
+ inserter.flush();
+ return id;
}
private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
new file mode 100644
index 0000000..17cfea8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -0,0 +1,271 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.notes.NoteMapMerger;
+import org.eclipse.jgit.notes.NoteMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+/**
+ * A utility class for updating a notes branch with automatic merge of note
+ * trees.
+ */
+public class NotesBranchUtil {
+ public interface Factory {
+ NotesBranchUtil create(Repository db);
+ }
+
+ private static final int MAX_LOCK_FAILURE_CALLS = 10;
+ private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
+
+ private PersonIdent gerritIdent;
+ private final Repository db;
+
+ private RevCommit baseCommit;
+ private NoteMap base;
+
+ private RevCommit oursCommit;
+ private NoteMap ours;
+
+ private RevWalk revWalk;
+ private ObjectInserter inserter;
+ private ObjectReader reader;
+ private boolean overwrite;
+
+ private ReviewNoteMerger noteMerger;
+
+ @Inject
+ public NotesBranchUtil(@GerritPersonIdent final PersonIdent gerritIdent,
+ @Assisted Repository db) {
+ this.gerritIdent = gerritIdent;
+ this.db = db;
+ }
+
+ /**
+ * Create a new commit in the <code>notesBranch</code> by updating existing
+ * or creating new notes from the <code>notes</code> map.
+ *
+ * @param notes map of notes
+ * @param notesBranch notes branch to update
+ * @param commitAuthor author of the commit in the notes branch
+ * @param commitMessage for the commit in the notes branch
+ * @throws IOException
+ * @throws ConcurrentRefUpdateException
+ */
+ public final void commitAllNotes(NoteMap notes, String notesBranch,
+ PersonIdent commitAuthor, String commitMessage) throws IOException,
+ ConcurrentRefUpdateException {
+ this.overwrite = true;
+ commitNotes(notes, notesBranch, commitAuthor, commitMessage);
+ }
+
+ /**
+ * Create a new commit in the <code>notesBranch</code> by creating not yet
+ * existing notes from the <code>notes</code> map. The notes from the
+ * <code>notes</code> map which already exist in the note-tree of the
+ * tip of the <code>notesBranch</code> will not be updated.
+ *
+ * @param notes map of notes
+ * @param notesBranch notes branch to update
+ * @param commitAuthor author of the commit in the notes branch
+ * @param commitMessage for the commit in the notes branch
+ * @return map with those notes from the <code>notes</code> that were newly
+ * created
+ * @throws IOException
+ * @throws ConcurrentRefUpdateException
+ */
+ public final NoteMap commitNewNotes(NoteMap notes, String notesBranch,
+ PersonIdent commitAuthor, String commitMessage) throws IOException,
+ ConcurrentRefUpdateException {
+ this.overwrite = false;
+ commitNotes(notes, notesBranch, commitAuthor, commitMessage);
+ NoteMap newlyCreated = NoteMap.newEmptyMap();
+ for (Note n : notes) {
+ if (base == null || !base.contains(n)) {
+ newlyCreated.set(n, n.getData());
+ }
+ }
+ return newlyCreated;
+ }
+
+ private void commitNotes(NoteMap notes, String notesBranch,
+ PersonIdent commitAuthor, String commitMessage) throws IOException,
+ ConcurrentRefUpdateException {
+ try {
+ revWalk = new RevWalk(db);
+ inserter = db.newObjectInserter();
+ reader = db.newObjectReader();
+ loadBase(notesBranch);
+ if (overwrite) {
+ addAllNotes(notes);
+ } else {
+ addNewNotes(notes);
+ }
+ if (base != null) {
+ oursCommit = createCommit(ours, commitAuthor, commitMessage, baseCommit);
+ } else {
+ oursCommit = createCommit(ours, commitAuthor, commitMessage);
+ }
+ updateRef(notesBranch);
+ } finally {
+ revWalk.release();
+ inserter.release();
+ reader.release();
+ }
+ }
+
+ private void addNewNotes(NoteMap notes) throws IOException {
+ for (Note n : notes) {
+ if (! ours.contains(n)) {
+ ours.set(n, n.getData());
+ }
+ }
+ }
+
+ private void addAllNotes(NoteMap notes) throws IOException {
+ for (Note n : notes) {
+ if (ours.contains(n)) {
+ // Merge the existing and the new note as if they are both new,
+ // means: base == null
+ // There is no really a common ancestry for these two note revisions
+ ObjectId noteContent = getNoteMerger().merge(null, n, ours.getNote(n),
+ reader, inserter).getData();
+ ours.set(n, noteContent);
+ } else {
+ ours.set(n, n.getData());
+ }
+ }
+ }
+
+ private NoteMerger getNoteMerger() {
+ if (noteMerger == null) {
+ noteMerger = new ReviewNoteMerger();
+ }
+ return noteMerger;
+ }
+
+ private void loadBase(String notesBranch) throws IOException {
+ Ref branch = db.getRef(notesBranch);
+ if (branch != null) {
+ baseCommit = revWalk.parseCommit(branch.getObjectId());
+ base = NoteMap.read(revWalk.getObjectReader(), baseCommit);
+ }
+ if (baseCommit != null) {
+ ours = NoteMap.read(revWalk.getObjectReader(), baseCommit);
+ } else {
+ ours = NoteMap.newEmptyMap();
+ }
+ }
+
+ private RevCommit createCommit(NoteMap map, PersonIdent author,
+ String message, RevCommit... parents) throws IOException {
+ CommitBuilder b = new CommitBuilder();
+ b.setTreeId(map.writeTree(inserter));
+ b.setAuthor(author != null ? author : gerritIdent);
+ b.setCommitter(gerritIdent);
+ if (parents.length > 0) {
+ b.setParentIds(parents);
+ }
+ b.setMessage(message);
+ ObjectId commitId = inserter.insert(b);
+ inserter.flush();
+ return revWalk.parseCommit(commitId);
+ }
+
+ private void updateRef(String notesBranch) throws IOException,
+ MissingObjectException, IncorrectObjectTypeException,
+ CorruptObjectException, ConcurrentRefUpdateException {
+ if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) {
+ // If the trees are identical, there is no change in the notes.
+ // Avoid saving this commit as it has no new information.
+ return;
+ }
+
+ int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+ RefUpdate refUpdate = createRefUpdate(notesBranch, oursCommit, baseCommit);
+
+ for (;;) {
+ Result result = refUpdate.update();
+
+ if (result == Result.LOCK_FAILURE) {
+ if (--remainingLockFailureCalls > 0) {
+ try {
+ Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ } else {
+ throw new ConcurrentRefUpdateException("Failed to lock the ref: "
+ + notesBranch, db.getRef(notesBranch), result);
+ }
+
+ } else if (result == Result.REJECTED) {
+ RevCommit theirsCommit =
+ revWalk.parseCommit(refUpdate.getOldObjectId());
+ NoteMap theirs =
+ NoteMap.read(revWalk.getObjectReader(), theirsCommit);
+ NoteMapMerger merger =
+ new NoteMapMerger(db, getNoteMerger(), MergeStrategy.RESOLVE);
+ NoteMap merged = merger.merge(base, ours, theirs);
+ RevCommit mergeCommit =
+ createCommit(merged, gerritIdent, "Merged note commits\n",
+ theirsCommit, oursCommit);
+ refUpdate = createRefUpdate(notesBranch, mergeCommit, theirsCommit);
+ remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+
+ } else if (result == Result.IO_FAILURE) {
+ throw new IOException("Couldn't update " + notesBranch + ". "
+ + result.name());
+ } else {
+ break;
+ }
+ }
+ }
+
+ private RefUpdate createRefUpdate(String notesBranch, ObjectId newObjectId,
+ ObjectId expectedOldObjectId) throws IOException {
+ RefUpdate refUpdate = db.updateRef(notesBranch);
+ refUpdate.setNewObjectId(newObjectId);
+ if (expectedOldObjectId == null) {
+ refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+ } else {
+ refUpdate.setExpectedOldObjectId(expectedOldObjectId);
+ }
+ return refUpdate;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 75c9bd1..13e9967 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -21,6 +21,7 @@
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
@@ -31,7 +32,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.State;
import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.mail.Address;
@@ -233,13 +234,13 @@
/**
* Check all GroupReferences use current group name, repairing stale ones.
*
- * @param groupCache cache to use when looking up group information by UUID.
+ * @param groupBackend cache to use when looking up group information by UUID.
* @return true if one or more group names was stale.
*/
- public boolean updateGroupNames(GroupCache groupCache) {
+ public boolean updateGroupNames(GroupBackend groupBackend) {
boolean dirty = false;
for (GroupReference ref : groupsByUUID.values()) {
- AccountGroup g = groupCache.get(ref.getUUID());
+ GroupDescription.Basic g = groupBackend.get(ref.getUUID());
if (g != null && !g.getName().equals(ref.getName())) {
dirty = true;
ref.setName(g.getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index b72b162..e9a35b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -15,6 +15,11 @@
package com.google.gerrit.server.git;
import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
@@ -80,7 +85,6 @@
import org.eclipse.jgit.transport.AdvertiseRefsHook;
import org.eclipse.jgit.transport.AdvertiseRefsHookChain;
import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.ReceivePack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -456,8 +460,7 @@
commandProgress = progress.beginSubTask("refs", UNKNOWN);
parseCommands(commands);
- if (newChange != null
- && newChange.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
+ if (newChange != null && newChange.getResult() == NOT_ATTEMPTED) {
createNewChanges();
}
newProgress.end();
@@ -474,7 +477,7 @@
}
for (final ReceiveCommand c : commands) {
- if (c.getResult() == Result.OK) {
+ if (c.getResult() == OK) {
switch (c.getType()) {
case CREATE:
if (isHead(c)) {
@@ -573,7 +576,7 @@
private void parseCommands(final Collection<ReceiveCommand> commands) {
for (final ReceiveCommand cmd : commands) {
- if (cmd.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+ if (cmd.getResult() != NOT_ATTEMPTED) {
// Already rejected by the core receive process.
//
continue;
@@ -621,7 +624,7 @@
continue;
}
- if (cmd.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+ if (cmd.getResult() != NOT_ATTEMPTED) {
continue;
}
@@ -687,7 +690,9 @@
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canCreate(rp.getRevWalk(), obj)) {
validateNewCommits(ctl, cmd);
- cmd.execute(rp);
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.execute(rp);
+ }
} else {
errors.put(Error.CREATE, ctl.getRefName());
reject(cmd, "can not create new references");
@@ -702,7 +707,9 @@
}
validateNewCommits(ctl, cmd);
- cmd.execute(rp);
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.execute(rp);
+ }
} else {
if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
errors.put(Error.CONFIG_UPDATE, GitRepositoryManager.REF_CONFIG);
@@ -735,7 +742,9 @@
private void parseDelete(final ReceiveCommand cmd) {
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canDelete()) {
- cmd.execute(rp);
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.execute(rp);
+ }
} else {
if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
reject(cmd, "cannot delete project configuration");
@@ -762,15 +771,17 @@
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (newObject != null) {
validateNewCommits(ctl, cmd);
- if (cmd.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+ if (cmd.getResult() != NOT_ATTEMPTED) {
return;
}
}
if (ctl.canForceUpdate()) {
- cmd.execute(rp);
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.execute(rp);
+ }
} else {
- cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, " need '"
+ cmd.setResult(REJECTED_NONFASTFORWARD, " need '"
+ PermissionRule.FORCE_PUSH + "' privilege.");
}
}
@@ -877,7 +888,7 @@
walk.setRevFilter(oldRevFilter);
}
} catch (IOException e) {
- newChange.setResult(Result.REJECTED_MISSING_OBJECT);
+ newChange.setResult(REJECTED_MISSING_OBJECT);
log.error("Invalid pack upload; one or more objects weren't sent", e);
return;
}
@@ -1052,7 +1063,7 @@
// Should never happen, the core receive process would have
// identified the missing object earlier before we got control.
//
- newChange.setResult(Result.REJECTED_MISSING_OBJECT);
+ newChange.setResult(REJECTED_MISSING_OBJECT);
log.error("Invalid pack upload; one or more objects weren't sent", e);
return;
} catch (OrmException e) {
@@ -1080,7 +1091,7 @@
}
newProgress.update(1);
}
- newChange.setResult(ReceiveCommand.Result.OK);
+ newChange.setResult(OK);
}
private static boolean isValidChangeId(String idStr) {
@@ -1207,7 +1218,7 @@
+ request.ontoChange + ", commit " + request.newCommit.name(), err);
reject(request.cmd, "database error");
}
- if (request.cmd.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
+ if (request.cmd.getResult() == NOT_ATTEMPTED) {
log.error("Replacement patch for change " + request.ontoChange
+ ", commit " + request.newCommit.name() + " wasn't attempted."
+ " This is a bug in the receive process implementation.");
@@ -1460,7 +1471,7 @@
}
replication.fire(project.getNameKey(), ru.getName());
hooks.doPatchsetCreatedHook(result.change, ps, db);
- request.cmd.setResult(ReceiveCommand.Result.OK);
+ request.cmd.setResult(OK);
workQueue.getDefaultQueue()
.submit(requestScopePropagator.wrap(new Runnable() {
@@ -1599,7 +1610,7 @@
}
}
} catch (IOException err) {
- cmd.setResult(Result.REJECTED_MISSING_OBJECT);
+ cmd.setResult(REJECTED_MISSING_OBJECT);
log.error("Invalid pack upload; one or more objects weren't sent", err);
}
}
@@ -2043,7 +2054,7 @@
}
private void reject(final ReceiveCommand cmd, final String why) {
- cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, why);
+ cmd.setResult(REJECTED_OTHER_REASON, why);
commandProgress.update(1);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
index f5e8fa8..7490006 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
@@ -123,11 +123,13 @@
ref.setName(newName);
md.getCommitBuilder().setAuthor(author);
md.setMessage("Rename group " + oldName + " to " + newName + "\n");
- if (config.commit(md)) {
+ try {
+ config.commit(md);
projectCache.evict(config.getProject());
success = true;
-
- } else {
+ } catch (IOException e) {
+ log.error("Could not commit rename of group " + oldName + " to "
+ + newName + " in " + md.getProjectName().get(), e);
try {
Thread.sleep(25 /* milliseconds */);
} catch (InterruptedException wakeUp) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java
index ac4882f..3c64229 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java
@@ -14,13 +14,12 @@
package com.google.gerrit.server.git;
+import com.google.common.cache.Cache;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.lib.ObjectId;
@@ -38,19 +37,17 @@
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<EntryKey, EntryVal>> type =
- new TypeLiteral<Cache<EntryKey, EntryVal>>() {};
- disk(type, CACHE_NAME);
+ persist(CACHE_NAME, String.class, EntryVal.class);
bind(TagCache.class);
}
};
}
- private final Cache<EntryKey, EntryVal> cache;
+ private final Cache<String, EntryVal> cache;
private final Object createLock = new Object();
@Inject
- TagCache(@Named(CACHE_NAME) Cache<EntryKey, EntryVal> cache) {
+ TagCache(@Named(CACHE_NAME) Cache<String, EntryVal> cache) {
this.cache = cache;
}
@@ -74,67 +71,43 @@
// never fail with an exception. Some of these references can be null
// (e.g. not all projects are cached, or the cache is not current).
//
- EntryVal val = cache.get(new EntryKey(name));
+ EntryVal val = cache.getIfPresent(name.get());
if (val != null) {
TagSetHolder holder = val.holder;
if (holder != null) {
TagSet tags = holder.getTagSet();
if (tags != null) {
- tags.updateFastForward(refName, oldValue, newValue);
+ if (tags.updateFastForward(refName, oldValue, newValue)) {
+ cache.put(name.get(), val);
+ }
}
}
}
}
TagSetHolder get(Project.NameKey name) {
- EntryKey key = new EntryKey(name);
- EntryVal val = cache.get(key);
+ EntryVal val = cache.getIfPresent(name.get());
if (val == null) {
synchronized (createLock) {
- val = cache.get(key);
+ val = cache.getIfPresent(name.get());
if (val == null) {
val = new EntryVal();
val.holder = new TagSetHolder(name);
- cache.put(key, val);
+ cache.put(name.get(), val);
}
}
}
return val.holder;
}
- static class EntryKey implements Serializable {
- static final long serialVersionUID = 1L;
-
- private transient String name;
-
- EntryKey(Project.NameKey name) {
- this.name = name.get();
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof EntryKey) {
- return name.equals(((EntryKey) o).name);
- }
- return false;
- }
-
- private void readObject(ObjectInputStream in) throws IOException {
- name = in.readUTF();
- }
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.writeUTF(name);
- }
+ void put(Project.NameKey name, TagSetHolder tags) {
+ EntryVal val = new EntryVal();
+ val.holder = tags;
+ cache.put(name.get(), val);
}
static class EntryVal implements Serializable {
- static final long serialVersionUID = EntryKey.serialVersionUID;
+ static final long serialVersionUID = 1L;
transient TagSetHolder holder;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java
index 6cf873d..7d95db2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java
@@ -30,15 +30,22 @@
final List<Ref> newRefs = new ArrayList<Ref>();
final List<LostRef> lostRefs = new ArrayList<LostRef>();
final TagSetHolder holder;
+ final TagCache cache;
final Repository db;
final Collection<Ref> include;
TagSet tags;
- boolean updated;
+ final boolean updated;
private boolean rebuiltForNewTags;
- TagMatcher(TagSetHolder holder, Repository db, Collection<Ref> include,
- TagSet tags, boolean updated) {
+ TagMatcher(
+ TagSetHolder holder,
+ TagCache cache,
+ Repository db,
+ Collection<Ref> include,
+ TagSet tags,
+ boolean updated) {
this.holder = holder;
+ this.cache = cache;
this.db = db;
this.include = include;
this.tags = tags;
@@ -63,7 +70,7 @@
}
rebuiltForNewTags = true;
- holder.rebuildForNewTags(this);
+ holder.rebuildForNewTags(cache, this);
return isReachable(tagRef);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
index 8830580..c57942c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
@@ -58,7 +58,7 @@
return tags.get(id);
}
- void updateFastForward(String refName, ObjectId oldValue,
+ boolean updateFastForward(String refName, ObjectId oldValue,
ObjectId newValue) {
CachedRef ref = refs.get(refName);
if (ref != null) {
@@ -68,9 +68,10 @@
//
ObjectId cur = ref.get();
if (cur.equals(oldValue)) {
- ref.compareAndSet(cur, newValue);
+ return ref.compareAndSet(cur, newValue);
}
}
+ return false;
}
void prepare(TagMatcher m) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
index 91c8a5c..d5120e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -42,51 +42,52 @@
this.tags = tags;
}
- TagMatcher matcher(Repository db, Collection<Ref> include) {
+ TagMatcher matcher(TagCache cache, Repository db, Collection<Ref> include) {
TagSet tags = this.tags;
if (tags == null) {
- tags = build(db);
+ tags = build(cache, db);
}
- TagMatcher m = new TagMatcher(this, db, include, tags, false);
+ TagMatcher m = new TagMatcher(this, cache, db, include, tags, false);
tags.prepare(m);
if (!m.newRefs.isEmpty() || !m.lostRefs.isEmpty()) {
- tags = rebuild(db, tags, m);
+ tags = rebuild(cache, db, tags, m);
- m = new TagMatcher(this, db, include, tags, true);
+ m = new TagMatcher(this, cache, db, include, tags, true);
tags.prepare(m);
}
return m;
}
- void rebuildForNewTags(TagMatcher m) {
- m.tags = rebuild(m.db, m.tags, null);
-
+ void rebuildForNewTags(TagCache cache, TagMatcher m) {
+ m.tags = rebuild(cache, m.db, m.tags, null);
m.mask.clear();
m.newRefs.clear();
m.lostRefs.clear();
m.tags.prepare(m);
}
- private TagSet build(Repository db) {
+ private TagSet build(TagCache cache, Repository db) {
synchronized (buildLock) {
TagSet tags = this.tags;
if (tags == null) {
tags = new TagSet(projectName);
tags.build(db, null, null);
this.tags = tags;
+ cache.put(projectName, this);
}
return tags;
}
}
- private TagSet rebuild(Repository db, TagSet old, TagMatcher m) {
+ private TagSet rebuild(TagCache cache, Repository db, TagSet old, TagMatcher m) {
synchronized (buildLock) {
TagSet cur = this.tags;
if (cur == old) {
cur = new TagSet(projectName);
cur.build(db, old, m);
this.tags = cur;
+ cache.put(projectName, this);
}
return cur;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 085424e..44536e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.base.Objects;
+
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -145,12 +146,12 @@
* Update this metadata branch, recording a new commit on its reference.
*
* @param update helper information to define the update that will occur.
- * @return true if the update was successful, false if it failed because of a
- * concurrent update to the same reference.
+ * @return the commit that was created
* @throws IOException if there is a storage problem and the update cannot be
- * executed as requested.
+ * executed as requested or if it failed because of a concurrent
+ * update to the same reference
*/
- public boolean commit(MetaDataUpdate update) throws IOException {
+ public RevCommit commit(MetaDataUpdate update) throws IOException {
BatchMetaDataUpdate batch = openUpdate(update);
try {
batch.write(update.getCommitBuilder());
@@ -160,11 +161,32 @@
}
}
+ /**
+ * Creates a new commit and a new ref based on this commit.
+ *
+ * @param update helper information to define the update that will occur.
+ * @param refName name of the ref that should be created
+ * @return the commit that was created
+ * @throws IOException if there is a storage problem and the update cannot be
+ * executed as requested or if it failed because of a concurrent
+ * update to the same reference
+ */
+ public RevCommit commitToNewRef(MetaDataUpdate update, String refName) throws IOException {
+ BatchMetaDataUpdate batch = openUpdate(update);
+ try {
+ batch.write(update.getCommitBuilder());
+ return batch.createRef(refName);
+ } finally {
+ batch.close();
+ }
+ }
+
public interface BatchMetaDataUpdate {
void write(CommitBuilder commit) throws IOException;
void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
- boolean commit() throws IOException;
- boolean commitAt(ObjectId revision) throws IOException;
+ RevCommit createRef(String refName) throws IOException;
+ RevCommit commit() throws IOException;
+ RevCommit commitAt(ObjectId revision) throws IOException;
void close();
}
@@ -224,14 +246,35 @@
}
@Override
- public boolean commit() throws IOException {
+ public RevCommit createRef(String refName) throws IOException {
+ if (Objects.equal(src, revision)) {
+ return revision;
+ }
+
+ RefUpdate ru = db.updateRef(refName);
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(src);
+ RefUpdate.Result result = ru.update();
+ switch (result) {
+ case NEW:
+ revision = rw.parseCommit(ru.getNewObjectId());
+ update.replicate(ru.getName());
+ return revision;
+ default:
+ throw new IOException("Cannot update " + ru.getName() + " in "
+ + db.getDirectory() + ": " + ru.getResult());
+ }
+ }
+
+ @Override
+ public RevCommit commit() throws IOException {
return commitAt(revision);
}
@Override
- public boolean commitAt(ObjectId expected) throws IOException {
+ public RevCommit commitAt(ObjectId expected) throws IOException {
if (Objects.equal(src, expected)) {
- return true;
+ return revision;
}
RefUpdate ru = db.updateRef(getRefName());
@@ -249,10 +292,7 @@
case FAST_FORWARD:
revision = rw.parseCommit(ru.getNewObjectId());
update.replicate(ru.getName());
- return true;
-
- case LOCK_FAILURE:
- return false;
+ return revision;
default:
throw new IOException("Cannot update " + ru.getName() + " in "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index fc47f10..bc9d9f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
@@ -60,6 +62,13 @@
}
public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeperately) {
+ if (projectCtl.allRefsAreVisibleExcept(
+ ImmutableSet.of(GitRepositoryManager.REF_CONFIG))) {
+ Map<String, Ref> r = Maps.newHashMap(refs);
+ r.remove(GitRepositoryManager.REF_CONFIG);
+ return r;
+ }
+
final Set<Change.Id> visibleChanges = visibleChanges();
final Map<String, Ref> result = new HashMap<String, Ref>();
final List<Ref> deferredTags = new ArrayList<Ref>();
@@ -92,8 +101,10 @@
// to identify what tags we can actually reach, and what we cannot.
//
if (!deferredTags.isEmpty() && (!result.isEmpty() || filterTagsSeperately)) {
- TagMatcher tags = tagCache.get(projectName).
- matcher(db, filterTagsSeperately ? filter(db.getAllRefs()).values() : result.values());
+ TagMatcher tags = tagCache.get(projectName).matcher(
+ tagCache,
+ db,
+ filterTagsSeperately ? filter(refs).values() : result.values());
for (Ref tag : deferredTags) {
if (tags.isReachable(tag)) {
result.put(tag.getName(), tag);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/ColumnFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/ColumnFormatter.java
new file mode 100644
index 0000000..a73f1cb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/ColumnFormatter.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.ioutil;
+
+import com.google.gerrit.server.StringUtil;
+
+import java.io.PrintWriter;
+
+/**
+ * Simple output formatter for column-oriented data, writing its output to
+ * a {@link java.io.PrintWriter} object. Handles escaping of the column
+ * data so that the resulting output is unambiguous and reasonably safe and
+ * machine parsable.
+ */
+public class ColumnFormatter {
+ private char columnSeparator;
+ private boolean firstColumn;
+ private final PrintWriter out;
+
+ /**
+ * @param out The writer to which output should be sent.
+ * @param columnSeparator A character that should serve as the separator
+ * token between columns of output. As only non-printable characters
+ * in the column text are ever escaped, the column separator must be
+ * a non-printable character if the output needs to be unambiguously
+ * parsed.
+ */
+ public ColumnFormatter(final PrintWriter out, final char columnSeparator) {
+ this.out = out;
+ this.columnSeparator = columnSeparator;
+ this.firstColumn = true;
+ }
+
+ /**
+ * Adds a text string as a new column in the current line of output,
+ * taking care of escaping as necessary.
+ *
+ * @param content the string to add.
+ */
+ public void addColumn(final String content) {
+ if (!firstColumn) {
+ out.print(columnSeparator);
+ }
+ out.print(StringUtil.escapeString(content));
+ firstColumn = false;
+ }
+
+ /**
+ * Finishes the output by flushing the current line and takes care of any
+ * other cleanup action.
+ */
+ public void finish() {
+ nextLine();
+ out.flush();
+ }
+
+ /**
+ * Flushes the current line of output and makes the formatter ready to
+ * start receiving new column data for a new line (or end-of-file).
+ * If the current line is empty nothing is done, i.e. consecutive calls
+ * to this method without intervening calls to {@link #addColumn} will
+ * be squashed.
+ */
+ public void nextLine() {
+ if (!firstColumn) {
+ out.print('\n');
+ firstColumn = true;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index e1a1725..31c8bd5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -35,6 +36,7 @@
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.Predicate;
@@ -270,13 +272,12 @@
}
}
-
/** Get the patch list corresponding to this patch set. */
- protected PatchList getPatchList() {
+ protected PatchList getPatchList() throws PatchListNotAvailableException {
if (patchSet != null) {
return args.patchListCache.get(change, patchSet);
}
- return null;
+ throw new PatchListNotAvailableException("no patchSet specified");
}
/** Get the project entity the change is in; null if its been deleted. */
@@ -387,7 +388,8 @@
private void add(Watchers matching, NotifyConfig nc, Project.NameKey project)
throws OrmException, QueryParseException {
for (GroupReference ref : nc.getGroups()) {
- AccountGroup group = args.groupCache.get(ref.getUUID());
+ AccountGroup group =
+ GroupDescriptions.toAccountGroup(args.groupBackend.get(ref.getUUID()));
if (group == null) {
log.warn(String.format(
"Project %s has invalid group %s in notify section %s",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index eaf9eee..e7cc1ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -77,11 +78,22 @@
}
public String getInlineComments() {
+ return getInlineComments(1);
+ }
+
+ public String getInlineComments(int lines) {
StringBuilder cmts = new StringBuilder();
final Repository repo = getRepository();
try {
- final PatchList patchList = repo != null ? getPatchList() : null;
+ PatchList patchList = null;
+ if (repo != null) {
+ try {
+ patchList = getPatchList();
+ } catch (PatchListNotAvailableException e) {
+ patchList = null;
+ }
+ }
Patch.Key currentFileKey = null;
PatchFile currentFileData = null;
@@ -113,19 +125,29 @@
}
}
- cmts.append("Line " + lineNbr);
if (currentFileData != null) {
+ int maxLines;
try {
- final String lineStr = currentFileData.getLine(side, lineNbr);
- cmts.append(": ");
- cmts.append(lineStr);
- } catch (Throwable cce) {
- // Don't quote the line if we can't safely convert it.
+ maxLines = currentFileData.getLineCount(side);
+ } catch (Throwable e) {
+ maxLines = lineNbr;
+ }
+
+ final int startLine = Math.max(1, lineNbr - lines + 1);
+ final int stopLine = Math.min(maxLines, lineNbr + lines);
+
+ for (int line = startLine; line <= lineNbr; ++line) {
+ appendFileLine(cmts, currentFileData, side, line);
+ }
+
+ cmts.append(c.getMessage().trim());
+ cmts.append("\n");
+
+ for (int line = lineNbr + 1; line < stopLine; ++line) {
+ appendFileLine(cmts, currentFileData, side, line);
}
}
- cmts.append("\n");
- cmts.append(c.getMessage().trim());
cmts.append("\n\n");
}
} finally {
@@ -136,6 +158,18 @@
return cmts.toString();
}
+ private void appendFileLine(StringBuilder cmts, PatchFile fileData, short side, int line) {
+ cmts.append("Line " + line);
+ try {
+ final String lineStr = fileData.getLine(side, line);
+ cmts.append(": ");
+ cmts.append(lineStr);
+ } catch (Throwable e) {
+ // Don't quote the line if we can't safely convert it.
+ }
+ cmts.append("\n");
+ }
+
private Repository getRepository() {
try {
return args.server.openRepository(projectState.getProject().getNameKey());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index fa49b06..e6eda82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -20,7 +20,7 @@
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -39,7 +39,7 @@
class EmailArguments {
final GitRepositoryManager server;
final ProjectCache projectCache;
- final GroupCache groupCache;
+ final GroupBackend groupBackend;
final AccountCache accountCache;
final PatchListCache patchListCache;
final FromAddressGenerator fromAddressGenerator;
@@ -55,10 +55,11 @@
final Provider<ChangeQueryRewriter> queryRewriter;
final Provider<ReviewDb> db;
final RuntimeInstance velocityRuntime;
+ final EmailSettings settings;
@Inject
EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
- GroupCache groupCache, AccountCache accountCache,
+ GroupBackend groupBackend, AccountCache accountCache,
PatchListCache patchListCache, FromAddressGenerator fromAddressGenerator,
EmailSender emailSender, PatchSetInfoFactory patchSetInfoFactory,
GenericFactory identifiedUserFactory,
@@ -68,10 +69,11 @@
AllProjectsName allProjectsName,
ChangeQueryBuilder.Factory queryBuilder,
Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db,
- RuntimeInstance velocityRuntime) {
+ RuntimeInstance velocityRuntime,
+ EmailSettings settings) {
this.server = server;
this.projectCache = projectCache;
- this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
this.accountCache = accountCache;
this.patchListCache = patchListCache;
this.fromAddressGenerator = fromAddressGenerator;
@@ -86,5 +88,6 @@
this.queryRewriter = queryRewriter;
this.db = db;
this.velocityRuntime = velocityRuntime;
+ this.settings = settings;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java
new file mode 100644
index 0000000..7e44877
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+class EmailSettings {
+ final boolean includeDiff;
+ final int maximumDiffSize;
+
+ @Inject
+ EmailSettings(@GerritServerConfig Config cfg) {
+ includeDiff = cfg.getBoolean("sendemail", "includeDiff", false);
+ maximumDiffSize = cfg.getInt("sendemail", "maximumDiffSize", 256 << 10);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index bb50374..86cebdc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail;
+import com.google.common.base.Charsets;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.GerritPersonIdent;
@@ -24,9 +25,13 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
/** Creates a {@link FromAddressGenerator} from the {@link GerritServerConfig} */
@Singleton
public class FromAddressGeneratorProvider implements
@@ -121,7 +126,7 @@
}
static final class PatternGen implements FromAddressGenerator {
- private final String senderEmail;
+ private final ParameterizedString senderEmailPattern;
private final Address serverAddress;
private final AccountCache accountCache;
private final String anonymousCowardName;
@@ -130,7 +135,7 @@
PatternGen(final Address serverAddress, final AccountCache accountCache,
final String anonymousCowardName,
final ParameterizedString namePattern, final String senderEmail) {
- this.senderEmail = senderEmail;
+ this.senderEmailPattern = new ParameterizedString(senderEmail);
this.serverAddress = serverAddress;
this.accountCache = accountCache;
this.anonymousCowardName = anonymousCowardName;
@@ -158,7 +163,25 @@
senderName = serverAddress.name;
}
+ String senderEmail;
+ if (senderEmailPattern.getParameterNames().isEmpty()) {
+ senderEmail = senderEmailPattern.getRawPattern();
+ } else {
+ senderEmail = senderEmailPattern
+ .replace("userHash", hashOf(senderName))
+ .toString();
+ }
return new Address(senderName, senderEmail);
}
}
+
+ private static String hashOf(String data) {
+ try {
+ MessageDigest hash = MessageDigest.getInstance("MD5");
+ byte[] bytes = hash.digest(data.getBytes(Charsets.UTF_8));
+ return Base64.encodeBase64URLSafeString(bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("No MD5 available", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
index 2e459d4..82c1405 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
@@ -16,10 +16,21 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.ssh.SshInfo;
import com.jcraft.jsch.HostKey;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -28,6 +39,9 @@
/** Sends an email alerting a user to a new change for them to review. */
public abstract class NewChangeSender extends ChangeEmail {
+ private static final Logger log =
+ LoggerFactory.getLogger(NewChangeSender.class);
+
private final SshInfo sshInfo;
private final Set<Account.Id> reviewers = new HashSet<Account.Id>();
private final Set<Account.Id> extraCC = new HashSet<Account.Id>();
@@ -85,4 +99,50 @@
}
return host;
}
+
+ public boolean getIncludeDiff() {
+ return args.settings.includeDiff;
+ }
+
+ /** Show patch set as unified difference. */
+ public String getUnifiedDiff() {
+ PatchList patchList;
+ try {
+ patchList = getPatchList();
+ if (patchList.getOldId() == null) {
+ // Octopus merges are not well supported for diff output by Gerrit.
+ // Currently these always have a null oldId in the PatchList.
+ return "";
+ }
+ } catch (PatchListNotAvailableException e) {
+ log.error("Cannot format patch", e);
+ return "";
+ }
+
+ TemporaryBuffer.Heap buf =
+ new TemporaryBuffer.Heap(args.settings.maximumDiffSize);
+ DiffFormatter fmt = new DiffFormatter(buf);
+ Repository git;
+ try {
+ git = args.server.openRepository(change.getProject());
+ } catch (IOException e) {
+ log.error("Cannot open repository to format patch", e);
+ return "";
+ }
+ try {
+ fmt.setRepository(git);
+ fmt.setDetectRenames(true);
+ fmt.format(patchList.getOldId(), patchList.getNewId());
+ return RawParseUtils.decode(buf.toByteArray());
+ } catch (IOException e) {
+ if (JGitText.get().inMemoryBufferLimitExceeded.equals(e.getMessage())) {
+ return "";
+ }
+ log.error("Cannot format patch", e);
+ return "";
+ } finally {
+ fmt.release();
+ git.close();
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
index c5c5925..08af5e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
@@ -70,11 +70,11 @@
return edits;
}
- ObjectId getBlobA() {
+ public ObjectId getBlobA() {
return aId;
}
- ObjectId getBlobB() {
+ public ObjectId getBlobB() {
return bId;
}
@@ -114,6 +114,9 @@
public String toString() {
StringBuilder n = new StringBuilder();
n.append("IntraLineDiffKey[");
+ if (projectKey != null) {
+ n.append(projectKey.get()).append(" ");
+ }
n.append(aId.name());
n.append("..");
n.append(bId.name());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
index 358d3ba..5b65920 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.server.cache.EntryCreator;
+import com.google.common.cache.CacheLoader;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
@@ -35,9 +35,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
-class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
- private static final Logger log = LoggerFactory
- .getLogger(IntraLineLoader.class);
+class IntraLineLoader extends CacheLoader<IntraLineDiffKey, IntraLineDiff> {
+ static final Logger log = LoggerFactory.getLogger(IntraLineLoader.class);
private static final Pattern BLANK_LINE_RE = Pattern
.compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
@@ -62,7 +61,7 @@
}
@Override
- public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
+ public IntraLineDiff load(IntraLineDiffKey key) throws Exception {
Worker w = workerPool.poll();
if (w == null) {
w = new Worker();
@@ -119,7 +118,7 @@
throws Exception {
if (!input.offer(new Input(key))) {
log.error("Cannot enqueue task to thread " + thread.getName());
- return null;
+ return Result.TIMEOUT;
}
Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineWeigher.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineWeigher.java
new file mode 100644
index 0000000..f6cff15
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineWeigher.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.patch;
+
+import com.google.common.cache.Weigher;
+
+/** Approximates memory usage for IntralineDiff in bytes of memory used. */
+public class IntraLineWeigher implements
+ Weigher<IntraLineDiffKey, IntraLineDiff> {
+ @Override
+ public int weigh(IntraLineDiffKey key, IntraLineDiff value) {
+ return 16 + 8*8 + 2*36 // Size of IntraLineDiffKey, 64 bit JVM
+ + 16 + 2*8 + 16+8+4+20 // Size of IntraLineDiff, 64 bit JVM
+ + (8 + 16 + 4*4) * value.getEdits().size();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
index f120ebf..6fcf581 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
@@ -111,6 +111,35 @@
}
}
+ /**
+ * Return number of lines in file.
+ *
+ * @param file the file index to extract.
+ * @return number of lines in file.
+ * @throws CorruptEntityException the patch cannot be read.
+ * @throws IOException the patch or complete file content cannot be read.
+ * @throws NoSuchEntityException the file is not exist.
+ */
+ public int getLineCount(final int file)
+ throws CorruptEntityException, IOException, NoSuchEntityException {
+ switch (file) {
+ case 0:
+ if (a == null) {
+ a = load(aTree, entry.getOldName());
+ }
+ return a.size();
+
+ case 1:
+ if (b == null) {
+ b = load(bTree, entry.getNewName());
+ }
+ return b.size();
+
+ default:
+ throw new NoSuchEntityException();
+ }
+ }
+
private Text load(final ObjectId tree, final String path)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
index aab8e39..93d7bf7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -142,8 +142,12 @@
}
private int search(final String fileName) {
+ if (Patch.COMMIT_MSG.equals(fileName)) {
+ return 0;
+ }
+
int high = patches.length;
- int low = 0;
+ int low = 1;
while (low < high) {
final int mid = (low + high) >>> 1;
final int cmp = patches[mid].getNewName().compareTo(fileName);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
index 8a61d30..fe77f5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -19,9 +19,10 @@
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
- public PatchList get(PatchListKey key);
+ public PatchList get(PatchListKey key) throws PatchListNotAvailableException;
- public PatchList get(Change change, PatchSet patchSet);
+ public PatchList get(Change change, PatchSet patchSet)
+ throws PatchListNotAvailableException;
public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 26dbe2d..967e6a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -15,24 +15,23 @@
package com.google.gerrit.server.patch;
-
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
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.AccountDiffPreference.Whitespace;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
+import java.util.concurrent.ExecutionException;
+
/** Provides a cached list of {@link PatchListEntry}. */
@Singleton
public class PatchListCacheImpl implements PatchListCache {
@@ -43,21 +42,15 @@
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<PatchListKey, PatchList>> fileType =
- new TypeLiteral<Cache<PatchListKey, PatchList>>() {};
- disk(fileType, FILE_NAME) //
- .memoryLimit(128) // very large items, cache only a few
- .evictionPolicy(EvictionPolicy.LRU) // prefer most recent
- .populateWith(PatchListLoader.class) //
- ;
+ persist(FILE_NAME, PatchListKey.class, PatchList.class)
+ .maximumWeight(10 << 20)
+ .loader(PatchListLoader.class)
+ .weigher(PatchListWeigher.class);
- final TypeLiteral<Cache<IntraLineDiffKey, IntraLineDiff>> intraType =
- new TypeLiteral<Cache<IntraLineDiffKey, IntraLineDiff>>() {};
- disk(intraType, INTRA_NAME) //
- .memoryLimit(128) // very large items, cache only a few
- .evictionPolicy(EvictionPolicy.LRU) // prefer most recent
- .populateWith(IntraLineLoader.class) //
- ;
+ persist(INTRA_NAME, IntraLineDiffKey.class, IntraLineDiff.class)
+ .maximumWeight(10 << 20)
+ .loader(IntraLineLoader.class)
+ .weigher(IntraLineWeigher.class);
bind(PatchListCacheImpl.class);
bind(PatchListCache.class).to(PatchListCacheImpl.class);
@@ -65,14 +58,14 @@
};
}
- private final Cache<PatchListKey, PatchList> fileCache;
- private final Cache<IntraLineDiffKey, IntraLineDiff> intraCache;
+ private final LoadingCache<PatchListKey, PatchList> fileCache;
+ private final LoadingCache<IntraLineDiffKey, IntraLineDiff> intraCache;
private final boolean computeIntraline;
@Inject
PatchListCacheImpl(
- @Named(FILE_NAME) final Cache<PatchListKey, PatchList> fileCache,
- @Named(INTRA_NAME) final Cache<IntraLineDiffKey, IntraLineDiff> intraCache,
+ @Named(FILE_NAME) LoadingCache<PatchListKey, PatchList> fileCache,
+ @Named(INTRA_NAME) LoadingCache<IntraLineDiffKey, IntraLineDiff> intraCache,
@GerritServerConfig Config cfg) {
this.fileCache = fileCache;
this.intraCache = intraCache;
@@ -82,11 +75,19 @@
cfg.getBoolean("cache", "diff", "intraline", true));
}
- public PatchList get(final PatchListKey key) {
- return fileCache.get(key);
+ @Override
+ public PatchList get(PatchListKey key) throws PatchListNotAvailableException {
+ try {
+ return fileCache.get(key);
+ } catch (ExecutionException e) {
+ PatchListLoader.log.warn("Error computing " + key, e);
+ throw new PatchListNotAvailableException(e.getCause());
+ }
}
- public PatchList get(final Change change, final PatchSet patchSet) {
+ @Override
+ public PatchList get(final Change change, final PatchSet patchSet)
+ throws PatchListNotAvailableException {
final Project.NameKey projectKey = change.getProject();
final ObjectId a = null;
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
@@ -97,11 +98,12 @@
@Override
public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key) {
if (computeIntraline) {
- IntraLineDiff d = intraCache.get(key);
- if (d == null) {
- d = new IntraLineDiff(IntraLineDiff.Status.ERROR);
+ try {
+ return intraCache.get(key);
+ } catch (ExecutionException e) {
+ IntraLineLoader.log.warn("Error computing " + key, e);
+ return new IntraLineDiff(IntraLineDiff.Status.ERROR);
}
- return d;
} else {
return new IntraLineDiff(IntraLineDiff.Status.DISABLED);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index 33ed54e..ff9e6cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -122,6 +122,22 @@
this.deletions = deletions;
}
+ int weigh() {
+ int size = 16 + 6*8 + 2*4 + 20 + 16+8+4+20;
+ size += stringSize(oldName);
+ size += stringSize(newName);
+ size += header.length;
+ size += (8 + 16 + 4*4) * edits.size();
+ return size;
+ }
+
+ private static int stringSize(String str) {
+ if (str != null) {
+ return 16 + 3*4 + 16 + str.length() * 2;
+ }
+ return 0;
+ }
+
public ChangeType getChangeType() {
return changeType;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 5bba42b..d27c205 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.reviewdb.client.Patch;
+import com.google.common.cache.CacheLoader;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
-import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -54,6 +54,8 @@
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.DisabledOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -62,7 +64,9 @@
import java.util.List;
import java.util.Map;
-class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
+class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
+ static final Logger log = LoggerFactory.getLogger(PatchListLoader.class);
+
private final GitRepositoryManager repoManager;
@Inject
@@ -71,7 +75,7 @@
}
@Override
- public PatchList createEntry(final PatchListKey key) throws Exception {
+ public PatchList load(final PatchListKey key) throws Exception {
final Repository repo = repoManager.openRepository(key.projectKey);
try {
return readPatchList(key, repo);
@@ -251,10 +255,24 @@
ObjectId treeId;
ResolveMerger m = (ResolveMerger) MergeStrategy.RESOLVE.newMerger(repo, true);
- ObjectInserter ins = m.getObjectInserter();
+ final ObjectInserter ins = repo.newObjectInserter();
try {
DirCache dc = DirCache.newInCore();
m.setDirCache(dc);
+ m.setObjectInserter(new ObjectInserter.Filter() {
+ @Override
+ protected ObjectInserter delegate() {
+ return ins;
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void release() {
+ }
+ });
boolean couldMerge = false;
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java
similarity index 71%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java
index 204d777..2ccc9f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListNotAvailableException.java
@@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.patch;
-public class IncompleteUserInfoException extends Exception {
+public class PatchListNotAvailableException extends Exception {
private static final long serialVersionUID = 1L;
- public IncompleteUserInfoException(final String userName, final String missingInfo) {
- super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
+ public PatchListNotAvailableException(String message) {
+ super(message);
+ }
+
+ public PatchListNotAvailableException(Throwable cause) {
+ super(cause);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListWeigher.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListWeigher.java
new file mode 100644
index 0000000..d715246
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListWeigher.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.patch;
+
+import com.google.common.cache.Weigher;
+
+/** Approximates memory usage for PatchList in bytes of memory used. */
+public class PatchListWeigher implements Weigher<PatchListKey, PatchList> {
+ @Override
+ public int weigh(PatchListKey key, PatchList value) {
+ int size = 16 + 4*8 + 2*36 // Size of PatchListKey, 64 bit JVM
+ + 16 + 3*8 + 3*4 + 20; // Size of PatchList, 64 bit JVM
+ for (PatchListEntry e : value.getPatches()) {
+ size += e.weigh();
+ }
+ return size;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
new file mode 100644
index 0000000..dca47e0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Option;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/** List projects visible to the calling user. */
+public class ListPlugins {
+ private final PluginLoader pluginLoader;
+
+ @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
+ private OutputFormat format = OutputFormat.TEXT;
+
+ @Inject
+ protected ListPlugins(PluginLoader pluginLoader) {
+ this.pluginLoader = pluginLoader;
+ }
+
+ public OutputFormat getFormat() {
+ return format;
+ }
+
+ public ListPlugins setFormat(OutputFormat fmt) {
+ this.format = fmt;
+ return this;
+ }
+
+ public void display(OutputStream out) {
+ final PrintWriter stdout;
+ try {
+ stdout =
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(out,
+ "UTF-8")));
+ } catch (UnsupportedEncodingException e) {
+ // Our encoding is required by the specifications for the runtime.
+ throw new RuntimeException("JVM lacks UTF-8 encoding", e);
+ }
+
+ Map<String, PluginInfo> output = Maps.newTreeMap();
+
+ List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins());
+ Collections.sort(plugins, new Comparator<Plugin>() {
+ @Override
+ public int compare(Plugin a, Plugin b) {
+ return a.getName().compareTo(b.getName());
+ }
+ });
+
+ if (!format.isJson()) {
+ stdout.format("%-30s %-10s\n", "Name", "Version");
+ stdout
+ .print("----------------------------------------------------------------------\n");
+ }
+
+ for (Plugin p : plugins) {
+ PluginInfo info = new PluginInfo();
+ info.version = p.getVersion();
+
+ if (format.isJson()) {
+ output.put(p.getName(), info);
+ } else {
+ stdout.format("%-30s %-10s\n", p.getName(),
+ Strings.nullToEmpty(info.version));
+ }
+ }
+
+ if (format.isJson()) {
+ format.newGson().toJson(output,
+ new TypeToken<Map<String, PluginInfo>>() {}.getType(), stdout);
+ stdout.print('\n');
+ }
+ stdout.flush();
+ }
+
+ private static class PluginInfo {
+ String version;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index 3aa259e..f16131c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -385,8 +385,15 @@
final Map<Key<?>, Binding<?>> bindings = Maps.newLinkedHashMap();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
- if (!dynamicTypes.contains(e.getKey().getTypeLiteral())
- && shouldCopy(e.getKey())) {
+ if (dynamicTypes.contains(e.getKey().getTypeLiteral())
+ && e.getKey().getAnnotation() != null) {
+ // A type used in DynamicSet or DynamicMap that has an annotation
+ // must be picked up by the set/map itself. A type used in either
+ // but without an annotation may be magic glue implementing F and
+ // using DynamicSet<F> or DynamicMap<F> internally. That should be
+ // exported to plugins.
+ continue;
+ } else if (shouldCopy(e.getKey())) {
bindings.put(e.getKey(), e.getValue());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index f232c5c..41f2aa6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -189,6 +189,7 @@
|| getRefControl().isOwner() // branch owner can abandon
|| getProjectControl().isOwner() // project owner can abandon
|| getCurrentUser().getCapabilities().canAdministrateServer() // site administers are god
+ || getRefControl().canAbandon() // user can abandon a specific ref
;
}
@@ -210,7 +211,8 @@
/** Can this user restore this change? */
public boolean canRestore() {
- return canAbandon(); // Anyone who can abandon the change can restore it back
+ return canAbandon() // Anyone who can abandon the change can restore it back
+ && getRefControl().canUpload(); // as long as you can upload too
}
/** All value ranges of any allowed label permission. */
@@ -292,13 +294,17 @@
return false;
}
+ public List<SubmitRecord> getSubmitRecords(ReviewDb db, PatchSet patchSet) {
+ return canSubmit(db, patchSet, null, false, true);
+ }
+
public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet) {
- return canSubmit(db, patchSet, null, false);
+ return canSubmit(db, patchSet, null, false, false);
}
public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
- @Nullable ChangeData cd, boolean fastEvalLabels) {
- if (change.getStatus().isClosed()) {
+ @Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed) {
+ if (!allowClosed && change.getStatus().isClosed()) {
SubmitRecord rec = new SubmitRecord();
rec.status = SubmitRecord.Status.CLOSED;
return Collections.singletonList(rec);
@@ -495,6 +501,9 @@
} else if ("need".equals(status.name())) {
lbl.status = SubmitRecord.Label.Status.NEED;
+ } else if ("may".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.MAY;
+
} else if ("impossible".equals(status.name())) {
lbl.status = SubmitRecord.Label.Status.IMPOSSIBLE;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 577a92d..3dbd7b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.project;
import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
@@ -26,7 +27,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.ProjectOwnerGroups;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -51,6 +52,8 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Set;
@@ -71,7 +74,7 @@
private final PersonIdent serverIdent;
private CreateProjectArgs createProjectArgs;
private ProjectCache projectCache;
- private GroupCache groupCache;
+ private GroupBackend groupBackend;
private MetaDataUpdate.User metaDataUpdateFactory;
@Inject
@@ -80,8 +83,8 @@
GitReferenceUpdated referenceUpdated,
DynamicSet<NewProjectCreatedListener> createdListener,
ReviewDb db,
- @GerritPersonIdent PersonIdent personIdent, final GroupCache groupCache,
- final MetaDataUpdate.User metaDataUpdateFactory,
+ @GerritPersonIdent PersonIdent personIdent, GroupBackend groupBackend,
+ MetaDataUpdate.User metaDataUpdateFactory,
@Assisted CreateProjectArgs createPArgs, ProjectCache pCache) {
this.projectOwnerGroups = pOwnerGroups;
this.currentUser = identifiedUser;
@@ -91,7 +94,7 @@
this.serverIdent = personIdent;
this.createProjectArgs = createPArgs;
this.projectCache = pCache;
- this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
}
@@ -101,7 +104,7 @@
try {
final String head =
createProjectArgs.permissionsOnly ? GitRepositoryManager.REF_CONFIG
- : createProjectArgs.branch;
+ : createProjectArgs.branch.get(0);
final Repository repo = repoManager.createRepository(nameKey);
try {
NewProjectCreatedListener.Event event = new NewProjectCreatedListener.Event() {
@@ -127,7 +130,7 @@
if (!createProjectArgs.permissionsOnly
&& createProjectArgs.createEmptyCommit) {
- createEmptyCommit(repo, nameKey, createProjectArgs.branch);
+ createEmptyCommits(repo, nameKey, createProjectArgs.branch);
}
} finally {
repo.close();
@@ -185,9 +188,9 @@
final AccessSection all =
config.getAccessSection(AccessSection.ALL, true);
for (AccountGroup.UUID ownerId : createProjectArgs.ownerIds) {
- AccountGroup accountGroup = groupCache.get(ownerId);
- if (accountGroup != null) {
- GroupReference group = config.resolve(accountGroup);
+ GroupDescription.Basic g = groupBackend.get(ownerId);
+ if (g != null) {
+ GroupReference group = config.resolve(GroupReference.forGroup(g));
all.getPermission(Permission.OWNER, true).add(
new PermissionRule(group));
}
@@ -195,10 +198,7 @@
}
md.setMessage("Created project\n");
- if (!config.commit(md)) {
- throw new IOException("Cannot create "
- + createProjectArgs.getProjectName());
- }
+ config.commit(md);
} finally {
md.close();
}
@@ -235,20 +235,32 @@
new ArrayList<AccountGroup.UUID>(projectOwnerGroups);
}
- while (createProjectArgs.branch.startsWith("/")) {
- createProjectArgs.branch = createProjectArgs.branch.substring(1);
+ List<String> transformedBranches = new ArrayList<String>();
+ if (createProjectArgs.branch == null ||
+ createProjectArgs.branch.isEmpty()) {
+ createProjectArgs.branch = Collections.singletonList(Constants.MASTER);
}
- if (!createProjectArgs.branch.startsWith(Constants.R_HEADS)) {
- createProjectArgs.branch = Constants.R_HEADS + createProjectArgs.branch;
+ for (String branch : createProjectArgs.branch) {
+ while (branch.startsWith("/")) {
+ branch = branch.substring(1);
+ }
+ if (!branch.startsWith(Constants.R_HEADS)) {
+ branch = Constants.R_HEADS + branch;
+ }
+ if (!Repository.isValidRefName(branch)) {
+ throw new ProjectCreationFailedException(String.format(
+ "Branch \"%s\" is not a valid name.", branch));
+ }
+ if (!transformedBranches.contains(branch)) {
+ transformedBranches.add(branch);
+ }
}
- if (!Repository.isValidRefName(createProjectArgs.branch)) {
- throw new ProjectCreationFailedException(String.format(
- "Branch \"%s\" is not a valid name.", createProjectArgs.branch));
- }
+ createProjectArgs.branch = transformedBranches;
}
- private void createEmptyCommit(final Repository repo,
- final Project.NameKey project, final String ref) throws IOException {
+ private void createEmptyCommits(final Repository repo,
+ final Project.NameKey project, final List<String> refs)
+ throws IOException {
ObjectInserter oi = repo.newObjectInserter();
try {
CommitBuilder cb = new CommitBuilder();
@@ -260,15 +272,18 @@
ObjectId id = oi.insert(cb);
oi.flush();
- RefUpdate ru = repo.updateRef(Constants.HEAD);
- ru.setNewObjectId(id);
- final Result result = ru.update();
- switch (result) {
- case NEW:
- referenceUpdated.fire(project, ref);
- break;
- default: {
- throw new IOException(result.name());
+ for (String ref : refs) {
+ RefUpdate ru = repo.updateRef(ref);
+ ru.setNewObjectId(id);
+ final Result result = ru.update();
+ switch (result) {
+ case NEW:
+ referenceUpdated.fire(project, ref);
+ break;
+ default: {
+ throw new IOException(String.format(
+ "Failed to create ref \"%s\": %s", ref, result.name()));
+ }
}
}
} catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
index 98adf85..2dee4f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -30,7 +30,7 @@
public boolean contributorAgreements;
public boolean signedOffBy;
public boolean permissionsOnly;
- public String branch;
+ public List<String> branch;
public boolean contentMerge;
public boolean changeIdRequired;
public boolean createEmptyCommit;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index 716a5a8..e5a11ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -19,6 +19,7 @@
import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.StringUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
@@ -39,8 +40,10 @@
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -56,6 +59,12 @@
return !PERMISSIONS.matches(git);
}
},
+ PARENT_CANDIDATES {
+ @Override
+ boolean matches(Repository git) {
+ return true;
+ }
+ },
PERMISSIONS {
@Override
boolean matches(Repository git) throws IOException {
@@ -154,6 +163,7 @@
int found = 0;
Map<String, ProjectInfo> output = Maps.newTreeMap();
Map<String, String> hiddenNames = Maps.newHashMap();
+ Set<String> rejected = new HashSet<String>();
final TreeMap<Project.NameKey, ProjectNode> treeMap =
new TreeMap<Project.NameKey, ProjectNode>();
@@ -165,84 +175,102 @@
//
continue;
}
-
- final ProjectControl pctl = e.controlFor(currentUser);
- final boolean isVisible = pctl.isVisible() || (all && pctl.isOwner());
- if (showTree && !format.isJson()) {
- treeMap.put(projectName,
- projectNodeFactory.create(pctl.getProject(), isVisible));
- continue;
- }
-
- if (!isVisible && !(showTree && pctl.isOwner())) {
- // Require the project itself to be visible to the user.
- //
- continue;
- }
-
ProjectInfo info = new ProjectInfo();
- info.name = projectName.get();
- if (showTree && format.isJson()) {
- ProjectState parent = e.getParentState();
- if (parent != null) {
- ProjectControl parentCtrl = parent.controlFor(currentUser);
+ if (type == FilterType.PARENT_CANDIDATES) {
+ ProjectState parentState = e.getParentState();
+ if (parentState != null
+ && !output.keySet().contains(parentState.getProject().getName())
+ && !rejected.contains(parentState.getProject().getName())) {
+ ProjectControl parentCtrl = parentState.controlFor(currentUser);
if (parentCtrl.isVisible() || parentCtrl.isOwner()) {
- info.parent = parent.getProject().getName();
+ info.name = parentState.getProject().getName();
+ info.description = parentState.getProject().getDescription();
} else {
- info.parent = hiddenNames.get(parent.getProject().getName());
- if (info.parent == null) {
- info.parent = "?-" + (hiddenNames.size() + 1);
- hiddenNames.put(parent.getProject().getName(), info.parent);
- }
+ rejected.add(parentState.getProject().getName());
+ continue;
}
+ } else {
+ continue;
}
- }
- if (showDescription && !e.getProject().getDescription().isEmpty()) {
- info.description = e.getProject().getDescription();
- }
- try {
- if (showBranch != null) {
- Repository git = repoManager.openRepository(projectName);
- try {
- if (!type.matches(git)) {
- continue;
- }
+ } else {
+ final ProjectControl pctl = e.controlFor(currentUser);
+ final boolean isVisible = pctl.isVisible() || (all && pctl.isOwner());
+ if (showTree && !format.isJson()) {
+ treeMap.put(projectName,
+ projectNodeFactory.create(pctl.getProject(), isVisible));
+ continue;
+ }
- List<Ref> refs = getBranchRefs(projectName, pctl);
- if (!hasValidRef(refs)) {
- continue;
- }
+ if (!isVisible && !(showTree && pctl.isOwner())) {
+ // Require the project itself to be visible to the user.
+ //
+ continue;
+ }
- for (int i = 0; i < showBranch.size(); i++) {
- Ref ref = refs.get(i);
- if (ref != null && ref.getObjectId() != null) {
- if (info.branches == null) {
- info.branches = Maps.newLinkedHashMap();
- }
- info.branches.put(showBranch.get(i), ref.getObjectId().name());
+ info.name = projectName.get();
+ if (showTree && format.isJson()) {
+ ProjectState parent = e.getParentState();
+ if (parent != null) {
+ ProjectControl parentCtrl = parent.controlFor(currentUser);
+ if (parentCtrl.isVisible() || parentCtrl.isOwner()) {
+ info.parent = parent.getProject().getName();
+ } else {
+ info.parent = hiddenNames.get(parent.getProject().getName());
+ if (info.parent == null) {
+ info.parent = "?-" + (hiddenNames.size() + 1);
+ hiddenNames.put(parent.getProject().getName(), info.parent);
}
}
- } finally {
- git.close();
- }
- } else if (!showTree && type != FilterType.ALL) {
- Repository git = repoManager.openRepository(projectName);
- try {
- if (!type.matches(git)) {
- continue;
- }
- } finally {
- git.close();
}
}
+ if (showDescription && !e.getProject().getDescription().isEmpty()) {
+ info.description = e.getProject().getDescription();
+ }
- } catch (RepositoryNotFoundException err) {
- // If the Git repository is gone, the project doesn't actually exist anymore.
- continue;
- } catch (IOException err) {
- log.warn("Unexpected error reading " + projectName, err);
- continue;
+ try {
+ if (showBranch != null) {
+ Repository git = repoManager.openRepository(projectName);
+ try {
+ if (!type.matches(git)) {
+ continue;
+ }
+
+ List<Ref> refs = getBranchRefs(projectName, pctl);
+ if (!hasValidRef(refs)) {
+ continue;
+ }
+
+ for (int i = 0; i < showBranch.size(); i++) {
+ Ref ref = refs.get(i);
+ if (ref != null && ref.getObjectId() != null) {
+ if (info.branches == null) {
+ info.branches = Maps.newLinkedHashMap();
+ }
+ info.branches.put(showBranch.get(i), ref.getObjectId().name());
+ }
+ }
+ } finally {
+ git.close();
+ }
+ } else if (!showTree && type != FilterType.ALL) {
+ Repository git = repoManager.openRepository(projectName);
+ try {
+ if (!type.matches(git)) {
+ continue;
+ }
+ } finally {
+ git.close();
+ }
+ }
+
+ } catch (RepositoryNotFoundException err) {
+ // If the Git repository is gone, the project doesn't actually exist anymore.
+ continue;
+ } catch (IOException err) {
+ log.warn("Unexpected error reading " + projectName, err);
+ continue;
+ }
}
if (limit > 0 && ++found > limit) {
@@ -269,7 +297,7 @@
if (info.description != null) {
// We still want to list every project as one-liners, hence escaping \n.
- stdout.print(" - " + info.description.replace("\n", "\\n"));
+ stdout.print(" - " + StringUtil.escapeString(info.description));
}
stdout.print('\n');
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 3b1f55c..cb18398 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.project;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
@@ -27,20 +28,24 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** Cache of project information, including access rights. */
@Singleton
public class ProjectCacheImpl implements ProjectCache {
+ private static final Logger log = LoggerFactory
+ .getLogger(ProjectCacheImpl.class);
+
private static final String CACHE_NAME = "projects";
private static final String CACHE_LIST = "project_list";
@@ -48,13 +53,14 @@
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<Project.NameKey, ProjectState>> nameType =
- new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
- core(nameType, CACHE_NAME).populateWith(Loader.class);
+ cache(CACHE_NAME, String.class, ProjectState.class)
+ .loader(Loader.class);
- final TypeLiteral<Cache<ListKey, SortedSet<Project.NameKey>>> listType =
- new TypeLiteral<Cache<ListKey, SortedSet<Project.NameKey>>>() {};
- core(listType, CACHE_LIST).populateWith(Lister.class);
+ cache(CACHE_LIST,
+ ListKey.class,
+ new TypeLiteral<SortedSet<Project.NameKey>>() {})
+ .maximumWeight(1)
+ .loader(Lister.class);
bind(ProjectCacheImpl.class);
bind(ProjectCache.class).to(ProjectCacheImpl.class);
@@ -63,16 +69,16 @@
}
private final AllProjectsName allProjectsName;
- private final Cache<Project.NameKey, ProjectState> byName;
- private final Cache<ListKey,SortedSet<Project.NameKey>> list;
+ private final LoadingCache<String, ProjectState> byName;
+ private final LoadingCache<ListKey, SortedSet<Project.NameKey>> list;
private final Lock listLock;
private final ProjectCacheClock clock;
@Inject
ProjectCacheImpl(
final AllProjectsName allProjectsName,
- @Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName,
- @Named(CACHE_LIST) final Cache<ListKey, SortedSet<Project.NameKey>> list,
+ @Named(CACHE_NAME) LoadingCache<String, ProjectState> byName,
+ @Named(CACHE_LIST) LoadingCache<ListKey, SortedSet<Project.NameKey>> list,
ProjectCacheClock clock) {
this.allProjectsName = allProjectsName;
this.byName = byName;
@@ -99,18 +105,26 @@
* @return the cached data; null if no such project exists.
*/
public ProjectState get(final Project.NameKey projectName) {
- ProjectState state = byName.get(projectName);
- if (state != null && state.needsRefresh(clock.read())) {
- byName.remove(projectName);
- state = byName.get(projectName);
+ if (projectName == null) {
+ return null;
}
- return state;
+ try {
+ ProjectState state = byName.get(projectName.get());
+ if (state != null && state.needsRefresh(clock.read())) {
+ byName.invalidate(projectName.get());
+ state = byName.get(projectName.get());
+ }
+ return state;
+ } catch (ExecutionException e) {
+ log.warn(String.format("Cannot read project %s", projectName.get()), e);
+ return null;
+ }
}
/** Invalidate the cached information about the given project. */
public void evict(final Project p) {
if (p != null) {
- byName.remove(p.getNameKey());
+ byName.invalidate(p.getNameKey().get());
}
}
@@ -118,10 +132,11 @@
public void remove(final Project p) {
listLock.lock();
try {
- SortedSet<Project.NameKey> n = list.get(ListKey.ALL);
- n = new TreeSet<Project.NameKey>(n);
+ SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
n.remove(p.getNameKey());
list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
+ } catch (ExecutionException e) {
+ log.warn("Cannot list avaliable projects", e);
} finally {
listLock.unlock();
}
@@ -132,10 +147,11 @@
public void onCreateProject(Project.NameKey newProjectName) {
listLock.lock();
try {
- SortedSet<Project.NameKey> n = list.get(ListKey.ALL);
- n = new TreeSet<Project.NameKey>(n);
+ SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
n.add(newProjectName);
list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
+ } catch (ExecutionException e) {
+ log.warn("Cannot list avaliable projects", e);
} finally {
listLock.unlock();
}
@@ -143,18 +159,28 @@
@Override
public Iterable<Project.NameKey> all() {
- return list.get(ListKey.ALL);
+ try {
+ return list.get(ListKey.ALL);
+ } catch (ExecutionException e) {
+ log.warn("Cannot list available projects", e);
+ return Collections.emptyList();
+ }
}
@Override
public Iterable<Project.NameKey> byName(final String pfx) {
+ final Iterable<Project.NameKey> src;
+ try {
+ src = list.get(ListKey.ALL).tailSet(new Project.NameKey(pfx));
+ } catch (ExecutionException e) {
+ return Collections.emptyList();
+ }
return new Iterable<Project.NameKey>() {
@Override
public Iterator<Project.NameKey> iterator() {
return new Iterator<Project.NameKey>() {
+ private Iterator<Project.NameKey> itr = src.iterator();
private Project.NameKey next;
- private Iterator<Project.NameKey> itr =
- list.get(ListKey.ALL).tailSet(new Project.NameKey(pfx)).iterator();
@Override
public boolean hasNext() {
@@ -196,7 +222,7 @@
};
}
- static class Loader extends EntryCreator<Project.NameKey, ProjectState> {
+ static class Loader extends CacheLoader<String, ProjectState> {
private final ProjectState.Factory projectStateFactory;
private final GitRepositoryManager mgr;
@@ -207,19 +233,15 @@
}
@Override
- public ProjectState createEntry(Project.NameKey key) throws Exception {
+ public ProjectState load(String projectName) throws Exception {
+ Project.NameKey key = new Project.NameKey(projectName);
+ Repository git = mgr.openRepository(key);
try {
- Repository git = mgr.openRepository(key);
- try {
- final ProjectConfig cfg = new ProjectConfig(key);
- cfg.load(git);
- return projectStateFactory.create(cfg);
- } finally {
- git.close();
- }
-
- } catch (RepositoryNotFoundException notFound) {
- return null;
+ ProjectConfig cfg = new ProjectConfig(key);
+ cfg.load(git);
+ return projectStateFactory.create(cfg);
+ } finally {
+ git.close();
}
}
}
@@ -231,7 +253,7 @@
}
}
- static class Lister extends EntryCreator<ListKey, SortedSet<Project.NameKey>> {
+ static class Lister extends CacheLoader<ListKey, SortedSet<Project.NameKey>> {
private final GitRepositoryManager mgr;
@Inject
@@ -240,7 +262,7 @@
}
@Override
- public SortedSet<Project.NameKey> createEntry(ListKey key) throws Exception {
+ public SortedSet<Project.NameKey> load(ListKey key) throws Exception {
return mgr.list();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 67d91d5..513f1b1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -38,6 +38,7 @@
import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -196,8 +197,12 @@
/** Can this user see all the refs in this projects? */
public boolean allRefsAreVisible() {
+ return allRefsAreVisibleExcept(Collections.<String> emptySet());
+ }
+
+ public boolean allRefsAreVisibleExcept(Set<String> except) {
return user instanceof InternalUser
- || canPerformOnAllRefs(Permission.READ);
+ || canPerformOnAllRefs(Permission.READ, except);
}
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
@@ -347,7 +352,7 @@
return false;
}
- private boolean canPerformOnAllRefs(String permission) {
+ private boolean canPerformOnAllRefs(String permission, Set<String> except) {
boolean canPerform = false;
Set<String> patterns = allRefPatterns(permission);
if (patterns.contains(AccessSection.ALL)) {
@@ -358,6 +363,8 @@
for (final String pattern : patterns) {
if (controlForRef(pattern).canPerform(permission)) {
canPerform = true;
+ } else if (except.contains(pattern)) {
+ continue;
} else {
return false;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 7de1fc1..e06c948 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
@@ -95,20 +96,24 @@
? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
: null;
- HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
- AccessSection all = config.getAccessSection(AccessSection.ALL);
- if (all != null) {
- Permission owner = all.getPermission(Permission.OWNER);
- if (owner != null) {
- for (PermissionRule rule : owner.getRules()) {
- GroupReference ref = rule.getGroup();
- if (ref.getUUID() != null) {
- groups.add(ref.getUUID());
+ if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
+ localOwners = Collections.emptySet();
+ } else {
+ HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
+ AccessSection all = config.getAccessSection(AccessSection.ALL);
+ if (all != null) {
+ Permission owner = all.getPermission(Permission.OWNER);
+ if (owner != null) {
+ for (PermissionRule rule : owner.getRules()) {
+ GroupReference ref = rule.getGroup();
+ if (ref.getUUID() != null) {
+ groups.add(ref.getUUID());
+ }
}
}
}
+ localOwners = Collections.unmodifiableSet(groups);
}
- localOwners = Collections.unmodifiableSet(groups);
}
boolean needsRefresh(long generation) {
@@ -175,6 +180,18 @@
Collection<AccessSection> fromConfig = config.getAccessSections();
sm = new ArrayList<SectionMatcher>(fromConfig.size());
for (AccessSection section : fromConfig) {
+ if (isAllProjects) {
+ List<Permission> copy =
+ Lists.newArrayListWithCapacity(section.getPermissions().size());
+ for (Permission p : section.getPermissions()) {
+ if (Permission.canBeOnAllProjects(section.getName(), p.getName())) {
+ copy.add(p);
+ }
+ }
+ section = new AccessSection(section.getName());
+ section.setPermissions(copy);
+ }
+
SectionMatcher matcher = SectionMatcher.wrap(section);
if (matcher != null) {
sm.add(matcher);
@@ -197,6 +214,7 @@
List<SectionMatcher> all = new ArrayList<SectionMatcher>();
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
+ ProjectState allProjects = projectCache.getAllProjects();
seen.add(getProject().getNameKey());
ProjectState s = this;
@@ -209,7 +227,9 @@
}
s = projectCache.get(parent);
} while (s != null);
- all.addAll(projectCache.getAllProjects().getLocalAccessSections());
+ if (seen.add(allProjects.getProject().getNameKey())) {
+ all.addAll(allProjects.getLocalAccessSections());
+ }
return all;
}
@@ -271,4 +291,8 @@
}
return projectCache.get(getProject().getParent(allProjectsName));
}
+
+ public boolean isAllProjects() {
+ return isAllProjects;
+ }
}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 82c3a6d..a6182d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -155,7 +155,14 @@
// rules. Allowing this to be done by a non-project-owner opens
// a security hole enabling editing of access rules, and thus
// granting of powers beyond pushing to the configuration.
- return false;
+
+ // On the AllProjects project the owner access right cannot be assigned,
+ // this why for the AllProjects project we allow administrators to push
+ // configuration changes if they have push without being project owner.
+ if (!(projectControl.getProjectState().isAllProjects() &&
+ getCurrentUser().getCapabilities().canAdministrateServer())) {
+ return false;
+ }
}
return canPerform(Permission.PUSH)
&& canWrite();
@@ -310,6 +317,11 @@
return canPerform(Permission.FORGE_SERVER);
}
+ /** @return true if this user can abandon a change for this ref */
+ public boolean canAbandon() {
+ return canPerform(Permission.ABANDON);
+ }
+
/** All value ranges of any allowed label permission. */
public List<PermissionRange> getLabelRanges() {
List<PermissionRange> r = new ArrayList<PermissionRange>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
index 40d4290..db879de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
@@ -14,16 +14,18 @@
package com.google.gerrit.server.project;
+import com.google.common.cache.Cache;
import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.util.MostSpecificComparator;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
@@ -32,15 +34,16 @@
/** Caches the order AccessSections should be sorted for evaluation. */
@Singleton
public class SectionSortCache {
+ private static final Logger log =
+ LoggerFactory.getLogger(SectionSortCache.class);
+
private static final String CACHE_NAME = "permission_sort";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<EntryKey, EntryVal>> type =
- new TypeLiteral<Cache<EntryKey, EntryVal>>() {};
- core(type, CACHE_NAME);
+ cache(CACHE_NAME, EntryKey.class, EntryVal.class);
bind(SectionSortCache.class);
}
};
@@ -60,7 +63,7 @@
}
EntryKey key = new EntryKey(ref, sections);
- EntryVal val = cache.get(key);
+ EntryVal val = cache.getIfPresent(key);
if (val != null) {
int[] srcIdx = val.order;
if (srcIdx != null) {
@@ -73,10 +76,11 @@
}
} else {
+ boolean poison = false;
IdentityHashMap<AccessSection, Integer> srcMap =
new IdentityHashMap<AccessSection, Integer>();
for (int i = 0; i < cnt; i++) {
- srcMap.put(sections.get(i), i);
+ poison |= srcMap.put(sections.get(i), i) != null;
}
Collections.sort(sections, new MostSpecificComparator(ref));
@@ -91,7 +95,11 @@
}
}
- cache.put(key, new EntryVal(srcIdx));
+ if (poison) {
+ log.error("Received duplicate AccessSection instances, not caching sort");
+ } else {
+ cache.put(key, new EntryVal(srcIdx));
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index db3470e..d6762db 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -142,7 +143,14 @@
return null;
}
- PatchList p = cache.get(c, ps);
+ PatchList p;
+ try {
+ p = cache.get(c, ps);
+ } catch (PatchListNotAvailableException e) {
+ currentFiles = new String[0];
+ return currentFiles;
+ }
+
List<String> r = new ArrayList<String>(p.getPatches().size());
for (PatchListEntry e : p.getPatches()) {
if (Patch.COMMIT_MSG.equals(e.getNewName())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 8c1157e..ddc4c28 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
@@ -26,7 +27,8 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
@@ -105,7 +107,7 @@
final ChangeControl.Factory changeControlFactory;
final ChangeControl.GenericFactory changeControlGenericFactory;
final AccountResolver accountResolver;
- final GroupCache groupCache;
+ final GroupBackend groupBackend;
final ApprovalTypes approvalTypes;
final AllProjectsName allProjectsName;
final PatchListCache patchListCache;
@@ -119,7 +121,8 @@
CapabilityControl.Factory capabilityControlFactory,
ChangeControl.Factory changeControlFactory,
ChangeControl.GenericFactory changeControlGenericFactory,
- AccountResolver accountResolver, GroupCache groupCache,
+ AccountResolver accountResolver,
+ GroupBackend groupBackend,
ApprovalTypes approvalTypes,
AllProjectsName allProjectsName,
PatchListCache patchListCache,
@@ -132,7 +135,7 @@
this.changeControlFactory = changeControlFactory;
this.changeControlGenericFactory = changeControlGenericFactory;
this.accountResolver = accountResolver;
- this.groupCache = groupCache;
+ this.groupBackend = groupBackend;
this.approvalTypes = approvalTypes;
this.allProjectsName = allProjectsName;
this.patchListCache = patchListCache;
@@ -367,18 +370,11 @@
// If its not an account, maybe its a group?
//
- AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
- if (g != null) {
- return visibleto(new SingleGroupUser(args.capabilityControlFactory,
- g.getGroupUUID()));
- }
-
- Collection<AccountGroup> matches =
- args.groupCache.get(new AccountGroup.ExternalNameKey(who));
- if (matches != null && !matches.isEmpty()) {
+ Collection<GroupReference> suggestions = args.groupBackend.suggest(who);
+ if (!suggestions.isEmpty()) {
HashSet<AccountGroup.UUID> ids = new HashSet<AccountGroup.UUID>();
- for (AccountGroup group : matches) {
- ids.add(group.getGroupUUID());
+ for (GroupReference ref : suggestions) {
+ ids.add(ref.getUUID());
}
return visibleto(new SingleGroupUser(args.capabilityControlFactory, ids));
}
@@ -410,11 +406,11 @@
@Operator
public Predicate<ChangeData> ownerin(String group)
throws QueryParseException {
- AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
+ GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, group);
if (g == null) {
throw error("Group " + group + " not found");
}
- return new OwnerinPredicate(args.dbProvider, args.userFactory, g.getGroupUUID());
+ return new OwnerinPredicate(args.dbProvider, args.userFactory, g.getUUID());
}
@Operator
@@ -431,11 +427,11 @@
@Operator
public Predicate<ChangeData> reviewerin(String group)
throws QueryParseException {
- AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
+ GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, group);
if (g == null) {
throw error("Group " + group + " not found");
}
- return new ReviewerinPredicate(args.dbProvider, args.userFactory, g.getGroupUUID());
+ return new ReviewerinPredicate(args.dbProvider, args.userFactory, g.getUUID());
}
@Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
index 6f9094a..d6a9d4f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
@@ -22,10 +22,12 @@
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.events.AccountAttribute;
import com.google.gerrit.server.project.ChangeControl;
@@ -43,6 +45,7 @@
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -200,11 +203,15 @@
out._number = in.getId().get();
out._sortkey = in.getSortKey();
out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
+ out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
out.labels = labelsFor(cd);
return out;
}
private AccountAttribute asAccountAttribute(Account.Id user) {
+ if (user == null) {
+ return null;
+ }
AccountAttribute a = accounts.get(user);
if (a == null) {
a = new AccountAttribute();
@@ -226,7 +233,7 @@
PatchSet ps = cd.currentPatchSet(db);
Map<String, LabelInfo> labels = Maps.newLinkedHashMap();
- for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true)) {
+ for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true, false)) {
if (rec.labels == null) {
continue;
}
@@ -243,6 +250,7 @@
n.rejected = asAccountAttribute(r.appliedBy);
break;
}
+ n.optional = n._status == SubmitRecord.Label.Status.MAY ? true : null;
labels.put(r.label, n);
}
}
@@ -287,6 +295,41 @@
return labels;
}
+ private boolean isChangeReviewed(ChangeData cd) throws OrmException {
+ if (user instanceof IdentifiedUser) {
+ PatchSet currentPatchSet = cd.currentPatchSet(db);
+ if (currentPatchSet == null) {
+ return false;
+ }
+
+ List<ChangeMessage> messages =
+ db.get().changeMessages().byPatchSet(currentPatchSet.getId()).toList();
+
+ if (messages.isEmpty()) {
+ return false;
+ }
+
+ // Sort messages to let the most recent ones at the beginning.
+ Collections.sort(messages, new Comparator<ChangeMessage>() {
+ @Override
+ public int compare(ChangeMessage a, ChangeMessage b) {
+ return b.getWrittenOn().compareTo(a.getWrittenOn());
+ }
+ });
+
+ Account.Id currentUserId = ((IdentifiedUser) user).getAccountId();
+ Account.Id changeOwnerId = cd.change(db).getOwner();
+ for (ChangeMessage cm : messages) {
+ if (currentUserId.equals(cm.getAuthor())) {
+ return true;
+ } else if (changeOwnerId.equals(cm.getAuthor())) {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
static class ChangeInfo {
String project;
String branch;
@@ -297,6 +340,7 @@
Timestamp created;
Timestamp updated;
Boolean starred;
+ Boolean reviewed;
String _sortkey;
int _number;
@@ -314,5 +358,6 @@
AccountAttribute recommended;
AccountAttribute disliked;
Short value;
+ Boolean optional;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 08d94af..fd379b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -252,13 +252,12 @@
all.getPermission(Permission.FORGE_AUTHOR, true) //
.add(rule(config, registered));
- meta.getPermission(Permission.READ, true) //
- .add(rule(config, owners));
+ Permission metaReadPermission = meta.getPermission(Permission.READ, true);
+ metaReadPermission.setExclusiveGroup(true);
+ metaReadPermission.add(rule(config, owners));
md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
- if (!config.commit(md)) {
- throw new IOException("Cannot create " + allProjectsName.get());
- }
+ config.commit(md);
} finally {
git.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 9c89c73..0a34b44 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_67> C = Schema_67.class;
+ public static final Class<Schema_69> C = Schema_69.class;
public static class Module extends AbstractModule {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
index 54ee9ab..8207c31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -184,9 +184,7 @@
}
md.setMessage("Import project configuration from SQL\n");
- if (!config.commit(md)) {
- throw new OrmException("Cannot export project " + name);
- }
+ config.commit(md);
} catch (ConfigInvalidException err) {
throw new OrmException("Cannot read project " + name, err);
} catch (IOException err) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
index 4699a00..3a288e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
@@ -135,9 +135,7 @@
}
md.setMessage("Upgrade to Gerrit Code Review schema 57\n");
- if (!config.commit(md)) {
- throw new OrmException("Cannot update " + allProjects);
- }
+ config.commit(md);
} finally {
git.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java
index 127f9c3..e665bdc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java
@@ -106,9 +106,7 @@
}
md.setMessage("Upgrade to Gerrit Code Review schema 64\n");
- if (!config.commit(md)) {
- throw new OrmException("Cannot update " + allProjects);
- }
+ config.commit(md);
} catch (IOException e) {
throw new OrmException(e);
} catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
index 3383364..1cdf25c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
@@ -207,9 +207,7 @@
batch.write(config, commit);
// Save the the final metadata.
- if (!batch.commitAt(config.getRevision())) {
- throw new OrmException("Cannot update " + allProjects);
- }
+ batch.commitAt(config.getRevision());
} finally {
batch.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
index 7c7b880..bec2f3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
@@ -18,7 +18,6 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -54,7 +53,6 @@
"SELECT group_id, owner_group_id FROM account_groups"
+ " WHERE owner_group_uuid is NULL or owner_group_uuid =''");
try {
- Map<Integer, ContributorAgreement> agreements = Maps.newHashMap();
while (rs.next()) {
AccountGroup.Id groupId = new AccountGroup.Id(rs.getInt(1));
AccountGroup.Id ownerId = new AccountGroup.Id(rs.getInt(2));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_68.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_68.java
new file mode 100644
index 0000000..4dc2b6e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_68.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_68 extends SchemaVersion {
+ @Inject
+ Schema_68(Provider<Schema_67> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(final ReviewDb db, final UpdateUI ui)
+ throws SQLException {
+ final Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ stmt.execute("CREATE INDEX submodule_subscription_access_bySubscription"
+ + " ON submodule_subscriptions (submodule_project_name, submodule_branch_name)");
+ } catch (SQLException e) {
+ // the index creation might have failed because the index exists already,
+ // in this case the exception can be safely ignored,
+ // but there are also other possible reasons for an exception here that
+ // should not be ignored,
+ // -> ask the user whether to ignore this exception or not
+ ui.message("warning: Cannot create index for submodule subscriptions");
+ ui.message(e.getMessage());
+
+ if (ui.isBatch()) {
+ ui.message("you may ignore this warning when running in interactive mode");
+ throw e;
+ } else {
+ final boolean answer = ui.yesno(false, "Ignore warning and proceed with schema upgrade");
+ if (!answer) {
+ throw e;
+ }
+ }
+ } finally {
+ stmt.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
new file mode 100644
index 0000000..fa56966
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
@@ -0,0 +1,228 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapName;
+
+public class Schema_69 extends SchemaVersion {
+ private final GitRepositoryManager mgr;
+ private final PersonIdent serverUser;
+
+ @Inject
+ Schema_69(Provider<Schema_68> prior,
+ GitRepositoryManager mgr,
+ @GerritPersonIdent PersonIdent serverUser) {
+ super(prior);
+ this.mgr = mgr;
+ this.serverUser = serverUser;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui)
+ throws OrmException, SQLException {
+
+ // Find all groups that have an LDAP type.
+ Map<AccountGroup.UUID, GroupReference> ldapUUIDMap = Maps.newHashMap();
+ Set<AccountGroup.UUID> toResolve = Sets.newHashSet();
+ List<AccountGroup.Id> toDelete = Lists.newArrayList();
+ List<AccountGroup.NameKey> namesToDelete = Lists.newArrayList();
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ ResultSet rs = stmt.executeQuery(
+ "SELECT group_id, group_uuid, external_name, name FROM account_groups"
+ + " WHERE group_type ='LDAP'");
+ try {
+ while (rs.next()) {
+ AccountGroup.Id groupId = new AccountGroup.Id(rs.getInt(1));
+ AccountGroup.UUID groupUUID = new AccountGroup.UUID(rs.getString(2));
+ AccountGroup.NameKey name = new AccountGroup.NameKey(rs.getString(4));
+ String dn = rs.getString(3);
+
+ if (isNullOrEmpty(dn)) {
+ // The LDAP group does not have a DN. Determine if the UUID is used.
+ toResolve.add(groupUUID);
+ } else {
+ toDelete.add(groupId);
+ namesToDelete.add(name);
+ GroupReference ref = groupReference(dn);
+ ldapUUIDMap.put(groupUUID, ref);
+ }
+ }
+ } catch (NamingException e) {
+ throw new RuntimeException(e);
+ } finally {
+ rs.close();
+ }
+ } finally {
+ stmt.close();
+ }
+ if (toDelete.isEmpty() && toResolve.isEmpty()) {
+ return; // No ldap groups. Nothing to do.
+ }
+
+ ui.message("Update LDAP groups to be GroupReferences.");
+
+ // Update the groupOwnerUUID for LDAP groups to point to the new UUID.
+ List<AccountGroup> toUpdate = Lists.newArrayList();
+ Set<AccountGroup.UUID> resolveToUpdate = Sets.newHashSet();
+ Map<AccountGroup.UUID, AccountGroup> resolveGroups = Maps.newHashMap();
+ for (AccountGroup g : db.accountGroups().all()) {
+ if (ldapUUIDMap.containsKey(g.getGroupUUID())) {
+ continue; // Ignore the LDAP groups with a valid DN.
+ } else if (toResolve.contains(g.getGroupUUID())) {
+ resolveGroups.put(g.getGroupUUID(), g); // Keep the ones to resolve.
+ continue;
+ }
+
+ GroupReference ref = ldapUUIDMap.get(g.getOwnerGroupUUID());
+ if (ref != null) {
+ // Update the owner group UUID to the new ldap UUID scheme.
+ g.setOwnerGroupUUID(ref.getUUID());
+ toUpdate.add(g);
+ } else if (toResolve.contains(g.getOwnerGroupUUID())) {
+ // The unresolved group is used as an owner.
+ // Add to the list of LDAP groups to be made INTERNAL.
+ resolveToUpdate.add(g.getOwnerGroupUUID());
+ }
+ }
+
+ toResolve.removeAll(resolveToUpdate);
+
+ // Update project.config group references to use the new LDAP GroupReference
+ for (Project.NameKey name : mgr.list()) {
+ Repository git;
+ try {
+ git = mgr.openRepository(name);
+ } catch (RepositoryNotFoundException e) {
+ throw new OrmException(e);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+
+ try {
+ MetaDataUpdate md =
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, name, git);
+ md.getCommitBuilder().setAuthor(serverUser);
+ md.getCommitBuilder().setCommitter(serverUser);
+
+ ProjectConfig config = ProjectConfig.read(md);
+
+ // Update the existing refences to the new reference.
+ boolean updated = false;
+ for (Map.Entry<AccountGroup.UUID, GroupReference> entry: ldapUUIDMap.entrySet()) {
+ GroupReference ref = config.getGroup(entry.getKey());
+ if (ref != null) {
+ updated = true;
+ ref.setName(entry.getValue().getName());
+ ref.setUUID(entry.getValue().getUUID());
+ config.resolve(ref);
+ }
+ }
+
+ // Determine if a toResolve group is used and should be made INTERNAL.
+ Iterator<AccountGroup.UUID> iter = toResolve.iterator();
+ while (iter.hasNext()) {
+ AccountGroup.UUID uuid = iter.next();
+ if (config.getGroup(uuid) != null) {
+ resolveToUpdate.add(uuid);
+ iter.remove();
+ }
+ }
+
+ if (!updated) {
+ continue;
+ }
+
+ md.setMessage("Switch LDAP group UUIDs to DNs\n");
+ config.commit(md);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ } catch (ConfigInvalidException e) {
+ throw new OrmException(e);
+ } finally {
+ git.close();
+ }
+ }
+
+ for (AccountGroup.UUID uuid : resolveToUpdate) {
+ AccountGroup group = resolveGroups.get(uuid);
+ group.setType(AccountGroup.Type.INTERNAL);
+ toUpdate.add(group);
+
+ ui.message(String.format(
+ "*** Group has no DN and is inuse. Updated to be INTERNAL: %s",
+ group.getName()));
+ }
+
+ for (AccountGroup.UUID uuid : toResolve) {
+ AccountGroup group = resolveGroups.get(uuid);
+ toDelete.add(group.getId());
+ namesToDelete.add(group.getNameKey());
+ }
+
+ // Update group owners
+ db.accountGroups().update(toUpdate);
+ // Delete existing LDAP groups
+ db.accountGroupNames().deleteKeys(namesToDelete);
+ db.accountGroups().deleteKeys(toDelete);
+ }
+
+ private static GroupReference groupReference(String dn)
+ throws NamingException {
+ LdapName name = new LdapName(dn);
+ Preconditions.checkState(!name.isEmpty(), "Invalid LDAP dn: %s", dn);
+ String cn = name.get(name.size() - 1);
+ int index = cn.indexOf('=');
+ if (index >= 0) {
+ cn = cn.substring(index + 1);
+ }
+ return new GroupReference(new AccountGroup.UUID("ldap:" + dn), "ldap/" + cn);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java
index 64b3afa..eff5575 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java
@@ -24,6 +24,8 @@
boolean yesno(boolean def, String msg);
+ boolean isBatch();
+
void pruneSchema(StatementExecutor e, List<String> pruneList)
throws OrmException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
index cc3c2f7..fa07176 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.util;
import com.google.common.collect.Maps;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
index 88240ca..b411512 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
@@ -21,7 +21,6 @@
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
-import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.name.Named;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
index d08bd1f..74c97f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
@@ -26,7 +26,6 @@
import com.google.gerrit.reviewdb.client.ApprovalCategory.Id;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -57,7 +56,7 @@
@Inject
FunctionState(final ApprovalTypes approvalTypes,
- final IdentifiedUser.GenericFactory userFactory, final GroupCache egc,
+ final IdentifiedUser.GenericFactory userFactory,
@Assisted final ChangeControl c, @Assisted final PatchSet.Id psId,
@Assisted final Collection<PatchSetApproval> all) {
this.approvalTypes = approvalTypes;
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 5acc831..a75acc0 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -149,6 +149,7 @@
is_all_ok([]).
is_all_ok([label(_, ok(__)) | Ls]) :- is_all_ok(Ls).
+is_all_ok([label(_, may(__)) | Ls]) :- is_all_ok(Ls).
is_all_ok(_) :- fail.
@@ -209,8 +210,8 @@
%%
legacy_submit_rule('MaxWithBlock', Label, Id, Min, Max, T) :- !, max_with_block(Label, Min, Max, T).
legacy_submit_rule('MaxNoBlock', Label, Id, Min, Max, T) :- !, max_no_block(Label, Max, T).
-legacy_submit_rule('NoBlock', Label, Id, Min, Max, T) :- !, T = ok(_).
-legacy_submit_rule('NoOp', Label, Id, Min, Max, T) :- !, T = ok(_).
+legacy_submit_rule('NoBlock', Label, Id, Min, Max, T) :- !, T = may(_).
+legacy_submit_rule('NoOp', Label, Id, Min, Max, T) :- !, T = may(_).
legacy_submit_rule(Fun, Label, Id, Min, Max, T) :- T = impossible(unsupported(Fun)).
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
index a67c38c..bb3c127 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
@@ -32,6 +32,6 @@
## subject line for ALL emails related to changes.
##
#macro(elipses $length $str)
-#if($str.length() > $length)${str.substring(0,$length)}...#else$str#end
+#if(($str.length()+3) > $length)${str.substring(0,$length)}...#else$str#end
#end
Change in $projectName.replaceAll('/.*/', '...')[$branch.shortName]: #elipses(60, $change.subject)
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
index 547c1b4..9af98a6 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
@@ -43,5 +43,11 @@
$email.coverLetter
#end
+##
+## It is possible to increase the span of the quoted lines by using the line
+## count parameter when calling $email.inlineComments as a function.
+##
+## Example: #if($email.inlineComments)$email.getInlineComments(5)#end
+##
#if($email.inlineComments)$email.inlineComments#end
#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
index 8e08dc4..42f2ca9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
@@ -43,6 +43,11 @@
#end
#else
$fromName has uploaded a new change for review.
+#if($email.changeUrl)
+
+ $email.changeUrl
+
+#end
#end
Change subject: $change.subject
@@ -52,3 +57,7 @@
#if($email.sshHost)
git pull ssh://$email.sshHost/$projectName $patchSet.refName
#end
+#if($email.includeDiff)
+
+$email.UnifiedDiff
+#end
\ No newline at end of file
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
new file mode 100644
index 0000000..24f3386
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import junit.framework.TestCase;
+
+public class StringUtilTest extends TestCase {
+ /**
+ * Test the boundary condition that the first character of a string
+ * should be escaped.
+ */
+ public void testEscapeFirstChar() {
+ assertEquals(StringUtil.escapeString("\tLeading tab"), "\\tLeading tab");
+ }
+
+ /**
+ * Test the boundary condition that the last character of a string
+ * should be escaped.
+ */
+ public void testEscapeLastChar() {
+ assertEquals(StringUtil.escapeString("Trailing tab\t"), "Trailing tab\\t");
+ }
+
+ /**
+ * Test that various forms of input strings are escaped (or left as-is)
+ * in the expected way.
+ */
+ public void testEscapeString() {
+ final String[] testPairs =
+ { "", "",
+ "plain string", "plain string",
+ "string with \"quotes\"", "string with \"quotes\"",
+ "string with 'quotes'", "string with 'quotes'",
+ "string with 'quotes'", "string with 'quotes'",
+ "C:\\Program Files\\MyProgram", "C:\\\\Program Files\\\\MyProgram",
+ "string\nwith\nnewlines", "string\\nwith\\nnewlines",
+ "string\twith\ttabs", "string\\twith\\ttabs" };
+ for (int i = 0; i < testPairs.length; i += 2) {
+ assertEquals(StringUtil.escapeString(testPairs[i]), testPairs[i + 1]);
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index 02bf815..a849e68 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -209,7 +209,7 @@
util.tick(5);
util.setAuthorAndCommitter(md.getCommitBuilder());
md.setMessage("Edit\n");
- assertTrue("commit finished", cfg.commit(md));
+ cfg.commit(md);
Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
return util.getRevWalk().parseCommit(ref.getObjectId());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
new file mode 100644
index 0000000..2d432e6
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
@@ -0,0 +1,138 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.ioutil;
+
+import com.google.gerrit.server.ioutil.ColumnFormatter;
+
+import junit.framework.TestCase;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class ColumnFormatterTest extends TestCase {
+ /**
+ * Holds an in-memory {@link java.io.PrintWriter} object and allows
+ * comparisons of its contents to a supplied string via an assert statement.
+ */
+ class PrintWriterComparator {
+ private PrintWriter printWriter;
+ private StringWriter stringWriter;
+
+ public PrintWriterComparator() {
+ stringWriter = new StringWriter();
+ printWriter = new PrintWriter(stringWriter);
+ }
+
+ public void assertEquals(String str) {
+ printWriter.flush();
+ TestCase.assertEquals(stringWriter.toString(), str);
+ }
+
+ public PrintWriter getPrintWriter() {
+ return printWriter;
+ }
+ }
+
+ /**
+ * Test that only lines with at least one column of text emit output.
+ */
+ public void testEmptyLine() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.nextLine();
+ formatter.nextLine();
+ formatter.nextLine();
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.finish();
+ comparator.assertEquals("foo\tbar\nfoo\tbar\n");
+ }
+
+ /**
+ * Test that there is no output if no columns are ever added.
+ */
+ public void testEmptyOutput() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.nextLine();
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("");
+ }
+
+ /**
+ * Test that there is no output (nor any exceptions) if we finalize
+ * the output immediately after the creation of the {@link ColumnFormatter}.
+ */
+ public void testNoNextLine() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.finish();
+ comparator.assertEquals("");
+ }
+
+ /**
+ * Test that the text in added columns is escaped while the column separator
+ * (which of course shouldn't be escaped) is left alone.
+ */
+ public void testEscapingTakesPlace() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.addColumn(
+ "\tan indented multi-line\ntext");
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("foo\t\\tan indented multi-line\\ntext\n");
+ }
+
+ /**
+ * Test that we get the correct output with multi-line input where the number
+ * of columns in each line varies.
+ */
+ public void testMultiLineDifferentColumnCount() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.addColumn("baz");
+ formatter.nextLine();
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("foo\tbar\tbaz\nfoo\tbar\n");
+ }
+
+ /**
+ * Test that we get the correct output with a single column of input.
+ */
+ public void testOneColumn() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("foo\n");
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 469dafe..d4b07ae 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -20,6 +20,8 @@
import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.common.data.Permission.SUBMIT;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.GroupReference;
@@ -36,7 +38,6 @@
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
-import com.google.gerrit.server.cache.ConcurrentHashMapCache;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -143,6 +144,18 @@
u.controlForRef("refs/heads/foobar").canUpload());
}
+ public void testInheritDuplicateSections() {
+ grant(parent, READ, admin, "refs/*");
+ grant(local, READ, devs, "refs/heads/*");
+ local.getProject().setParentName(parent.getProject().getName());
+ assertTrue("a can read", user("a", admin).isVisible());
+
+ local = new ProjectConfig(new Project.NameKey("local"));
+ local.createInMemory();
+ grant(local, READ, devs, "refs/*");
+ assertTrue("d can read", user("d", devs).isVisible());
+ }
+
public void testInheritRead_OverrideWithDeny() {
grant(parent, READ, registered, "refs/*");
grant(local, READ, registered, "refs/*").setDeny();
@@ -319,12 +332,10 @@
local = new ProjectConfig(new Project.NameKey("local"));
local.createInMemory();
- local.getProject().setParentName(parent.getProject().getName());
- sectionSorter =
- new PermissionCollection.Factory(
- new SectionSortCache(
- new ConcurrentHashMapCache<SectionSortCache.EntryKey, SectionSortCache.EntryVal>()));
+ Cache<SectionSortCache.EntryKey, SectionSortCache.EntryVal> c =
+ CacheBuilder.newBuilder().build();
+ sectionSorter = new PermissionCollection.Factory(new SectionSortCache(c));
}
private static void assertOwner(String ref, ProjectControl u) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index 34f7430..cc8d47d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -108,6 +108,11 @@
}
@Override
+ public boolean isBatch() {
+ return true;
+ }
+
+ @Override
public void pruneSchema(StatementExecutor e, List<String> pruneList)
throws OrmException {
for (String sql : pruneList) {
diff --git a/gerrit-sshd/.settings/org.eclipse.core.resources.prefs b/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
index 0871ea8..839d647 100644
--- a/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Tue May 15 09:21:12 PDT 2012
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 1c197a0..31b2422 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -67,7 +67,7 @@
<dependency>
<groupId>com.google.gerrit</groupId>
- <artifactId>gerrit-ehcache</artifactId>
+ <artifactId>gerrit-cache-h2</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
index d28d102..9582c93 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.Provider;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 2b4543b..9e04f05 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -371,6 +371,14 @@
return new UnloggedFailure(1, "fatal: " + why.getMessage(), why);
}
+ public void checkExclusivity(final Object arg1, final String arg1name,
+ final Object arg2, final String arg2name) throws UnloggedFailure {
+ if (arg1 != null && arg2 != null) {
+ throw new UnloggedFailure(String.format(
+ "%s and %s options are mutually exclusive.", arg1name, arg2name));
+ }
+ }
+
private final class TaskThunk implements CancelableRunnable, ProjectRunnable {
private final CommandRunnable thunk;
private final Context context;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index f0810ba..301d68d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -16,9 +16,10 @@
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.sshd.args4j.SubcommandHandler;
+import com.google.gerrit.server.args4j.SubcommandHandler;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index 1f5ac28..0a1f708 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -16,13 +16,13 @@
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
@@ -42,6 +42,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ExecutionException;
/** Provides the {@link SshKeyCacheEntry}. */
@Singleton
@@ -57,9 +58,10 @@
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>> type =
- new TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>>() {};
- core(type, CACHE_NAME).populateWith(Loader.class);
+ cache(CACHE_NAME,
+ String.class,
+ new TypeLiteral<Iterable<SshKeyCacheEntry>>(){})
+ .loader(Loader.class);
bind(SshKeyCacheImpl.class);
bind(SshKeyCache.class).to(SshKeyCacheImpl.class);
}
@@ -71,20 +73,27 @@
.asList(new SshKeyCacheEntry[0]));
}
- private final Cache<String, Iterable<SshKeyCacheEntry>> cache;
+ private final LoadingCache<String, Iterable<SshKeyCacheEntry>> cache;
@Inject
SshKeyCacheImpl(
- @Named(CACHE_NAME) final Cache<String, Iterable<SshKeyCacheEntry>> cache) {
+ @Named(CACHE_NAME) LoadingCache<String, Iterable<SshKeyCacheEntry>> cache) {
this.cache = cache;
}
- public Iterable<SshKeyCacheEntry> get(String username) {
- return cache.get(username);
+ Iterable<SshKeyCacheEntry> get(String username) {
+ try {
+ return cache.get(username);
+ } catch (ExecutionException e) {
+ log.warn("Cannot load SSH keys for " + username, e);
+ return Collections.emptyList();
+ }
}
public void evict(String username) {
- cache.remove(username);
+ if (username != null) {
+ cache.invalidate(username);
+ }
}
@Override
@@ -107,7 +116,7 @@
}
}
- static class Loader extends EntryCreator<String, Iterable<SshKeyCacheEntry>> {
+ static class Loader extends CacheLoader<String, Iterable<SshKeyCacheEntry>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -116,8 +125,7 @@
}
@Override
- public Iterable<SshKeyCacheEntry> createEntry(String username)
- throws Exception {
+ public Iterable<SshKeyCacheEntry> load(String username) throws Exception {
final ReviewDb db = schema.open();
try {
final AccountExternalId.Key key =
@@ -143,11 +151,6 @@
}
}
- @Override
- public Iterable<SshKeyCacheEntry> missing(String username) {
- return Collections.emptyList();
- }
-
private void add(ReviewDb db, List<SshKeyCacheEntry> kl, AccountSshKey k) {
try {
kl.add(new SshKeyCacheEntry(k.getKey(), SshUtil.parse(k)));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index 8fbea9d..a55d715 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -18,6 +18,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.sshd.SshScope.Context;
@@ -33,6 +34,7 @@
import org.apache.log4j.Logger;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.LoggingEvent;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.util.QuotedString;
import java.io.File;
@@ -59,7 +61,7 @@
@Inject
SshLog(final Provider<SshSession> session, final Provider<Context> context,
- final SitePaths site) {
+ final SitePaths site, @GerritServerConfig Config config) {
this.session = session;
this.context = context;
@@ -77,7 +79,7 @@
async = new AsyncAppender();
async.setBlocking(true);
- async.setBufferSize(64);
+ async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
async.setLocationInfo(false);
async.addAppender(dst);
async.activateOptions();
@@ -99,7 +101,7 @@
void onAuthFail(final SshSession sd) {
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
- null, // logger (optional)
+ log, // logger
System.currentTimeMillis(), // when
Level.INFO, // level
"AUTH FAILURE FROM " + sd.getRemoteAddressAsString(), // message text
@@ -168,7 +170,7 @@
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
- null, // logger (optional)
+ log, // logger
System.currentTimeMillis(), // when
Level.INFO, // level
msg, // message text
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index bcb1d9f..7f4a1f7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -19,12 +19,7 @@
import com.google.common.collect.Maps;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CmdLineParserModule;
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountManager;
@@ -37,21 +32,10 @@
import com.google.gerrit.server.plugins.ModuleGenerator;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.sshd.args4j.AccountGroupIdHandler;
-import com.google.gerrit.sshd.args4j.AccountGroupUUIDHandler;
-import com.google.gerrit.sshd.args4j.AccountIdHandler;
-import com.google.gerrit.sshd.args4j.ChangeIdHandler;
-import com.google.gerrit.sshd.args4j.ObjectIdHandler;
-import com.google.gerrit.sshd.args4j.PatchSetIdHandler;
-import com.google.gerrit.sshd.args4j.ProjectControlHandler;
-import com.google.gerrit.sshd.args4j.SocketAddressHandler;
import com.google.gerrit.sshd.commands.DefaultCommandModule;
import com.google.gerrit.sshd.commands.QueryShell;
-import com.google.gerrit.util.cli.CmdLineParser;
-import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.inject.Inject;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.servlet.RequestScoped;
@@ -60,9 +44,6 @@
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.kohsuke.args4j.spi.OptionHandler;
-
import java.net.SocketAddress;
import java.util.Map;
@@ -85,7 +66,7 @@
bind(SshScope.class).in(SINGLETON);
configureRequestScope();
- configureCmdLineParser();
+ install(new CmdLineParserModule());
configureAliases();
install(SshKeyCacheImpl.module());
@@ -157,22 +138,4 @@
install(new GerritRequestModule());
}
-
- private void configureCmdLineParser() {
- factory(CmdLineParser.Factory.class);
-
- registerOptionHandler(Account.Id.class, AccountIdHandler.class);
- registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class);
- registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class);
- registerOptionHandler(Change.Id.class, ChangeIdHandler.class);
- registerOptionHandler(ObjectId.class, ObjectIdHandler.class);
- registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
- registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
- registerOptionHandler(SocketAddress.class, SocketAddressHandler.class);
- }
-
- private <T> void registerOptionHandler(Class<T> type,
- Class<? extends OptionHandler<T>> impl) {
- install(OptionHandlerUtil.moduleFor(type, impl));
- }
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
index 5f1992c..08c650c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
@@ -15,8 +15,8 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index 047cdd4..6483e24 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
@@ -22,7 +23,6 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -137,9 +137,7 @@
config.getProject().setParentName(newParentKey);
md.setMessage("Inherit access from "
+ (newParentKey != null ? newParentKey.get() : allProjectsName.get()) + "\n");
- if (!config.commit(md)) {
- err.append("error: Could not update project " + name + "\n");
- }
+ config.commit(md);
} finally {
md.close();
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index f13e1a6..939d68a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -17,12 +17,12 @@
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.BanCommitResult;
-import com.google.gerrit.server.git.IncompleteUserInfoException;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -77,12 +77,12 @@
throw die(e);
} catch (IOException e) {
throw die(e);
- } catch (IncompleteUserInfoException e) {
- throw die(e);
} catch (MergeException e) {
throw die(e);
} catch (InterruptedException e) {
throw die(e);
+ } catch (ConcurrentRefUpdateException e) {
+ throw die(e);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
index 1e7c5b3..500c84a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
@@ -14,37 +14,33 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.ehcache.EhcachePoolImpl;
+import com.google.common.cache.Cache;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
-
-import java.util.Arrays;
import java.util.SortedSet;
-import java.util.TreeSet;
abstract class CacheCommand extends SshCommand {
@Inject
- protected EhcachePoolImpl cachePool;
+ protected DynamicMap<Cache<?, ?>> cacheMap;
protected SortedSet<String> cacheNames() {
- final SortedSet<String> names = new TreeSet<String>();
- for (final Ehcache c : getAllCaches()) {
- names.add(c.getName());
+ SortedSet<String> names = Sets.newTreeSet();
+ for (String plugin : cacheMap.plugins()) {
+ for (String name : cacheMap.byPlugin(plugin).keySet()) {
+ names.add(cacheNameOf(plugin, name));
+ }
}
return names;
}
- protected Ehcache[] getAllCaches() {
- final CacheManager cacheMgr = cachePool.getCacheManager();
- final String[] cacheNames = cacheMgr.getCacheNames();
- Arrays.sort(cacheNames);
- final Ehcache[] r = new Ehcache[cacheNames.length];
- for (int i = 0; i < cacheNames.length; i++) {
- r[i] = cacheMgr.getEhcache(cacheNames[i]);
+ protected String cacheNameOf(String plugin, String name) {
+ if ("gerrit".equals(plugin)) {
+ return name;
+ } else {
+ return plugin + "." + name;
}
- return r;
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index 29f2294..ac7ee08 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -27,7 +28,6 @@
import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.ssh.SshKeyCache;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
index 28b6f48..728c20c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -17,10 +17,10 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.PermissionDeniedException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.PerformCreateGroup;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 1f5bc6f..5f5b1e3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.ProjectCreationFailedException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.SubmitType;
@@ -23,11 +24,9 @@
import com.google.gerrit.server.project.CreateProjectArgs;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SuggestParentCandidates;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Constants;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -79,7 +78,7 @@
@Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", usage = "initial branch name\n"
+ "(default: master)")
- private String branch = Constants.MASTER;
+ private List<String> branch;
@Option(name = "--empty-commit", usage = "to create initial empty commit")
private boolean createEmptyCommit;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 9ba20ed..fa63041 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -14,18 +14,19 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.cache.Cache;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.sshd.BaseCommand;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.inject.Inject;
-
-import net.sf.ehcache.Ehcache;
+import com.google.inject.Provider;
import org.kohsuke.args4j.Option;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.SortedSet;
/** Causes the caches to purge all entries and reload. */
@@ -95,13 +96,16 @@
private void doBulkFlush() {
try {
- for (final Ehcache c : getAllCaches()) {
- final String name = c.getName();
- if (flush(name)) {
- try {
- c.removeAll();
- } catch (Throwable e) {
- stderr.println("error: cannot flush cache \"" + name + "\": " + e);
+ for (String plugin : cacheMap.plugins()) {
+ for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
+ cacheMap.byPlugin(plugin).entrySet()) {
+ String n = cacheNameOf(plugin, entry.getKey());
+ if (flush(n)) {
+ try {
+ entry.getValue().get().invalidateAll();
+ } catch (Throwable err) {
+ stderr.println("error: cannot flush cache \"" + n + "\": " + err);
+ }
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
index 12ab225..83e88e5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -15,11 +15,11 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index a729f43..aa439c6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -20,9 +20,12 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.VisibleGroups;
+import com.google.gerrit.server.ioutil.ColumnFormatter;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.sshd.SshCommand;
+import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -33,6 +36,9 @@
public class ListGroupsCommand extends SshCommand {
@Inject
+ private GroupCache groupCache;
+
+ @Inject
private VisibleGroups.Factory visibleGroupsFactory;
@Inject
@@ -52,6 +58,12 @@
usage = "user for which the groups should be listed")
private Account.Id user;
+ @Option(name = "--verbose", aliases = {"-v"},
+ usage = "verbose output format with tab-separated columns for the " +
+ "group name, UUID, description, type, owner group name, " +
+ "owner group UUID, and whether the group is visible to all")
+ private boolean verboseOutput;
+
@Override
protected void run() throws Failure {
try {
@@ -70,9 +82,26 @@
} else {
groupList = visibleGroups.get();
}
+
+ final ColumnFormatter formatter = new ColumnFormatter(stdout, '\t');
for (final GroupDetail groupDetail : groupList.getGroups()) {
- stdout.print(groupDetail.group.getName() + "\n");
+ final AccountGroup g = groupDetail.group;
+ formatter.addColumn(g.getName());
+ if (verboseOutput) {
+ formatter.addColumn(KeyUtil.decode(g.getGroupUUID().toString()));
+ formatter.addColumn(
+ g.getDescription() != null ? g.getDescription() : "");
+ formatter.addColumn(g.getType().toString());
+ final AccountGroup owningGroup =
+ groupCache.get(g.getOwnerGroupUUID());
+ formatter.addColumn(
+ owningGroup != null ? owningGroup.getName() : "n/a");
+ formatter.addColumn(KeyUtil.decode(g.getOwnerGroupUUID().toString()));
+ formatter.addColumn(Boolean.toString(g.isVisibleToAll()));
+ }
+ formatter.nextLine();
}
+ formatter.finish();
} catch (OrmException e) {
throw die(e);
} catch (NoSuchGroupException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
index c5cdbd5..db4e985 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
@@ -36,5 +36,6 @@
command(gerrit, "set-project-parent").to(AdminSetParent.class);
command(gerrit, "review").to(ReviewCommand.class);
command(gerrit, "set-account").to(SetAccountCommand.class);
+ command(gerrit, "set-project").to(SetProjectCommand.class);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 2328847..12722ec 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -16,9 +16,9 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginInstallException;
import com.google.gerrit.server.plugins.PluginLoader;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 644cf13..6d7490f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -14,39 +14,29 @@
package com.google.gerrit.sshd.commands;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.server.plugins.Plugin;
-import com.google.gerrit.server.plugins.PluginLoader;
-import com.google.gerrit.sshd.RequiresCapability;
-import com.google.gerrit.sshd.SshCommand;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.plugins.ListPlugins;
+import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Inject;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
+import org.apache.sshd.server.Environment;
+
+import java.io.IOException;
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-final class PluginLsCommand extends SshCommand {
+final class PluginLsCommand extends BaseCommand {
@Inject
- private PluginLoader loader;
+ private ListPlugins impl;
@Override
- protected void run() {
- List<Plugin> running = Lists.newArrayList(loader.getPlugins());
- Collections.sort(running, new Comparator<Plugin>() {
+ public void start(Environment env) throws IOException {
+ startThread(new CommandRunnable() {
@Override
- public int compare(Plugin a, Plugin b) {
- return a.getName().compareTo(b.getName());
+ public void run() throws Exception {
+ parseCommandLine(impl);
+ impl.display(out);
}
});
-
- stdout.format("%-30s %-10s\n", "Name", "Version");
- stdout.print("----------------------------------------------------------------------\n");
- for (Plugin p : running) {
- stdout.format("%-30s %-10s\n", p.getName(),
- Strings.nullToEmpty(p.getVersion()));
- }
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
index d60465c..d2429a9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
@@ -17,8 +17,8 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.server.plugins.InvalidPluginException;
import com.google.gerrit.server.plugins.PluginInstallException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginLoader;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
index 6444e71..8baab77 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
@@ -16,8 +16,8 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginLoader;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index 0e7ff83..b4de75b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -24,11 +24,14 @@
import com.google.gerrit.sshd.AbstractGitCommand;
import com.google.inject.Inject;
+import org.eclipse.jgit.errors.TooLargeObjectInPackException;
import org.eclipse.jgit.errors.UnpackException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
import org.eclipse.jgit.transport.ReceivePack;
import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -39,6 +42,8 @@
/** Receives change upload over SSH using the Git receive-pack protocol. */
final class Receive extends AbstractGitCommand {
+ private static final Logger log = LoggerFactory.getLogger(Receive.class);
+
@Inject
private AsyncReceiveCommits.Factory factory;
@@ -92,6 +97,23 @@
receive.advertiseHistory();
rp.receive(in, out, err);
} catch (UnpackException badStream) {
+ // In case this was caused by the user pushing an object whose size
+ // is larger than the receive.maxObjectSizeLimit gerrit.config parameter
+ // we want to present this error to the user
+ if (badStream.getCause() instanceof TooLargeObjectInPackException) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Receive error on project \""
+ + projectControl.getProject().getName() + "\"");
+ msg.append(" (user ");
+ msg.append(currentUser.getAccount().getUserName());
+ msg.append(" account ");
+ msg.append(currentUser.getAccountId());
+ msg.append("): ");
+ msg.append(badStream.getCause().getMessage());
+ log.info(msg.toString());
+ throw new UnloggedFailure(128, "error: " + badStream.getCause().getMessage());
+ }
+
// This may have been triggered by branch level access controls.
// Log what the heck is going on, as detailed as we can.
//
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index f38e17e..5ebb6c7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -38,6 +38,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.kohsuke.args4j.Argument;
@@ -67,7 +68,8 @@
private final Set<PatchSet.Id> patchSetIds = new HashSet<PatchSet.Id>();
- @Argument(index = 0, required = true, multiValued = true, metaVar = "{COMMIT | CHANGE,PATCHSET}", usage = "patch to review")
+ @Argument(index = 0, required = true, multiValued = true, metaVar = "{COMMIT | CHANGE,PATCHSET}",
+ usage = "list of commits or patch sets to review")
void addPatchSetId(final String token) {
try {
patchSetIds.addAll(parsePatchSetId(token));
@@ -78,29 +80,29 @@
}
}
- @Option(name = "--project", aliases = "-p", usage = "project containing the patch set")
+ @Option(name = "--project", aliases = "-p", usage = "project containing the specified patch set(s)")
private ProjectControl projectControl;
- @Option(name = "--message", aliases = "-m", usage = "cover message to publish on change", metaVar = "MESSAGE")
+ @Option(name = "--message", aliases = "-m", usage = "cover message to publish on change(s)", metaVar = "MESSAGE")
private String changeComment;
- @Option(name = "--abandon", usage = "abandon the patch set")
+ @Option(name = "--abandon", usage = "abandon the specified change(s)")
private boolean abandonChange;
- @Option(name = "--restore", usage = "restore an abandoned the patch set")
+ @Option(name = "--restore", usage = "restore the specified abandoned change(s)")
private boolean restoreChange;
- @Option(name = "--submit", aliases = "-s", usage = "submit the patch set")
+ @Option(name = "--submit", aliases = "-s", usage = "submit the specified patch set(s)")
private boolean submitChange;
@Option(name = "--force-message", usage = "publish the message, "
- + "even if the label score cannot be applied due to change being closed")
+ + "even if the label score cannot be applied due to the change being closed")
private boolean forceMessage = false;
- @Option(name = "--publish", usage = "publish a draft patch set")
+ @Option(name = "--publish", usage = "publish the specified draft patch set(s)")
private boolean publishPatchSet;
- @Option(name = "--delete", usage = "delete a draft patch set")
+ @Option(name = "--delete", usage = "delete the specified draft patch set(s)")
private boolean deleteDraftPatchSet;
@Inject
@@ -113,7 +115,7 @@
private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
@Inject
- private AbandonChange.Factory abandonChangeFactory;
+ private Provider<AbandonChange> abandonChangeProvider;
@Inject
private PublishComments.Factory publishCommentsFactory;
@@ -122,7 +124,7 @@
private PublishDraft.Factory publishDraftFactory;
@Inject
- private RestoreChange.Factory restoreChangeFactory;
+ private Provider<RestoreChange> restoreChangeProvider;
@Inject
private Submit.Factory submitFactory;
@@ -201,12 +203,16 @@
publishCommentsFactory.create(patchSetId, changeComment, aps, forceMessage).call();
if (abandonChange) {
- final ReviewResult result = abandonChangeFactory.create(
- patchSetId.getParentKey(), changeComment).call();
+ final AbandonChange abandonChange = abandonChangeProvider.get();
+ abandonChange.setChangeId(patchSetId.getParentKey());
+ abandonChange.setMessage(changeComment);
+ final ReviewResult result = abandonChange.call();
handleReviewResultErrors(result);
} else if (restoreChange) {
- final ReviewResult result = restoreChangeFactory.create(
- patchSetId.getParentKey(), changeComment).call();
+ final RestoreChange restoreChange = restoreChangeProvider.get();
+ restoreChange.setChangeId(patchSetId.getParentKey());
+ restoreChange.setMessage(changeComment);
+ final ReviewResult result = restoreChange.call();
handleReviewResultErrors(result);
}
if (submitChange) {
@@ -248,6 +254,9 @@
case CHANGE_IS_CLOSED:
errMsg += "change is closed";
break;
+ case CHANGE_NOT_ABANDONED:
+ errMsg += "change is not abandoned";
+ break;
case PUBLISH_NOT_PERMITTED:
errMsg += "not permitted to publish change";
break;
@@ -258,7 +267,7 @@
errMsg += "rule error";
break;
case NOT_A_DRAFT:
- errMsg += "change is not a draft";
+ errMsg += "change/patch set is not a draft";
break;
case GIT_ERROR:
errMsg += "error writing change to git repository";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
new file mode 100644
index 0000000..9143f5b
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -0,0 +1,171 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.State;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+final class SetProjectCommand extends SshCommand {
+ private static final Logger log = LoggerFactory
+ .getLogger(SetProjectCommand.class);
+
+ @Argument(index = 0, required = true, metaVar = "NAME", usage = "name of the project")
+ private ProjectControl projectControl;
+
+ @Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of project")
+ private String projectDescription;
+
+ @Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type\n"
+ + "(default: MERGE_IF_NECESSARY)")
+ private SubmitType submitType;
+
+ @Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required")
+ private Boolean contributorAgreements;
+
+ @Option(name = "--no-contributor-agreements", aliases = {"--nca"}, usage = "if contributor agreement is not required")
+ private Boolean noContributorAgreements;
+
+ @Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
+ private Boolean signedOffBy;
+
+ @Option(name = "--no-signed-off-by", aliases = {"--nso"}, usage = "if signed-off-by is not required")
+ private Boolean noSignedOffBy;
+
+ @Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
+ private Boolean contentMerge;
+
+ @Option(name = "--no-content-merge", usage = "don't allow automatic conflict resolving within files")
+ private Boolean noContentMerge;
+
+ @Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
+ private Boolean requireChangeID;
+
+ @Option(name = "--no-change-id", aliases = {"--nid"}, usage = "if change-id is not required")
+ private Boolean noRequireChangeID;
+
+ @Option(name = "--project-state", aliases = {"--ps"}, usage = "project's visibility state")
+ private State state;
+
+ @Inject
+ private MetaDataUpdate.User metaDataUpdateFactory;
+
+ @Inject
+ private ProjectCache projectCache;
+
+ @Override
+ protected void run() throws Failure {
+ validate();
+ Project ctlProject = projectControl.getProject();
+ Project.NameKey nameKey = ctlProject.getNameKey();
+ String name = ctlProject.getName();
+ final StringBuilder err = new StringBuilder();
+
+ try {
+ MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
+ try {
+ ProjectConfig config = ProjectConfig.read(md);
+ Project project = config.getProject();
+
+ project.setRequireChangeID(requireChangeID != null ? requireChangeID
+ : project.isRequireChangeID());
+
+ project.setRequireChangeID(noRequireChangeID != null
+ ? !noRequireChangeID : project.isRequireChangeID());
+
+ project.setSubmitType(submitType != null ? submitType : project
+ .getSubmitType());
+
+ project.setUseContentMerge(contentMerge != null ? contentMerge
+ : project.isUseContentMerge());
+
+ project.setUseContentMerge(noContentMerge != null ? !noContentMerge
+ : project.isUseContentMerge());
+
+ project.setUseContributorAgreements(contributorAgreements != null
+ ? contributorAgreements : project.isUseContributorAgreements());
+
+ project.setUseContributorAgreements(noContributorAgreements != null
+ ? !noContributorAgreements : project.isUseContributorAgreements());
+
+ project.setUseSignedOffBy(signedOffBy != null ? signedOffBy : project
+ .isUseSignedOffBy());
+
+ project.setUseContentMerge(noSignedOffBy != null ? !noSignedOffBy
+ : project.isUseContentMerge());
+
+ project.setDescription(projectDescription != null ? projectDescription
+ : project.getDescription());
+
+ project.setState(state != null ? state : project.getState());
+
+ md.setMessage("Project settings updated");
+ config.commit(md);
+ } finally {
+ md.close();
+ }
+ } catch (RepositoryNotFoundException notFound) {
+ err.append("error: Project " + name + " not found\n");
+ } catch (IOException e) {
+ final String msg = "Cannot update project " + name;
+ log.error(msg, e);
+ err.append("error: " + msg + "\n");
+ } catch (ConfigInvalidException e) {
+ final String msg = "Cannot update project " + name;
+ log.error(msg, e);
+ err.append("error: " + msg + "\n");
+ }
+ projectCache.evict(ctlProject);
+
+ if (err.length() > 0) {
+ while (err.charAt(err.length() - 1) == '\n') {
+ err.setLength(err.length() - 1);
+ }
+ throw new UnloggedFailure(1, err.toString());
+ }
+ }
+
+ private void validate() throws UnloggedFailure {
+ checkExclusivity(contentMerge, "--use-content-merge",
+ noContentMerge, "--no-content-merge");
+
+ checkExclusivity(contributorAgreements, "--use-contributor-agreements",
+ noContributorAgreements, "--no-contributor-agreements");
+
+ checkExclusivity(signedOffBy, "--use-signed-off-by",
+ noSignedOffBy, "--no-signed-off-by");
+
+ checkExclusivity(requireChangeID, "--require-change-id",
+ noRequireChangeID, "--no-change-id");
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 97a0d86..bdcb4fb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -14,22 +14,24 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheStats;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.cache.h2.H2CacheImpl;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshDaemon;
import com.google.inject.Inject;
-
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Statistics;
-import net.sf.ehcache.config.CacheConfiguration;
+import com.google.inject.Provider;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.server.Environment;
import org.eclipse.jgit.storage.file.WindowCacheStatAccessor;
import org.kohsuke.args4j.Option;
@@ -43,6 +45,8 @@
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
+import java.util.Map;
+import java.util.SortedMap;
/** Show the current cache states. */
@RequiresCapability(GlobalCapability.VIEW_CACHES)
@@ -76,8 +80,26 @@
@SitePath
private File sitePath;
+ @Option(name = "--width", aliases = {"-w"}, metaVar = "COLS", usage = "width of output table")
+ private int columns = 80;
+ private int nw;
+
+ @Override
+ public void start(Environment env) throws IOException {
+ String s = env.getEnv().get(Environment.ENV_COLUMNS);
+ if (s != null && !s.isEmpty()) {
+ try {
+ columns = Integer.parseInt(s);
+ } catch (NumberFormatException err) {
+ columns = 80;
+ }
+ }
+ super.start(env);
+ }
+
@Override
protected void run() {
+ nw = columns - 50;
Date now = new Date();
stdout.format(
"%-25s %-20s now %16s\n",
@@ -91,60 +113,46 @@
stdout.print('\n');
stdout.print(String.format(//
- "%1s %-18s %-4s|%-20s| %-5s |%-14s|\n" //
+ "%1s %-"+nw+"s|%-21s| %-5s |%-9s|\n" //
, "" //
, "Name" //
- , "Max" //
- , "Object Count" //
+ , "Entries" //
, "AvgGet" //
, "Hit Ratio" //
));
stdout.print(String.format(//
- "%1s %-18s %-4s|%6s %6s %6s| %-5s |%-4s %-4s %-4s|\n" //
+ "%1s %-"+nw+"s|%6s %6s %7s| %-5s |%-4s %-4s|\n" //
, "" //
, "" //
- , "Age" //
- , "Disk" //
, "Mem" //
- , "Cnt" //
- , "" //
, "Disk" //
+ , "Space" //
+ , "" //
, "Mem" //
- , "Agg" //
+ , "Disk" //
));
- stdout.print("------------------"
- + "-------+--------------------+----------+--------------+\n");
- for (final Ehcache cache : getAllCaches()) {
- final CacheConfiguration cfg = cache.getCacheConfiguration();
- final boolean useDisk = cfg.isDiskPersistent() || cfg.isOverflowToDisk();
- final Statistics stat = cache.getStatistics();
- final long total = stat.getCacheHits() + stat.getCacheMisses();
+ stdout.print("--");
+ for (int i = 0; i < nw; i++) {
+ stdout.print('-');
+ }
+ stdout.print("+---------------------+---------+---------+\n");
- if (useDisk) {
- stdout.print(String.format(//
- "D %-18s %-4s|%6s %6s %6s| %7s |%4s %4s %4s|\n" //
- , cache.getName() //
- , interval(cfg.getTimeToLiveSeconds()) //
- , count(stat.getDiskStoreObjectCount()) //
- , count(stat.getMemoryStoreObjectCount()) //
- , count(stat.getObjectCount()) //
- , duration(stat.getAverageGetTime()) //
- , percent(stat.getOnDiskHits(), total) //
- , percent(stat.getInMemoryHits(), total) //
- , percent(stat.getCacheHits(), total) //
- ));
- } else {
- stdout.print(String.format(//
- " %-18s %-4s|%6s %6s %6s| %7s |%4s %4s %4s|\n" //
- , cache.getName() //
- , interval(cfg.getTimeToLiveSeconds()) //
- , "", "" //
- , count(stat.getObjectCount()) //
- , duration(stat.getAverageGetTime()) //
- , "", "" //
- , percent(stat.getCacheHits(), total) //
- ));
- }
+ Map<String, H2CacheImpl<?, ?>> disks = Maps.newTreeMap();
+ printMemoryCaches(disks, sortedCoreCaches());
+ printMemoryCaches(disks, sortedPluginCaches());
+ for (Map.Entry<String, H2CacheImpl<?, ?>> entry : disks.entrySet()) {
+ H2CacheImpl<?, ?> cache = entry.getValue();
+ CacheStats stat = cache.stats();
+ H2CacheImpl.DiskStats disk = cache.diskStats();
+ stdout.print(String.format(
+ "D %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
+ entry.getKey(),
+ count(cache.size()),
+ count(disk.size()),
+ bytes(disk.space()),
+ duration(stat.averageLoadPenalty()),
+ percent(stat.hitCount(), stat.requestCount()),
+ percent(disk.hitCount(), disk.requestCount())));
}
stdout.print('\n');
@@ -165,6 +173,51 @@
stdout.flush();
}
+ private void printMemoryCaches(
+ Map<String, H2CacheImpl<?, ?>> disks,
+ Map<String, Cache<?,?>> caches) {
+ for (Map.Entry<String, Cache<?,?>> entry : caches.entrySet()) {
+ Cache<?,?> cache = entry.getValue();
+ if (cache instanceof H2CacheImpl) {
+ disks.put(entry.getKey(), (H2CacheImpl<?,?>)cache);
+ continue;
+ }
+ CacheStats stat = cache.stats();
+ stdout.print(String.format(
+ " %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
+ entry.getKey(),
+ count(cache.size()),
+ "",
+ "",
+ duration(stat.averageLoadPenalty()),
+ percent(stat.hitCount(), stat.requestCount()),
+ ""));
+ }
+ }
+
+ private Map<String, Cache<?, ?>> sortedCoreCaches() {
+ SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
+ for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
+ cacheMap.byPlugin("gerrit").entrySet()) {
+ m.put(cacheNameOf("gerrit", entry.getKey()), entry.getValue().get());
+ }
+ return m;
+ }
+
+ private Map<String, Cache<?, ?>> sortedPluginCaches() {
+ SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
+ for (String plugin : cacheMap.plugins()) {
+ if ("gerrit".equals(plugin)) {
+ continue;
+ }
+ for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
+ cacheMap.byPlugin(plugin).entrySet()) {
+ m.put(cacheNameOf(plugin, entry.getKey()), entry.getValue().get());
+ }
+ }
+ return m;
+ }
+
private void memSummary() {
final Runtime r = Runtime.getRuntime();
final long mMax = r.maxMemory();
@@ -300,45 +353,24 @@
return String.format("%6d", cnt);
}
- private String duration(double ms) {
- if (Math.abs(ms) <= 0.05) {
+ private String duration(double ns) {
+ if (ns < 0.5) {
return "";
}
- String suffix = "ms";
- if (ms >= 1000) {
- ms /= 1000;
+ String suffix = "ns";
+ if (ns >= 1000.0) {
+ ns /= 1000.0;
+ suffix = "us";
+ }
+ if (ns >= 1000.0) {
+ ns /= 1000.0;
+ suffix = "ms";
+ }
+ if (ns >= 1000.0) {
+ ns /= 1000.0;
suffix = "s ";
}
- return String.format("%4.1f%s", ms, suffix);
- }
-
- private String interval(double ttl) {
- if (ttl == 0) {
- return "inf";
- }
-
- String suffix = "s";
- if (ttl >= 60) {
- ttl /= 60;
- suffix = "m";
-
- if (ttl >= 60) {
- ttl /= 60;
- suffix = "h";
- }
-
- if (ttl >= 24) {
- ttl /= 24;
- suffix = "d";
-
- if (ttl >= 365) {
- ttl /= 365;
- suffix = "y";
- }
- }
- }
-
- return Integer.toString((int) ttl) + suffix;
+ return String.format("%4.1f%s", ns, suffix);
}
private String percent(final long value, final long total) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 4085dcb..a1a5b8f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -15,10 +15,10 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.util.IdGenerator;
-import com.google.gerrit.sshd.RequiresCapability;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
import com.google.gerrit.sshd.SshSession;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
old mode 100755
new mode 100644
diff --git a/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs b/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs b/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
diff --git a/gerrit-war/.settings/org.eclipse.core.resources.prefs b/gerrit-war/.settings/org.eclipse.core.resources.prefs
index d404b00..abdea9ac 100644
--- a/gerrit-war/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-war/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:37 PDT 2011
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 58d6116..519dec8 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -18,12 +18,12 @@
import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.common.ChangeHookRunner;
-import com.google.gerrit.ehcache.EhcachePoolImpl;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
@@ -200,7 +200,7 @@
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
- modules.add(new EhcachePoolImpl.Module());
+ modules.add(new DefaultCacheFactory.Module());
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginModule());
diff --git a/gerrit-war/src/main/resources/log4j.properties b/gerrit-war/src/main/resources/log4j.properties
index 5993790..45f630e 100644
--- a/gerrit-war/src/main/resources/log4j.properties
+++ b/gerrit-war/src/main/resources/log4j.properties
@@ -48,10 +48,6 @@
log4j.logger.org.openid4java.server.RealmVerifier=ERROR
log4j.logger.org.openid4java.message.AuthSuccess=ERROR
-# Silence non-critical messages from ehcache
-#
-log4j.logger.net.sf.ehcache=WARN
-
# Silence non-critical messages from c3p0 (if used).
#
log4j.logger.com.mchange.v2.c3p0=WARN
diff --git a/pom.xml b/pom.xml
index ad32cbd..d7b5988 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,10 +46,10 @@
</issueManagement>
<properties>
- <jgitVersion>1.3.0.201202151440-r.140-g8c73245</jgitVersion>
+ <jgitVersion>2.0.0.201206130900-r.23-gb3dbf19</jgitVersion>
<gwtormVersion>1.4</gwtormVersion>
<gwtjsonrpcVersion>1.3</gwtjsonrpcVersion>
- <gwtexpuiVersion>1.2.5</gwtexpuiVersion>
+ <gwtexpuiVersion>1.2.6</gwtexpuiVersion>
<gwtVersion>2.4.0</gwtVersion>
<slf4jVersion>1.6.1</slf4jVersion>
<guiceVersion>3.0</guiceVersion>
@@ -74,7 +74,7 @@
<module>gerrit-antlr</module>
<module>gerrit-common</module>
- <module>gerrit-ehcache</module>
+ <module>gerrit-cache-h2</module>
<module>gerrit-httpd</module>
<module>gerrit-launcher</module>
<module>gerrit-main</module>
@@ -88,11 +88,26 @@
<module>gerrit-war</module>
<module>gerrit-extension-api</module>
- <module>gerrit-plugin-api</module>
<module>gerrit-gwtui</module>
</modules>
+ <profiles>
+ <profile>
+ <id>all</id>
+ <modules>
+ <module>gerrit-plugin-api</module>
+ <module>gerrit-plugin-archetype</module>
+ </modules>
+ </profile>
+ <profile>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <id>no-plugins</id>
+ </profile>
+ </profiles>
+
<licenses>
<license>
<name>Apache License, 2.0</name>
@@ -461,6 +476,12 @@
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>12.0.1</version>
+ </dependency>
+
+ <dependency>
<groupId>gwtorm</groupId>
<artifactId>gwtorm</artifactId>
<version>${gwtormVersion}</version>
@@ -553,12 +574,6 @@
</dependency>
<dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache-core</artifactId>
- <version>1.7.2</version>
- </dependency>
-
- <dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.0.16</version>
@@ -833,13 +848,13 @@
<repositories>
<repository>
- <id>jgit-repository</id>
- <url>http://download.eclipse.org/jgit/maven</url>
+ <id>gerrit-maven</id>
+ <url>https://gerrit-maven.commondatastorage.googleapis.com</url>
</repository>
<repository>
- <id>gerrit-maven-repository</id>
- <url>https://gerrit-maven-repository.googlecode.com/svn/</url>
+ <id>jgit-repository</id>
+ <url>http://download.eclipse.org/jgit/maven</url>
</repository>
<repository>
diff --git a/tools/release.sh b/tools/release.sh
index 4a872a1..79e6605 100755
--- a/tools/release.sh
+++ b/tools/release.sh
@@ -25,7 +25,7 @@
fi
./tools/version.sh --release &&
-mvn clean package $include_docs
+mvn clean package $include_docs -P all
rc=$?
./tools/version.sh --reset