Merge branch 'stable'
* stable:
Update 2.1.8 release notes
Allow serving static files in subdirectories
Normalize OpenID URLs with http:// prefix
Ignore PartialResultException from LDAP.
Fix MySQL counter resets
Substantially speed up pushing changes for review
Avoid costly findMergedInto during push to refs/for/*
Add cache for tag advertisements
Conflicts:
gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
pom.xml
Change-Id: Ife316e255a8045bd28ca399d55e70eb8a075c48f
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 0984935..70cd619 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -120,7 +120,7 @@
----------------------------
A system wide access control list affecting all projects is stored in
-project "`\-- All Projects \--`". This inheritance can be configured
+project "`All-Projects`". This inheritance can be configured
through link:cmd-set-project-parent.html[gerrit set-project-parent].
Per-project access control lists are also supported.
@@ -237,13 +237,13 @@
All Projects
~~~~~~~~~~~~
-Any access right granted to a group within `\-- All Projects \--`
+Any access right granted to a group within `All-Projects`
is automatically inherited by every other project in the same
Gerrit instance. These rights can be seen, but not modified,
in any other project's `Access` administration tab.
Only members of the group `Administrators` may edit the access
-control list for `\-- All Projects \--`.
+control list for `All-Projects`.
Ownership of this project cannot be delegated to another group.
This restriction is by design. Granting ownership to another
@@ -255,7 +255,7 @@
~~~~~~~~~~~
The per-project ACL is evaluated before the global
-`\-- All Projects \--` ACL, permitting some limited override
+`All-Projects` ACL, permitting some limited override
capability to project owners. This behavior is generally only
useful on the `Read Access` category when granting `-1 No Access`
within a specific project to deny access to a group.
diff --git a/Documentation/cmd-cherry-pick.txt b/Documentation/cmd-cherry-pick.txt
index 0831d9d..568c872 100644
--- a/Documentation/cmd-cherry-pick.txt
+++ b/Documentation/cmd-cherry-pick.txt
@@ -9,10 +9,8 @@
--------
[verse]
'gerrit-cherry-pick' <remote> <changeid>...
-
-'gerrit-cherry-pick' \--continue | \--skip | \--abort
-
-'gerrit-cherry-pick' \--close <remote>
+'gerrit-cherry-pick' --continue | --skip | --abort
+'gerrit-cherry-pick' --close <remote>
DESCRIPTION
-----------
@@ -22,13 +20,13 @@
If a merge failure prevents this from being completely automatic,
you will be asked to resolve the conflict and restart the command
-with the `\--continue` option.
+with the `--continue` option.
Change ids may be specified as either the change id (e.g. 1234)
or as change id slash patch set number (e.g. 1234/8). If the patch
set number is not supplied, `/1` is assumed.
-The `\--close` command line option is now deprecated, as closing
+The `--close` command line option is now deprecated, as closing
existing changes post cherry-pick is better handled simply by
ensuring link:user-changeid.html[Change-Id lines] are present in
each commit message.
@@ -38,9 +36,11 @@
To obtain the 'gerrit-cherry-pick' script use scp, curl or wget to
copy it to your local system:
+====
$ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
$ curl http://review.example.com/tools/bin/gerrit-cherry-pick
+====
GERRIT
------
diff --git a/Documentation/cmd-create-account.txt b/Documentation/cmd-create-account.txt
index ab5663c..31bc482 100644
--- a/Documentation/cmd-create-account.txt
+++ b/Documentation/cmd-create-account.txt
@@ -8,12 +8,12 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit create-account' \
-[\--group <GROUP>] \
-[\--full-name <FULLNAME>] \
-[\--email <EMAIL>] \
-[\--ssh-key -|<KEY>] \
-<USERNAME>
+'ssh' -p <port> <host> 'gerrit create-account'
+ [--group <GROUP>]
+ [--full-name <FULLNAME>]
+ [--email <EMAIL>]
+ [--ssh-key - | <KEY>]
+ <USERNAME>
DESCRIPTION
-----------
@@ -27,7 +27,8 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'Create Account' global capability.
SCRIPTING
---------
@@ -38,23 +39,23 @@
<USERNAME>::
Required; SSH username of the user account.
-\--ssh-key::
+--ssh-key::
Content of the public SSH key to load into the account's
keyring. If `-` the key is read from stdin, rather than
from the command line.
-\--group::
- Name of the group to put the user into. Multiple \--group
+--group::
+ Name of the group to put the user into. Multiple --group
options may be specified to add the user to multiple groups.
-\--full-name::
+--full-name::
Display name of the user account.
+
-Names containing spaces should be quoted in single quotes (\').
+Names containing spaces should be quoted in single quotes (').
This most likely requires double quoting the value, for example
-`\--full-name "\'A description string\'"`.
+`--full-name "'A description string'"`.
-\--email::
+--email::
Preferred email address for the user account.
EXAMPLES
diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt
index 1e46e14..1a6c168 100644
--- a/Documentation/cmd-create-group.txt
+++ b/Documentation/cmd-create-group.txt
@@ -8,12 +8,12 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit create-group' \
-[\--owner <GROUP>] \
-[\--description <DESC>] \
-[\--member <USERNAME>] \
-[\--group <GROUP>] \
-<GROUP>
+'ssh' -p <port> <host> 'gerrit create-group'
+ [--owner <GROUP>]
+ [--description <DESC>]
+ [--member <USERNAME>]
+ [--group <GROUP>]
+ <GROUP>
DESCRIPTION
-----------
@@ -26,7 +26,8 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'Create Group' global capability.
SCRIPTING
---------
@@ -37,22 +38,22 @@
<GROUP>::
Required; name of the new group.
-\--owner, -o::
+--owner, -o::
Name of the owning group. If not specified the group will be self-owning.
-\--description, -d::
+--description, -d::
Description of group.
+
Description values containing spaces should be quoted in single quotes
-(\'). This most likely requires double quoting the value, for example
-`\--description "\'A description string\'"`.
+('). This most likely requires double quoting the value, for example
+`--description "'A description string'"`.
-\--member::
- User name to become initial member of the group. Multiple \--member
+--member::
+ User name to become initial member of the group. Multiple --member
options may be specified to add more initial members.
-\--group::
- Group name to include in the group. Multiple \--group options may
+--group::
+ Group name to include in the group. Multiple --group options may
be specified to include more initial groups.
EXAMPLES
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index 71c0d1f..9d2b98b 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -8,18 +8,19 @@
SYNOPSIS
--------
[verse]
- 'ssh' -p <port> <host> 'gerrit create-project' \
- --name <NAME> \
- [--branch <REF>] \
- [\--owner <GROUP> ...] \
- [\--parent <NAME>] \
- [\--permissions-only] \
- [\--description <DESC>] \
- [\--submit-type <TYPE>] \
- [\--use-content-merge] \
- [\--use-contributor-agreements] \
- [\--use-signed-off-by] \
- [\--empty-commit]
+'ssh' -p <port> <host> 'gerrit create-project'
+ [--owner <GROUP> ... | -o <GROUP> ...]
+ [--parent <NAME> | -p <NAME> ]
+ [--permissions-only]
+ [--description <DESC> | -d <DESC>]
+ [--submit-type <TYPE> | -t <TYPE>]
+ [--use-contributor-agreements | --ca]
+ [--use-signed-off-by | --so]
+ [--use-content-merge]
+ [--require-change-id | --id]
+ [--branch <REF> | -b <REF>]
+ [--empty-commit]
+ { <NAME> | --name <NAME> }
DESCRIPTION
-----------
@@ -36,11 +37,8 @@
ACCESS
------
-Caller must be a member of any of the groups defined by
-repository.*.createGroup in gerrit.config.
-
-If there is no such declaration, caller is required to be a member
-of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'Create Project' global capability.
SCRIPTING
---------
@@ -48,43 +46,51 @@
OPTIONS
-------
-\--name::
- Required; name of the project to create. If name ends with
- `.git` the suffix will be automatically removed.
+<NAME>::
+ Required; name of the new project to create. If name ends
+ with `.git` the suffix will be automatically removed.
-\--branch::
+--name::
+-n::
+ Deprecated alias for the <NAME> argument. This option may
+ be removed in a future release.
+
+--branch::
+-b::
Name of the initial branch in the newly created project.
Defaults to 'master'.
-\--owner::
+--owner::
+-o::
Name of the group(s) which will initially own this repository.
The specified group(s) must already be defined within Gerrit.
Several groups can be specified on the command line.
+
-Defaults to what is specified by repository.*.ownerGroup
-in gerrit.config. If no such declaration(s) exist,
-repository.*.createGroup will be used. If they don't exist,
-`Administrators` will be used.
+Defaults to what is specified by `repository.*.ownerGroup`
+in gerrit.config.
-\--parent::
+--parent::
+-p::
Name of the parent project to inherit access rights
through. If not specified, the parent is set to the default
- project `\-- All Projects \--`.
+ project `All-Projects`.
-\--permissions-only::
+--permissions-only::
Create the project only to serve as a parent for other
- projects. The new project's Git repository will not be
- initialized, and cannot be cloned.
+ projects. The new project's Git repository will be
+ initialized to have 'HEAD' point to 'refs/meta/config'.
-\--description::
+--description::
+-d::
Initial description of the project. If not specified,
no description is stored.
+
Description values containing spaces should be quoted in single quotes
-(\'). This most likely requires double quoting the value, for example
-`\--description "\'A description string\'"`.
+('). This most likely requires double quoting the value, for example
+`--description "'A description string'"`.
-\--submit-type::
+--submit-type::
+-t::
Action used by Gerrit to submit an approved change to its
destination branch. Supported options are:
+
@@ -97,24 +103,32 @@
Defaults to MERGE_IF_NECESSARY. For more details see
link:project-setup.html#submit_type[Change Submit Actions].
-\--use-content-merge::
+--use-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. Disabled by default.
-\--use-contributor-agreements::
+--use-contributor-agreements::
+--ca::
If enabled, authors must complete a contributor agreement
on the site before pushing any commits or changes to this
project. Disabled by default.
-\--use-signed-off-by::
+--use-signed-off-by::
+--so:
If enabled, each change must contain a Signed-off-by line
from either the author or the uploader in the commit message.
Disabled by default.
-\--empty-commit:
+--require-change-id::
+--id::
+ 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.
+
+--empty-commit::
Creates an initial empty commit for the Git repository of the
project that is newly created.
@@ -124,13 +138,13 @@
Create a new project called `tools/gerrit`:
====
- $ ssh -p 29418 review.example.com gerrit create-project --name tools/gerrit.git
+ $ ssh -p 29418 review.example.com gerrit create-project tools/gerrit.git
====
Create a new project with a description:
====
- $ ssh -p 29418 review.example.com gerrit create-project --name tool.git --description "'Tools used by build system'"
+ $ ssh -p 29418 review.example.com gerrit create-project tool.git --description "'Tools used by build system'"
====
Note that it is necessary to quote the description twice. The local
diff --git a/Documentation/cmd-flush-caches.txt b/Documentation/cmd-flush-caches.txt
index 573cd78..f8882c8 100644
--- a/Documentation/cmd-flush-caches.txt
+++ b/Documentation/cmd-flush-caches.txt
@@ -8,8 +8,9 @@
SYNOPSIS
--------
[verse]
- 'ssh' -p <port> <host> 'gerrit flush-caches' \
- [\--all | \--list | \--cache <NAME> ...]
+'ssh' -p <port> <host> 'gerrit flush-caches' --all
+'ssh' -p <port> <host> 'gerrit flush-caches' --list
+'ssh' -p <port> <host> 'gerrit flush-caches' --cache <NAME> ...
DESCRIPTION
-----------
@@ -24,7 +25,8 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have granted the 'Flush Caches' global capability.
SCRIPTING
---------
@@ -32,19 +34,19 @@
OPTIONS
-------
-\--all::
+--all::
Flush all known caches. This is like applying a big hammer,
it will force everything out, potentially more than was
- necessary for the change made. This option automatically
+ necessary for the change made. This option automatically
skips flushing potentially dangerous caches such as
"web_sessions". To flush one of these caches, the caller
must specifically name them on the command line, e.g. pass
- `\--cache=web_sessions`.
+ `--cache web_sessions`.
-\--list::
+--list::
Show a list of the caches.
-\--cache=<NAME>::
+--cache <NAME>::
Flush only the cache called <NAME>. May be supplied more
than once to flush multiple caches in a single command
execution.
diff --git a/Documentation/cmd-gsql.txt b/Documentation/cmd-gsql.txt
index 7fc8a9b..3c4f1b5 100644
--- a/Documentation/cmd-gsql.txt
+++ b/Documentation/cmd-gsql.txt
@@ -8,9 +8,9 @@
SYNOPSIS
--------
[verse]
- 'ssh' -p <port> <host> 'gerrit gsql' \
- [\--format \{PRETTY | JSON\}] \
- [\-c QUERY]
+'ssh' -p <port> <host> 'gerrit gsql'
+ [--format {PRETTY | JSON}]
+ [-c QUERY]
DESCRIPTION
-----------
@@ -20,7 +20,7 @@
OPTIONS
-------
-\--format::
+--format::
Set the format records are output in. In PRETTY (the
default) records are displayed in a tabular output suitable
for reading by a human on a sufficiently wide terminal.
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index b21f5c0..3b06b06 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -56,9 +56,11 @@
To obtain the 'commit-msg' script use scp, wget or curl to copy it
to your local system:
+====
$ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
$ curl http://review.example.com/tools/hooks/commit-msg
+====
SEE ALSO
--------
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 8a6cb6d3..11e0296 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -51,6 +51,27 @@
[[user_commands]]User Commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'gerrit approve'::
+ 'Deprecated alias for `gerrit review`.'
+
+link:cmd-ls-projects.html[gerrit ls-projects]::
+ List projects visible to the caller.
+
+link:cmd-modify-reviewers.html[gerrit modify-reviewers]::
+ Add or remove reviewers on a change.
+
+link:cmd-query.html[gerrit query]::
+ Query the change database.
+
+'gerrit receive-pack'::
+ 'Depreated alias for `git receive-pack`.'
+
+link:cmd-review.html[gerrit review]::
+ Verify, approve and/or submit a patch set from the command line.
+
+link:cmd-stream-events.html[gerrit stream-events]::
+ Monitor events occuring in real time.
+
git upload-pack::
Standard Git server side command for client side `git fetch`.
@@ -60,24 +81,6 @@
Also implements the magic associated with uploading commits for
review. See link:user-upload.html#push_create[Creating Changes].
-link:cmd-review.html[gerrit approve]::
- Alias for 'gerrit review'.
-
-link:cmd-ls-projects.html[gerrit ls-projects]::
- List projects visible to the caller.
-
-link:cmd-query.html[gerrit query]::
- Query the change database.
-
-link:cmd-review.html[gerrit review]::
- Verify, approve and/or submit a patch set from the command line.
-
-link:cmd-stream-events.html[gerrit stream-events]::
- Monitor events occuring in real time.
-
-gerrit receive-pack::
- Legacy alias for `git receive-pack`.
-
[[admin_commands]]Adminstrator Commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -96,6 +99,9 @@
link:cmd-gsql.html[gerrit gsql]::
Administrative interface to active database.
+link:cmd-replicate.html[gerrit replicate]::
+ Manually trigger replication, to recover a node.
+
link:cmd-set-project-parent.html[gerrit set-project-parent]::
Change the project permissions are inherited from.
@@ -108,9 +114,6 @@
link:cmd-show-queue.html[gerrit show-queue]::
Display the background work queues, including replication.
-link:cmd-replicate.html[gerrit replicate]::
- Manually trigger replication, to recover a node.
-
link:cmd-kill.html[kill]::
Kills a scheduled or running task.
diff --git a/Documentation/cmd-kill.txt b/Documentation/cmd-kill.txt
index a76be84..57a98f7 100644
--- a/Documentation/cmd-kill.txt
+++ b/Documentation/cmd-kill.txt
@@ -18,7 +18,8 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'Kill Task' global capability.
SCRIPTING
---------
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index 1e7b4cc..0332c69 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -8,7 +8,10 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit ls-projects' [\--show-branch <BRANCH>]
+'ssh' -p <port> <host> 'gerrit ls-projects'
+ [--show-branch <BRANCH> ...]
+ [--tree]
+ [--type {code | permissions | all}]
DESCRIPTION
-----------
@@ -28,15 +31,31 @@
OPTIONS
-------
-\--show-branch::
-\-b::
- Name of the branch for which the command will display the sha of each project.
+--show-branch::
+-b::
+ Branch for which the command will display the sha of each project.
+ The command may have multiple --show-branch parameters, in this case
+ sha will be shown for each of the branches.
+ If the user does not have READ access to some branch or the branch does not
+ exist then stub (40 `-` symbols) is shown.
+ If the user does not have access to any branch in the project then the
+ whole project is not shown.
-\--tree::
-\-t::
+--tree::
+-t::
Displays project inheritance in a tree-like format.
This option does not work together with the show-branch option.
+--type::
+ Display only projects of the specified type. If not
+ specified, defaults to `code`. Supported types:
++
+--
+`code`:: Any project likely to contain user files.
+`permissions`:: Projects created with the `--permissions-only` flag.
+`all`:: Any type of project.
+--
+
EXAMPLES
--------
diff --git a/Documentation/cmd-modify-reviewers.txt b/Documentation/cmd-modify-reviewers.txt
new file mode 100644
index 0000000..0e77909
--- /dev/null
+++ b/Documentation/cmd-modify-reviewers.txt
@@ -0,0 +1,79 @@
+gerrit modify-reviewers
+=======================
+
+NAME
+----
+gerrit modify-reviewers - Add or remove reviewers to a change
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit modify-reviewers'
+ [--project <PROJECT>]
+ [--add EMAIL ...]
+ [--remove EMAIL ...]
+ [--]
+ {COMMIT | CHANGE-ID}...
+
+DESCRIPTION
+-----------
+Adds or removes reviewers to the specified change, sending email
+notifications when changes are made.
+
+Changes should be specified as complete or abbreviated Change-Ids
+such as 'Iac6b2ac2'. They may also be specified by numeric change
+identifiers, such as '8242' or by complete or abbreviated commit
+SHA-1s.
+
+OPTIONS
+-------
+
+--project::
+-p::
+ Name of the project the intended change is contained within. This
+ option must be supplied before Change-ID in order to take effect.
+
+--add::
+-a::
+ Add this reviewer to the change. Multiple reviewers can be
+ added at once by using this option multiple times.
+
+--remove::
+-r::
+ Remove this reviewer to the change. Multiple reviewers can be
+ removed at once by using this option multiple times.
+
+--help::
+-h::
+ Display site-specific usage information
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+EXAMPLES
+--------
+
+Add reviewers alice and bob, but remove eve from change Iac6b2ac2.
+=====
+ $ ssh -p 29418 review.example.com gerrit modify-reviewers \
+ -a alice@example.com -a bob@example.com \
+ -r eve@example.com \
+ Iac6b2ac2
+=====
+
+Add reviewer elvis to old-style change id 1935 specifying that the change is in project "graceland"
+=====
+ $ ssh -p 29418 review.example.com gerrit modify-reviewers \
+ --project graceland \
+ -a elvis@example.com \
+ 1935
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index c8996eb..bdbe3e4 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -8,14 +8,14 @@
SYNOPSIS
--------
[verse]
- 'ssh' -p <port> <host> 'gerrit query' \
- [\--format {TEXT | JSON}] \
- [\--current-patch-set] \
- [\--patch-sets|--all-approvals] \
- [\--] \
- <query> \
- [limit:<n>] \
- [resume\_sortkey:<sortKey>]
+'ssh' -p <port> <host> 'gerrit query'
+ [--format {TEXT | JSON}]
+ [--current-patch-set]
+ [--patch-sets | --all-approvals]
+ [--]
+ <query>
+ [limit:<n>]
+ [resume_sortkey:<sortKey>]
DESCRIPTION
-----------
@@ -39,18 +39,23 @@
OPTIONS
-------
-\--current-patch-set::
+--format::
+ Formatting method for the results. TEXT is the default,
+ presenting a human readable display. JSON creates one line
+ per matching record, with embedded LFs escaped.
+
+--current-patch-set::
Include information about the current patch set in the results.
-\--patch-sets::
+--patch-sets::
Include information about all patch sets. If combined with
- the \--current-patch-set flag then the current patch set
+ the --current-patch-set flag then the current patch set
information will be output twice, once in each field.
-\--all-approvals::
+--all-approvals::
Include information about all patch sets along with the
approval information for each patch set. If combined with
- the \--current-patch-set flag then the current patch set
+ the --current-patch-set flag then the current patch set
information will be output twice, once in each field.
limit:<n>::
@@ -59,7 +64,7 @@
than one limit: operator is provided, the smallest limit
will be used to cut the result set.
-resume\_sortkey:<sortKey>::
+resume_sortkey:<sortKey>::
Resume results from this sort key. Callers should pass
the sortKey of the last change of the prior result set to
resume a prior query. This is actually a query operator,
@@ -77,20 +82,20 @@
--------
Find the 2 most recent open changes in the tools/gerrit project:
------
+====
$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
{"project":"tools/gerrit", ...}
{"project":"tools/gerrit", ..., sortKey:"000e6aee00003e26", ...}
{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
------
+====
Resume the same query and obtain the final results:
------
+====
$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2 resume_sortkey:000e6aee00003e26
{"project":"tools/gerrit", ...}
{"project":"tools/gerrit", ...}
{"type":"stats","rowCount":1,"runningTimeMilliseconds:15}
------
+====
SCHEMA
diff --git a/Documentation/cmd-receive-pack.txt b/Documentation/cmd-receive-pack.txt
index ca96550..7e5ca09 100644
--- a/Documentation/cmd-receive-pack.txt
+++ b/Documentation/cmd-receive-pack.txt
@@ -8,7 +8,7 @@
SYNOPSIS
--------
[verse]
-git receive-pack [\--reviewer <address>] [\--cc <address>] <project>
+'git receive-pack' [--reviewer <address>] [--cc <address>] <project>
DESCRIPTION
-----------
@@ -24,11 +24,11 @@
<project>::
The remote repository that will receive the pushed objects,
and create (or update) changes. Within Gerrit Code Review
- this is the name of a project. The optional leading `/`
+ this is the name of a project. The optional leading `/`
and or trailing `.git` suffix will be removed, if supplied.
-\--re <address>::
-\--reviewer <address>::
+--reviewer <address>::
+--re <address>::
Automatically add <address> as a reviewer to any change
created or updated by the pushed commit objects. These
changes will appear in the reviewer's dashboard, and will
@@ -38,10 +38,10 @@
+
This is a Gerrit Code Review specific extension.
-\--cc <address>::
+--cc <address>::
Carbon-copy <address> on the created or updated changes,
but don't request them to perform a review. Like with
- \--reviewer the changes will appear in the CC'd user's
+ --reviewer the changes will appear in the CC'd user's
dashboard, and will be emailed to them.
+
May be specified more than once to specify multiple CCs.
@@ -82,12 +82,12 @@
====
afterwards `.git/config` contains the following:
-====
- [remote "charlie"]
- url = ssh://review.example.com:29418/project
- push = HEAD:refs/for/master
- receivepack = git receive-pack --reviewer charlie@example.com --cc alice@example.com --cc bob@example.com
-====
+----
+[remote "charlie"]
+ url = ssh://review.example.com:29418/project
+ push = HEAD:refs/for/master
+ receivepack = git receive-pack --reviewer charlie@example.com --cc alice@example.com --cc bob@example.com
+----
and now sending a new change for review to charlie, CC'ing both
alice and bob is much easier:
diff --git a/Documentation/cmd-replicate.txt b/Documentation/cmd-replicate.txt
index 7f88b47..b4ffb0e 100644
--- a/Documentation/cmd-replicate.txt
+++ b/Documentation/cmd-replicate.txt
@@ -8,9 +8,9 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit replicate' \
-[\--url <PATTERN>] \
-\{\--all | <PROJECT> ...}
+'ssh' -p <port> <host> 'gerrit replicate'
+ [--url <PATTERN>]
+ {--all | <PROJECT> ...}
DESCRIPTION
-----------
@@ -32,13 +32,13 @@
pack files to the destinations.
+
If the local server is repacked, and then the resulting pack files
-are sent to remote peers using `rsync -a \--delete-after`, there
+are sent to remote peers using `rsync -a --delete-after`, there
is a chance that the rsync missed a change that was added during
the rsync data transfer, and the rsync will remove that changes's
data from the remote, even though the automatic replication pushed
it there in parallel to the rsync.
+
-Its a good idea to run replicate with `\--all` to ensure all
+Its a good idea to run replicate with `--all` to ensure all
projects are consistent after the rsync is complete.
* After deleting a ref by hand.
@@ -52,7 +52,8 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'Start Replication' global capability.
SCRIPTING
---------
@@ -60,10 +61,10 @@
OPTIONS
-------
-\--all::
+--all::
Schedule replicating for all projects.
-\--url=<PATTERN>::
+--url <PATTERN>::
Replicate only to replication destinations whose URL
contains the substring <PATTERN>. This can be useful to
replicate only to a previously down node, which has been
@@ -74,21 +75,21 @@
Replicate every project, to every configured remote:
====
- $ ssh -p 29418 review.example.com gerrit replicate --all
+ $ ssh -p 29418 review.example.com gerrit replicate --all
====
Replicate only to `srv2` now that it is back online:
====
- $ ssh -p 29418 review.example.com gerrit replicate --url=srv2 --all
+ $ ssh -p 29418 review.example.com gerrit replicate --url srv2 --all
====
Replicate only the `tools/gerrit` project, after deleting a ref
locally by hand:
====
- $ git --git-dir=/home/git/tools/gerrit.git update-ref -d refs/changes/00/100/1
- $ ssh -p 29418 review.example.com gerrit replicate tools/gerrit
+ $ git --git-dir=/home/git/tools/gerrit.git update-ref -d refs/changes/00/100/1
+ $ ssh -p 29418 review.example.com gerrit replicate tools/gerrit
====
SEE ALSO
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 5645f51..4c87c26 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -8,8 +8,13 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit approve' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--abandon] [\--restore] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
-'ssh' -p <port> <host> 'gerrit review' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--abandon] [\--restore] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
+'ssh' -p <port> <host> 'gerrit review'
+ [--project <PROJECT>]
+ [--message <MESSAGE>]
+ [--submit]
+ [--abandon | --restore]
+ [--verified <N>] [--code-review <N>]
+ {COMMIT | CHANGEID,PATCHSET}...
DESCRIPTION
-----------
@@ -19,7 +24,7 @@
Patch sets should be specified as complete or abbreviated commit
SHA-1s. If the same commit is available in multiple projects the
-\--project option may be used to limit where Gerrit searches for
+--project option may be used to limit where Gerrit searches for
the change to only the contents of the specified project.
For current backward compatibility with user tools patch sets may
@@ -31,42 +36,42 @@
OPTIONS
-------
-\--project::
+--project::
-p::
Name of the project the intended changes are contained
within. This option must be supplied before the commit
SHA-1 in order to take effect.
-\--message::
+--message::
-m::
Optional cover letter to include as part of the message
sent to reviewers when the approval states are updated.
-\--help::
+--help::
-h::
Display site-specific usage information, including the
complete listing of supported approval categories and values.
-\--code-review::
-\--verified::
- Set the approval category to the value 'N'. The exact
- option names supported and the range of values permitted
- differs per site, check the output of \--help, or contact
- your site administrator for further details.
-
-\--abandon::
+--abandon::
Abandon the specified patch set(s).
(option is mutually exclusive with --submit and --restore)
-\--restore::
+--restore::
Restore the specified abandonned patch set(s).
(option is mutually exclusive with --abandon)
-\--submit::
+--submit::
-s::
Submit the specified patch set(s) for merging.
(option is mutually exclusive with --abandon)
+--code-review::
+--verified::
+ Set the approval category to the value 'N'. The exact
+ option names supported and the range of values permitted
+ differs per site, check the output of --help, or contact
+ your site administrator for further details.
+
ACCESS
------
Any user who has configured an SSH key.
@@ -80,25 +85,25 @@
Approve the change with commit c0ff33 as "Verified +1"
=====
- $ ssh -p 29418 review.example.com gerrit review --verified=+1 c0ff33
+ $ ssh -p 29418 review.example.com gerrit review --verified +1 c0ff33
=====
Append the message "Build Successful". Notice two levels of quoting is
required, one for the local shell, and another for the argument parser
inside the Gerrit server:
=====
- $ ssh -p 29418 review.example.com gerrit review -m '"Build Successful"'
+ $ ssh -p 29418 review.example.com gerrit review -m '"Build Successful"' c0ff33
=====
Mark the unmerged commits both "Verified +1" and "Code Review +2" and
submit them for merging:
====
- $ ssh -p 29418 review.example.com gerrit review \
- --verified=+1 \
- --code-review=+2 \
- --submit \
- --project=this/project \
- $(git rev-list origin/master..HEAD)
+ $ ssh -p 29418 review.example.com gerrit review \
+ --verified +1 \
+ --code-review +2 \
+ --submit \
+ --project this/project \
+ $(git rev-list origin/master..HEAD)
====
SEE ALSO
diff --git a/Documentation/cmd-set-project-parent.txt b/Documentation/cmd-set-project-parent.txt
index 699d76f..9dd11d7 100644
--- a/Documentation/cmd-set-project-parent.txt
+++ b/Documentation/cmd-set-project-parent.txt
@@ -8,15 +8,15 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit set-project-parent' \
-[\--parent <NAME>] \
-<NAME> ...
+'ssh' -p <port> <host> 'gerrit set-project-parent'
+ [--parent <NAME>]
+ <NAME> ...
DESCRIPTION
-----------
Changes the project that permissions are inherited through.
Every project inherits permissions from another project, by
-default this is `\-- All Projects \--`. This command sets
+default this is `All-Projects`. This command sets
the project to inherit through another one.
ACCESS
@@ -29,9 +29,9 @@
OPTIONS
-------
-\--parent::
- Name of the parent to inherit through. If not specified,
- the parent is set back to the default `\-- All Projects \--`.
+--parent::
+ Name of the parent to inherit through. If not specified,
+ the parent is set back to the default `All-Projects`.
EXAMPLES
--------
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 5998859..a841fc1 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -8,15 +8,27 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit show-caches'
+'ssh' -p <port> <host> 'gerrit show-caches' [--gc] [--show-jvm]
DESCRIPTION
-----------
Display statistics about the size and hit ratio of in-memory caches.
+OPTIONS
+-------
+--gc::
+ Request Java garbage collection before displaying information
+ about the Java memory heap.
+
+--show-jvm::
+ List the name and version of the Java virtual machine, host
+ operating system, and other details about the environment
+ that Gerrit Code Review is running in.
+
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'View Caches' global capability.
SCRIPTING
---------
@@ -27,27 +39,33 @@
====
$ ssh -p 29418 review.example.com gerrit show-caches
+ Gerrit Code Review 2.2.2 now 10:03:34 PDT
+ uptime 1 min 39 sec
+
Name Max |Object Count | AvgGet |Hit Ratio |
Age | Disk Mem Cnt| |Disk Mem Agg |
-------------------------+--------------------+----------+--------------+
- accounts 90d | 295| | 99%|
- accounts_byemail 90d | 109| | 97%|
- D diff 90d | 2695 128 2707| 0.4ms | 11% 86% 98%|
- groups 90d | 94| | 80%|
- openid 5m | 30| 0.4ms | 9%|
- projects 90d | 188| | 99%|
- sshkeys 90d | 9| | 94%|
- D web_sessions 12h | 30 30| | 0% 99% 99%|
-
- JGit Buffer Cache:
- open files : 23
- loaded : 6.82 mb
- mem% : 2%
-
- JVM Heap:
- max : 880.00 mb
- inuse : 136.57 mb
- mem% : 44%
+ accounts 90d | 1| | 95%|
+ accounts_byemail 90d | | | |
+ accounts_byname 90d | 1| | |
+ adv_bases 10m | | | |
+ D diff 90d | 8 8| | |
+ D diff_intraline 90d | 1 1| | |
+ groups 90d | 19| | 0%|
+ groups_byext 90d | | | |
+ groups_byinclude 90d | 21| | 80%|
+ groups_byname 90d | | | |
+ groups_byuuid 90d | | | |
+ project_list 90d | | | |
+ projects 90d | 1| | 80%|
+ sshkeys 90d | 1| | 90%|
+ D web_sessions 12h | | | |
+
+ SSH: 1 users, oldest session started 782 ms ago
+ Tasks: 2 total = 1 running + 0 ready + 1 sleeping
+ Mem: 46.13m total = 16.17m used + 29.96m free + 0.00k buffers
+ 246.56m max
+ 0 open files, 6 cpus available, 23 threads
====
SEE ALSO
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index 177a915..16cd5b4 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -18,7 +18,8 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+Caller must be a member of the privileged 'Administrators' group,
+or have been granted the 'View Connections' global capability.
SCRIPTING
---------
@@ -26,8 +27,8 @@
OPTIONS
-------
+--numeric::
-n::
-\--numeric::
Show client hostnames as IP addresses instead of DNS hostname.
DISPLAY
@@ -35,7 +36,7 @@
Session::
Unique session identifier on this server. Session
- identifiers have a period of 2\^32-1 and start from a
+ identifiers have a period of 2^32-1 and start from a
random value.
Start::
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index 3e3638e..1e97f48 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -8,8 +8,8 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'ps'
'ssh' -p <port> <host> 'gerrit show-queue'
+'ssh' -p <port> <host> 'ps'
DESCRIPTION
-----------
@@ -24,7 +24,12 @@
ACCESS
------
-Caller must be a member of the privileged 'Administrators' group.
+End-users may see a task in the queue only if they can also see
+the project the task is associated with. Tasks operating on other
+projects, or that do not have a specific project are hidden.
+
+Members of the group 'Administrators', or any group that has been
+granted the 'View Queue' capability can see all queue entries.
SCRIPTING
---------
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index fb54f67..bd23baa 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -32,11 +32,11 @@
EXAMPLES
--------
------
+====
$ ssh -p 29418 review.example.com gerrit stream-events
{"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
{"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
------
+====
SCHEMA
------
diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt
index 98a66ad..baffd53 100644
--- a/Documentation/cmd-suexec.txt
+++ b/Documentation/cmd-suexec.txt
@@ -8,28 +8,38 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> 'Gerrit Code Review'@localhost -i <private host key> 'suexec' \--as <EMAIL> [\--from HOST:PORT] [\--] [COMMAND]
+'ssh' -p <port>
+ -i SITE_PATH/etc/ssh_host_rsa_key
+ '"Gerrit Code Review@localhost"'
+ 'suexec'
+ --as <EMAIL>
+ [--from HOST:PORT]
+ [--]
+ [COMMAND]
DESCRIPTION
-----------
-The suexec command can only be invoked by the magic user Gerrit Code Review
-and permits executing any other command as any other registered user account.
+The suexec command can only be invoked by the magic user `Gerrit
+Code Review` and permits executing any other command as any other
+registered user account.
OPTIONS
-------
-\--as::
+--as::
Email address of the user you want to impersonate.
-\--from::
- Hostname and port of the machine you want to impersonate the command
- coming from.
+
+--from::
+ Hostname and port of the machine you want to impersonate
+ the command coming from.
+
COMMAND::
Gerrit command you want to run.
ACCESS
------
-Caller must be the magic user Gerrit Code Review using the SSH daemon's host key
-or a key on this daemon's peer host key ring.
+Caller must be the magic user Gerrit Code Review using the SSH
+daemon's host key or a key on this daemon's peer host key ring.
SCRIPTING
---------
@@ -40,9 +50,13 @@
Approve the change with commit c0ff33 as "Verified +1" as user bob@example.com
=====
- $ sudo -u gerrit ssh -p 29418 -i site_path/etc/ssh_host_rsa_key \
- 'Gerrit Code Review'@localhost suexec --as bob@example.com -- \
- gerrit approve --verified=+1 c0ff33
+ $ sudo -u gerrit ssh -p 29418 \
+ -i site_path/etc/ssh_host_rsa_key \
+ "Gerrit Code Review@localhost" \
+ suexec \
+ --as bob@example.com \
+ -- \
+ gerrit approve --verified +1 c0ff33
=====
GERRIT
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8f751b4..9bbc5e0 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -165,6 +165,22 @@
+
Default is -1, permitting infinite time between authentications.
+[[auth.maxRegisterEmailTokenAge]]auth.maxRegisterEmailTokenAge::
++
+Time in seconds before an email verification token sent to a user in
+order to validate their email address expires.
++
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+* d, day, days
+* w, week, weeks (`1 week` is treated as `7 days`)
+* mon, month, months (`1 month` is treated as `30 days`)
+* y, year, years (`1 year` is treated as `365 days`)
+
++
+Default is 5 days.
+
[[auth.httpHeader]]auth.httpHeader::
+
HTTP header to trust the username from, or unset to select HTTP basic
@@ -497,6 +513,21 @@
+
Default is true, enabled.
+cache.projects.checkFrequency::
++
+How often project configuration should be checked for update from Git.
+Gerrit Code Review caches project access rules and configuration in
+memory, checking the refs/meta/config branch every checkFrequency
+minutes to see if a new revision should be loaded and used for future
+access. Values can be specified using standard time unit abbreviations
+('ms', 'sec', 'min', etc.).
++
+If set to 0, checks occur every time, which may slow down operations.
+Administrators may force the cache to flush with
+link:cmd-flush-caches.html[gerrit flush-caches].
++
+Default is 5 minutes.
+
[[commentlink]]Section commentlink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -897,6 +928,15 @@
+
If relative, the path is resolved relative to `'$site_path'`.
+[[gerrit.allProjects]]gerrit.allProjects::
++
+Name of the permissions-only project defining global server
+access controls and settings. These are inherited into every
+other project managed by the running server. The name is
+relative to `gerrit.basePath`.
++
+Defaults to `All-Projects` if not set.
+
[[gerrit.canonicalWebUrl]]gerrit.canonicalWebUrl::
+
The default URL for Gerrit to be accessed through.
@@ -1494,15 +1534,11 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Repositories in this sense are the same as projects.
-In the following example configuration the `Administrators` and the
-`Registered Users` groups are set to be the ones to be allowed to
-create projects matching `*` (any project). `Registered Users` is
-set to be the default owner of new projects.
+In the following example configuration `Registered Users` is set
+to be the default owner of new projects.
----
[repository "*"]
- createGroup = Administrators
- createGroup = Registered Users
ownerGroup = Registered Users
----
@@ -1510,24 +1546,11 @@
Currently only the repository name `*` is supported.
This is a wildcard designating all repositories.
-[[repository.name.createGroup]]repository.<name>.createGroup::
-+
-A name of a group which exists in the database. Zero, one or many
-groups are allowed. Each on its own line. Groups which don't exist
-in the database are ignored.
-+
-If no groups are declared (or only non-existing ones), the default
-value `Administrators` is used.
-
[[repository.name.ownerGroup]]repository.<name>.ownerGroup::
+
A name of a group which exists in the database. Zero, one or many
groups are allowed. Each on its own line. Groups which don't exist
in the database are ignored.
-+
-If no groups are declared (or only non-existing ones), it defaults
-to whatever is declared by `repository.<name>.createGroup` (including
-any fallback to `Administrators`.)
[[sendemail]]Section sendemail
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1661,6 +1684,24 @@
+
By default, *:29418.
+[[sshd.advertisedAddress]]sshd.advertisedAddress::
++
+Specifies the addresses clients should be told to connect to.
+This may differ from sshd.listenAddress if a firewall based port
+redirector is being used, making Gerrit appear to answer on port
+22. The following forms may be used to specify an address. In any
+form, `:'port'` may be omitted to use the default SSH port of 22.
++
+* 'hostname':'port' (for example `review.example.com:22`)
+* 'IPv4':'port' (for example `10.0.0.1:29418`)
+* ['IPv6']:'port' (for example `[ff02::1]:29418`)
+
++
+If multiple values are supplied, the daemon will advertise all
+of them.
++
+By default, sshd.listenAddress.
+
[[sshd.reuseAddress]]sshd.reuseAddress::
+
If true, permits the daemon to bind to the port even if the port
@@ -1975,6 +2016,9 @@
Sample `etc/secure.config`:
----
+[auth]
+ registerEmailPrivateKey = 2zHNrXE2bsoylzUqDxZp0H1cqUmjgWb6
+
[database]
username = webuser
password = s3kr3t
@@ -2028,64 +2072,6 @@
* link:config-headerfooter.html[Site Header/Footer]
* link:config-replication.html[Git Replication/Mirroring]
-Not User Serviceable
-~~~~~~~~~~~~~~~~~~~~
-
-These fields generally shouldn't be modified.
-
-register_email_private_key::
-+
-Private key used to sign the links emailed to users when they
-request to register a new email address on their user account.
-When the link is activated, the private key authenticates the link
-was created and sent by this Gerrit server, proving that the user
-can receive email at the address they are registering.
-+
-This column is automatically generated when the database is
-initialized. Changing it to a new value would cause all current
-links to be invalidated.
-+
-Changing it is not recommended.
-
-admin_group_id::
-+
-Unique identity of the group with full privileges. Any user who
-is a member of this group may manage any other group, any project,
-and other system settings over the web.
-+
-This is initialized by Gerrit to be the "Administrators" group.
-+
-Changing it is not recommended.
-
-anonymous_group_id::
-+
-Unique identity of the group for anonymous (not authenticated) users.
-+
-All users are a member of this group, whether or not they are
-actually signed in to Gerrit. Any access rights assigned to
-this group are inherited by all users.
-+
-This is initialized by Gerrit to be the "Anonymous Users" group.
-+
-Changing it is not recommended.
-
-registered_group_id::
-+
-Unique identity of the group for all authenticated users.
-+
-All signed-in users are a member of this group. Any access rights
-assigned to this group are inherited by all users once they have
-authenticated to Gerrit.
-+
-Since account registration is open and fairly easy to obtain,
-moving from the "Anonymous Users" group to this group is not
-very difficult. Caution should be taken when assigning any
-permissions to this group.
-+
-This is initialized by Gerrit to be the "Registered Users" group.
-+
-Changing it is not recommended.
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 6442e9c..69018d8 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -36,6 +36,7 @@
|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>>
|Google Code Prettify | <<apache2,Apache License 2.0>>
|JGit | <<jgit,New-Style BSD>>
|JSch | <<sshd,New-Style BSD>>
@@ -399,6 +400,23 @@
POSSIBILITY OF SUCH DAMAGE.
----
+[[prolog_cafe]]
+Prolog Cafe - EPL or GPL
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Originally developed by Mutsunori BANBARA and Naoyuki TAMURA at the
+Kobe University, JAPAN. Gerrit Code Review uses a fork derived from
+the 1.2.5 release.
+
+Prolog Cafe is dual licensed and available under either the
+link:http://opensource.org/licenses/eclipse-1.0.php[Eclipse Public License],
+or the
+link:http://www.gnu.org/licenses/gpl-2.0.html[GPL version 2.0 (or later)].
+
+In the context of Gerrit Code Review, Prolog Cafe is consumed
+under either the EPL or GPL version 3.0 as GPL version 2.0 is
+not compatible with Apache License 2.0.
+
[[h2]]
H2 Database - EPL or modified MPL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
index ce9de71..4db4ab0 100644
--- a/Documentation/pgm-index.txt
+++ b/Documentation/pgm-index.txt
@@ -10,23 +10,32 @@
--------------------
link:pgm-init.html[init]::
- Initialize a new Gerrit server installation
+ Initialize a new Gerrit server installation.
link:pgm-daemon.html[daemon]::
- Gerrit HTTP, SSH network server.
+ Gerrit HTTP, SSH network server.
link:pgm-gsql.html[gsql]::
Administrative interface to idle database.
+link:pgm-prolog-shell.html[prolog-shell]::
+ Simple interactive Prolog interpreter.
+
+link:pgm-rulec.html[rulec]::
+ Compile project-specific Prolog rules to JARs.
+
+version::
+ Display the release version of Gerrit Code Review.
+
+Transition Utilities
+--------------------
+
link:pgm-ExportReviewNotes.html[ExportReviewNotes]::
Export submitted review information to refs/notes/review.
link:pgm-ScanTrackingIds.html[ScanTrackingIds]::
Rescan all changes after configuring trackingids.
-version::
- Display the release version of Gerrit Code Review.
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index 9644e92..d3094fd 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -11,7 +11,6 @@
'java' -jar gerrit.war 'init'
-d <SITE_PATH>
[\--batch]
- [\--import-projects]
[\--no-auto-start]
DESCRIPTION
@@ -21,7 +20,7 @@
into a newly created `$site_path`.
If run an an existing `$site_path`, init will upgrade some resources
-as necessary. This can be useful to import newly created projects.
+as necessary.
OPTIONS
-------
@@ -30,12 +29,6 @@
configuration defaults are chosen based on the whims of
the Gerrit developers.
-\--import-projects::
- Recursively search
- link:config-gerrit.html#gerrit.basePath[gerrit.basePath]
- for any Git repositories not yet registered as a project,
- and initializes a new project for them.
-
\--no-auto-start::
Don't automatically start the daemon after initializing a
newly created site path. This permits the administartor
diff --git a/Documentation/pgm-prolog-shell.txt b/Documentation/pgm-prolog-shell.txt
new file mode 100644
index 0000000..f3fa2d8
--- /dev/null
+++ b/Documentation/pgm-prolog-shell.txt
@@ -0,0 +1,57 @@
+prolog-shell
+============
+
+NAME
+----
+prolog-shell - Simple interactive Prolog interpreter
+
+SYNOPSIS
+--------
+[verse]
+'java' -jar gerrit.war 'prolog-shell' [-s FILE.pl ...]
+
+DESCRIPTION
+-----------
+Provides a simple interactive Prolog interpreter for development
+and testing.
+
+OPTIONS
+-------
+-s::
+ Dynamically load the Prolog source code at startup,
+ as though the user had entered `['FILE.pl'].` into
+ the interepter once it was running. This option may
+ be supplied more than once to load multiple files.
+
+EXAMPLES
+--------
+Define a simple predicate and test it:
+
+====
+ $ cat >simple.pl
+ food(apple).
+ food(orange).
+ ^D
+
+ $ java -jar gerrit.war prolog-shell -s simple.pl
+ Gerrit Code Review 2.2.1-84-ge9c3992 - Interactive Prolog Shell
+ based on Prolog Cafe 1.2.5 (mantis)
+ Copyright(C) 1997-2009 M.Banbara and N.Tamura
+ (type Ctrl-D or "halt." to exit, "['path/to/file.pl']." to load a file)
+
+ {consulting /usr/local/google/users/sop/gerrit2/gerrit/simple.pl ...}
+ {/usr/local/google/users/sop/gerrit2/gerrit/simple.pl consulted 99 msec}
+
+ | ?- food(Type).
+
+ Type = apple ? ;
+
+ Type = orange ? ;
+
+ no
+ | ?-
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/pgm-rulec.txt b/Documentation/pgm-rulec.txt
new file mode 100644
index 0000000..40aad2d
--- /dev/null
+++ b/Documentation/pgm-rulec.txt
@@ -0,0 +1,54 @@
+rulec
+=====
+
+NAME
+----
+rulec - Compile project-specific Prolog rules to JARs
+
+SYNOPSIS
+--------
+[verse]
+'java' -jar gerrit.war 'rulec' -d <SITE_PATH> [--all | <PROJECT>...]
+
+DESCRIPTION
+-----------
+Looks for a Prolog rule file named `rules.pl` on the repository's
+`refs/meta/config` branch. If rules.pl exists, creates a JAR file
+named `rules-'SHA1'.jar` in `'$site_path'/cache/rules`.
+
+OPTIONS
+-------
+-d::
+--site-path::
+ Location of the gerrit.config file, and all other per-site
+ configuration data, supporting libaries and log files.
+
+--all::
+ Compile rules for all projects.
+
+--quiet::
+ Suppress non-error output messages.
+
+<PROJECT>:
+ Compile rules for the specified project.
+
+CONTEXT
+-------
+This command can only be run on a server which has direct
+connectivity to the metadata database, and local access to the
+managed Git repositories.
+
+Caching needs to be enabled. See
+link:config-gerrit.html#cache.directory[cache.directory].
+
+EXAMPLES
+--------
+To compile a rule JAR file for test/project:
+
+====
+ $ java -jar gerrit.war rulec -d site_path test/project
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/project-setup.txt b/Documentation/project-setup.txt
index e775e9a..9e4f556 100644
--- a/Documentation/project-setup.txt
+++ b/Documentation/project-setup.txt
@@ -1,10 +1,6 @@
Gerrit Code Review - Project Configuration
==========================================
-All Git repositories under gerrit.basePath must be registered in
-the Gerrit database in order to be accessed through SSH, or through
-the web interface.
-
Create Through SSH
------------------
@@ -22,7 +18,7 @@
Manual Creation
---------------
-Projects may also be manually registered with the database.
+Projects may also be manually created.
Create Git Repository
~~~~~~~~~~~~~~~~~~~~~
@@ -47,23 +43,10 @@
Register Project
~~~~~~~~~~~~~~~~
-One insert is needed to register a project with Gerrit.
-
-[NOTE]
-Note that the `.git` suffix is not typically included in the
-project name, as it looks cleaner in the web when not shown.
-Gerrit automatically assumes that `project.git` is the Git repository
-for a project named `project`.
+Either restart the server, or flush the `project_list` cache:
====
- INSERT INTO projects
- (use_contributor_agreements
- ,submit_type
- ,name)
- VALUES
- ('N'
- ,'M'
- ,'new/project');
+ ssh -p 29418 localhost gerrit flush-caches --cache project_list
====
[[submit_type]]
diff --git a/ReleaseNotes/ReleaseNotes-2.1.7.2.txt b/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
index 2d7622e..9172b4a 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
@@ -1,9 +1,9 @@
-Release notes for Gerrit 2.1.7.1
+Release notes for Gerrit 2.1.7.2
================================
-Gerrit 2.1.7.1 is now available:
+Gerrit 2.1.7.2 is now available:
-link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.7.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.7.1.war]
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.7.2.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.7.2.war]
Bug Fixes
---------
diff --git a/ReleaseNotes/ReleaseNotes-2.2.0.txt b/ReleaseNotes/ReleaseNotes-2.2.0.txt
new file mode 100644
index 0000000..58605fe
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.2.0.txt
@@ -0,0 +1,65 @@
+Release notes for Gerrit 2.2.0
+==============================
+
+Gerrit 2.2.0 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.0.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.0.war]
+
+Schema Change
+-------------
+*WARNING:* Upgrading to 2.2.0 requires the server be first upgraded
+to 2.1.7, and then to 2.2.0.
+
+*WARNING:* This release contains schema changes. To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+*WARNING:* The "projects" and "ref_rights" tables are no longer
+stored in the SQL database. The tables have been moved to Git
+storage, inside of the `refs/meta/config` branch of each managed
+Git repository. The init based upgrade tool will automatically
+export the current table contents and create the Git data.
+
+New Features
+------------
+
+Project Administration
+~~~~~~~~~~~~~~~~~~~~~~
+* issue 436 List projects by scanning the managed Git directory
++
+Instead of generating the list of projects from SQL database, the
+project list is obtained by recursively scanning the Git directory.
+Adding new projects is now simply a matter of creating the Git
+repository under the directory and flushing the "projects" cache
+to force the server to rescan the directory. Administrators may
+also continue to use `gerrit create-project`.
+
+* Move "projects" table into Git
++
+The projects table columns are now stored in the `project.config`
+file of the `refs/meta/config` branch of each managed Git repository.
+
+* Move "ref_rights" table into Git
++
+The "ref_rights" table is now stored in the "access" sections of
+the `project.config` file on the `refs/meta/config` branch of each
+managed Git repository. This brings version control auditing to the
+access data of each project.
+
+* New project Access screen to edit access controls
++
+The Access panel of the project administration has been rewritten
+with a new UI that reflects the new Git based storage format.
+
+Bug Fixes
+---------
+
+Project Administration
+~~~~~~~~~~~~~~~~~~~~~~
+* Avoid unnecessary updates to $GIT_DIR/description
++
+Gerrit always tried to rewrite the gitweb "description" file when the
+project was modified. This lead to unnecessary changes in the local
+filesystem, leading to more data to rsync to backups than necessary.
+Fixed to only update the file if the content changes.
diff --git a/ReleaseNotes/ReleaseNotes-2.2.1.txt b/ReleaseNotes/ReleaseNotes-2.2.1.txt
new file mode 100644
index 0000000..19af06d
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.2.1.txt
@@ -0,0 +1,79 @@
+Release notes for Gerrit 2.2.1
+==============================
+
+Gerrit 2.2.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.1.war]
+
+Schema Change
+-------------
+*WARNING:* This release contains schema changes. To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+*WARNING:* Upgrading to 2.2.x requires the server be first upgraded
+to 2.1.7, and then to 2.2.x.
+
+New Features
+------------
+* Add 'Expand All Comments' checkbox in PatchScreen
++
+Allows users to save a user preference that automatically expands
+any inline comment boxes when a page displays.
+
+* Multiple branches in ls-project
++
+The -b option may be supplied multiple times to ls-project, each
+usage adds a new column of output per project line listing the
+current value of that branch.
+
+Bug Fixes
+---------
+* issue 994 Rename "-- All Projects --" to "All-Projects"
++
+The name "-- All Projects --.git" is difficult to work with on
+the UNIX command line, due to tools assuming the name is actually
+part of a long option. The project has been renamed to remove these
+leading hypens, and remove spaces, making it more friendly to work
+with on the command line.
+
+* issue 997 Resolve Project Owners when checking access rights
++
+Members of the 'Project Owners' magical group did not always have
+their project owner privileges when Gerrit Code Review was looking
+for "access to any ref" at the project level. This was caused by
+not expanding the 'Project Owner's group to the actual ownership
+list. Fixed.
+
+* issue 999 Do not reset Patch History selection on navigation
++
+Navigating to the next/previous file lost the setting of the
+"Old Version" made under the "Patch History" expandable control
+on a specific file view. This was accidentally broken when the
+"Old Version History" control was added to the change page. Fixed.
+
+* issue 1001 Fix search by codereview status
++
+Searching for labels (or any approval scores) was broken due to an
+incorrect usage of the Java "equals()" method. Fixed.
+
+* issue 1000 Fix administration of projects with no access controls
++
+Projects that had no access sections could not have additional
+sections added to them, due to a bug in the web UI. Fixed.
+
+* Fix API breakage on ChangeDetailService
++
+Version 2.1.7 broke the Gerrit Code Review plugin for Mylyn Reviews
+due to an accidential signature change of one of the remote JSON
+APIs. The ChangeDetailService.patchSetDetail() method is back to the
+old signature and a new patchSetDetail2() method has been added to
+handle the newer calling convention used in some contexts of the
+web UI.
+
+* Add error messages for abandon and restore when in bad state
++
+Instead of crashing with internal NullPointerExceptions, report
+a better error message to clients when a change is being moved
+between states.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 48bf6ef..2039222 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -1,6 +1,12 @@
Gerrit Code Review - Release Notes
==================================
+[[2_2]]
+Version 2.2.x
+-------------
+* link:ReleaseNotes-2.2.1.html[2.2.1],
+* link:ReleaseNotes-2.2.0.html[2.2.0]
+
[[2_1]]
Version 2.1.x
-------------
diff --git a/gerrit-antlr/pom.xml b/gerrit-antlr/pom.xml
index 139cb30..4b9e3ad 100644
--- a/gerrit-antlr/pom.xml
+++ b/gerrit-antlr/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-antlr</artifactId>
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index e878b80..814afad 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-common</artifactId>
@@ -90,6 +90,17 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</project>
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 efe311f..db4f348 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
@@ -54,6 +54,10 @@
return "change," + ps.getParentKey().toString() + ",patchset=" + ps.get();
}
+ public static String toProjectAcceess(final Project.NameKey p) {
+ return "admin,project," + p.get() + ",access";
+ }
+
public static String toAccountDashboard(final AccountInfo acct) {
return toAccountDashboard(acct.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
new file mode 100644
index 0000000..12e504c
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -0,0 +1,143 @@
+// 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.common.data;
+
+import com.google.gerrit.reviewdb.Project;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/** Portion of a {@link Project} describing access rules. */
+public class AccessSection implements Comparable<AccessSection> {
+ /** Special name given to the global capabilities; not a valid reference. */
+ public static final String GLOBAL_CAPABILITIES = "GLOBAL_CAPABILITIES";
+
+ /** Pattern that matches all references in a project. */
+ public static final String ALL = "refs/*";
+
+ /** Pattern that matches all branches in a project. */
+ public static final String HEADS = "refs/heads/*";
+
+ /** Prefix that triggers a regular expression pattern. */
+ public static final String REGEX_PREFIX = "^";
+
+ /** @return true if the name is likely to be a valid access section name. */
+ public static boolean isAccessSection(String name) {
+ return name.startsWith("refs/") || name.startsWith("^refs/");
+ }
+
+ protected String name;
+ protected List<Permission> permissions;
+
+ protected AccessSection() {
+ }
+
+ public AccessSection(String refPattern) {
+ setName(refPattern);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List<Permission> getPermissions() {
+ if (permissions == null) {
+ permissions = new ArrayList<Permission>();
+ }
+ return permissions;
+ }
+
+ public void setPermissions(List<Permission> list) {
+ Set<String> names = new HashSet<String>();
+ for (Permission p : list) {
+ if (!names.add(p.getName().toLowerCase())) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ permissions = list;
+ }
+
+ public Permission getPermission(String name) {
+ return getPermission(name, false);
+ }
+
+ public Permission getPermission(String name, boolean create) {
+ for (Permission p : getPermissions()) {
+ if (p.getName().equalsIgnoreCase(name)) {
+ return p;
+ }
+ }
+
+ if (create) {
+ Permission p = new Permission(name);
+ permissions.add(p);
+ return p;
+ } else {
+ return null;
+ }
+ }
+
+ public void remove(Permission permission) {
+ if (permission != null) {
+ removePermission(permission.getName());
+ }
+ }
+
+ public void removePermission(String name) {
+ if (permissions != null) {
+ for (Iterator<Permission> itr = permissions.iterator(); itr.hasNext();) {
+ if (name.equalsIgnoreCase(itr.next().getName())) {
+ itr.remove();
+ }
+ }
+ }
+ }
+
+ public void mergeFrom(AccessSection section) {
+ for (Permission src : section.getPermissions()) {
+ Permission dst = getPermission(src.getName());
+ if (dst != null) {
+ dst.mergeFrom(src);
+ } else {
+ permissions.add(dst);
+ }
+ }
+ }
+
+ @Override
+ public int compareTo(AccessSection o) {
+ return comparePattern().compareTo(o.comparePattern());
+ }
+
+ private String comparePattern() {
+ if (getName().startsWith(REGEX_PREFIX)) {
+ return getName().substring(REGEX_PREFIX.length());
+ }
+ return getName();
+ }
+
+ @Override
+ public String toString() {
+ return "AccessSection[" + getName() + "]";
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
index 9834693..318c60b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
@@ -21,9 +21,9 @@
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Comparator;
-import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
public class ApprovalDetail {
public static final Comparator<ApprovalDetail> SORT =
@@ -43,6 +43,8 @@
protected List<PatchSetApproval> approvals;
protected boolean canRemove;
+ private transient Set<String> approved;
+ private transient Set<String> rejected;
private transient int hasNonZero;
private transient Timestamp sortOrder = EG_D;
@@ -66,13 +68,17 @@
canRemove = removeable;
}
- public Map<ApprovalCategory.Id, PatchSetApproval> getApprovalMap() {
- final HashMap<ApprovalCategory.Id, PatchSetApproval> r;
- r = new HashMap<ApprovalCategory.Id, PatchSetApproval>();
- for (final PatchSetApproval ca : approvals) {
- r.put(ca.getCategoryId(), ca);
+ public List<PatchSetApproval> getPatchSetApprovals() {
+ return approvals;
+ }
+
+ public PatchSetApproval getPatchSetApproval(ApprovalCategory.Id category) {
+ for (PatchSetApproval psa : approvals) {
+ if (psa.getCategoryId().equals(category)) {
+ return psa;
+ }
}
- return r;
+ return null;
}
public void sortFirst() {
@@ -91,4 +97,26 @@
hasNonZero = 1;
}
}
+
+ public void approved(String label) {
+ if (approved == null) {
+ approved = new HashSet<String>();
+ }
+ approved.add(label);
+ }
+
+ public void rejected(String label) {
+ if (rejected == null) {
+ rejected = new HashSet<String>();
+ }
+ rejected.add(label);
+ }
+
+ public boolean isApproved(String label) {
+ return approved != null && approved.contains(label);
+ }
+
+ public boolean isRejected(String label) {
+ return rejected != null && rejected.contains(label);
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalType.java
index ea9aedb..7d03457 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalType.java
@@ -31,6 +31,7 @@
protected short maxNegative;
protected short maxPositive;
+ private transient List<Integer> intList;
private transient Map<Short, ApprovalCategoryValue> byValue;
protected ApprovalType() {
@@ -56,6 +57,9 @@
maxPositive = values.get(values.size() - 1).getValue();
}
}
+
+ // Force the label name to pre-compute so we don't have data race conditions.
+ getCategory().getLabelName();
}
public ApprovalCategory getCategory() {
@@ -107,4 +111,16 @@
}
}
}
+
+ public List<Integer> getValuesAsList() {
+ if (intList == null) {
+ intList = new ArrayList<Integer>(values.size());
+ for (ApprovalCategoryValue acv : values) {
+ intList.add(Integer.valueOf(acv.getValue()));
+ }
+ Collections.sort(intList);
+ Collections.reverse(intList);
+ }
+ return intList;
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalTypes.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalTypes.java
index 1b6d4a3..0518010 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalTypes.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalTypes.java
@@ -19,20 +19,17 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
public class ApprovalTypes {
protected List<ApprovalType> approvalTypes;
- protected List<ApprovalType> actionTypes;
- private transient Map<ApprovalCategory.Id, ApprovalType> byCategoryId;
+ private transient Map<ApprovalCategory.Id, ApprovalType> byId;
+ private transient Map<String, ApprovalType> byLabel;
protected ApprovalTypes() {
}
- public ApprovalTypes(final List<ApprovalType> approvals,
- final List<ApprovalType> actions) {
+ public ApprovalTypes(final List<ApprovalType> approvals) {
approvalTypes = approvals;
- actionTypes = actions;
byCategory();
}
@@ -40,33 +37,35 @@
return approvalTypes;
}
- public List<ApprovalType> getActionTypes() {
- return actionTypes;
- }
-
- public ApprovalType getApprovalType(final ApprovalCategory.Id id) {
+ public ApprovalType byId(final ApprovalCategory.Id id) {
return byCategory().get(id);
}
- public Set<ApprovalCategory.Id> getApprovalCategories() {
- return byCategory().keySet();
- }
-
private Map<ApprovalCategory.Id, ApprovalType> byCategory() {
- if (byCategoryId == null) {
- byCategoryId = new HashMap<ApprovalCategory.Id, ApprovalType>();
- if (actionTypes != null) {
- for (final ApprovalType t : actionTypes) {
- byCategoryId.put(t.getCategory().getId(), t);
- }
- }
-
+ if (byId == null) {
+ byId = new HashMap<ApprovalCategory.Id, ApprovalType>();
if (approvalTypes != null) {
for (final ApprovalType t : approvalTypes) {
- byCategoryId.put(t.getCategory().getId(), t);
+ byId.put(t.getCategory().getId(), t);
}
}
}
- return byCategoryId;
+ return byId;
+ }
+
+ public ApprovalType byLabel(String labelName) {
+ return byLabel().get(labelName.toLowerCase());
+ }
+
+ private Map<String, ApprovalType> byLabel() {
+ if (byLabel == null) {
+ byLabel = new HashMap<String, ApprovalType>();
+ if (approvalTypes != null) {
+ for (ApprovalType t : approvalTypes) {
+ byLabel.put(t.getCategory().getLabelName().toLowerCase(), t);
+ }
+ }
+ }
+ return byLabel;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Capable.java
similarity index 64%
rename from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.java
rename to gerrit-common/src/main/java/com/google/gerrit/common/data/Capable.java
index 83bca7b..0be4b76 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_42.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Capable.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// 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.
@@ -12,14 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.common.data;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+public class Capable {
+ public static final Capable OK = new Capable("OK");
-public class Schema_42 extends SchemaVersion {
- @Inject
- Schema_42(Provider<Schema_41> prior) {
- super(prior);
+ private final String message;
+
+ public Capable(String msg) {
+ message = msg;
+ }
+
+ public String getMessage() {
+ return message;
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
index 572fd7a..c4c56d8 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
@@ -14,7 +14,6 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
@@ -23,7 +22,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
/** Detail necessary to display a change. */
public class ChangeDetail {
@@ -38,11 +36,11 @@
protected List<ChangeInfo> neededBy;
protected List<PatchSet> patchSets;
protected List<ApprovalDetail> approvals;
- protected Set<ApprovalCategory.Id> missingApprovals;
+ protected List<SubmitRecord> submitRecords;
+ protected boolean canSubmit;
protected List<ChangeMessage> messages;
protected PatchSet.Id currentPatchSetId;
protected PatchSetDetail currentDetail;
- protected Set<ApprovalCategory.Id> currentActions;
public ChangeDetail() {
}
@@ -87,6 +85,14 @@
canRevert = a;
}
+ public boolean canSubmit() {
+ return canSubmit;
+ }
+
+ public void setCanSubmit(boolean a) {
+ canSubmit = a;
+ }
+
public Change getChange() {
return change;
}
@@ -145,20 +151,12 @@
Collections.sort(approvals, ApprovalDetail.SORT);
}
- public Set<ApprovalCategory.Id> getMissingApprovals() {
- return missingApprovals;
+ public void setSubmitRecords(List<SubmitRecord> all) {
+ submitRecords = all;
}
- public void setMissingApprovals(Set<ApprovalCategory.Id> a) {
- missingApprovals = a;
- }
-
- public Set<ApprovalCategory.Id> getCurrentActions() {
- return currentActions;
- }
-
- public void setCurrentActions(Set<ApprovalCategory.Id> a) {
- currentActions = a;
+ public List<SubmitRecord> getSubmitRecords() {
+ return submitRecords;
}
public boolean isCurrentPatchSet(final PatchSetDetail detail) {
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
new file mode 100644
index 0000000..2e46791
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -0,0 +1,108 @@
+// 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 java.util.ArrayList;
+import java.util.List;
+
+/** Server wide capabilities. Represented as {@link Permission} objects. */
+public class GlobalCapability {
+ /**
+ * Denotes the server's administrators.
+ * <p>
+ * This is similar to UNIX root, or Windows SYSTEM account. Any user that
+ * has this capability can perform almost any other action, or can grant
+ * themselves the power to perform any other action on the site. Most of
+ * the other capabilities and permissions fall-back to the predicate
+ * "OR user has capablity ADMINISTRATE_SERVER".
+ */
+ public static final String ADMINISTRATE_SERVER = "administrateServer";
+
+ /** Can create any account on the server. */
+ public static final String CREATE_ACCOUNT = "createAccount";
+
+ /** Can create any group on the server. */
+ public static final String CREATE_GROUP = "createGroup";
+
+ /** Can create any project on the server. */
+ public static final String CREATE_PROJECT = "createProject";
+
+ /** Can flush any cache except the active web_sessions cache. */
+ public static final String FLUSH_CACHES = "flushCaches";
+
+ /** Can terminate any task using the kill command. */
+ public static final String KILL_TASK = "killTask";
+
+ /** Queue a user can access to submit their tasks to. */
+ public static final String PRIORITY = "priority";
+
+ /** Maximum result limit per executed query. */
+ public static final String QUERY_LIMIT = "queryLimit";
+
+ /** Forcefully restart replication to any configured destination. */
+ public static final String START_REPLICATION = "startReplication";
+
+ /** Can view the server's current cache states. */
+ public static final String VIEW_CACHES = "viewCaches";
+
+ /** Can view open connections to the server's SSH port. */
+ public static final String VIEW_CONNECTIONS = "viewConnections";
+
+ /** Can view all pending tasks in the queue (not just the filtered set). */
+ public static final String VIEW_QUEUE = "viewQueue";
+
+ private static final List<String> NAMES_LC;
+
+ static {
+ NAMES_LC = new ArrayList<String>();
+ NAMES_LC.add(ADMINISTRATE_SERVER.toLowerCase());
+ NAMES_LC.add(CREATE_ACCOUNT.toLowerCase());
+ NAMES_LC.add(CREATE_GROUP.toLowerCase());
+ NAMES_LC.add(CREATE_PROJECT.toLowerCase());
+ NAMES_LC.add(FLUSH_CACHES.toLowerCase());
+ NAMES_LC.add(KILL_TASK.toLowerCase());
+ NAMES_LC.add(PRIORITY.toLowerCase());
+ NAMES_LC.add(QUERY_LIMIT.toLowerCase());
+ NAMES_LC.add(START_REPLICATION.toLowerCase());
+ NAMES_LC.add(VIEW_CACHES.toLowerCase());
+ NAMES_LC.add(VIEW_CONNECTIONS.toLowerCase());
+ NAMES_LC.add(VIEW_QUEUE.toLowerCase());
+ }
+
+ /** @return true if the name is recognized as a capability name. */
+ public static boolean isCapability(String varName) {
+ return NAMES_LC.contains(varName.toLowerCase());
+ }
+
+ /** @return true if the capability should have a range attached. */
+ public static boolean hasRange(String varName) {
+ return QUERY_LIMIT.equalsIgnoreCase(varName);
+ }
+
+ /** @return the valid range for the capability if it has one, otherwise null. */
+ public static PermissionRange.WithDefaults getRange(String varName) {
+ if (QUERY_LIMIT.equalsIgnoreCase(varName)) {
+ return new PermissionRange.WithDefaults(
+ varName,
+ 0, Integer.MAX_VALUE,
+ 0, 500);
+ }
+ return null;
+ }
+
+ private GlobalCapability() {
+ // Utility class, do not create instances.
+ }
+}
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 ce508cc..087f047 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
@@ -30,13 +30,14 @@
@RpcImpl(version = Version.V2_0)
public interface GroupAdminService extends RemoteJsonService {
@SignInRequired
- void visibleGroups(AsyncCallback<List<AccountGroup>> callback);
+ void visibleGroups(AsyncCallback<GroupList> callback);
@SignInRequired
void createGroup(String newName, AsyncCallback<AccountGroup.Id> callback);
@SignInRequired
- void groupDetail(AccountGroup.Id groupId, AsyncCallback<GroupDetail> callback);
+ void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID uuid,
+ AsyncCallback<GroupDetail> callback);
@SignInRequired
void changeGroupDescription(AccountGroup.Id groupId, String description,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
new file mode 100644
index 0000000..1f061209
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
@@ -0,0 +1,40 @@
+// 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.AccountGroup;
+
+import java.util.List;
+
+public class GroupList {
+ protected List<AccountGroup> groups;
+ protected boolean canCreateGroup;
+
+ public List<AccountGroup> getGroups() {
+ return groups;
+ }
+
+ public void setGroups(List<AccountGroup> groups) {
+ this.groups = groups;
+ }
+
+ public boolean isCanCreateGroup() {
+ return canCreateGroup;
+ }
+
+ public void setCanCreateGroup(boolean set) {
+ canCreateGroup = set;
+ }
+}
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
new file mode 100644
index 0000000..cd6e172
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupReference.java
@@ -0,0 +1,76 @@
+// 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.common.data;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+
+/** Describes a group within a projects {@link AccessSection}s. */
+public class GroupReference implements Comparable<GroupReference> {
+ /** @return a new reference to the given group description. */
+ public static GroupReference forGroup(AccountGroup group) {
+ return new GroupReference(group.getGroupUUID(), group.getName());
+ }
+
+ protected String uuid;
+ protected String name;
+
+ protected GroupReference() {
+ }
+
+ public GroupReference(AccountGroup.UUID uuid, String name) {
+ setUUID(uuid);
+ setName(name);
+ }
+
+ public AccountGroup.UUID getUUID() {
+ return uuid != null ? new AccountGroup.UUID(uuid) : null;
+ }
+
+ public void setUUID(AccountGroup.UUID newUUID) {
+ uuid = newUUID != null ? newUUID.get() : null;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String newName) {
+ this.name = newName;
+ }
+
+ @Override
+ public int compareTo(GroupReference o) {
+ return uuid(this).compareTo(uuid(o));
+ }
+
+ private static String uuid(GroupReference a) {
+ return a.getUUID() != null ? a.getUUID().get() : "?";
+ }
+
+ @Override
+ public int hashCode() {
+ return uuid(this).hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof GroupReference && compareTo((GroupReference) o) == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Group[" + getName() + " / " + getUUID() + "]";
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/InheritedRefRight.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/InheritedRefRight.java
deleted file mode 100644
index 4dc998b..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/InheritedRefRight.java
+++ /dev/null
@@ -1,74 +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.common.data;
-
-import com.google.gerrit.reviewdb.RefRight;
-
-/**
- * Additional data about a {@link RefRight} not normally loaded: defines if a
- * right is inherited from a parent structure (e.g. a parent project).
- */
-public class InheritedRefRight {
- private RefRight right;
- private boolean inherited;
- private boolean owner;
-
- /**
- * Creates a instance of a {@link RefRight} with data about inheritance
- */
- protected InheritedRefRight() {
- }
-
- /**
- * Creates a instance of a {@link RefRight} with data about inheritance
- *
- * @param right the right
- * @param inherited true if the right is inherited, false otherwise
- * @param owner true if right is owned by current user, false otherwise
- */
- public InheritedRefRight(RefRight right, boolean inherited, boolean owner) {
- this.right = right;
- this.inherited = inherited;
- this.owner = owner;
- }
-
- public RefRight getRight() {
- return right;
- }
-
- public boolean isInherited() {
- return inherited;
- }
-
- public boolean isOwner() {
- return owner;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof InheritedRefRight) {
- InheritedRefRight a = this;
- InheritedRefRight b = (InheritedRefRight) o;
- return a.getRight().equals(b.getRight())
- && a.isInherited() == b.isInherited();
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return getRight().hashCode();
- }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index 24cdee4..2214af9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -155,6 +155,10 @@
return intralineFailure;
}
+ public boolean isExpandAllComments() {
+ return diffPrefs.isExpandAllComments();
+ }
+
public SparseFileContent getA() {
return a;
}
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 7a0ada3..273f18d 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
@@ -15,39 +15,35 @@
package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
public class PatchSetPublishDetail {
protected AccountInfoCache accounts;
protected PatchSetInfo patchSetInfo;
protected Change change;
protected List<PatchLineComment> drafts;
- protected Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed;
- protected Map<ApprovalCategory.Id, PatchSetApproval> given;
- protected boolean isSubmitAllowed;
+ protected List<PermissionRange> labels;
+ protected List<PatchSetApproval> given;
+ protected boolean canSubmit;
- public Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> getAllowed() {
- return allowed;
+ public List<PermissionRange> getLabels() {
+ return labels;
}
- public void setAllowed(
- Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed) {
- this.allowed = allowed;
+ public void setLabels(List<PermissionRange> labels) {
+ this.labels = labels;
}
- public Map<ApprovalCategory.Id, PatchSetApproval> getGiven() {
+ public List<PatchSetApproval> getGiven() {
return given;
}
- public void setGiven(Map<ApprovalCategory.Id, PatchSetApproval> given) {
+ public void setGiven(List<PatchSetApproval> given) {
this.given = given;
}
@@ -67,8 +63,8 @@
this.drafts = drafts;
}
- public void setSubmitAllowed(boolean allowed) {
- isSubmitAllowed = allowed;
+ public void setCanSubmit(boolean allowed) {
+ canSubmit = allowed;
}
public AccountInfoCache getAccounts() {
@@ -87,20 +83,25 @@
return drafts;
}
- public boolean isAllowed(final ApprovalCategory.Id id) {
- final Set<ApprovalCategoryValue.Id> s = getAllowed(id);
- return s != null && !s.isEmpty();
+ public PermissionRange getRange(final String permissionName) {
+ for (PermissionRange s : labels) {
+ if (s.getName().equals(permissionName)) {
+ return s;
+ }
+ }
+ return null;
}
- public Set<ApprovalCategoryValue.Id> getAllowed(final ApprovalCategory.Id id) {
- return allowed.get(id);
+ public PatchSetApproval getChangeApproval(ApprovalCategory.Id id) {
+ for (PatchSetApproval a : given) {
+ if (a.getCategoryId().equals(id)) {
+ return a;
+ }
+ }
+ return null;
}
- public PatchSetApproval getChangeApproval(final ApprovalCategory.Id id) {
- return given.get(id);
- }
-
- public boolean isSubmitAllowed() {
- return isSubmitAllowed;
+ public boolean canSubmit() {
+ return canSubmit;
}
}
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
new file mode 100644
index 0000000..f818d7b
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -0,0 +1,209 @@
+// 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.common.data;
+
+import java.util.ArrayList;
+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 CREATE = "create";
+ public static final String FORGE_AUTHOR = "forgeAuthor";
+ public static final String FORGE_COMMITTER = "forgeCommitter";
+ public static final String FORGE_SERVER = "forgeServerAsCommitter";
+ public static final String LABEL = "label-";
+ public static final String OWNER = "owner";
+ public static final String PUSH = "push";
+ public static final String PUSH_MERGE = "pushMerge";
+ public static final String PUSH_TAG = "pushTag";
+ public static final String READ = "read";
+ public static final String SUBMIT = "submit";
+
+ private static final List<String> NAMES_LC;
+ private static final int labelIndex;
+
+ static {
+ NAMES_LC = new ArrayList<String>();
+ NAMES_LC.add(OWNER.toLowerCase());
+ NAMES_LC.add(READ.toLowerCase());
+ NAMES_LC.add(CREATE.toLowerCase());
+ NAMES_LC.add(FORGE_AUTHOR.toLowerCase());
+ NAMES_LC.add(FORGE_COMMITTER.toLowerCase());
+ NAMES_LC.add(FORGE_SERVER.toLowerCase());
+ NAMES_LC.add(PUSH.toLowerCase());
+ NAMES_LC.add(PUSH_MERGE.toLowerCase());
+ NAMES_LC.add(PUSH_TAG.toLowerCase());
+ NAMES_LC.add(LABEL.toLowerCase());
+ NAMES_LC.add(SUBMIT.toLowerCase());
+
+ labelIndex = NAMES_LC.indexOf(Permission.LABEL);
+ }
+
+ /** @return true if the name is recognized as a permission name. */
+ public static boolean isPermission(String varName) {
+ String lc = varName.toLowerCase();
+ if (lc.startsWith(LABEL)) {
+ return LABEL.length() < lc.length();
+ }
+ return NAMES_LC.contains(lc);
+ }
+
+ /** @return true if the permission name is actually for a review label. */
+ public static boolean isLabel(String varName) {
+ return varName.startsWith(LABEL) && LABEL.length() < varName.length();
+ }
+
+ /** @return permission name for the given review label. */
+ public static String forLabel(String labelName) {
+ return LABEL + labelName;
+ }
+
+ protected String name;
+ protected boolean exclusiveGroup;
+ protected List<PermissionRule> rules;
+
+ protected Permission() {
+ }
+
+ public Permission(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isLabel() {
+ return isLabel(getName());
+ }
+
+ public String getLabel() {
+ if (isLabel()) {
+ return getName().substring(LABEL.length());
+ }
+ return null;
+ }
+
+ public Boolean getExclusiveGroup() {
+ // Only permit exclusive group behavior on non OWNER permissions,
+ // otherwise an owner might lose access to a delegated subspace.
+ //
+ return exclusiveGroup && !OWNER.equals(getName());
+ }
+
+ public void setExclusiveGroup(Boolean newExclusiveGroup) {
+ exclusiveGroup = newExclusiveGroup;
+ }
+
+ public List<PermissionRule> getRules() {
+ initRules();
+ return rules;
+ }
+
+ public void setRules(List<PermissionRule> list) {
+ rules = list;
+ }
+
+ public void add(PermissionRule rule) {
+ initRules();
+ rules.add(rule);
+ }
+
+ public void remove(PermissionRule rule) {
+ if (rule != null) {
+ removeRule(rule.getGroup());
+ }
+ }
+
+ public void removeRule(GroupReference group) {
+ if (rules != null) {
+ for (Iterator<PermissionRule> itr = rules.iterator(); itr.hasNext();) {
+ if (sameGroup(itr.next(), group)) {
+ itr.remove();
+ }
+ }
+ }
+ }
+
+ public PermissionRule getRule(GroupReference group) {
+ return getRule(group, false);
+ }
+
+ public PermissionRule getRule(GroupReference group, boolean create) {
+ initRules();
+
+ for (PermissionRule r : rules) {
+ if (sameGroup(r, group)) {
+ return r;
+ }
+ }
+
+ if (create) {
+ PermissionRule r = new PermissionRule(group);
+ rules.add(r);
+ return r;
+ } else {
+ return null;
+ }
+ }
+
+ void mergeFrom(Permission src) {
+ for (PermissionRule srcRule : src.getRules()) {
+ PermissionRule dstRule = getRule(srcRule.getGroup());
+ if (dstRule != null) {
+ dstRule.mergeFrom(srcRule);
+ } else {
+ add(srcRule);
+ }
+ }
+ }
+
+ private static boolean sameGroup(PermissionRule rule, GroupReference group) {
+ if (group.getUUID() != null) {
+ return group.getUUID().equals(rule.getGroup().getUUID());
+
+ } else if (group.getName() != null) {
+ return group.getName().equals(rule.getGroup().getName());
+
+ } else {
+ return false;
+ }
+ }
+
+ private void initRules() {
+ if (rules == null) {
+ rules = new ArrayList<PermissionRule>(4);
+ }
+ }
+
+ @Override
+ public int compareTo(Permission b) {
+ int cmp = index(this) - index(b);
+ if (cmp == 0) {
+ cmp = getName().compareTo(b.getName());
+ }
+ return cmp;
+ }
+
+ private static int index(Permission a) {
+ if (a.isLabel()) {
+ return labelIndex;
+ }
+
+ int index = NAMES_LC.indexOf(a.getName().toLowerCase());
+ return 0 <= index ? index : NAMES_LC.size();
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
new file mode 100644
index 0000000..3490dd7
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
@@ -0,0 +1,138 @@
+// 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.common.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PermissionRange implements Comparable<PermissionRange> {
+ public static class WithDefaults extends PermissionRange {
+ protected int defaultMin;
+ protected int defaultMax;
+
+ protected WithDefaults() {
+ }
+
+ public WithDefaults(String name, int min, int max, int defMin, int defMax) {
+ super(name, min, max);
+ setDefaultRange(defMin, defMax);
+ }
+
+ public int getDefaultMin() {
+ return defaultMin;
+ }
+
+ public int getDefaultMax() {
+ return defaultMax;
+ }
+
+ public void setDefaultRange(int min, int max) {
+ defaultMin = min;
+ defaultMax = max;
+ }
+
+ /** @return all values between {@link #getMin()} and {@link #getMax()} */
+ public List<Integer> getValuesAsList() {
+ ArrayList<Integer> r = new ArrayList<Integer>(getRangeSize());
+ for (int i = min; i <= max; i++) {
+ r.add(i);
+ }
+ return r;
+ }
+
+ /** @return number of values between {@link #getMin()} and {@link #getMax()} */
+ public int getRangeSize() {
+ return max - min;
+ }
+ }
+
+ protected String name;
+ protected int min;
+ protected int max;
+
+ protected PermissionRange() {
+ }
+
+ public PermissionRange(String name, int min, int max) {
+ this.name = name;
+
+ if (min <= max) {
+ this.min = min;
+ this.max = max;
+ } else {
+ this.min = max;
+ this.max = min;
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isLabel() {
+ return Permission.isLabel(getName());
+ }
+
+ public String getLabel() {
+ return isLabel() ? getName().substring(Permission.LABEL.length()) : null;
+ }
+
+ public int getMin() {
+ return min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+
+ /** True if the value is within the range. */
+ public boolean contains(int value) {
+ return getMin() <= value && value <= getMax();
+ }
+
+ /** Normalize the value to fit within the bounds of the range. */
+ public int squash(int value) {
+ return Math.min(Math.max(getMin(), value), getMax());
+ }
+
+ /** True both {@link #getMin()} and {@link #getMax()} are 0. */
+ public boolean isEmpty() {
+ return getMin() == 0 && getMax() == 0;
+ }
+
+ @Override
+ public int compareTo(PermissionRange o) {
+ return getName().compareTo(o.getName());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder r = new StringBuilder();
+ if (getMin() < 0 && getMax() == 0) {
+ r.append(getMin());
+ r.append(' ');
+ } else {
+ if (getMin() != getMax()) {
+ if (0 <= getMin()) r.append('+');
+ r.append(getMin());
+ r.append("..");
+ }
+ if (0 <= getMax()) r.append('+');
+ r.append(getMax());
+ r.append(' ');
+ }
+ return r.toString();
+ }
+}
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
new file mode 100644
index 0000000..4d5e28f
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
@@ -0,0 +1,240 @@
+// 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.common.data;
+
+public class PermissionRule implements Comparable<PermissionRule> {
+ public static enum Action {
+ ALLOW, DENY,
+
+ INTERACTIVE, BATCH;
+ }
+
+ protected Action action = Action.ALLOW;
+ protected boolean force;
+ protected int min;
+ protected int max;
+ protected GroupReference group;
+
+ public PermissionRule() {
+ }
+
+ public PermissionRule(GroupReference group) {
+ this.group = group;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+
+ public void setAction(Action action) {
+ if (action == null) {
+ throw new NullPointerException("action");
+ }
+ this.action = action;
+ }
+
+ public boolean getDeny() {
+ return action == Action.DENY;
+ }
+
+ public void setDeny(boolean newDeny) {
+ action = Action.DENY;
+ }
+
+ public Boolean getForce() {
+ return force;
+ }
+
+ public void setForce(Boolean newForce) {
+ force = newForce;
+ }
+
+ public Integer getMin() {
+ return min;
+ }
+
+ public void setMin(Integer min) {
+ this.min = min;
+ }
+
+ public void setMax(Integer max) {
+ this.max = max;
+ }
+
+ public Integer getMax() {
+ return max;
+ }
+
+ public void setRange(int newMin, int newMax) {
+ if (newMax < newMin) {
+ min = newMax;
+ max = newMin;
+ } else {
+ min = newMin;
+ max = newMax;
+ }
+ }
+
+ public GroupReference getGroup() {
+ return group;
+ }
+
+ public void setGroup(GroupReference newGroup) {
+ group = newGroup;
+ }
+
+ void mergeFrom(PermissionRule src) {
+ if (getAction() != src.getAction()) {
+ if (getAction() == Action.DENY || src.getAction() == Action.DENY) {
+ setAction(Action.DENY);
+
+ } else if (getAction() == Action.BATCH || src.getAction() == Action.BATCH) {
+ setAction(Action.BATCH);
+
+ }
+ }
+
+ setForce(getForce() || src.getForce());
+ setRange(Math.min(getMin(), src.getMin()), Math.max(getMax(), src.getMax()));
+ }
+
+ @Override
+ public int compareTo(PermissionRule o) {
+ int cmp = action(this) - action(o);
+ if (cmp == 0) cmp = range(o) - range(this);
+ if (cmp == 0) cmp = group(this).compareTo(group(o));
+ return cmp;
+ }
+
+ private static int action(PermissionRule a) {
+ switch (a.getAction()) {
+ case DENY:
+ return 0;
+ default:
+ return 1 + a.getAction().ordinal();
+ }
+ }
+
+ private static int range(PermissionRule a) {
+ return Math.abs(a.getMin()) + Math.abs(a.getMax());
+ }
+
+ private static String group(PermissionRule a) {
+ return a.getGroup().getName() != null ? a.getGroup().getName() : "";
+ }
+
+ @Override
+ public String toString() {
+ return asString(true);
+ }
+
+ public String asString(boolean canUseRange) {
+ StringBuilder r = new StringBuilder();
+
+ switch (getAction()) {
+ case ALLOW:
+ break;
+
+ case DENY:
+ r.append("deny ");
+ break;
+
+ case INTERACTIVE:
+ r.append("interactive ");
+ break;
+
+ case BATCH:
+ r.append("batch ");
+ break;
+ }
+
+ if (getForce()) {
+ r.append("+force ");
+ }
+
+ if (canUseRange && (getMin() != 0 || getMax() != 0)) {
+ if (0 <= getMin()) r.append('+');
+ r.append(getMin());
+ r.append("..");
+ if (0 <= getMax()) r.append('+');
+ r.append(getMax());
+ r.append(' ');
+ }
+
+ r.append("group ");
+ r.append(getGroup().getName());
+
+ return r.toString();
+ }
+
+ public static PermissionRule fromString(String src, boolean mightUseRange) {
+ final String orig = src;
+ final PermissionRule rule = new PermissionRule();
+
+ src = src.trim();
+
+ if (src.startsWith("deny ")) {
+ rule.setAction(Action.DENY);
+ src = src.substring("deny ".length()).trim();
+
+ } else if (src.startsWith("interactive ")) {
+ rule.setAction(Action.INTERACTIVE);
+ src = src.substring("interactive ".length()).trim();
+
+ } else if (src.startsWith("batch ")) {
+ rule.setAction(Action.BATCH);
+ src = src.substring("batch ".length()).trim();
+ }
+
+ if (src.startsWith("+force ")) {
+ rule.setForce(true);
+ src = src.substring("+force ".length()).trim();
+ }
+
+ if (mightUseRange && !src.startsWith("group ")) {
+ int sp = src.indexOf(' ');
+ String range = src.substring(0, sp);
+
+ if (range.matches("^([+-]?\\d+)\\.\\.([+-]?\\d+)$")) {
+ int dotdot = range.indexOf("..");
+ int min = parseInt(range.substring(0, dotdot));
+ int max = parseInt(range.substring(dotdot + 2));
+ rule.setRange(min, max);
+ } else {
+ throw new IllegalArgumentException("Invalid range in rule: " + orig);
+ }
+
+ src = src.substring(sp + 1).trim();
+ }
+
+ if (src.startsWith("group ")) {
+ src = src.substring(6).trim();
+ GroupReference group = new GroupReference();
+ group.setName(src);
+ rule.setGroup(group);
+ } else {
+ throw new IllegalArgumentException("Rule must include group: " + orig);
+ }
+
+ return rule;
+ }
+
+ private static int parseInt(String value) {
+ if (value.startsWith("+")) {
+ value = value.substring(1);
+ }
+ return Integer.parseInt(value);
+ }
+}
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
new file mode 100644
index 0000000..168544d
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -0,0 +1,79 @@
+// 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.Project;
+
+import java.util.List;
+import java.util.Set;
+
+public class ProjectAccess {
+ protected String revision;
+ protected Project.NameKey inheritsFrom;
+ protected List<AccessSection> local;
+ protected Set<String> ownerOf;
+
+ public ProjectAccess() {
+ }
+
+ public String getRevision() {
+ return revision;
+ }
+
+ public void setRevision(String name) {
+ revision = name;
+ }
+
+ public Project.NameKey getInheritsFrom() {
+ return inheritsFrom;
+ }
+
+ public void setInheritsFrom(Project.NameKey name) {
+ inheritsFrom = name;
+ }
+
+ public List<AccessSection> getLocal() {
+ return local;
+ }
+
+ public void setLocal(List<AccessSection> as) {
+ local = as;
+ }
+
+ public AccessSection getLocal(String name) {
+ for (AccessSection s : local) {
+ if (s.getName().equals(name)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ public boolean isOwnerOf(AccessSection section) {
+ return isOwnerOf(section.getName());
+ }
+
+ public boolean isOwnerOf(String name) {
+ return ownerOf.contains(name);
+ }
+
+ public Set<String> getOwnerOf() {
+ return ownerOf;
+ }
+
+ public void setOwnerOf(Set<String> refs) {
+ ownerOf = refs;
+ }
+}
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 b5a986f..cd02785 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
@@ -15,10 +15,8 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.client.RpcImpl;
@@ -34,18 +32,17 @@
void projectDetail(Project.NameKey projectName,
AsyncCallback<ProjectDetail> callback);
+ void projectAccess(Project.NameKey projectName,
+ AsyncCallback<ProjectAccess> callback);
+
@SignInRequired
void changeProjectSettings(Project update,
AsyncCallback<ProjectDetail> callback);
@SignInRequired
- void deleteRight(Project.NameKey projectName, Set<RefRight.Key> ids,
- AsyncCallback<ProjectDetail> callback);
-
- @SignInRequired
- void addRight(Project.NameKey projectName, ApprovalCategory.Id categoryId,
- String groupName, String refName, short min, short max,
- AsyncCallback<ProjectDetail> callback);
+ void changeProjectAccess(Project.NameKey projectName, String baseRevision,
+ String message, List<AccessSection> sections,
+ AsyncCallback<ProjectAccess> callback);
void listBranches(Project.NameKey projectName,
AsyncCallback<ListBranchesResult> callback);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java
index 2aa8c62..02aaf80 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java
@@ -14,16 +14,10 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
-import java.util.List;
-import java.util.Map;
-
public class ProjectDetail {
public Project project;
- public Map<AccountGroup.Id, AccountGroup> groups;
- public List<InheritedRefRight> rights;
public boolean canModifyDescription;
public boolean canModifyMergeType;
public boolean canModifyAgreements;
@@ -36,14 +30,6 @@
project = p;
}
- public void setGroups(final Map<AccountGroup.Id, AccountGroup> g) {
- groups = g;
- }
-
- public void setRights(final List<InheritedRefRight> r) {
- rights = r;
- }
-
public void setCanModifyDescription(final boolean cmd) {
canModifyDescription = cmd;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
index 678ec79..a535c17 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
@@ -56,8 +56,11 @@
/** The account is not permitted to see the change. */
CHANGE_NOT_VISIBLE,
- /** Could not remove this reviewer from the change. */
- COULD_NOT_REMOVE
+ /** Could not remove this reviewer from the change due to ORMException. */
+ COULD_NOT_REMOVE,
+
+ /** Not permitted to remove this reviewer from the change. */
+ REMOVE_NOT_PERMITTED
}
protected Type type;
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
new file mode 100644
index 0000000..953ae99
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
@@ -0,0 +1,82 @@
+// 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.Account;
+
+import java.util.List;
+
+/**
+ * Describes the state required to submit a change.
+ */
+public class SubmitRecord {
+ public static enum Status {
+ /** The change is ready for submission. */
+ OK,
+
+ /** The change is missing a required label. */
+ NOT_READY,
+
+ /** The change has been closed. */
+ CLOSED,
+
+ /**
+ * An internal server error occurred preventing computation.
+ * <p>
+ * Additional detail may be available in {@link SubmitRecord#errorMessage}.
+ */
+ RULE_ERROR;
+ }
+
+ public Status status;
+ public List<Label> labels;
+ public String errorMessage;
+
+ public static class Label {
+ public static enum Status {
+ /**
+ * This label provides what is necessary for submission.
+ * <p>
+ * If provided, {@link Label#appliedBy} describes the user account
+ * that applied this label to the change.
+ */
+ OK,
+
+ /**
+ * This label prevents the change from being submitted.
+ * <p>
+ * If provided, {@link Label#appliedBy} describes the user account
+ * that applied this label to the change.
+ */
+ REJECT,
+
+ /**
+ * The label is required for submission, but has not been satisfied.
+ */
+ NEED,
+
+ /**
+ * 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.
+ */
+ IMPOSSIBLE;
+ }
+
+ public String label;
+ public Status status;
+ public Account.Id appliedBy;
+ }
+}
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 9dae169..976004f 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
@@ -14,7 +14,6 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.Project;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
@@ -32,5 +31,5 @@
AsyncCallback<List<AccountInfo>> callback);
void suggestAccountGroup(String query, int limit,
- AsyncCallback<List<AccountGroupName>> callback);
+ AsyncCallback<List<GroupReference>> callback);
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java
index 4a66a416a..53792e4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidQueryException.java
@@ -18,6 +18,10 @@
public class InvalidQueryException extends Exception {
private static final long serialVersionUID = 1L;
+ public InvalidQueryException(String message) {
+ super(message);
+ }
+
public InvalidQueryException(String message, String query) {
super("Invalid query: " + query + "\n\n" + message);
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java
index 4e117b1..179bc3a 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java
@@ -26,10 +26,18 @@
this(key, null);
}
+ public NoSuchGroupException(final AccountGroup.UUID key) {
+ this(key, null);
+ }
+
public NoSuchGroupException(final AccountGroup.Id key, final Throwable why) {
super(MESSAGE + key.toString(), why);
}
+ public NoSuchGroupException(final AccountGroup.UUID key, final Throwable why) {
+ super(MESSAGE + key.toString(), why);
+ }
+
public NoSuchGroupException(final AccountGroup.NameKey k) {
this(k, null);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java
similarity index 69%
copy from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
copy to gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java
index 0977ee9..76520aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java
@@ -12,15 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.common.errors;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+/** Indicats the user cannot perform this task. */
+public class PermissionDeniedException extends Exception {
+ private static final long serialVersionUID = 1L;
-public class Schema_49 extends SchemaVersion {
-
- @Inject
- Schema_49(Provider<Schema_48> prior) {
- super(prior);
+ public PermissionDeniedException(String msg) {
+ super(msg);
}
}
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 88246ac..b3b1083 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtdebug</artifactId>
@@ -83,5 +83,18 @@
<version>140</version>
<scope>provided</scope>
</dependency>
+
+ <!-- GWT should require these itself, but doesn't. -->
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <classifier>sources</classifier>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index f1e7438..bb0da11 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-gwtui</artifactId>
@@ -135,6 +135,19 @@
<classifier>sources</classifier>
<type>jar</type>
</dependency>
+
+ <!-- GWT should require these itself, but doesn't. -->
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <classifier>sources</classifier>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<profiles>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
index ec0f74f..8555c75 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<module rename-to="gerrit">
+ <inherits name='com.google.gwt.editor.Editor'/>
<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.resources.Resources'/>
<inherits name='com.google.gwt.user.theme.chrome.Chrome'/>
@@ -31,6 +32,7 @@
<extend-property name='locale' values='en'/>
<set-property-fallback name='locale' value='en'/>
<set-property name='locale' value='en'/>
+ <set-configuration-property name='UiBinder.useSafeHtmlTemplates' value='true'/>
<entry-point class='com.google.gerrit.client.Gerrit'/>
</module>
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 2b3ace4..7e9ffc9 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
@@ -83,10 +83,22 @@
return "patch," + type + "," + id.toString();
}
+ public static String toPatch(final PatchScreen.Type type, final Patch.Key id) {
+ if (type == PatchScreen.Type.SIDE_BY_SIDE) {
+ return toPatchSideBySide(id);
+ } else {
+ return toPatchUnified(id);
+ }
+ }
+
public static String toAccountGroup(final AccountGroup.Id id) {
return "admin,group," + id.toString();
}
+ public static String toGroup(final AccountGroup.UUID uuid) {
+ return "admin,group,uuid-" + uuid.toString();
+ }
+
public static String toProjectAdmin(final Project.NameKey n, final String tab) {
return "admin,project," + n.toString() + "," + tab;
}
@@ -411,6 +423,10 @@
private Screen select() {
String p;
+ p = "admin,group,uuid-";
+ if (token.startsWith(p))
+ return new AccountGroupScreen(AccountGroup.UUID.parse(skip(p, token)));
+
p = "admin,group,";
if (token.startsWith(p))
return new AccountGroupScreen(AccountGroup.Id.parse(skip(p, token)));
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 d0e5192..4eab73d 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
@@ -16,10 +16,14 @@
import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
+import com.google.gerrit.client.changes.ChangeConstants;
import com.google.gerrit.client.changes.ChangeListScreen;
+import com.google.gerrit.client.patches.PatchScreen;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.LinkMenuBar;
import com.google.gerrit.client.ui.LinkMenuItem;
+import com.google.gerrit.client.ui.MorphingTabPanel;
+import com.google.gerrit.client.ui.PatchLink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.ClientVersion;
import com.google.gerrit.common.PageLinks;
@@ -34,6 +38,7 @@
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.URL;
@@ -50,7 +55,6 @@
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.user.client.UserAgent;
@@ -64,6 +68,7 @@
public class Gerrit implements EntryPoint {
public static final GerritConstants C = GWT.create(GerritConstants.class);
+ public static final ChangeConstants CC = GWT.create(ChangeConstants.class);
public static final GerritMessages M = GWT.create(GerritMessages.class);
public static final GerritResources RESOURCES =
GWT.create(GerritResources.class);
@@ -76,14 +81,15 @@
private static Account myAccount;
private static AccountDiffPreference myAccountDiffPref;
- private static TabPanel menuLeft;
+ private static MorphingTabPanel menuLeft;
private static LinkMenuBar menuRight;
+ private static LinkMenuBar diffBar;
private static RootPanel siteHeader;
private static RootPanel siteFooter;
private static SearchPanel searchPanel;
private static final Dispatcher dispatcher = new Dispatcher();
private static ViewSite<Screen> body;
-
+ private static PatchScreen patchScreen;
private static String lastChangeListToken;
static {
@@ -145,6 +151,25 @@
}
/**
+ * Update any top level menus which can vary based on the view which was
+ * loaded.
+ * @param view the loaded view.
+ */
+ public static void updateMenus(Screen view) {
+ if (view instanceof PatchScreen) {
+ patchScreen = (PatchScreen) view;
+ menuLeft.setVisible(diffBar, true);
+ menuLeft.selectTab(menuLeft.getWidgetIndex(diffBar));
+ } else {
+ if (patchScreen != null && menuLeft.getSelectedWidget() == diffBar) {
+ menuLeft.selectTab(isSignedIn() ? 1 : 0);
+ }
+ patchScreen = null;
+ menuLeft.setVisible(diffBar, false);
+ }
+ }
+
+ /**
* Update the current history token after a screen change.
* <p>
* The caller has already updated the UI, but wants to publish a different
@@ -356,7 +381,7 @@
gBody.setStyleName(RESOURCES.css().gerritBody());
final Grid menuLine = new Grid(1, 3);
- menuLeft = new TabPanel();
+ menuLeft = new MorphingTabPanel();
menuRight = new LinkMenuBar();
searchPanel = new SearchPanel();
menuLeft.setStyleName(RESOURCES.css().topmenuMenuLeft());
@@ -466,6 +491,16 @@
menuLeft.selectTab(0);
}
+ patchScreen = null;
+ diffBar = new LinkMenuBar();
+ menuLeft.addInvisible(diffBar, C.menuDiff());
+ addDiffLink(diffBar, CC.patchTableDiffSideBySide(), PatchScreen.Type.SIDE_BY_SIDE);
+ addDiffLink(diffBar, CC.patchTableDiffUnified(), PatchScreen.Type.UNIFIED);
+ addDiffLink(diffBar, C.menuDiffCommit(), PatchScreen.TopView.COMMIT);
+ addDiffLink(diffBar, C.menuDiffPreferences(), PatchScreen.TopView.PREFERENCES);
+ addDiffLink(diffBar, C.menuDiffPatchSets(), PatchScreen.TopView.PATCH_SETS);
+ addDiffLink(diffBar, C.menuDiffFiles(), PatchScreen.TopView.FILES);
+
if (signedIn) {
m = new LinkMenuBar();
addLink(m, C.menuGroups(), PageLinks.ADMIN_GROUPS);
@@ -561,6 +596,36 @@
m.addItem(new LinkMenuItem(text, historyToken));
}
+ private static void addDiffLink(final LinkMenuBar m, final String text,
+ final PatchScreen.TopView tv) {
+ m.addItem(new LinkMenuItem(text, "") {
+ @Override
+ public void go() {
+ if (patchScreen != null) {
+ patchScreen.setTopView(tv);
+ }
+ AnchorElement.as(getElement()).blur();
+ }
+ });
+ }
+
+ private static void addDiffLink(final LinkMenuBar m, final String text,
+ final PatchScreen.Type type) {
+ m.addItem(new LinkMenuItem(text, "") {
+ @Override
+ public void go() {
+ if (patchScreen != null) {
+ if (type == patchScreen.getPatchScreenType()) {
+ patchScreen.setTopView(PatchScreen.TopView.MAIN);
+ AnchorElement.as(getElement()).blur();
+ } else {
+ new PatchLink("", type, patchScreen).go();
+ }
+ }
+ }
+ });
+ }
+
private static void addDocLink(final LinkMenuBar m, final String text,
final String href) {
final Anchor atag = anchor(text, "Documentation/" + href);
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 fbec6aa..b4293dc 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
@@ -61,6 +61,12 @@
String menuMyWatchedChanges();
String menuMyStarredChanges();
+ String menuDiff();
+ String menuDiffCommit();
+ String menuDiffPreferences();
+ String menuDiffPatchSets();
+ String menuDiffFiles();
+
String menuAdmin();
String menuPeople();
String menuGroups();
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 617ab6b..7b06788 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
@@ -44,6 +44,12 @@
menuMyStarredChanges = Starred Changes
menuMyWatchedChanges = Watched Changes
+menuDiff = Differences
+menuDiffCommit = Commit Message
+menuDiffPreferences = Preferences
+menuDiffPatchSets = Patch Sets
+menuDiffFiles = Files
+
menuAdmin = Admin
menuPeople = People
menuGroups = Groups
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java
deleted file mode 100644
index 8f6831b..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessRightEditor.java
+++ /dev/null
@@ -1,404 +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.client.admin;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
-import com.google.gerrit.client.ui.HintTextBox;
-import com.google.gerrit.client.ui.RPCSuggestOracle;
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.common.data.ProjectDetail;
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gwt.event.dom.client.ChangeEvent;
-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.event.logical.shared.HasValueChangeHandlers;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.ui.Button;
-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.ListBox;
-import com.google.gwt.user.client.ui.SuggestBox;
-
-public class AccessRightEditor extends Composite
- implements HasValueChangeHandlers<ProjectDetail> {
- private Project.NameKey projectKey;
- private ListBox catBox;
- private HintTextBox nameTxt;
- private SuggestBox nameSug;
- private HintTextBox referenceTxt;
- private ListBox topBox;
- private ListBox botBox;
- private Button addBut;
- private Button clearBut;
-
- public AccessRightEditor(final Project.NameKey key) {
- projectKey = key;
-
- initWidgets();
- initCategories();
-
- final Grid grid = new Grid(5, 2);
- grid.setText(0, 0, Util.C.columnApprovalCategory() + ":");
- grid.setWidget(0, 1, catBox);
-
- grid.setText(1, 0, Util.C.columnGroupName() + ":");
- grid.setWidget(1, 1, nameSug);
-
- grid.setText(2, 0, Util.C.columnRefName() + ":");
- grid.setWidget(2, 1, referenceTxt);
-
- grid.setText(3, 0, Util.C.columnRightRange() + ":");
- grid.setWidget(3, 1, topBox);
-
- grid.setText(4, 0, "");
- grid.setWidget(4, 1, botBox);
-
- FlowPanel fp = new FlowPanel();
- fp.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
-
- fp.add(grid);
- fp.add(addBut);
- fp.add(clearBut);
- initWidget(fp);
- }
-
- protected void initWidgets() {
- catBox = new ListBox();
- catBox.addChangeHandler(new ChangeHandler() {
- @Override
- public void onChange(final ChangeEvent event) {
- updateCategorySelection();
- }
- });
-
- nameTxt = new HintTextBox();
- nameSug = new SuggestBox(new RPCSuggestOracle(
- new AccountGroupSuggestOracle()), nameTxt);
- nameTxt.setVisibleLength(50);
- nameTxt.setHintText(Util.C.defaultAccountGroupName());
-
- referenceTxt = new HintTextBox();
- referenceTxt.setVisibleLength(50);
- referenceTxt.setText("");
- referenceTxt.addKeyPressHandler(new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
- doAddNewRight();
- }
- }
- });
-
- topBox = new ListBox();
- botBox = new ListBox();
-
- addBut = new Button(Util.C.buttonAddProjectRight());
- addBut.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- doAddNewRight();
- }
- });
-
- clearBut = new Button(Util.C.buttonClearProjectRight());
- clearBut.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- clear();
- }
- });
- }
-
- protected void initCategories() {
- for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
- .getApprovalTypes()) {
- final ApprovalCategory c = at.getCategory();
- catBox.addItem(c.getName(), c.getId().get());
- }
- for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
- .getActionTypes()) {
- final ApprovalCategory c = at.getCategory();
- if (Gerrit.getConfig().getWildProject().equals(projectKey)
- && !c.getId().canBeOnWildProject()) {
- // Giving out control of the WILD_PROJECT to other groups beyond
- // Administrators is dangerous. Having control over WILD_PROJECT
- // is about the same as having Administrator access as users are
- // able to affect grants in all projects on the system.
- //
- continue;
- }
- catBox.addItem(c.getName(), c.getId().get());
- }
-
- if (catBox.getItemCount() > 0) {
- catBox.setSelectedIndex(0);
- updateCategorySelection();
- }
- }
-
- public void enableForm(final boolean on) {
- final boolean canAdd = on && catBox.getItemCount() > 0;
- addBut.setEnabled(canAdd);
- clearBut.setEnabled(canAdd);
- nameTxt.setEnabled(canAdd);
- referenceTxt.setEnabled(canAdd);
- catBox.setEnabled(canAdd);
- topBox.setEnabled(canAdd);
- botBox.setEnabled(canAdd);
- }
-
- public void clear() {
- setCat(null);
- setName("");
- setReference("");
- }
-
- public void load(final RefRight right, final AccountGroup group) {
- final ApprovalType atype =
- Gerrit.getConfig().getApprovalTypes().getApprovalType(
- right.getApprovalCategoryId());
-
- setCat(atype != null ? atype.getCategory().getName()
- : right.getApprovalCategoryId().get() );
-
- setName(group.getName());
- setReference(right.getRefPatternForDisplay());
-
- setRange(atype.getCategory().isRange() ? atype.getValue(right.getMinValue())
- : null, atype.getValue(right.getMaxValue()) );
- }
-
- protected void doAddNewRight() {
- final ApprovalType at = getApprovalType();
- ApprovalCategoryValue min = getMin(at);
- ApprovalCategoryValue max = getMax(at);
-
- if (at == null || min == null || max == null) {
- return;
- }
-
- final String groupName = nameSug.getText();
- if ("".equals(groupName)
- || Util.C.defaultAccountGroupName().equals(groupName)) {
- return;
- }
-
- final String refPattern = referenceTxt.getText();
-
- addBut.setEnabled(false);
- Util.PROJECT_SVC.addRight(projectKey, at.getCategory().getId(),
- groupName, refPattern, min.getValue(), max.getValue(),
- new GerritCallback<ProjectDetail>() {
- public void onSuccess(final ProjectDetail result) {
- addBut.setEnabled(true);
- nameSug.setText("");
- referenceTxt.setText("");
- ValueChangeEvent.fire(AccessRightEditor.this, result);
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- addBut.setEnabled(true);
- super.onFailure(caught);
- }
- });
- }
-
- protected void updateCategorySelection() {
- final ApprovalType at = getApprovalType();
-
- if (at == null || at.getValues().isEmpty()) {
- topBox.setEnabled(false);
- botBox.setEnabled(false);
- referenceTxt.setEnabled(false);
- addBut.setEnabled(false);
- clearBut.setEnabled(false);
- return;
- }
-
- updateRanges(at);
- }
-
- protected void updateRanges(final ApprovalType at) {
- ApprovalCategoryValue min = null, max = null, last = null;
-
- topBox.clear();
- botBox.clear();
-
- for(final ApprovalCategoryValue v : at.getValues()) {
- final int nval = v.getValue();
- final String vStr = String.valueOf(nval);
-
- String nStr = vStr + ": " + v.getName();
- if (nval > 0) {
- nStr = "+" + nStr;
- }
-
- topBox.addItem(nStr, vStr);
- botBox.addItem(nStr, vStr);
-
- if (min == null || nval < 0) {
- min = v;
- } else if (max == null && nval > 0) {
- max = v;
- }
- last = v;
- }
-
- if (max == null) {
- max = last;
- }
-
- if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
- // Special case; for READ the most logical range is just
- // +1 READ, so assume that as the default for both.
- min = max;
- }
-
- if (! at.getCategory().isRange()) {
- max = null;
- }
-
- setRange(min, max);
- }
-
- protected void setCat(final String cat) {
- if (cat == null) {
- catBox.setSelectedIndex(0);
- } else {
- setSelectedText(catBox, cat);
- }
- updateCategorySelection();
- }
-
- protected void setName(final String name) {
- nameTxt.setText(name);
- }
-
- protected void setReference(final String ref) {
- referenceTxt.setText(ref);
- }
-
- protected void setRange(final ApprovalCategoryValue min,
- final ApprovalCategoryValue max) {
- if (min == null || max == null) {
- botBox.setVisible(false);
- if (max != null) {
- setSelectedValue(topBox, "" + max.getValue());
- return;
- }
- } else {
- botBox.setVisible(true);
- setSelectedValue(botBox, "" + max.getValue());
- }
- setSelectedValue(topBox, "" + min.getValue());
- }
-
- private ApprovalType getApprovalType() {
- int idx = catBox.getSelectedIndex();
- if (idx < 0) {
- return null;
- }
- return Gerrit.getConfig().getApprovalTypes().getApprovalType(
- new ApprovalCategory.Id(catBox.getValue(idx)));
- }
-
- public ApprovalCategoryValue getMin(ApprovalType at) {
- final ApprovalCategoryValue top = getTop(at);
- final ApprovalCategoryValue bot = getBot(at);
- if (bot == null) {
- for (final ApprovalCategoryValue v : at.getValues()) {
- if (0 <= v.getValue() && v.getValue() <= top.getValue()) {
- return v;
- }
- }
- return at.getMin();
- }
-
- if (top.getValue() > bot.getValue()) {
- return bot;
- }
- return top;
- }
-
- public ApprovalCategoryValue getMax(ApprovalType at) {
- final ApprovalCategoryValue top = getTop(at);
- final ApprovalCategoryValue bot = getBot(at);
- if (bot == null || bot.getValue() < top.getValue()) {
- return top;
- }
- return bot;
- }
-
- protected ApprovalCategoryValue getTop(ApprovalType at) {
- int idx = topBox.getSelectedIndex();
- if (idx < 0) {
- return null;
- }
- return at.getValue(Short.parseShort(topBox.getValue(idx)));
- }
-
- protected ApprovalCategoryValue getBot(ApprovalType at) {
- int idx = botBox.getSelectedIndex();
- if (idx < 0 || ! botBox.isVisible()) {
- return null;
- }
- return at.getValue(Short.parseShort(botBox.getValue(idx)));
- }
-
- public HandlerRegistration addValueChangeHandler(
- final ValueChangeHandler<ProjectDetail> handler) {
- return addHandler(handler, ValueChangeEvent.getType());
- }
-
- public static boolean setSelectedText(ListBox box, String text) {
- if (text == null) {
- return false;
- }
- for (int i =0 ; i < box.getItemCount(); i++) {
- if (text.equals(box.getItemText(i))) {
- box.setSelectedIndex(i);
- return true;
- }
- }
- return false;
- }
-
- public static boolean setSelectedValue(ListBox box, String value) {
- if (value == null) {
- return false;
- }
- for (int i =0 ; i < box.getItemCount(); i++) {
- if (value.equals(box.getValue(i))) {
- box.setSelectedIndex(i);
- return true;
- }
- }
- return false;
- }
-}
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
new file mode 100644
index 0000000..f4257b5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -0,0 +1,291 @@
+// 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.admin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.ProjectAccess;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.ValueAwareEditor;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ValueListBox;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class AccessSectionEditor extends Composite implements
+ Editor<AccessSection>, ValueAwareEditor<AccessSection> {
+ interface Binder extends UiBinder<HTMLPanel, AccessSectionEditor> {
+ }
+
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField
+ ValueEditor<String> name;
+
+ @UiField
+ FlowPanel permissionContainer;
+ ListEditor<Permission, PermissionEditor> permissions;
+
+ @UiField
+ DivElement addContainer;
+ @UiField(provided = true)
+ @Editor.Ignore
+ ValueListBox<String> permissionSelector;
+
+ @UiField
+ SpanElement deletedName;
+
+ @UiField
+ Anchor deleteSection;
+
+ @UiField
+ DivElement normal;
+ @UiField
+ DivElement deleted;
+
+ @UiField
+ SpanElement sectionType;
+ @UiField
+ SpanElement sectionName;
+
+ private final ProjectAccess projectAccess;
+ private AccessSection value;
+ private boolean editing;
+ private boolean readOnly;
+ private boolean isDeleted;
+
+ public AccessSectionEditor(ProjectAccess access) {
+ projectAccess = access;
+
+ permissionSelector =
+ new ValueListBox<String>(PermissionNameRenderer.INSTANCE);
+ permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event) {
+ if (!Util.C.addPermission().equals(event.getValue())) {
+ onAddPermission(event.getValue());
+ }
+ }
+ });
+
+ initWidget(uiBinder.createAndBindUi(this));
+ permissions = ListEditor.of(new PermissionEditorSource());
+ }
+
+ @UiHandler("deleteSection")
+ void onDeleteHover(MouseOverEvent event) {
+ normal.addClassName(AdminResources.I.css().deleteSectionHover());
+ }
+
+ @UiHandler("deleteSection")
+ void onDeleteNonHover(MouseOutEvent event) {
+ normal.removeClassName(AdminResources.I.css().deleteSectionHover());
+ }
+
+ @UiHandler("deleteSection")
+ void onDeleteSection(ClickEvent event) {
+ isDeleted = true;
+
+ if (name.isVisible() && AccessSection.isAccessSection(name.getValue())){
+ deletedName.setInnerText(Util.M.deletedReference(name.getValue()));
+
+ } else {
+ String name = Util.C.sectionNames().get(value.getName());
+ if (name == null) {
+ name = value.getName();
+ }
+ deletedName.setInnerText(Util.M.deletedSection(name));
+ }
+
+ normal.getStyle().setDisplay(Display.NONE);
+ deleted.getStyle().setDisplay(Display.BLOCK);
+ }
+
+ @UiHandler("undoDelete")
+ void onUndoDelete(ClickEvent event) {
+ isDeleted = false;
+ deleted.getStyle().setDisplay(Display.NONE);
+ normal.getStyle().setDisplay(Display.BLOCK);
+ }
+
+ void onAddPermission(String varName) {
+ int idx = permissions.getList().size();
+
+ Permission p = value.getPermission(varName, true);
+ permissions.getList().add(p);
+
+ PermissionEditor e = permissions.getEditors().get(idx);
+ e.beginAddRule();
+
+ rebuildPermissionSelector();
+ }
+
+ void editRefPattern() {
+ name.edit();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ name.setFocus(true);
+ }});
+ }
+
+ void enableEditing() {
+ readOnly = false;
+ addContainer.getStyle().setDisplay(Display.BLOCK);
+ rebuildPermissionSelector();
+ }
+
+ boolean isDeleted() {
+ return isDeleted;
+ }
+
+ @Override
+ public void setValue(AccessSection value) {
+ Collections.sort(value.getPermissions());
+
+ this.value = value;
+ this.readOnly = !editing || !projectAccess.isOwnerOf(value);
+
+ name.setEnabled(!readOnly);
+ deleteSection.setVisible(!readOnly);
+
+ if (AccessSection.isAccessSection(value.getName())) {
+ name.setVisible(true);
+ name.setIgnoreEditorValue(false);
+ sectionType.setInnerText(Util.C.sectionTypeReference());
+
+ } else {
+ name.setVisible(false);
+ name.setIgnoreEditorValue(true);
+
+ String name = Util.C.sectionNames().get(value.getName());
+ if (name != null) {
+ sectionType.setInnerText(name);
+ sectionName.getStyle().setDisplay(Display.NONE);
+ } else {
+ sectionType.setInnerText(Util.C.sectionTypeSection());
+ sectionName.setInnerText(value.getName());
+ sectionName.getStyle().clearDisplay();
+ }
+ }
+
+ if (readOnly) {
+ addContainer.getStyle().setDisplay(Display.NONE);
+ } else {
+ enableEditing();
+ }
+ }
+
+ void setEditing(final boolean editing) {
+ this.editing = editing;
+ }
+
+ private void rebuildPermissionSelector() {
+ List<String> perms = new ArrayList<String>();
+
+ if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
+ for (String varName : Util.C.capabilityNames().keySet()) {
+ if (value.getPermission(varName) == null) {
+ perms.add(varName);
+ }
+ }
+ } else if (AccessSection.isAccessSection(value.getName())) {
+ for (ApprovalType t : Gerrit.getConfig().getApprovalTypes().getApprovalTypes()) {
+ String varName = Permission.LABEL + t.getCategory().getLabelName();
+ if (value.getPermission(varName) == null) {
+ perms.add(varName);
+ }
+ }
+ for (String varName : Util.C.permissionNames().keySet()) {
+ if (value.getPermission(varName) == null) {
+ perms.add(varName);
+ }
+ }
+ }
+ if (perms.isEmpty()) {
+ addContainer.getStyle().setDisplay(Display.NONE);
+ } else {
+ addContainer.getStyle().setDisplay(Display.BLOCK);
+ perms.add(0, Util.C.addPermission());
+ permissionSelector.setValue(Util.C.addPermission());
+ permissionSelector.setAcceptableValues(perms);
+ }
+ }
+
+ @Override
+ public void flush() {
+ List<Permission> src = permissions.getList();
+ List<Permission> keep = new ArrayList<Permission>(src.size());
+
+ for (int i = 0; i < src.size(); i++) {
+ PermissionEditor e = (PermissionEditor) permissionContainer.getWidget(i);
+ if (!e.isDeleted()) {
+ keep.add(src.get(i));
+ }
+ }
+ value.setPermissions(keep);
+ }
+
+ @Override
+ public void onPropertyChange(String... paths) {
+ }
+
+ @Override
+ public void setDelegate(EditorDelegate<AccessSection> delegate) {
+ }
+
+ private class PermissionEditorSource extends EditorSource<PermissionEditor> {
+ @Override
+ public PermissionEditor create(int index) {
+ PermissionEditor subEditor = new PermissionEditor(readOnly, value);
+ permissionContainer.insert(subEditor, index);
+ return subEditor;
+ }
+
+ @Override
+ public void dispose(PermissionEditor subEditor) {
+ subEditor.removeFromParent();
+ }
+
+ @Override
+ public void setIndex(PermissionEditor subEditor, int index) {
+ permissionContainer.insert(subEditor, index);
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml
new file mode 100644
index 0000000..29ae2d6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.ui.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:e='urn:import:com.google.gwt.editor.ui.client'
+ xmlns:my='urn:import:com.google.gerrit.client.admin'
+ ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
+ ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
+ ui:generateLocales='default,en'
+ >
+<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
+<ui:style>
+ @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+ @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+ .panel {
+ position: relative;
+ }
+
+ .content {
+ margin-top: 4px;
+ margin-bottom: 4px;
+ padding-bottom: 2px;
+ }
+
+ .normal {
+ background-color: trimColor;
+ }
+
+ .deleted {
+ padding-left: 7px;
+ padding-bottom: 2px;
+ }
+
+ .header {
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ .headerText {
+ vertical-align: top;
+ white-space: nowrap;
+ font-weight: bold;
+ }
+ .headerTable {
+ border: 0;
+ width: 100%;
+ padding-right: 40px;
+ }
+
+ .header:hover {
+ background-color: selectionColor;
+ }
+
+ .name {
+ width: 100%;
+ }
+ .nameEdit {
+ width: 100%;
+ }
+
+ .permissionList {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+
+ .addContainer {
+ padding-left: 16px;
+ padding-right: 16px;
+ font-size: 80%;
+ }
+ .addContainer:hover {
+ background-color: selectionColor;
+ }
+ .addSelector {
+ font-size: 80%;
+ }
+
+ .deleteIcon {
+ position: absolute;
+ top: 5px;
+ right: 17px;
+ }
+
+ .undoIcon {
+ position: absolute;
+ top: 2px;
+ right: 17px;
+ }
+</ui:style>
+
+<g:HTMLPanel styleName='{style.panel}'>
+<div ui:field='normal' class='{style.normal} {style.content}'>
+ <div class='{style.header}'>
+ <table class='{style.headerTable}'><tr>
+ <td class='{style.headerText}'>
+ <span ui:field='sectionType'/>
+ </td>
+ <td width='100%'>
+ <my:ValueEditor
+ ui:field='name'
+ addStyleNames='{style.name}'
+ editTitle='Edit reference pattern'>
+ <ui:attribute name='editTitle'/>
+ <my:editor>
+ <my:RefPatternBox styleName='{style.nameEdit}'/>
+ </my:editor>
+ </my:ValueEditor>
+ <span ui:field='sectionName' class='{style.name}'/>
+ </td>
+ </tr></table>
+
+ <g:Anchor
+ ui:field='deleteSection'
+ href='javascript:void'
+ styleName='{style.deleteIcon} {res.css.deleteIcon}'
+ title='Delete this section (and nested rules)'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+ </div>
+
+ <g:FlowPanel
+ ui:field='permissionContainer'
+ styleName='{style.permissionList}'/>
+ <div ui:field='addContainer' class='{style.addContainer}'>
+ <g:ValueListBox
+ ui:field='permissionSelector'
+ styleName='{style.addSelector}' />
+ </div>
+</div>
+
+<div
+ ui:field='deleted'
+ class='{style.deleted} {res.css.deleted}'
+ style='display: none'>
+ <span ui:field='deletedName'/>
+ <g:Anchor
+ ui:field='undoDelete'
+ href='javascript:void'
+ styleName='{style.undoIcon} {res.css.undoIcon}'
+ title='Undo deletion'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+</div>
+</g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
index 593ab34..9f8117a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
@@ -63,7 +63,9 @@
import java.util.List;
public class AccountGroupScreen extends AccountScreen {
- private final AccountGroup.Id groupId;
+ private AccountGroup.Id groupId;
+ private AccountGroup.UUID groupUUID;
+
private AccountInfoCache accounts = AccountInfoCache.empty();
private GroupInfoCache groups = GroupInfoCache.empty();
private MemberTable members;
@@ -106,24 +108,31 @@
groupId = toShow;
}
+ public AccountGroupScreen(final AccountGroup.UUID toShow) {
+ groupUUID = toShow;
+ }
+
@Override
protected void onLoad() {
super.onLoad();
- Util.GROUP_SVC.groupDetail(groupId, new ScreenLoadCallback<GroupDetail>(
- this) {
- @Override
- protected void preDisplay(final GroupDetail result) {
- enableForm(result.canModify);
- saveName.setVisible(result.canModify);
- saveOwner.setVisible(result.canModify);
- saveDesc.setVisible(result.canModify);
- saveGroupOptions.setVisible(result.canModify);
- delMember.setVisible(result.canModify);
- saveType.setVisible(result.canModify);
- delInclude.setVisible(result.canModify);
- display(result);
- }
- });
+ Util.GROUP_SVC.groupDetail(groupId, groupUUID,
+ new ScreenLoadCallback<GroupDetail>(this) {
+ @Override
+ protected void preDisplay(final GroupDetail result) {
+ groupId = result.group.getId();
+ groupUUID = result.group.getGroupUUID();
+ display(result);
+
+ enableForm(result.canModify);
+ saveName.setVisible(result.canModify);
+ saveOwner.setVisible(result.canModify);
+ saveDesc.setVisible(result.canModify);
+ saveGroupOptions.setVisible(result.canModify);
+ delMember.setVisible(result.canModify);
+ saveType.setVisible(result.canModify);
+ delInclude.setVisible(result.canModify);
+ }
+ });
}
@Override
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 4631a29..1f70516 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
@@ -16,6 +16,8 @@
import com.google.gwt.i18n.client.Constants;
+import java.util.Map;
+
public interface AdminConstants extends Constants {
String defaultAccountName();
String defaultAccountGroupName();
@@ -101,4 +103,20 @@
String noGroupSelected();
String errorNoMatchingGroups();
String errorNoGitRepository();
+
+ String addPermission();
+ Map<String,String> permissionNames();
+
+ String refErrorEmpty();
+ String refErrorBeginSlash();
+ String refErrorDoubleSlash();
+ String refErrorNoSpace();
+ String refErrorPrintable();
+ String errorsMustBeFixed();
+
+ Map<String, String> capabilityNames();
+
+ String sectionTypeReference();
+ String sectionTypeSection();
+ Map<String, String> sectionNames();
}
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 4bad368..5e333d8 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
@@ -83,3 +83,70 @@
noGroupSelected = (No group selected)
errorNoMatchingGroups = No Matching Groups
errorNoGitRepository = No Git Repository
+
+
+addPermission = Add Permission ...
+
+# Permission Names
+permissionNames = \
+ create, \
+ forgeAuthor, \
+ forgeCommitter, \
+ forgeServerAsCommitter, \
+ owner, \
+ push, \
+ pushMerge, \
+ pushTag, \
+ read, \
+ submit
+create = Create Reference
+forgeAuthor = Forge Author Identity
+forgeCommitter = Forge Committer Identity
+forgeServerAsCommitter = Forge Server Identity
+owner = Owner
+push = Push
+pushMerge = Push Merge Commit
+pushTag = Push Annotated Tag
+read = Read
+submit = Submit
+
+refErrorEmpty = Reference must be supplied
+refErrorBeginSlash = Reference must not start with '/'
+refErrorDoubleSlash = References cannot contain '//'
+refErrorNoSpace = References cannot contain spaces
+refErrorPrintable = References may contain only printable characters
+errorsMustBeFixed = Errors must be fixed before committing changes.
+
+# Capability Names
+capabilityNames = \
+ administrateServer, \
+ createAccount, \
+ createGroup, \
+ createProject, \
+ flushCaches, \
+ killTask, \
+ priority, \
+ queryLimit, \
+ startReplication, \
+ viewCaches, \
+ viewConnections, \
+ viewQueue
+administrateServer = Administrate Server
+createAccount = Create Account
+createGroup = Create Group
+createProject = Create Project
+flushCaches = Flush Caches
+killTask = Kill Task
+priority = Priority
+queryLimit = Query Limit
+startReplication = Start Replication
+viewCaches = View Caches
+viewConnections = View Connections
+viewQueue = View Queue
+
+# Section Names
+sectionTypeReference = Reference:
+sectionTypeSection = Section:
+sectionNames = \
+ GLOBAL_CAPABILITIES
+GLOBAL_CAPABILITIES = Global Capabilities
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminCss.java
similarity index 69%
copy from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminCss.java
index 0977ee9..55a9bf3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminCss.java
@@ -12,15 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.client.admin;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.google.gwt.resources.client.CssResource;
-public class Schema_49 extends SchemaVersion {
+public interface AdminCss extends CssResource {
+ String deleteIcon();
+ String undoIcon();
- @Inject
- Schema_49(Provider<Schema_48> prior) {
- super(prior);
- }
+ String deleted();
+ String deletedBorder();
+
+ String deleteSectionHover();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index ab3541e..3b4d7d4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -18,6 +18,10 @@
public interface AdminMessages extends Messages {
String group(String name);
+ String label(String name);
String project(String name);
String deletedGroup(int id);
+
+ String deletedReference(String name);
+ String deletedSection(String name);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index 6feb69a..7f8cd56 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -1,3 +1,6 @@
group = Group {0}
+label = Label {0}
project = Project {0}
deletedGroup = Deleted Group {0}
+deletedReference = Reference {0} was deleted
+deletedSection = Section {0} was deleted
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java
new file mode 100644
index 0000000..cd366f3
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java
@@ -0,0 +1,38 @@
+// 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.admin;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface AdminResources extends ClientBundle {
+ public static final AdminResources I = GWT.create(AdminResources.class);
+
+ @Source("admin.css")
+ AdminCss css();
+
+ @Source("editText.png")
+ public ImageResource editText();
+
+ @Source("deleteNormal.png")
+ public ImageResource deleteNormal();
+
+ @Source("deleteHover.png")
+ public ImageResource deleteHover();
+
+ @Source("undoNormal.png")
+ public ImageResource undoNormal();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index a6dbedc..05b6382 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -22,6 +22,7 @@
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
@@ -37,11 +38,10 @@
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
-import java.util.List;
-
public class GroupListScreen extends AccountScreen {
private GroupTable groups;
+ private VerticalPanel addPanel;
private NpTextBox addTxt;
private Button addNew;
@@ -49,10 +49,11 @@
protected void onLoad() {
super.onLoad();
Util.GROUP_SVC
- .visibleGroups(new ScreenLoadCallback<List<AccountGroup>>(this) {
+ .visibleGroups(new ScreenLoadCallback<GroupList>(this) {
@Override
- protected void preDisplay(final List<AccountGroup> result) {
- groups.display(result);
+ protected void preDisplay(GroupList result) {
+ addPanel.setVisible(result.isCanCreateGroup());
+ groups.display(result.getGroups());
groups.finishDisplay();
}
});
@@ -66,9 +67,9 @@
groups = new GroupTable(true /* hyperlink to admin */, PageLinks.ADMIN_GROUPS);
add(groups);
- final VerticalPanel fp = new VerticalPanel();
- fp.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
- fp.add(new SmallHeading(Util.C.headingCreateGroup()));
+ addPanel = new VerticalPanel();
+ addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
+ addPanel.add(new SmallHeading(Util.C.headingCreateGroup()));
addTxt = new NpTextBox();
addTxt.setVisibleLength(60);
@@ -80,7 +81,7 @@
}
}
});
- fp.add(addTxt);
+ addPanel.add(addTxt);
addNew = new Button(Util.C.buttonCreateGroup());
addNew.setEnabled(false);
@@ -109,8 +110,8 @@
groups.setRegisterKeys(true);
}
});
- fp.add(addNew);
- add(fp);
+ addPanel.add(addNew);
+ add(addPanel);
new OnEditEnabler(addNew, addTxt);
}
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
new file mode 100644
index 0000000..b51f0c0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupReferenceBox.java
@@ -0,0 +1,143 @@
+// 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.admin;
+
+import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
+import com.google.gerrit.client.ui.RPCSuggestOracle;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gwt.editor.client.LeafValueEditor;
+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.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+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.ui.Composite;
+import com.google.gwt.user.client.ui.Focusable;
+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.NpTextBox;
+
+public class GroupReferenceBox extends Composite implements
+ LeafValueEditor<GroupReference>, HasSelectionHandlers<GroupReference>,
+ HasCloseHandlers<GroupReferenceBox>, Focusable {
+ private final DefaultSuggestionDisplay suggestions;
+ private final NpTextBox textBox;
+ private final AccountGroupSuggestOracle oracle;
+ private final SuggestBox suggestBox;
+
+ private boolean submitOnSelection;
+
+ public GroupReferenceBox() {
+ suggestions = new DefaultSuggestionDisplay();
+ textBox = new NpTextBox();
+ oracle = new AccountGroupSuggestOracle();
+ suggestBox = new SuggestBox( //
+ new RPCSuggestOracle(oracle), //
+ textBox, //
+ suggestions);
+ initWidget(suggestBox);
+
+ suggestBox.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ submitOnSelection = false;
+
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ if (suggestions.isSuggestionListShowing()) {
+ submitOnSelection = true;
+ } else {
+ SelectionEvent.fire(GroupReferenceBox.this, getValue());
+ }
+ }
+ }
+ });
+ suggestBox.addKeyUpHandler(new KeyUpHandler() {
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+ suggestBox.setText("");
+ CloseEvent.fire(GroupReferenceBox.this, GroupReferenceBox.this);
+ }
+ }
+ });
+ suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ @Override
+ public void onSelection(SelectionEvent<Suggestion> event) {
+ if (submitOnSelection) {
+ submitOnSelection = false;
+ SelectionEvent.fire(GroupReferenceBox.this, getValue());
+ }
+ }
+ });
+ }
+
+ public void setVisibleLength(int len) {
+ textBox.setVisibleLength(len);
+ }
+
+ @Override
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<GroupReference> handler) {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+ @Override
+ public HandlerRegistration addCloseHandler(
+ CloseHandler<GroupReferenceBox> handler) {
+ return addHandler(handler, CloseEvent.getType());
+ }
+
+ @Override
+ public GroupReference getValue() {
+ String name = suggestBox.getText();
+ if (name != null && !name.isEmpty()) {
+ return new GroupReference(oracle.getUUID(name), name);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void setValue(GroupReference value) {
+ suggestBox.setText(value != null ? value.getName() : "");
+ }
+
+ @Override
+ public int getTabIndex() {
+ return suggestBox.getTabIndex();
+ }
+
+ @Override
+ public void setTabIndex(int index) {
+ suggestBox.setTabIndex(index);
+ }
+
+ public void setFocus(boolean focused) {
+ suggestBox.setFocus(focused);
+ }
+
+ @Override
+ public void setAccessKey(char key) {
+ suggestBox.setAccessKey(key);
+ }
+}
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
new file mode 100644
index 0000000..5e3ad00
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -0,0 +1,333 @@
+// 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.admin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.SuggestUtil;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.ValueAwareEditor;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.event.dom.client.ClickEvent;
+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.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ValueLabel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PermissionEditor extends Composite implements Editor<Permission>,
+ ValueAwareEditor<Permission> {
+ interface Binder extends UiBinder<HTMLPanel, PermissionEditor> {
+ }
+
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField(provided = true)
+ @Path("name")
+ ValueLabel<String> normalName;
+
+ @UiField(provided = true)
+ @Path("name")
+ ValueLabel<String> deletedName;
+
+ @UiField
+ CheckBox exclusiveGroup;
+
+ @UiField
+ FlowPanel ruleContainer;
+ ListEditor<PermissionRule, PermissionRuleEditor> rules;
+
+ @UiField
+ DivElement addContainer;
+ @UiField
+ DivElement addStage1;
+ @UiField
+ DivElement addStage2;
+ @UiField
+ Anchor beginAddRule;
+ @UiField
+ @Editor.Ignore
+ GroupReferenceBox groupToAdd;
+ @UiField
+ Button addRule;
+
+ @UiField
+ Anchor deletePermission;
+
+ @UiField
+ DivElement normal;
+ @UiField
+ DivElement deleted;
+
+ private final boolean readOnly;
+ private final AccessSection section;
+ private Permission value;
+ private PermissionRange.WithDefaults validRange;
+ private boolean isDeleted;
+
+ public PermissionEditor(boolean readOnly, AccessSection section) {
+ this.readOnly = readOnly;
+ this.section = section;
+
+ normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
+ deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
+
+ initWidget(uiBinder.createAndBindUi(this));
+ rules = ListEditor.of(new RuleEditorSource());
+
+ exclusiveGroup.setEnabled(!readOnly);
+ exclusiveGroup.setVisible(AccessSection.isAccessSection(section.getName()));
+
+ if (readOnly) {
+ addContainer.removeFromParent();
+ addContainer = null;
+
+ deletePermission.removeFromParent();
+ deletePermission = null;
+ }
+
+ groupToAdd.addHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ addGroup();
+ }
+ }
+ }, KeyPressEvent.getType());
+ }
+
+ @UiHandler("deletePermission")
+ void onDeleteHover(MouseOverEvent event) {
+ addStyleName(AdminResources.I.css().deleteSectionHover());
+ }
+
+ @UiHandler("deletePermission")
+ void onDeleteNonHover(MouseOutEvent event) {
+ removeStyleName(AdminResources.I.css().deleteSectionHover());
+ }
+
+ @UiHandler("deletePermission")
+ void onDeletePermission(ClickEvent event) {
+ isDeleted = true;
+ normal.getStyle().setDisplay(Display.NONE);
+ deleted.getStyle().setDisplay(Display.BLOCK);
+ }
+
+ @UiHandler("undoDelete")
+ void onUndoDelete(ClickEvent event) {
+ isDeleted = false;
+ deleted.getStyle().setDisplay(Display.NONE);
+ normal.getStyle().setDisplay(Display.BLOCK);
+ }
+
+ @UiHandler("beginAddRule")
+ void onBeginAddRule(ClickEvent event) {
+ beginAddRule();
+ }
+
+ void beginAddRule() {
+ addStage1.getStyle().setDisplay(Display.NONE);
+ addStage2.getStyle().setDisplay(Display.BLOCK);
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ groupToAdd.setFocus(true);
+ }
+ });
+ }
+
+ @UiHandler("addRule")
+ void onAddGroupByClick(ClickEvent event) {
+ addGroup();
+ }
+
+ private void addGroup() {
+ GroupReference ref = groupToAdd.getValue();
+ if (ref != null) {
+ addGroup(ref);
+ } else {
+ groupToAdd.setFocus(true);
+ }
+ }
+
+ @UiHandler("groupToAdd")
+ void onAddGroupByEnter(SelectionEvent<GroupReference> event) {
+ GroupReference ref = event.getSelectedItem();
+ if (ref != null) {
+ addGroup(ref);
+ }
+ }
+
+ @UiHandler("groupToAdd")
+ void onAbortAddGroup(CloseEvent<GroupReferenceBox> event) {
+ hideAddGroup();
+ }
+
+ @UiHandler("hideAddGroup")
+ void hideAddGroup(ClickEvent event) {
+ hideAddGroup();
+ }
+
+ private void hideAddGroup() {
+ addStage1.getStyle().setDisplay(Display.BLOCK);
+ addStage2.getStyle().setDisplay(Display.NONE);
+ }
+
+ private void addGroup(GroupReference ref) {
+ if (ref.getUUID() != null) {
+ if (value.getRule(ref) == null) {
+ PermissionRule newRule = value.getRule(ref, true);
+ if (validRange != null) {
+ int min = validRange.getDefaultMin();
+ int max = validRange.getDefaultMax();
+ newRule.setRange(min, max);
+
+ } else if (GlobalCapability.PRIORITY.equals(value.getName())) {
+ newRule.setAction(PermissionRule.Action.BATCH);
+ }
+
+ rules.getList().add(newRule);
+ }
+ groupToAdd.setValue(null);
+ groupToAdd.setFocus(true);
+
+ } else {
+ // If the oracle didn't get to complete a UUID, resolve it now.
+ //
+ addRule.setEnabled(false);
+ SuggestUtil.SVC.suggestAccountGroup(ref.getName(), 1,
+ new GerritCallback<List<GroupReference>>() {
+ @Override
+ public void onSuccess(List<GroupReference> result) {
+ addRule.setEnabled(true);
+ if (result.size() == 1) {
+ addGroup(result.get(0));
+ } else {
+ groupToAdd.setFocus(true);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ addRule.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+ }
+
+ boolean isDeleted() {
+ return isDeleted;
+ }
+
+ @Override
+ public void setValue(Permission value) {
+ this.value = value;
+
+ if (value.isLabel()) {
+ ApprovalType at = Gerrit.getConfig().getApprovalTypes().byLabel(value.getLabel());
+ if (at != null) {
+ validRange = new PermissionRange.WithDefaults(
+ value.getName(),
+ at.getMin().getValue(), at.getMax().getValue(),
+ at.getMin().getValue(), at.getMax().getValue());
+ }
+ } else if (GlobalCapability.isCapability(value.getName())) {
+ validRange = GlobalCapability.getRange(value.getName());
+
+ } else {
+ validRange = null;
+ }
+
+ if (value != null && Permission.OWNER.equals(value.getName())) {
+ exclusiveGroup.setEnabled(false);
+ } else {
+ exclusiveGroup.setEnabled(!readOnly);
+ }
+ }
+
+ @Override
+ public void flush() {
+ List<PermissionRule> src = rules.getList();
+ List<PermissionRule> keep = new ArrayList<PermissionRule>(src.size());
+
+ for (int i = 0; i < src.size(); i++) {
+ PermissionRuleEditor e =
+ (PermissionRuleEditor) ruleContainer.getWidget(i);
+ if (!e.isDeleted()) {
+ keep.add(src.get(i));
+ }
+ }
+ value.setRules(keep);
+ }
+
+ @Override
+ public void onPropertyChange(String... paths) {
+ }
+
+ @Override
+ public void setDelegate(EditorDelegate<Permission> delegate) {
+ }
+
+ private class RuleEditorSource extends EditorSource<PermissionRuleEditor> {
+ @Override
+ public PermissionRuleEditor create(int index) {
+ PermissionRuleEditor subEditor =
+ new PermissionRuleEditor(readOnly, section, value, validRange);
+ ruleContainer.insert(subEditor, index);
+ return subEditor;
+ }
+
+ @Override
+ public void dispose(PermissionRuleEditor subEditor) {
+ subEditor.removeFromParent();
+ }
+
+ @Override
+ public void setIndex(PermissionRuleEditor subEditor, int index) {
+ ruleContainer.insert(subEditor, index);
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml
new file mode 100644
index 0000000..25995d9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.ui.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:e='urn:import:com.google.gwt.editor.ui.client'
+ xmlns:my='urn:import:com.google.gerrit.client.admin'
+ ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
+ ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
+ ui:generateLocales='default,en'
+ >
+<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
+<ui:style>
+ @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+ @eval backgroundColor com.google.gerrit.client.Gerrit.getTheme().backgroundColor;
+
+ .panel {
+ position: relative;
+ }
+
+ .normal {
+ border: 1px solid backgroundColor;
+ margin-top: -1px;
+ margin-bottom: -1px;
+ }
+
+ .header {
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-bottom: 1px;
+ white-space: nowrap;
+ }
+
+ .header:hover {
+ background-color: selectionColor;
+ }
+
+ .name {
+ font-style: italic;
+ }
+
+ .exclusiveGroup {
+ position: absolute;
+ top: 0;
+ right: 36px;
+ width: 7em;
+ font-size: 80%;
+ }
+
+ .addContainer {
+ padding-left: 10px;
+ position: relative;
+ }
+ .addContainer:hover {
+ background-color: selectionColor;
+ }
+ .addLink {
+ font-size: 80%;
+ }
+
+ .deleteIcon {
+ position: absolute;
+ top: 1px;
+ right: 12px;
+ }
+</ui:style>
+
+<g:HTMLPanel stylePrimaryName='{style.panel}'>
+<div ui:field='normal' class='{style.normal}'>
+ <div class='{style.header}'>
+ <g:ValueLabel styleName='{style.name}' ui:field='normalName'/>
+ <g:CheckBox
+ ui:field='exclusiveGroup'
+ addStyleNames='{style.exclusiveGroup}'
+ text='Exclusive'>
+ <ui:attribute name='text'/>
+ </g:CheckBox>
+ <g:Anchor
+ ui:field='deletePermission'
+ href='javascript:void'
+ styleName='{style.deleteIcon} {res.css.deleteIcon}'
+ title='Delete this permission (and nested rules)'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+ </div>
+ <g:FlowPanel ui:field='ruleContainer'/>
+ <div ui:field='addContainer' class='{style.addContainer}'>
+ <div ui:field='addStage1'>
+ <g:Anchor
+ ui:field='beginAddRule'
+ styleName='{style.addLink}'
+ href='javascript:void'
+ text='Add Group'>
+ <ui:attribute name='text'/>
+ </g:Anchor>
+ </div>
+ <div ui:field='addStage2' style='display: none'>
+ <ui:msg>Group Name: <my:GroupReferenceBox
+ ui:field='groupToAdd'
+ visibleLength='45'/></ui:msg>
+ <g:Button
+ ui:field='addRule'
+ text='Add'>
+ <ui:attribute name='text'/>
+ </g:Button>
+ <g:Anchor
+ ui:field='hideAddGroup'
+ href='javascript:void'
+ styleName='{style.deleteIcon} {res.css.deleteIcon}'
+ title='Cancel additional group'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+ </div>
+ </div>
+</div>
+
+<div
+ ui:field='deleted'
+ class='{res.css.deleted} {res.css.deletedBorder}'
+ style='display: none'>
+ <ui:msg>Permission <g:ValueLabel styleName='{style.name}' ui:field='deletedName'/> was deleted</ui:msg>
+ <g:Anchor
+ ui:field='undoDelete'
+ href='javascript:void'
+ styleName='{style.deleteIcon} {res.css.undoIcon}'
+ title='Undo deletion'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+</div>
+</g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
new file mode 100644
index 0000000..ad3473c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -0,0 +1,58 @@
+// 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.admin;
+
+import com.google.gerrit.common.data.Permission;
+import com.google.gwt.text.shared.Renderer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+class PermissionNameRenderer implements Renderer<String> {
+ static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer();
+
+ private static final Map<String, String> all;
+
+ static {
+ all = new HashMap<String, String>();
+ for (Map.Entry<String, String> e : Util.C.capabilityNames().entrySet()) {
+ all.put(e.getKey(), e.getValue());
+ all.put(e.getKey().toLowerCase(), e.getValue());
+ }
+ for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
+ all.put(e.getKey(), e.getValue());
+ all.put(e.getKey().toLowerCase(), e.getValue());
+ }
+ }
+
+ @Override
+ public String render(String varName) {
+ if (Permission.isLabel(varName)) {
+ return Util.M.label(new Permission(varName).getLabel());
+ }
+
+ String desc = all.get(varName);
+ if (desc == null) {
+ desc = all.get(varName.toLowerCase());
+ }
+ return desc != null ? desc : varName;
+ }
+
+ @Override
+ public void render(String object, Appendable appendable) throws IOException {
+ appendable.append(render(object));
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..98e6283
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
@@ -0,0 +1,218 @@
+// 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.admin;
+
+import static com.google.gerrit.common.data.Permission.PUSH;
+import static com.google.gerrit.common.data.Permission.PUSH_TAG;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.ValueAwareEditor;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.ValueListBox;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+public class PermissionRuleEditor extends Composite implements
+ Editor<PermissionRule>, ValueAwareEditor<PermissionRule> {
+ interface Binder extends UiBinder<HTMLPanel, PermissionRuleEditor> {
+ }
+
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField(provided = true)
+ ValueListBox<PermissionRule.Action> action;
+
+ @UiField(provided = true)
+ RangeBox min;
+
+ @UiField(provided = true)
+ RangeBox max;
+
+ @UiField
+ CheckBox force;
+
+ @UiField
+ Hyperlink groupNameLink;
+ @UiField
+ SpanElement groupNameSpan;
+ @UiField
+ SpanElement deletedGroupName;
+
+ @UiField
+ Anchor deleteRule;
+
+ @UiField
+ DivElement normal;
+ @UiField
+ DivElement deleted;
+
+ @UiField
+ SpanElement rangeEditor;
+
+ private boolean isDeleted;
+
+ public PermissionRuleEditor(boolean readOnly, AccessSection section,
+ Permission permission, PermissionRange.WithDefaults validRange) {
+ action = new ValueListBox<PermissionRule.Action>(actionRenderer);
+
+ if (validRange != null && 10 < validRange.getRangeSize()) {
+ min = new RangeBox.Box();
+ max = new RangeBox.Box();
+
+ } else if (validRange != null) {
+ RangeBox.List minList = new RangeBox.List();
+ RangeBox.List maxList = new RangeBox.List();
+ List<Integer> valueList = validRange.getValuesAsList();
+
+ minList.list.setValue(validRange.getMin());
+ maxList.list.setValue(validRange.getMax());
+
+ minList.list.setAcceptableValues(valueList);
+ maxList.list.setAcceptableValues(valueList);
+
+ min = minList;
+ max = maxList;
+
+ } else {
+ min = new RangeBox.Box();
+ max = new RangeBox.Box();
+
+ if (GlobalCapability.PRIORITY.equals(permission.getName())) {
+ action.setValue(PermissionRule.Action.INTERACTIVE);
+ action.setAcceptableValues(Arrays.asList(
+ PermissionRule.Action.INTERACTIVE,
+ PermissionRule.Action.BATCH));
+
+ } else {
+ action.setValue(PermissionRule.Action.ALLOW);
+ action.setAcceptableValues(Arrays.asList(
+ PermissionRule.Action.ALLOW,
+ PermissionRule.Action.DENY));
+ }
+ }
+
+ initWidget(uiBinder.createAndBindUi(this));
+
+ String name = permission.getName();
+ boolean canForce = PUSH.equals(name) || PUSH_TAG.equals(name);
+ if (canForce) {
+ String ref = section.getName();
+ canForce = !ref.startsWith("refs/for/") && !ref.startsWith("^refs/for/");
+ }
+ force.setVisible(canForce);
+ force.setEnabled(!readOnly);
+
+ if (validRange != null) {
+ min.setEnabled(!readOnly);
+ max.setEnabled(!readOnly);
+ action.getElement().getStyle().setDisplay(Display.NONE);
+
+ } else {
+ rangeEditor.getStyle().setDisplay(Display.NONE);
+ DOM.setElementPropertyBoolean(action.getElement(), "disabled", readOnly);
+ }
+
+ if (readOnly) {
+ deleteRule.removeFromParent();
+ deleteRule = null;
+ }
+ }
+
+ boolean isDeleted() {
+ return isDeleted;
+ }
+
+ @UiHandler("deleteRule")
+ void onDeleteRule(ClickEvent event) {
+ isDeleted = true;
+ normal.getStyle().setDisplay(Display.NONE);
+ deleted.getStyle().setDisplay(Display.BLOCK);
+ }
+
+ @UiHandler("undoDelete")
+ void onUndoDelete(ClickEvent event) {
+ isDeleted = false;
+ deleted.getStyle().setDisplay(Display.NONE);
+ normal.getStyle().setDisplay(Display.BLOCK);
+ }
+
+ @Override
+ public void setValue(PermissionRule value) {
+ GroupReference ref = value.getGroup();
+ if (ref.getUUID() != null) {
+ groupNameLink.setTargetHistoryToken(Dispatcher.toGroup(ref.getUUID()));
+ }
+
+ groupNameLink.setText(ref.getName());
+ groupNameSpan.setInnerText(ref.getName());
+ deletedGroupName.setInnerText(ref.getName());
+
+ groupNameLink.setVisible(ref.getUUID() != null);
+ UIObject.setVisible(groupNameSpan, ref.getUUID() == null);
+ }
+
+ @Override
+ public void setDelegate(EditorDelegate<PermissionRule> delegate) {
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void onPropertyChange(String... paths) {
+ }
+
+ private static class ActionRenderer implements
+ Renderer<PermissionRule.Action> {
+ @Override
+ public String render(PermissionRule.Action object) {
+ return object != null ? object.toString() : "";
+ }
+
+ @Override
+ public void render(PermissionRule.Action object, Appendable appendable)
+ throws IOException {
+ appendable.append(render(object));
+ }
+ }
+
+ private static final ActionRenderer actionRenderer = new ActionRenderer();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml
new file mode 100644
index 0000000..e60f3df
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:e='urn:import:com.google.gwt.editor.ui.client'
+ xmlns:my='urn:import:com.google.gerrit.client.admin'
+ xmlns:q='urn:import:com.google.gerrit.client.ui'
+ ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
+ ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
+ ui:generateLocales='default,en'
+ >
+<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
+<ui:style>
+ @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+
+ .panel {
+ position: relative;
+ height: 1.5em;
+ }
+
+ .panel:hover {
+ background-color: selectionColor;
+ }
+
+ .normal {
+ padding-left: 10px;
+ white-space: nowrap;
+ height: 100%;
+ }
+
+ .deleted {
+ height: 100%;
+ }
+
+ .actionList, .minmax {
+ font-size: 80%;
+ }
+
+ .forcePush {
+ position: absolute;
+ top: 0;
+ right: 36px;
+ width: 7em;
+ font-size: 80%;
+ }
+
+ .deleteIcon {
+ position: absolute;
+ top: 2px;
+ right: 11px;
+ }
+
+ .groupName {
+ display: inline;
+ }
+</ui:style>
+
+<g:HTMLPanel styleName='{style.panel}'>
+<div ui:field='normal' class='{style.normal}'>
+ <g:ValueListBox ui:field='action' styleName='{style.actionList}'/>
+ <span ui:field='rangeEditor'>
+ <g:Widget ui:field='min' styleName='{style.minmax}'/>
+ <g:Widget ui:field='max' styleName='{style.minmax}'/>
+ </span>
+
+ <q:Hyperlink ui:field='groupNameLink' styleName='{style.groupName}'/>
+ <span ui:field='groupNameSpan' styleName='{style.groupName}'/>
+
+ <g:CheckBox
+ ui:field='force'
+ addStyleNames='{style.forcePush}'
+ text='Force Push'>
+ <ui:attribute name='text'/>
+ </g:CheckBox>
+
+ <g:Anchor
+ ui:field='deleteRule'
+ href='javascript:void'
+ styleName='{style.deleteIcon} {res.css.deleteIcon}'
+ title='Delete this rule'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+</div>
+
+<div
+ ui:field='deleted'
+ class='{res.css.deleted} {style.deleted}'
+ style='display: none'>
+ <ui:msg>Group <span ui:field='deletedGroupName'/> was deleted</ui:msg>
+ <g:Anchor
+ ui:field='undoDelete'
+ href='javascript:void'
+ styleName='{style.deleteIcon} {res.css.undoIcon}'
+ title='Undo deletion'>
+ <ui:attribute name='title'/>
+ </g:Anchor>
+</div>
+</g:HTMLPanel>
+</ui:UiBinder>
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
new file mode 100644
index 0000000..f3c133b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -0,0 +1,159 @@
+// 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.admin;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.ProjectAccess;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.ValueAwareEditor;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProjectAccessEditor extends Composite implements
+ Editor<ProjectAccess>, ValueAwareEditor<ProjectAccess> {
+ interface Binder extends UiBinder<HTMLPanel, ProjectAccessEditor> {
+ }
+
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField
+ DivElement inheritsFrom;
+
+ @UiField
+ Hyperlink parentProject;
+
+ @UiField
+ FlowPanel localContainer;
+ ListEditor<AccessSection, AccessSectionEditor> local;
+
+ @UiField
+ Anchor addSection;
+
+ private ProjectAccess value;
+
+ private boolean editing;
+
+ public ProjectAccessEditor() {
+ initWidget(uiBinder.createAndBindUi(this));
+ local = ListEditor.of(new Source(localContainer));
+ }
+
+ @UiHandler("addSection")
+ void onAddSection(ClickEvent event) {
+ int index = local.getList().size();
+ local.getList().add(new AccessSection("refs/heads/*"));
+
+ AccessSectionEditor editor = local.getEditors().get(index);
+ editor.enableEditing();
+ editor.editRefPattern();
+ }
+
+ @Override
+ public void setValue(ProjectAccess value) {
+ // If the owner can edit the Global Capabilities but they don't exist in this
+ // project, create an empty one at the beginning of the list making it
+ // possible to add permissions to it.
+ if (editing
+ && value.isOwnerOf(AccessSection.GLOBAL_CAPABILITIES)
+ && value.getLocal(AccessSection.GLOBAL_CAPABILITIES) == null) {
+ value.getLocal().add(0, new AccessSection(AccessSection.GLOBAL_CAPABILITIES));
+ }
+
+ this.value = value;
+
+ Project.NameKey parent = value.getInheritsFrom();
+ if (parent != null) {
+ inheritsFrom.getStyle().setDisplay(Display.BLOCK);
+ parentProject.setText(parent.get());
+ parentProject.setTargetHistoryToken( //
+ Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS));
+ } else {
+ inheritsFrom.getStyle().setDisplay(Display.NONE);
+ }
+
+ addSection.setVisible(value != null && editing && !value.getOwnerOf().isEmpty());
+ }
+
+ @Override
+ public void flush() {
+ List<AccessSection> src = local.getList();
+ List<AccessSection> keep = new ArrayList<AccessSection>(src.size());
+
+ for (int i = 0; i < src.size(); i++) {
+ AccessSectionEditor e = (AccessSectionEditor) localContainer.getWidget(i);
+ if (!e.isDeleted() && !src.get(i).getPermissions().isEmpty()) {
+ keep.add(src.get(i));
+ }
+ }
+ value.setLocal(keep);
+ }
+
+ @Override
+ public void onPropertyChange(String... paths) {
+ }
+
+ @Override
+ public void setDelegate(EditorDelegate<ProjectAccess> delegate) {
+ }
+
+ void setEditing(final boolean editing) {
+ this.editing = editing;
+ addSection.setVisible(editing);
+ }
+
+ private class Source extends EditorSource<AccessSectionEditor> {
+ private final FlowPanel container;
+
+ Source(FlowPanel container) {
+ this.container = container;
+ }
+
+ @Override
+ public AccessSectionEditor create(int index) {
+ AccessSectionEditor subEditor = new AccessSectionEditor(value);
+ subEditor.setEditing(editing);
+ container.insert(subEditor, index);
+ return subEditor;
+ }
+
+ @Override
+ public void dispose(AccessSectionEditor subEditor) {
+ subEditor.removeFromParent();
+ }
+
+ @Override
+ public void setIndex(AccessSectionEditor subEditor, int index) {
+ container.insert(subEditor, index);
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
new file mode 100644
index 0000000..9360daa
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:q='urn:import:com.google.gerrit.client.ui'
+ ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
+ ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
+ ui:generateLocales='default,en'
+ >
+<ui:style>
+ .inheritsFrom {
+ margin-bottom: 0.5em;
+ }
+ .parentTitle {
+ font-weight: bold;
+ }
+ .parentLink {
+ display: inline;
+ }
+
+ .addContainer {
+ margin-top: 5px;
+ font-size: 80%;
+ }
+ .addContainer:hover {
+ background-color: selectionColor;
+ }
+</ui:style>
+
+<g:HTMLPanel>
+ <div ui:field='inheritsFrom' class='{style.inheritsFrom}'>
+ <span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span>
+ <q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/>
+ </div>
+
+ <g:FlowPanel ui:field='localContainer'/>
+ <div class='{style.addContainer}'>
+ <g:Anchor
+ ui:field='addSection'
+ href='javascript:void'
+ text='Add Reference'>
+ <ui:attribute name='text'/>
+ </g:Anchor>
+ </div>
+</g:HTMLPanel>
+</ui:UiBinder>
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 b87f6f1..fc864de 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
@@ -1,4 +1,4 @@
-// Copyright (C) 2008 The Android Open Source Project
+// 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.
@@ -14,46 +14,63 @@
package com.google.gerrit.client.admin;
-import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.FancyFlexTable;
-import com.google.gerrit.client.ui.Hyperlink;
-import com.google.gerrit.client.ui.SmallHeading;
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.common.data.GerritConfig;
-import com.google.gerrit.common.data.InheritedRefRight;
-import com.google.gerrit.common.data.ProjectDetail;
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.VerticalPanel;
-import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
public class ProjectAccessScreen extends ProjectScreen {
- private Panel parentPanel;
- private Hyperlink parentName;
+ interface Binder extends UiBinder<HTMLPanel, ProjectAccessScreen> {
+ }
- private RightsTable rights;
- private Button delRight;
- private AccessRightEditor rightEditor;
- private CheckBox showInherited;
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ interface Driver extends SimpleBeanEditorDriver< //
+ ProjectAccess, //
+ ProjectAccessEditor> {
+ }
+
+ @UiField
+ DivElement editTools;
+
+ @UiField
+ Button edit;
+
+ @UiField
+ Button cancel1;
+
+ @UiField
+ Button cancel2;
+
+ @UiField
+ ProjectAccessEditor accessEditor;
+
+ @UiField
+ DivElement commitTools;
+
+ @UiField
+ NpTextArea commitMessage;
+
+ @UiField
+ Button commit;
+
+ private Driver driver;
+
+ private ProjectAccess access;
public ProjectAccessScreen(final Project.NameKey toShow) {
super(toShow);
@@ -62,266 +79,89 @@
@Override
protected void onInitUI() {
super.onInitUI();
- initParent();
- initRights();
+ add(uiBinder.createAndBindUi(this));
+
+ driver = GWT.create(Driver.class);
+ accessEditor.setEditing(false);
+ driver.initialize(accessEditor);
}
@Override
protected void onLoad() {
super.onLoad();
- Util.PROJECT_SVC.projectDetail(getProjectKey(),
- new ScreenLoadCallback<ProjectDetail>(this) {
- public void preDisplay(final ProjectDetail result) {
- enableForm(true);
- display(result);
+ Util.PROJECT_SVC.projectAccess(getProjectKey(),
+ new ScreenLoadCallback<ProjectAccess>(this) {
+ @Override
+ public void preDisplay(ProjectAccess access) {
+ displayReadOnly(access);
}
});
}
- private void enableForm(final boolean on) {
- delRight.setEnabled(on);
- rightEditor.enableForm(on);
+ private void displayReadOnly(ProjectAccess access) {
+ this.access = access;
+ accessEditor.setEditing(false);
+ UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty());
+ edit.setEnabled(!access.getOwnerOf().isEmpty());
+ cancel1.setVisible(false);
+ UIObject.setVisible(commitTools, false);
+ driver.edit(access);
}
- private void initParent() {
- parentName = new Hyperlink("", "");
-
- showInherited = new CheckBox();
- showInherited.setValue(true);
- showInherited.addClickHandler(new ClickHandler() {
- public void onClick(ClickEvent event) {
- rights.showInherited(showInherited.getValue());
- }
- });
-
- Grid g = new Grid(2, 3);
- g.setWidget(0, 0, new SmallHeading(Util.C.headingParentProjectName()));
- g.setWidget(1, 0, parentName);
- g.setWidget(1, 1, showInherited);
- g.setText(1, 2, Util.C.headingShowInherited());
-
- parentPanel = new VerticalPanel();
- parentPanel.add(g);
- add(parentPanel);
+ @UiHandler("edit")
+ void onEdit(ClickEvent event) {
+ edit.setEnabled(false);
+ cancel1.setVisible(true);
+ UIObject.setVisible(commitTools, true);
+ accessEditor.setEditing(true);
+ driver.edit(access);
}
- private void initRights() {
- rights = new RightsTable();
-
- delRight = new Button(Util.C.buttonDeleteGroupMembers());
- delRight.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- final HashSet<RefRight.Key> refRightIds = rights.getRefRightIdsChecked();
- doDeleteRefRights(refRightIds);
- }
- });
-
- rightEditor = new AccessRightEditor(getProjectKey());
- rightEditor.addValueChangeHandler(new ValueChangeHandler<ProjectDetail>() {
- @Override
- public void onValueChange(ValueChangeEvent<ProjectDetail> event) {
- display(event.getValue());
- }
- });
-
- add(new SmallHeading(Util.C.headingAccessRights()));
- add(rights);
- add(delRight);
- add(rightEditor);
+ @UiHandler(value={"cancel1", "cancel2"})
+ void onCancel(ClickEvent event) {
+ Gerrit.display(PageLinks.toProjectAcceess(getProjectKey()));
}
- void display(final ProjectDetail result) {
- final Project project = result.project;
+ @UiHandler("commit")
+ void onCommit(ClickEvent event) {
+ ProjectAccess access = driver.flush();
- final Project.NameKey wildKey = Gerrit.getConfig().getWildProject();
- final boolean isWild = wildKey.equals(project.getNameKey());
- Project.NameKey parent = project.getParent();
- if (parent == null) {
- parent = wildKey;
+ if (driver.hasErrors()) {
+ Window.alert(Util.C.errorsMustBeFixed());
+ return;
}
- parentPanel.setVisible(!isWild);
- parentName.setTargetHistoryToken(Dispatcher.toProjectAdmin(parent, ACCESS));
- parentName.setText(parent.get());
+ String message = commitMessage.getText().trim();
+ if ("".equals(message)) {
+ message = null;
+ }
- rights.display(result.groups, result.rights);
+ enable(false);
+ Util.PROJECT_SVC.changeProjectAccess( //
+ getProjectKey(), //
+ access.getRevision(), //
+ message, //
+ access.getLocal(), //
+ new GerritCallback<ProjectAccess>() {
+ @Override
+ public void onSuccess(ProjectAccess access) {
+ enable(true);
+ commitMessage.setText("");
+ displayReadOnly(access);
+ }
- rightEditor.setVisible(result.canModifyAccess);
- delRight.setVisible(rights.getCanDelete());
+ @Override
+ public void onFailure(Throwable caught) {
+ enable(true);
+ super.onFailure(caught);
+ }
+ });
}
- private void doDeleteRefRights(final HashSet<RefRight.Key> refRightIds) {
- if (!refRightIds.isEmpty()) {
- Util.PROJECT_SVC.deleteRight(getProjectKey(), refRightIds,
- new GerritCallback<ProjectDetail>() {
- @Override
- public void onSuccess(final ProjectDetail result) {
- //The user could no longer modify access after deleting a ref right.
- display(result);
- }
- });
- }
- }
-
- private class RightsTable extends FancyFlexTable<InheritedRefRight> {
- boolean canDelete;
- Map<AccountGroup.Id, AccountGroup> groups;
-
- RightsTable() {
- table.setWidth("");
- table.setText(0, 2, Util.C.columnRightOrigin());
- table.setText(0, 3, Util.C.columnApprovalCategory());
- table.setText(0, 4, Util.C.columnGroupName());
- table.setText(0, 5, Util.C.columnRefName());
- table.setText(0, 6, Util.C.columnRightRange());
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
- fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(0, 5, Gerrit.RESOURCES.css().dataHeader());
- fmt.addStyleName(0, 6, Gerrit.RESOURCES.css().dataHeader());
-
- table.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(final ClickEvent event) {
- onOpenRow(table.getCellForEvent(event).getRowIndex());
- }
- });
- }
-
- HashSet<RefRight.Key> getRefRightIdsChecked() {
- final HashSet<RefRight.Key> refRightIds = new HashSet<RefRight.Key>();
- for (int row = 1; row < table.getRowCount(); row++) {
- RefRight r = getRowItem(row).getRight();
- if (r != null && table.getWidget(row, 1) instanceof CheckBox
- && ((CheckBox) table.getWidget(row, 1)).getValue()) {
- refRightIds.add(r.getKey());
- }
- }
- return refRightIds;
- }
-
- void display(final Map<AccountGroup.Id, AccountGroup> grps,
- final List<InheritedRefRight> refRights) {
- groups = grps;
- canDelete = false;
-
- while (1 < table.getRowCount())
- table.removeRow(table.getRowCount() - 1);
-
- for (final InheritedRefRight r : refRights) {
- final int row = table.getRowCount();
- table.insertRow(row);
- if (! showInherited.getValue() && r.isInherited()) {
- table.getRowFormatter().setVisible(row, false);
- }
- applyDataRowStyle(row);
- populate(row, r);
- }
- }
-
- protected void onOpenRow(final int row) {
- if (row > 0) {
- RefRight right = getRowItem(row).getRight();
- rightEditor.load(right, groups.get(right.getAccountGroupId()));
- }
- }
-
- void populate(final int row, final InheritedRefRight r) {
- final GerritConfig config = Gerrit.getConfig();
- final RefRight right = r.getRight();
- final ApprovalType ar =
- config.getApprovalTypes().getApprovalType(
- right.getApprovalCategoryId());
- final AccountGroup group = groups.get(right.getAccountGroupId());
-
- if (r.isInherited() || !r.isOwner()) {
- table.setText(row, 1, "");
- } else {
- table.setWidget(row, 1, new CheckBox());
- canDelete = true;
- }
-
- if (r.isInherited()) {
- Project.NameKey fromProject = right.getKey().getProjectNameKey();
- table.setWidget(row, 2, new Hyperlink(fromProject.get(), Dispatcher
- .toProjectAdmin(fromProject, ACCESS)));
- } else {
- table.setText(row, 2, "");
- }
-
- table.setText(row, 3, ar != null ? ar.getCategory().getName()
- : right.getApprovalCategoryId().get() );
-
- if (group != null) {
- table.setWidget(row, 4, new Hyperlink(group.getName(), Dispatcher
- .toAccountGroup(group.getId())));
- } else {
- table.setText(row, 4, Util.M.deletedGroup(right.getAccountGroupId()
- .get()));
- }
-
- table.setText(row, 5, right.getRefPatternForDisplay());
-
- {
- final SafeHtmlBuilder m = new SafeHtmlBuilder();
- final ApprovalCategoryValue min, max;
- min = ar != null ? ar.getValue(right.getMinValue()) : null;
- max = ar != null ? ar.getValue(right.getMaxValue()) : null;
-
- if (ar != null && ar.getCategory().isRange()) {
- formatValue(m, right.getMinValue(), min);
- m.br();
- }
- formatValue(m, right.getMaxValue(), max);
- SafeHtml.set(table, row, 6, m);
- }
-
- final FlexCellFormatter fmt = table.getFlexCellFormatter();
- fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
- fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 5, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 6, Gerrit.RESOURCES.css().dataCell());
- fmt.addStyleName(row, 6, Gerrit.RESOURCES.css()
- .projectAdminApprovalCategoryRangeLine());
-
- setRowItem(row, r);
- }
-
- public void showInherited(boolean visible) {
- for (int r = 0; r < table.getRowCount(); r++) {
- if (getRowItem(r) != null && getRowItem(r).isInherited()) {
- table.getRowFormatter().setVisible(r, visible);
- }
- }
- }
-
- private void formatValue(final SafeHtmlBuilder m, final short v,
- final ApprovalCategoryValue e) {
- m.openSpan();
- m
- .setStyleName(Gerrit.RESOURCES.css()
- .projectAdminApprovalCategoryValue());
- if (v == 0) {
- m.append(' ');
- } else if (v > 0) {
- m.append('+');
- }
- m.append(v);
- m.closeSpan();
- if (e != null) {
- m.append(": ");
- m.append(e.getName());
- }
- }
-
- private boolean getCanDelete() {
- return canDelete;
- }
+ private void enable(boolean enabled) {
+ commitMessage.setEnabled(enabled);
+ commit.setEnabled(enabled);
+ 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
new file mode 100644
index 0000000..2536159
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:my='urn:import:com.google.gerrit.client.admin'
+ xmlns:expui='urn:import:com.google.gwtexpui.globalkey.client'
+ ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
+ ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
+ ui:generateLocales='default,en'
+ >
+<ui:style>
+ @external .gwt-TextArea;
+
+ .commitMessage {
+ margin-top: 2em;
+ }
+ .commitMessage .gwt-TextArea {
+ margin: 5px 5px 5px 5px;
+ }
+</ui:style>
+
+<g:HTMLPanel>
+ <div ui:field='editTools'>
+ <g:Button
+ ui:field='edit'
+ text='Edit'>
+ <ui:attribute name='text'/>
+ </g:Button>
+ <g:Button
+ ui:field='cancel1'
+ text='Cancel'>
+ <ui:attribute name='text'/>
+ </g:Button>
+ </div>
+ <my:ProjectAccessEditor ui:field='accessEditor'/>
+ <div ui:field='commitTools'>
+ <div class='{style.commitMessage}'>
+ <ui:msg>Commit Message (optional):</ui:msg><br/>
+ <expui:NpTextArea
+ ui:field='commitMessage'
+ visibleLines='4'
+ characterWidth='60'
+ spellCheck='true'
+ />
+ </div>
+ <g:Button
+ ui:field='commit'
+ text='Save Changes'>
+ <ui:attribute name='text'/>
+ </g:Button>
+ <g:Button
+ ui:field='cancel2'
+ text='Cancel'>
+ <ui:attribute name='text'/>
+ </g:Button>
+ </div>
+ <div style='width: 35em; visibility: hidden;' />
+</g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RangeBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RangeBox.java
new file mode 100644
index 0000000..e07829e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RangeBox.java
@@ -0,0 +1,90 @@
+// 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.admin;
+
+import com.google.gwt.editor.client.IsEditor;
+import com.google.gwt.editor.client.adapters.TakesValueEditor;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.IntegerBox;
+import com.google.gwt.user.client.ui.ValueListBox;
+import com.google.gwt.user.client.ui.ValueBoxBase.TextAlignment;
+
+import java.io.IOException;
+
+abstract class RangeBox extends Composite implements
+ IsEditor<TakesValueEditor<Integer>> {
+ static final RangeRenderer rangeRenderer = new RangeRenderer();
+
+ private static class RangeRenderer implements Renderer<Integer> {
+ @Override
+ public String render(Integer object) {
+ if (0 <= object) {
+ return "+" + object;
+ } else {
+ return String.valueOf(object);
+ }
+ }
+
+ @Override
+ public void render(Integer object, Appendable appendable)
+ throws IOException {
+ appendable.append(render(object));
+ }
+ }
+
+ static class List extends RangeBox {
+ final ValueListBox<Integer> list;
+
+ List() {
+ list = new ValueListBox<Integer>(rangeRenderer);
+ initWidget(list);
+ }
+
+ @Override
+ void setEnabled(boolean on) {
+ DOM.setElementPropertyBoolean(list.getElement(), "disabled", !on);
+ }
+
+ @Override
+ public TakesValueEditor<Integer> asEditor() {
+ return list.asEditor();
+ }
+ }
+
+ static class Box extends RangeBox {
+ private final IntegerBox box;
+
+ Box() {
+ box = new IntegerBox();
+ box.setVisibleLength(10);
+ box.setAlignment(TextAlignment.RIGHT);
+ initWidget(box);
+ }
+
+ @Override
+ void setEnabled(boolean on) {
+ DOM.setElementPropertyBoolean(box.getElement(), "disabled", !on);
+ }
+
+ @Override
+ public TakesValueEditor<Integer> asEditor() {
+ return box.asEditor();
+ }
+ }
+
+ abstract void setEnabled(boolean on);
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
new file mode 100644
index 0000000..0cf5d48
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
@@ -0,0 +1,90 @@
+// 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.admin;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.text.shared.Parser;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.user.client.ui.ValueBox;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+public class RefPatternBox extends ValueBox<String> {
+ private static final Renderer<String> RENDERER = new Renderer<String>() {
+ public String render(String ref) {
+ return ref;
+ }
+
+ public void render(String ref, Appendable dst) throws IOException {
+ dst.append(render(ref));
+ }
+ };
+
+ private static final Parser<String> PARSER = new Parser<String>() {
+ public String parse(CharSequence text) throws ParseException {
+ String ref = text.toString();
+
+ if (ref.isEmpty()) {
+ throw new ParseException(Util.C.refErrorEmpty(), 0);
+ }
+
+ if (ref.charAt(0) == '/') {
+ throw new ParseException(Util.C.refErrorBeginSlash(), 0);
+ }
+
+ if (ref.charAt(0) == '^') {
+ if (!ref.startsWith("^refs/")) {
+ ref = "^refs/heads/" + ref.substring(1);
+ }
+ } else if (!ref.startsWith("refs/")) {
+ ref = "refs/heads/" + ref;
+ }
+
+ for (int i = 0; i < ref.length(); i++) {
+ final char c = ref.charAt(i);
+
+ if (c == '/' && 0 < i && ref.charAt(i - 1) == '/') {
+ throw new ParseException(Util.C.refErrorDoubleSlash(), i);
+ }
+
+ if (c == ' ') {
+ throw new ParseException(Util.C.refErrorNoSpace(), i);
+ }
+
+ if (c < ' ') {
+ throw new ParseException(Util.C.refErrorPrintable(), i);
+ }
+ }
+ return ref;
+ }
+ };
+
+ public RefPatternBox() {
+ super(Document.get().createTextInputElement(), RENDERER, PARSER);
+ addKeyPressHandler(GlobalKey.STOP_PROPAGATION);
+ addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == ' ') {
+ event.preventDefault();
+ }
+ }
+ });
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 4167116..c599ee9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -32,6 +32,8 @@
PROJECT_SVC = GWT.create(ProjectAdminService.class);
JsonUtil.bind(PROJECT_SVC, "rpc/ProjectAdminService");
+
+ AdminResources.I.css().ensureInjected();
}
public static String toLongString(final Project.SubmitType type) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java
new file mode 100644
index 0000000..a7463b4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.java
@@ -0,0 +1,213 @@
+// 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.admin;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.HasEditorErrors;
+import com.google.gwt.editor.client.IsEditor;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.ui.client.adapters.ValueBoxEditor;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiChild;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.ValueBoxBase;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.text.ParseException;
+import java.util.List;
+
+public class ValueEditor<T> extends Composite implements HasEditorErrors<T>,
+ IsEditor<ValueBoxEditor<T>>, LeafValueEditor<T>, Focusable {
+ interface Binder extends UiBinder<Widget, ValueEditor<?>> {
+ }
+
+ static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField
+ SimplePanel textPanel;
+ private Label textLabel;
+ private StartEditHandlers startHandlers;
+
+ @UiField
+ Image editIcon;
+
+ @UiField
+ SimplePanel editPanel;
+
+ @UiField
+ DivElement errorLabel;
+
+ private ValueBoxBase<T> editChild;
+ private ValueBoxEditor<T> editProxy;
+ private boolean ignoreEditorValue;
+ private T value;
+
+ public ValueEditor() {
+ startHandlers = new StartEditHandlers();
+ initWidget(uiBinder.createAndBindUi(this));
+ editPanel.setVisible(false);
+ editIcon.addClickHandler(startHandlers);
+ }
+
+ public void edit() {
+ textPanel.removeFromParent();
+ textPanel = null;
+ textLabel = null;
+
+ editIcon.removeFromParent();
+ editIcon = null;
+ startHandlers = null;
+
+ editPanel.setVisible(true);
+ }
+
+ public ValueBoxEditor<T> asEditor() {
+ if (editProxy == null) {
+ editProxy = new EditorProxy();
+ }
+ return editProxy;
+ }
+
+ @Override
+ public T getValue() {
+ return ignoreEditorValue ? value : asEditor().getValue();
+ }
+
+ @Override
+ public void setValue(T value) {
+ this.value = value;
+ asEditor().setValue(value);
+ }
+
+ void setIgnoreEditorValue(boolean off) {
+ ignoreEditorValue = off;
+ }
+
+ public void setEditTitle(String title) {
+ editIcon.setTitle(title);
+ }
+
+ @UiChild(limit = 1, tagname = "display")
+ public void setDisplay(Label widget) {
+ textLabel = widget;
+ textPanel.add(textLabel);
+
+ textLabel.addClickHandler(startHandlers);
+ textLabel.addDoubleClickHandler(startHandlers);
+ }
+
+ @UiChild(limit = 1, tagname = "editor")
+ public void setEditor(ValueBoxBase<T> widget) {
+ editChild = widget;
+ editPanel.add(editChild);
+ editProxy = null;
+ }
+
+ public void setEnabled(boolean enabled) {
+ editIcon.setVisible(enabled);
+ startHandlers.enabled = enabled;
+ }
+
+ public void showErrors(List<EditorError> errors) {
+ StringBuilder buf = new StringBuilder();
+ for (EditorError error : errors) {
+ if (error.getEditor().equals(editProxy)) {
+ buf.append("\n");
+ if (error.getUserData() instanceof ParseException) {
+ buf.append(((ParseException) error.getUserData()).getMessage());
+ } else {
+ buf.append(error.getMessage());
+ }
+ }
+ }
+
+ if (0 < buf.length()) {
+ errorLabel.setInnerText(buf.substring(1));
+ errorLabel.getStyle().setDisplay(Display.BLOCK);
+ } else {
+ errorLabel.setInnerText("");
+ errorLabel.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ @Override
+ public void setAccessKey(char key) {
+ editChild.setAccessKey(key);
+ }
+
+ @Override
+ public void setFocus(boolean focused) {
+ editChild.setFocus(focused);
+ if (focused) {
+ editChild.setCursorPos(editChild.getText().length());
+ }
+ }
+
+ @Override
+ public int getTabIndex() {
+ return editChild.getTabIndex();
+ }
+
+ @Override
+ public void setTabIndex(int index) {
+ editChild.setTabIndex(index);
+ }
+
+ private class StartEditHandlers implements ClickHandler, DoubleClickHandler {
+ boolean enabled;
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (enabled && event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
+ edit();
+ }
+ }
+
+ @Override
+ public void onDoubleClick(DoubleClickEvent event) {
+ if (enabled && event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
+ edit();
+ }
+ }
+ }
+
+ private class EditorProxy extends ValueBoxEditor<T> {
+ EditorProxy() {
+ super(editChild);
+ }
+
+ @Override
+ public void setValue(T value) {
+ super.setValue(value);
+ if (textLabel == null) {
+ setDisplay(new Label());
+ }
+ textLabel.setText(editChild.getText());
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml
new file mode 100644
index 0000000..9862848
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ValueEditor.ui.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<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.admin.AdminResources'/>
+<ui:style>
+ .panel {
+ position: relative;
+ white-space: nowrap;
+ }
+
+ .textPanel {
+ width: 100%;
+ padding-right: 21px;
+ }
+
+ .editIcon {
+ position: absolute;
+ top: 0;
+ right: 5px;
+ }
+
+ .editPanel {
+ width: 100%;
+ }
+
+ .errorLabel {
+ display: none;
+ color: red;
+ white-space: pre;
+ }
+</ui:style>
+<g:HTMLPanel stylePrimaryName='{style.panel}'>
+ <g:Image
+ ui:field='editIcon'
+ resource='{res.editText}'
+ stylePrimaryName='{style.editIcon}'
+ title='Edit'>
+ <ui:attribute name='title'/>
+ </g:Image>
+ <g:SimplePanel ui:field='textPanel' stylePrimaryName='{style.textPanel}'/>
+
+ <g:SimplePanel ui:field='editPanel' stylePrimaryName='{style.editPanel}'/>
+ <div
+ ui:field='errorLabel'
+ class='{style.errorLabel}'/>
+</g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/admin.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/admin.css
new file mode 100644
index 0000000..eca4823
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/admin.css
@@ -0,0 +1,53 @@
+/* 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.
+ */
+
+@eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+@eval textColor com.google.gerrit.client.Gerrit.getTheme().textColor;
+@def deletedBackground #a9a9a9;
+
+@sprite .deleteIcon {
+ gwt-image: 'deleteNormal';
+ border: none;
+}
+
+@sprite .deleteIcon:hover {
+ gwt-image: 'deleteHover';
+ border: none;
+}
+
+@sprite .undoIcon {
+ gwt-image: 'undoNormal';
+ border: none;
+}
+
+.deleted {
+ background-color: deletedBackground;
+ color: #ffffff;
+ white-space: nowrap;
+ padding-left: 50px;
+}
+
+.deleted:hover {
+ background-color: selectionColor;
+ color: textColor;
+ }
+
+.deletedBorder {
+ background: 1px solid deletedBackground;
+}
+
+.deleteSectionHover {
+ background-color: selectionColor !important;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png
new file mode 100644
index 0000000..839e8ef
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png
new file mode 100644
index 0000000..ffddb6f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png
new file mode 100644
index 0000000..188e1c1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png
new file mode 100644
index 0000000..8b0fef9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png
Binary files differ
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 a20b02e..26d4a12 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
@@ -24,11 +24,11 @@
import com.google.gerrit.common.data.AccountInfoCache;
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.ChangeDetail;
import com.google.gerrit.common.data.ReviewerResult;
+import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -46,13 +46,15 @@
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
/** Displays a table of {@link ApprovalDetail} objects for a change record. */
public class ApprovalTable extends Composite {
- private final List<ApprovalType> types;
+ private final ApprovalTypes types;
private final Grid table;
private final Widget missing;
private final Panel addReviewer;
@@ -61,10 +63,9 @@
private AccountInfoCache accountCache = AccountInfoCache.empty();
public ApprovalTable() {
- types = Gerrit.getConfig().getApprovalTypes().getApprovalTypes();
- table = new Grid(1, 3 + types.size());
+ types = Gerrit.getConfig().getApprovalTypes();
+ table = new Grid(1, 3);
table.addStyleName(Gerrit.RESOURCES.css().infoTable());
- displayHeader();
missing = new Widget() {
{
@@ -95,7 +96,9 @@
setStyleName(Gerrit.RESOURCES.css().approvalTable());
}
- private void displayHeader() {
+ private void displayHeader(List<String> labels) {
+ table.resizeColumns(2 + labels.size());
+
final CellFormatter fmt = table.getCellFormatter();
int col = 0;
@@ -107,16 +110,12 @@
fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header());
col++;
- for (final ApprovalType t : types) {
- table.setText(0, col, t.getCategory().getName());
+ for (String name : labels) {
+ table.setText(0, col, name);
fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header());
col++;
}
-
- table.clearCell(0, col);
- fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header());
- fmt.addStyleName(0, col, Gerrit.RESOURCES.css().rightmost());
- col++;
+ fmt.addStyleName(0, col - 1, Gerrit.RESOURCES.css().rightmost());
}
public void setAccountInfoCache(final AccountInfoCache aic) {
@@ -128,40 +127,115 @@
return AccountDashboardLink.link(accountCache, id);
}
- public void display(final Change change, final Set<ApprovalCategory.Id> need,
- final List<ApprovalDetail> rows) {
- changeId = change.getId();
+ void display(ChangeDetail detail) {
+ List<String> columns = new ArrayList<String>();
+ List<ApprovalDetail> rows = detail.getApprovals();
- if (rows.isEmpty()) {
- table.setVisible(false);
- } else {
- table.resizeRows(1 + rows.size());
- for (int i = 0; i < rows.size(); i++) {
- displayRow(i + 1, rows.get(i), change);
- }
- table.setVisible(true);
- }
+ changeId = detail.getChange().getId();
final Element missingList = missing.getElement();
while (DOM.getChildCount(missingList) > 0) {
DOM.removeChild(missingList, DOM.getChild(missingList, 0));
}
-
missing.setVisible(false);
- if (need != null) {
- for (final ApprovalType at : types) {
- if (need.contains(at.getCategory().getId())) {
- final Element li = DOM.createElement("li");
- li.setClassName(Gerrit.RESOURCES.css().missingApproval());
- DOM.setInnerText(li, Util.M.needApproval(at.getCategory().getName(),
- at.getMax().formatValue(), at.getMax().getName()));
- DOM.appendChild(missingList, li);
- missing.setVisible(true);
+
+ if (detail.getSubmitRecords() != null) {
+ HashSet<String> reportedMissing = new HashSet<String>();
+
+ HashMap<Account.Id, ApprovalDetail> byUser =
+ new HashMap<Account.Id, ApprovalDetail>();
+ for (ApprovalDetail ad : detail.getApprovals()) {
+ byUser.put(ad.getAccount(), ad);
+ }
+
+ for (SubmitRecord rec : detail.getSubmitRecords()) {
+ if (rec.labels == null) {
+ continue;
}
+
+ for (SubmitRecord.Label lbl : rec.labels) {
+ if (!columns.contains(lbl.label)) {
+ columns.add(lbl.label);
+ }
+
+ switch (lbl.status) {
+ case OK: {
+ ApprovalDetail ad = byUser.get(lbl.appliedBy);
+ if (ad != null) {
+ ad.approved(lbl.label);
+ }
+ break;
+ }
+
+ case REJECT: {
+ ApprovalDetail ad = byUser.get(lbl.appliedBy);
+ if (ad != null) {
+ ad.rejected(lbl.label);
+ }
+ break;
+ }
+
+ case NEED:
+ case IMPOSSIBLE:
+ if (reportedMissing.add(lbl.label)) {
+ Element li = DOM.createElement("li");
+ li.setClassName(Gerrit.RESOURCES.css().missingApproval());
+ DOM.setInnerText(li, Util.M.needApproval(lbl.label));
+ DOM.appendChild(missingList, li);
+ }
+ break;
+ }
+ }
+ }
+ missing.setVisible(!reportedMissing.isEmpty());
+
+ } else {
+ for (ApprovalDetail ad : rows) {
+ 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 (!columns.contains(labelName)) {
+ columns.add(labelName);
+ }
+ }
+ Collections.sort(columns, new Comparator<String>() {
+ @Override
+ public int compare(String o1, String o2) {
+ ApprovalType a = types.byLabel(o1);
+ ApprovalType b = types.byLabel(o2);
+ int cmp = 0;
+ if (a != null && b != null) {
+ cmp = a.getCategory().getPosition() - b.getCategory().getPosition();
+ }
+ if (cmp == 0) {
+ cmp = o1.compareTo(o2);
+ }
+ return cmp;
+ }
+ });
}
}
- addReviewer.setVisible(Gerrit.isSignedIn() && change.getStatus().isOpen());
+ if (rows.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.setVisible(true);
+ }
+
+ addReviewer.setVisible(Gerrit.isSignedIn());
}
private void doAddReviewer() {
@@ -210,10 +284,11 @@
final ChangeDetail r = result.getChange();
if (r != null) {
setAccountInfoCache(r.getAccounts());
- display(r.getChange(), r.getMissingApprovals(), r.getApprovals());
+ display(r);
}
}
+
@Override
public void onFailure(final Throwable caught) {
addMemberBox.setEnabled(true);
@@ -223,10 +298,8 @@
}
private void displayRow(final int row, final ApprovalDetail ad,
- final Change change) {
+ final Change change, List<String> columns) {
final CellFormatter fmt = table.getCellFormatter();
- final Map<ApprovalCategory.Id, PatchSetApproval> am = ad.getApprovalMap();
- final StringBuilder hint = new StringBuilder();
int col = 0;
table.setWidget(row, col++, link(ad.getAccount()));
@@ -250,31 +323,30 @@
}
fmt.setStyleName(row, col++, Gerrit.RESOURCES.css().removeReviewerCell());
- for (final ApprovalType type : types) {
+ for (String labelName : columns) {
fmt.setStyleName(row, col, Gerrit.RESOURCES.css().approvalscore());
- final PatchSetApproval ca = am.get(type.getCategory().getId());
- if (ca == null || ca.getValue() == 0) {
- table.clearCell(row, col);
- col++;
- continue;
- }
-
- final ApprovalCategoryValue acv = type.getValue(ca);
- if (acv != null) {
- if (hint.length() > 0) {
- hint.append("; ");
- }
- hint.append(acv.getName());
- }
-
- if (type.isMaxNegative(ca)) {
+ if (ad.isRejected(labelName)) {
table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot()));
- } else if (type.isMaxPositive(ca)) {
+ } else if (ad.isApproved(labelName)) {
table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck()));
} else {
+ ApprovalType legacyType = types.byLabel(labelName);
+ if (legacyType == null) {
+ table.clearCell(row, col);
+ col++;
+ continue;
+ }
+
+ PatchSetApproval ca = ad.getPatchSetApproval(legacyType.getCategory().getId());
+ if (ca == null || ca.getValue() == 0) {
+ table.clearCell(row, col);
+ col++;
+ continue;
+ }
+
String vstr = String.valueOf(ca.getValue());
if (ca.getValue() > 0) {
vstr = "+" + vstr;
@@ -288,10 +360,7 @@
col++;
}
- table.setText(row, col, hint.toString());
- fmt.setStyleName(row, col, Gerrit.RESOURCES.css().rightmost());
- fmt.addStyleName(row, col, Gerrit.RESOURCES.css().approvalhint());
- col++;
+ fmt.addStyleName(row, col - 1, Gerrit.RESOURCES.css().rightmost());
}
private void doRemove(final ApprovalDetail ad, final PushButton remove) {
@@ -302,9 +371,20 @@
public void onSuccess(ReviewerResult result) {
if (result.getErrors().isEmpty()) {
final ChangeDetail r = result.getChange();
- display(r.getChange(), r.getMissingApprovals(), r.getApprovals());
+ display(r);
} else {
- new ErrorDialog(result.getErrors().get(0).toString()).center();
+ final ReviewerResult.Error resultError =
+ result.getErrors().get(0);
+ String message;
+ switch (resultError.getType()) {
+ case REMOVE_NOT_PERMITTED:
+ message = Util.C.approvalTableRemoveNotPermitted();
+ break;
+ case COULD_NOT_REMOVE:
+ default:
+ message = Util.C.approvalTableCouldNotRemove();
+ }
+ new ErrorDialog(message + " " + resultError.getName()).center();
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index e4fe6d2..0618294 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -78,6 +78,8 @@
String approvalTableReviewer();
String approvalTableAddReviewer();
+ String approvalTableRemoveNotPermitted();
+ String approvalTableCouldNotRemove();
String changeInfoBlockOwner();
String changeInfoBlockProject();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 7032dda..e65832d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -55,6 +55,8 @@
approvalTableReviewer = Reviewer
approvalTableAddReviewer = Add Reviewer
+approvalTableRemoveNotPermitted = Not allowed to remove reviewer
+approvalTableCouldNotRemove = Could not remove reviewer
changeInfoBlockOwner = Owner
changeInfoBlockProject = Project
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index cbf4a6b..53d1c18 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -43,7 +43,7 @@
String copiedFrom(String sourcePath);
String otherFrom(String sourcePath);
- String needApproval(String categoryName, String value, String valueName);
+ String needApproval(String labelName);
String publishComments(String changeId, int ps);
String lineHeader(int line);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index 5982ad5..6f6e34e2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -24,7 +24,7 @@
copiedFrom = copied from {0}
otherFrom = from {0}
-needApproval = Need {0} {1} ({2})
+needApproval = Need {0}
publishComments = Change {0} - Patch Set {1}: Publish Comments
lineHeader = Line {0}:
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 edb25e4..6bd4ebf 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
@@ -306,8 +306,7 @@
.getCurrentPatchSetDetail().getInfo(), detail.getAccounts());
dependsOn.display(detail.getDependsOn());
neededBy.display(detail.getNeededBy());
- approvals.display(detail.getChange(), detail.getMissingApprovals(), detail
- .getApprovals());
+ approvals.display(detail);
for (PatchSet pId : detail.getPatchSets()) {
if (patchesList != null) {
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 48e257d..1a6ea3e 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
@@ -18,7 +18,6 @@
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -26,20 +25,9 @@
private final HTML description;
public CommitMessageBlock() {
- this(null);
- }
-
- public CommitMessageBlock(String height) {
description = new HTML();
description.setStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
- if (height != null) {
- ScrollPanel scrollPanel = new ScrollPanel();
- scrollPanel.setHeight(height);
- scrollPanel.add(description);
- initWidget(scrollPanel);
- } else {
- initWidget(description);
- }
+ initWidget(description);
}
public void display(final String commitMessage) {
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 740e08c..f4862ee 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,8 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
-import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Patch;
@@ -35,8 +36,6 @@
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.UserIdentity;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadCommand;
-import com.google.gerrit.reviewdb.AccountGeneralPreferences.DownloadScheme;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -54,7 +53,6 @@
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -402,12 +400,8 @@
private void populateActions(final PatchSetDetail detail) {
final boolean isOpen = changeDetail.getChange().getStatus().isOpen();
- Set<ApprovalCategory.Id> allowed = changeDetail.getCurrentActions();
- if (allowed == null) {
- allowed = Collections.emptySet();
- }
- if (isOpen && allowed.contains(ApprovalCategory.SUBMIT)) {
+ if (isOpen && changeDetail.canSubmit()) {
final Button b =
new Button(Util.M
.submitPatchSet(detail.getPatchSet().getPatchSetId()));
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 0ed58b4..274f8f5 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
@@ -25,8 +25,10 @@
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
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.PermissionRange;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
@@ -39,11 +41,11 @@
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -54,7 +56,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
public class PublishCommentScreen extends AccountScreen implements
ClickHandler, CommentEditorContainer {
@@ -218,16 +219,20 @@
}
private void initApprovals(final PatchSetPublishDetail r, final Panel body) {
- for (final ApprovalType ct : Gerrit.getConfig().getApprovalTypes()
- .getApprovalTypes()) {
- if (r.isAllowed(ct.getCategory().getId())) {
- initApprovalType(r, body, ct);
+ ApprovalTypes types = Gerrit.getConfig().getApprovalTypes();
+ for (PermissionRange range : r.getLabels()) {
+ ApprovalType type = types.byLabel(range.getLabel());
+ if (type != null) {
+ // Legacy type, use radio buttons.
+ initApprovalType(r, body, type, range);
+ } else {
+ // TODO Newer style label.
}
}
}
private void initApprovalType(final PatchSetPublishDetail r,
- final Panel body, final ApprovalType ct) {
+ final Panel body, final ApprovalType ct, final PermissionRange range) {
body.add(new SmallHeading(ct.getCategory().getName() + ":"));
final VerticalPanel vp = new VerticalPanel();
@@ -236,11 +241,10 @@
new ArrayList<ApprovalCategoryValue>(ct.getValues());
Collections.reverse(lst);
final ApprovalCategory.Id catId = ct.getCategory().getId();
- final Set<ApprovalCategoryValue.Id> allowed = r.getAllowed(catId);
final PatchSetApproval prior = r.getChangeApproval(catId);
for (final ApprovalCategoryValue buttonValue : lst) {
- if (!allowed.contains(buttonValue.getId())) {
+ if (!range.contains(buttonValue.getValue())) {
continue;
}
@@ -306,7 +310,7 @@
}
}
- submit.setVisible(r.isSubmitAllowed());
+ submit.setVisible(r.canSubmit());
}
private void onSend(final boolean submit) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index 6452e08..20589de 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -175,7 +175,7 @@
protected abstract void onInsertComment(PatchLine pl);
- public abstract void display(CommentDetail comments);
+ public abstract void display(CommentDetail comments, boolean expandComments);
@Override
protected MyFlexTable createFlexTable() {
@@ -331,9 +331,12 @@
return getRowItem(row) instanceof CommentList;
}
- /** Invoked when the user clicks on a table cell. */
+ /** Invoked when the user double clicks on a table cell. */
protected abstract void onCellDoubleClick(int row, int column);
+ /** Invoked when the user clicks on a table cell. */
+ protected abstract void onCellSingleClick(int row, int column);
+
/**
* Invokes createCommentEditor() with an empty string as value for the comment
* parent UUID. This method is invoked by callers that want to create an
@@ -529,7 +532,7 @@
}
protected void bindComment(final int row, final int col,
- final PatchLineComment line, final boolean isLast) {
+ final PatchLineComment line, final boolean isLast, boolean expandComment) {
if (line.getStatus() == PatchLineComment.Status.DRAFT) {
final CommentEditorPanel plc = new CommentEditorPanel(line);
plc.addFocusHandler(this);
@@ -541,6 +544,7 @@
final AccountInfo author = accountCache.get(line.getAuthor());
final PublishedCommentPanel panel =
new PublishedCommentPanel(author, line);
+ panel.setOpen(expandComment);
panel.addFocusHandler(this);
panel.addBlurHandler(this);
table.setWidget(row, col, panel);
@@ -619,6 +623,7 @@
final int row = rowOf(td);
if (getRowItem(row) != null) {
movePointerTo(row);
+ onCellSingleClick(rowOf(td), columnOf(td));
return;
}
break;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.java
index d48c2f8..0f4adab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.java
@@ -19,8 +19,6 @@
import java.util.Date;
public interface PatchMessages extends Messages {
- String patchWindowTitle(String changeId, String file);
- String patchPageTitle(String changeId, String path);
String patchSkipRegion(@PluralCount int lineCnt);
String draftSaved(Date when);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.properties
index 961b0e5e..9d7a2fb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchMessages.properties
@@ -1,6 +1,3 @@
-patchWindowTitle = Change {0}: {1}
-patchPageTitle = Change {0}: {1}
-
patchSkipRegion = (... skipping {0} common lines ...)
draftSaved = Draft saved at {0,time,short}
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 736d1f0..dcfb34a 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
@@ -30,25 +30,17 @@
import com.google.gerrit.prettify.client.ClientSideFormatter;
import com.google.gerrit.prettify.common.PrettyFactory;
import com.google.gerrit.reviewdb.AccountDiffPreference;
-import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.event.logical.shared.OpenEvent;
-import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
@@ -70,7 +62,7 @@
}
@Override
- protected PatchScreen.Type getPatchScreenType() {
+ public PatchScreen.Type getPatchScreenType() {
return PatchScreen.Type.SIDE_BY_SIDE;
}
}
@@ -87,7 +79,7 @@
}
@Override
- protected PatchScreen.Type getPatchScreenType() {
+ public PatchScreen.Type getPatchScreenType() {
return PatchScreen.Type.UNIFIED;
}
}
@@ -96,21 +88,12 @@
private static PatchSet.Id diffSideA = null;
private static PatchSet.Id diffSideB = null;
- private static Boolean historyOpen = null;
- private static final OpenHandler<DisclosurePanel> cacheOpenState =
- new OpenHandler<DisclosurePanel>() {
- @Override
- public void onOpen(OpenEvent<DisclosurePanel> event) {
- historyOpen = true;
- }
- };
- private static final CloseHandler<DisclosurePanel> cacheCloseState =
- new CloseHandler<DisclosurePanel>() {
- @Override
- public void onClose(CloseEvent<DisclosurePanel> event) {
- historyOpen = false;
- }
- };
+ /**
+ * What should be displayed in the top of the screen
+ */
+ public static enum TopView {
+ MAIN, COMMIT, PREFERENCES, PATCH_SETS, FILES
+ }
protected final Patch.Key patchKey;
protected PatchSetDetail patchSetDetail;
@@ -119,8 +102,8 @@
protected PatchSet.Id idSideB;
protected PatchScriptSettingsPanel settingsPanel;
- private DisclosurePanel historyPanel;
private HistoryTable historyTable;
+ private FlowPanel topPanel;
private FlowPanel contentPanel;
private Label noDifference;
private AbstractPatchContentTable contentTable;
@@ -158,9 +141,6 @@
} else {
diffSideA = null;
}
- if (diffSideA == null) {
- historyOpen = null;
- }
idSideA = diffSideA; // null here means we're diff'ing from the Base
idSideB = diffSideB != null ? diffSideB : id.getParentKey();
@@ -251,26 +231,11 @@
keysNavigation.add(new FileListCmd(0, 'f', PatchUtil.C.fileList()));
historyTable = new HistoryTable(this);
- historyPanel = new DisclosurePanel(PatchUtil.C.patchHistoryTitle());
- historyPanel.setContent(historyTable);
- historyPanel.setVisible(false);
- // If the user selected a different patch set than the default for either
- // side, expand the history panel
- historyPanel.setOpen(diffSideA != null || diffSideB != null
- || (historyOpen != null && historyOpen));
- historyPanel.addOpenHandler(cacheOpenState);
- historyPanel.addCloseHandler(cacheCloseState);
+ commitMessageBlock = new CommitMessageBlock();
- VerticalPanel vp = new VerticalPanel();
- vp.add(historyPanel);
- vp.add(settingsPanel);
- commitMessageBlock = new CommitMessageBlock("6em");
- HorizontalPanel hp = new HorizontalPanel();
- hp.setWidth("100%");
- hp.add(vp);
- hp.add(commitMessageBlock);
- add(hp);
+ topPanel = new FlowPanel();
+ add(topPanel);
noDifference = new Label(PatchUtil.C.noDifference());
noDifference.setStyleName(Gerrit.RESOURCES.css().patchNoDifference());
@@ -360,7 +325,23 @@
protected abstract AbstractPatchContentTable createContentTable();
- protected abstract PatchScreen.Type getPatchScreenType();
+ public abstract PatchScreen.Type getPatchScreenType();
+
+ public Patch.Key getPatchKey() {
+ return patchKey;
+ }
+
+ public int getPatchIndex() {
+ return patchIndex;
+ }
+
+ public PatchSetDetail getPatchSetDetail() {
+ return patchSetDetail;
+ }
+
+ public PatchTable getFileList() {
+ return fileList;
+ }
protected void refresh(final boolean isFirst) {
final int rpcseq = ++rpcSequence;
@@ -386,8 +367,6 @@
}
private void onResult(final PatchScript script, final boolean isFirst) {
-
- final Change.Key cid = script.getChangeId();
final String path = PatchTable.getDisplayFileName(patchKey);
String fileName = path;
final int last = fileName.lastIndexOf('/');
@@ -395,8 +374,8 @@
fileName = fileName.substring(last + 1);
}
- setWindowTitle(PatchUtil.M.patchWindowTitle(cid.abbreviate(), fileName));
- setPageTitle(PatchUtil.M.patchPageTitle(cid.abbreviate(), path));
+ setWindowTitle(fileName);
+ setPageTitle(path);
if (idSideB.equals(patchSetDetail.getPatchSet().getId())) {
commitMessageBlock.setVisible(true);
@@ -414,7 +393,6 @@
}
historyTable.display(script.getHistory());
- historyPanel.setVisible(true);
// True if there are differences between the two patch sets
boolean hasEdits = !script.getEdits().isEmpty();
@@ -438,7 +416,7 @@
if (hasDifferences) {
contentTable.display(patchKey, idSideA, idSideB, script);
- contentTable.display(script.getCommentDetail());
+ contentTable.display(script.getCommentDetail(), script.isExpandAllComments());
contentTable.finishDisplay();
}
showPatch(hasDifferences);
@@ -489,6 +467,20 @@
diffSideB = patchSetId;
}
+ public void setTopView(TopView tv) {
+ topPanel.clear();
+ switch(tv) {
+ case COMMIT: topPanel.add(commitMessageBlock);
+ break;
+ case PREFERENCES: topPanel.add(settingsPanel);
+ break;
+ case PATCH_SETS: topPanel.add(historyTable);
+ break;
+ case FILES: topPanel.add(fileList);
+ break;
+ }
+ }
+
public class FileListCmd extends KeyCommand {
public FileListCmd(int mask, int key, String help) {
super(mask, key, help);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
index 404eeeb..bce7f55 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
@@ -87,6 +87,9 @@
@UiField
CheckBox skipUncommented;
+ @UiField
+ CheckBox expandAllComments;
+
@UiField
Button update;
@@ -209,6 +212,7 @@
showTabs.setValue(dp.isShowTabs());
skipDeleted.setValue(dp.isSkipDeleted());
skipUncommented.setValue(dp.isSkipUncommented());
+ expandAllComments.setValue(dp.isExpandAllComments());
}
@UiHandler("update")
@@ -233,6 +237,7 @@
dp.setShowTabs(showTabs.getValue());
dp.setSkipDeleted(skipDeleted.getValue());
dp.setSkipUncommented(skipUncommented.getValue());
+ dp.setExpandAllComments(expandAllComments.getValue());
listenablePrefs.set(dp);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
index f0f1b4d..8bda032 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
@@ -115,18 +115,27 @@
</g:CheckBox>
</td>
+ <td>
+ <g:CheckBox
+ ui:field='expandAllComments'
+ text='Expand All Comments'
+ tabIndex='9'>
+ <ui:attribute name='text'/>
+ </g:CheckBox>
+ </td>
+
<td rowspan='2'>
<g:CheckBox
ui:field='skipUncommented'
text='Skip Uncommented Files'
- tabIndex='9'>
+ tabIndex='10'>
<ui:attribute name='text'/>
</g:CheckBox>
<br/>
<g:CheckBox
ui:field='skipDeleted'
text='Skip Deleted Files'
- tabIndex='10'>
+ tabIndex='11'>
<ui:attribute name='text'/>
</g:CheckBox>
</td>
@@ -136,7 +145,7 @@
ui:field='update'
text='Update'
styleName='{style.updateButton}'
- tabIndex='11'>
+ tabIndex='12'>
<ui:attribute name='text'/>
</g:Button>
</td>
@@ -145,7 +154,7 @@
<g:CheckBox
ui:field='reviewed'
text='Reviewed'
- tabIndex='12'>
+ tabIndex='13'>
<ui:attribute name='text'/>
</g:CheckBox>
</td>
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 2939e71..6a8f60b 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
@@ -64,6 +64,13 @@
}
@Override
+ protected void onCellSingleClick(int row, int column) {
+ if (column == 1 || column == 3) {
+ onCellDoubleClick(row, column);
+ }
+ }
+
+ @Override
protected void onInsertComment(final PatchLine line) {
final int row = getCurrentRow();
createCommentEditor(row + 1, 4, line.getLineB(), (short) 1);
@@ -186,7 +193,7 @@
}
@Override
- public void display(final CommentDetail cd) {
+ public void display(final CommentDetail cd, boolean expandComments) {
if (cd.isEmpty()) {
return;
}
@@ -205,13 +212,13 @@
final PatchLineComment ac = ai.next();
final PatchLineComment bc = bi.next();
insertRow(row);
- bindComment(row, COL_A, ac, !ai.hasNext());
- bindComment(row, COL_B, bc, !bi.hasNext());
+ bindComment(row, COL_A, ac, !ai.hasNext(), expandComments);
+ bindComment(row, COL_B, bc, !bi.hasNext(), expandComments);
row++;
}
- row = finish(ai, row, COL_A);
- row = finish(bi, row, COL_B);
+ row = finish(ai, row, COL_A, expandComments);
+ row = finish(bi, row, COL_B, expandComments);
} else {
row++;
}
@@ -228,11 +235,11 @@
fmt.addStyleName(row, COL_B, Gerrit.RESOURCES.css().diffText());
}
- private int finish(final Iterator<PatchLineComment> i, int row, final int col) {
+ private int finish(final Iterator<PatchLineComment> i, int row, final int col, boolean expandComment) {
while (i.hasNext()) {
final PatchLineComment c = i.next();
insertRow(row);
- bindComment(row, col, c, !i.hasNext());
+ bindComment(row, col, c, !i.hasNext(), expandComment);
row++;
}
return row;
@@ -335,7 +342,7 @@
final boolean fullBlock) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- m.append(lineNumberMinusOne + 1);
+ m.append(SafeHtml.asis("<a href=\"javascript:void(0)\">"+ (lineNumberMinusOne + 1) + "</a>"));
m.closeTd();
m.openTd();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
index 508e508..ef6a181 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
@@ -65,6 +65,15 @@
}
@Override
+ protected void onCellSingleClick(int row, int column) {
+ if (column == 1 || column == 2) {
+ if (!"".equals(table.getText(row, column))) {
+ onCellDoubleClick(row, column);
+ }
+ }
+ }
+
+ @Override
protected void onInsertComment(final PatchLine pl) {
final int row = getCurrentRow();
switch (pl.getType()) {
@@ -205,7 +214,7 @@
}
@Override
- public void display(final CommentDetail cd) {
+ public void display(final CommentDetail cd, boolean expandComments) {
if (cd.isEmpty()) {
return;
}
@@ -224,13 +233,13 @@
all.addAll(fora);
all.addAll(forb);
Collections.sort(all, BY_DATE);
- row = insert(all, row);
+ row = insert(all, row, expandComments);
} else if (!fora.isEmpty()) {
- row = insert(fora, row);
+ row = insert(fora, row, expandComments);
} else if (!forb.isEmpty()) {
- row = insert(forb, row);
+ row = insert(forb, row, expandComments);
}
} else {
row++;
@@ -248,11 +257,11 @@
fmt.addStyleName(row, PC, Gerrit.RESOURCES.css().diffText());
}
- private int insert(final List<PatchLineComment> in, int row) {
+ private int insert(final List<PatchLineComment> in, int row, boolean expandComment) {
for (Iterator<PatchLineComment> ci = in.iterator(); ci.hasNext();) {
final PatchLineComment c = ci.next();
insertRow(row);
- bindComment(row, PC, c, !ci.hasNext());
+ bindComment(row, PC, c, !ci.hasNext(), expandComment);
row++;
}
return row;
@@ -374,7 +383,7 @@
private void appendLineNumber(final SafeHtmlBuilder m, final int idx) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
- m.append(idx + 1);
+ m.append(SafeHtml.asis("<a href=\"javascript:void(0)\">"+ (idx + 1) + "</a>"));
m.closeTd();
}
}
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 0a9cedf..5d8ee8b 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
@@ -16,26 +16,34 @@
import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.reviewdb.AccountGroupName;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/** Suggestion Oracle for AccountGroup entities. */
public class AccountGroupSuggestOracle extends HighlightSuggestOracle {
+ private Map<String, AccountGroup.UUID> priorResults =
+ new HashMap<String, AccountGroup.UUID>();
+
@Override
public void onRequestSuggestions(final Request req, final Callback callback) {
RpcStatus.hide(new Runnable() {
public void run() {
SuggestUtil.SVC.suggestAccountGroup(req.getQuery(), req.getLimit(),
- new GerritCallback<List<AccountGroupName>>() {
- public void onSuccess(final List<AccountGroupName> result) {
+ new GerritCallback<List<GroupReference>>() {
+ public void onSuccess(final List<GroupReference> result) {
+ priorResults.clear();
final ArrayList<AccountGroupSuggestion> r =
new ArrayList<AccountGroupSuggestion>(result.size());
- for (final AccountGroupName p : result) {
+ for (final GroupReference p : result) {
r.add(new AccountGroupSuggestion(p));
+ priorResults.put(p.getName(), p.getUUID());
}
callback.onSuggestionsReady(req, new Response(r));
}
@@ -46,9 +54,9 @@
private static class AccountGroupSuggestion implements
SuggestOracle.Suggestion {
- private final AccountGroupName info;
+ private final GroupReference info;
- AccountGroupSuggestion(final AccountGroupName k) {
+ AccountGroupSuggestion(final GroupReference k) {
info = k;
}
@@ -60,4 +68,9 @@
return info.getName();
}
}
+
+ /** @return the group UUID, or null if it cannot be found. */
+ public AccountGroup.UUID getUUID(String name) {
+ return priorResults.get(name);
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java
index 6c18db1..1261b42 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java
@@ -25,6 +25,10 @@
public class Hyperlink extends com.google.gwt.user.client.ui.Hyperlink {
static final HyperlinkImpl impl = GWT.create(HyperlinkImpl.class);
+ /** Initialize a default hyperlink with no target and no text. */
+ public Hyperlink() {
+ }
+
/**
* Creates a hyperlink with its text and target history token specified.
*
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java
new file mode 100644
index 0000000..8c6fe07
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java
@@ -0,0 +1,101 @@
+// 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.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.user.client.ui.TabPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A TabPanel which allows entries to be hidden. This class is not yet
+ * designed to handle removes or any other add methods than the one
+ * overridden here. It is also not designed to handle anything other
+ * than text for the tab.
+ */
+public class MorphingTabPanel extends TabPanel {
+ // Keep track of the order the widgets/texts should be in when not hidden.
+ private List<Widget> widgets = new ArrayList<Widget>();
+ private List<String> texts = new ArrayList<String>();
+
+ // currently visible widgets
+ private List<Widget> visibles = new ArrayList<Widget>();
+
+ private int selection;
+
+ public MorphingTabPanel() {
+ addSelectionHandler(new SelectionHandler<Integer>() {
+ @Override
+ public void onSelection(SelectionEvent<Integer> ev) {
+ selection = ev.getSelectedItem();
+ }
+ });
+ }
+
+ public int getSelectedIndex() {
+ return selection;
+ }
+
+ public Widget getSelectedWidget() {
+ return getWidget(getSelectedIndex());
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ widgets.clear();
+ texts.clear();
+ visibles.clear();
+ }
+
+ @Override
+ public void add(Widget w, String tabText) {
+ addInvisible(w, tabText);
+ visibles.add(w);
+ super.add(w, tabText);
+ }
+
+ public void addInvisible(Widget w, String tabText) {
+ widgets.add(w);
+ texts.add(tabText);
+ }
+
+ public void setVisible(Widget w, boolean visible) {
+ if (visible) {
+ if (visibles.indexOf(w) == -1) {
+ int origPos = widgets.indexOf(w);
+
+ /* Re-insert the widget right after the first visible widget found
+ when scanning backwards from the current widget */
+ for (int pos = origPos -1; pos >=0 ; pos--) {
+ int visiblePos = visibles.indexOf(widgets.get(pos));
+ if (visiblePos != -1) {
+ visibles.add(visiblePos + 1, w);
+ insert(w, texts.get(origPos), visiblePos + 1);
+ break;
+ }
+ }
+ }
+ } else {
+ int i = visibles.indexOf(w);
+ if (i != -1) {
+ visibles.remove(i);
+ remove(i);
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
index 9979edf..7f6de05 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PatchLink.java
@@ -16,10 +16,11 @@
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.changes.PatchTable;
+import com.google.gerrit.client.patches.PatchScreen;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.Patch;
-public abstract class PatchLink extends InlineHyperlink {
+public class PatchLink extends InlineHyperlink {
protected Patch.Key patchKey;
protected int patchIndex;
protected PatchSetDetail patchSetDetail;
@@ -30,10 +31,10 @@
* @param patchKey The key for this patch
* @param patchIndex The index of the current patch in the patch set
* @param historyToken The history token
- * @parma patchSetDetail Detailed information about the patch set.
+ * @param patchSetDetail Detailed information about the patch set.
* @param parentPatchTable The table used to display this link
*/
- public PatchLink(final String text, final Patch.Key patchKey,
+ protected PatchLink(final String text, final Patch.Key patchKey,
final int patchIndex, final String historyToken,
final PatchSetDetail patchSetDetail,
final PatchTable parentPatchTable) {
@@ -44,6 +45,22 @@
this.parentPatchTable = parentPatchTable;
}
+ /**
+ * @param text The text of this link
+ * @param type The type of the link to create (unified/side-by-side)
+ * @param patchScreen The patchScreen to grab contents to link to from
+ */
+ public PatchLink(final String text, final PatchScreen.Type type,
+ final PatchScreen patchScreen) {
+ this(text, //
+ patchScreen.getPatchKey(), //
+ patchScreen.getPatchIndex(), //
+ Dispatcher.toPatch(type, patchScreen.getPatchKey()), //
+ patchScreen.getPatchSetDetail(), //
+ patchScreen.getFileList() //
+ );
+ }
+
@Override
public void go() {
Dispatcher.patch( //
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 a854abc..68026ae 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
@@ -127,6 +127,7 @@
if (windowTitle != null) {
Gerrit.setWindowTitle(this, windowTitle);
}
+ Gerrit.updateMenus(this);
registerKeys();
}
}
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index 2a9fa7d..5154515 100644
--- a/gerrit-httpd/pom.xml
+++ b/gerrit-httpd/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-httpd</artifactId>
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 dd2ef69..8fe2858 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
@@ -18,12 +18,11 @@
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.GitwebLink;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.DownloadSchemeConfig;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.contact.ContactStore;
import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.ssh.SshInfo;
@@ -50,7 +49,7 @@
private final AuthConfig authConfig;
private final DownloadSchemeConfig schemeConfig;
private final GitWebConfig gitWebConfig;
- private final Project.NameKey wildProject;
+ private final AllProjectsName wildProject;
private final SshInfo sshInfo;
private final ApprovalTypes approvalTypes;
@@ -61,7 +60,7 @@
@Inject
GerritConfigProvider(final Realm r, @GerritServerConfig final Config gsc,
final AuthConfig ac, final GitWebConfig gwc,
- @WildProjectName final Project.NameKey wp, final SshInfo si,
+ final AllProjectsName wp, final SshInfo si,
final ApprovalTypes at, final ContactStore cs, final ServletContext sc,
final DownloadSchemeConfig dc) {
realm = r;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
index a071c77..6e1366ae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.Capable;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
@@ -172,14 +173,21 @@
public Repository open(HttpServletRequest req, String projectName)
throws RepositoryNotFoundException, ServiceNotAuthorizedException,
ServiceNotEnabledException {
+ while (projectName.endsWith("/")) {
+ projectName = projectName.substring(0, projectName.length() - 1);
+ }
+
if (projectName.endsWith(".git")) {
// Be nice and drop the trailing ".git" suffix, which we never keep
// in our database, but clients might mistakenly provide anyway.
//
projectName = projectName.substring(0, projectName.length() - 4);
+ while (projectName.endsWith("/")) {
+ projectName = projectName.substring(0, projectName.length() - 1);
+ }
}
- if (projectName.startsWith("/")) {
+ while (projectName.startsWith("/")) {
// Be nice and drop the leading "/" if supplied by an absolute path.
// We don't have a file system hierarchy, just a flat namespace in
// the database's Project entities. We never encode these with a
@@ -259,8 +267,8 @@
if (pc.getCurrentUser() instanceof IdentifiedUser) {
final IdentifiedUser user = (IdentifiedUser) pc.getCurrentUser();
final ReceiveCommits rc = factory.create(pc, db);
- final ReceiveCommits.Capable s = rc.canUpload();
- if (s != ReceiveCommits.Capable.OK) {
+ final Capable s = rc.canUpload();
+ if (s != Capable.OK) {
// TODO We should alert the user to this message on the HTTP
// response channel, assuming Git will even report it to them.
//
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 c20f8a1..a2b7fc2 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
@@ -31,6 +31,7 @@
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;
@@ -64,7 +65,7 @@
private final HttpServletResponse response;
private final WebSessionManager manager;
private final AuthConfig authConfig;
- private final AnonymousUser anonymous;
+ private final Provider<AnonymousUser> anonymousProvider;
private final IdentifiedUser.RequestFactory identified;
private AccessPath accessPath = AccessPath.WEB_UI;
private Cookie outCookie;
@@ -75,13 +76,14 @@
@Inject
WebSession(final HttpServletRequest request,
final HttpServletResponse response, final WebSessionManager manager,
- final AuthConfig authConfig, final AnonymousUser anonymous,
+ final AuthConfig authConfig,
+ final Provider<AnonymousUser> anonymousProvider,
final IdentifiedUser.RequestFactory identified) {
this.request = request;
this.response = response;
this.manager = manager;
this.authConfig = authConfig;
- this.anonymous = anonymous;
+ this.anonymousProvider = anonymousProvider;
this.identified = identified;
final String cookie = readCookie();
@@ -138,7 +140,7 @@
if (isSignedIn()) {
return identified.create(accessPath, val.getAccountId());
}
- return anonymous;
+ return anonymousProvider.get();
}
public void login(final AuthResult res, final boolean rememberMe) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 947fbb4..c893f7e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -33,12 +33,14 @@
import com.google.gerrit.httpd.GitWebConfig;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -83,15 +85,19 @@
private final URI gitwebUrl;
private final LocalDiskRepositoryManager repoManager;
private final ProjectControl.Factory projectControl;
+ private final Provider<AnonymousUser> anonymousUserProvider;
private final EnvList _env;
@Inject
GitWebServlet(final LocalDiskRepositoryManager repoManager,
- final ProjectControl.Factory projectControl, final SitePaths site,
+ final ProjectControl.Factory projectControl,
+ final Provider<AnonymousUser> anonymousUserProvider,
+ final SitePaths site,
final GerritConfig gerritConfig, final GitWebConfig gitWebConfig)
throws IOException {
this.repoManager = repoManager;
this.projectControl = projectControl;
+ this.anonymousUserProvider = anonymousUserProvider;
this.gitwebCgi = gitWebConfig.getGitwebCGI();
this.deniedActions = new HashSet<String>();
@@ -507,7 +513,7 @@
env.set("GERRIT_CONTEXT_PATH", req.getContextPath() + "/");
env.set("GERRIT_PROJECT_NAME", project.getProject().getName());
- if (project.forAnonymousUser().isVisible()) {
+ if (project.forUser(anonymousUserProvider.get()).isVisible()) {
env.set("GERRIT_ANONYMOUS_READ", "1");
}
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 5c19e11..28a0d03 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
@@ -17,6 +17,7 @@
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;
@@ -79,12 +80,6 @@
}
};
- private static final int MAX_PER_PAGE = 100;
-
- private static int safePageSize(final int pageSize) {
- return 0 < pageSize && pageSize <= MAX_PER_PAGE ? pageSize : MAX_PER_PAGE;
- }
-
private final Provider<CurrentUser> currentUser;
private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
@@ -118,25 +113,33 @@
@Override
public void allQueryPrev(final String query, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
- 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);
- }
- });
+ 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) {
- 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);
- }
- });
+ 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")
@@ -290,6 +293,16 @@
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 ArrayList<ChangeInfo> r = new ArrayList<ChangeInfo>();
@@ -309,7 +322,7 @@
protected final int limit;
protected final int slim;
- QueryNext(final int pageSize, final String pos) {
+ QueryNext(final int pageSize, final String pos) throws InvalidQueryException {
this.pos = pos;
this.limit = safePageSize(pageSize);
this.slim = limit + 1;
@@ -353,7 +366,7 @@
}
private abstract class QueryPrev extends QueryNext {
- QueryPrev(int pageSize, String pos) {
+ QueryPrev(int pageSize, String pos) throws InvalidQueryException {
super(pageSize, pos);
}
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 379fee6..bc05ec6 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
@@ -15,23 +15,24 @@
package com.google.gerrit.httpd.rpc;
import com.google.gerrit.common.data.AccountInfo;
+import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.SuggestService;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroup.Id;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectControl;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -51,24 +52,26 @@
SuggestService {
private static final String MAX_SUFFIX = "\u9fa5";
- private final AuthConfig authConfig;
+ private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final AccountCache accountCache;
private final GroupControl.Factory groupControlFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<CurrentUser> currentUser;
private final SuggestAccountsEnum suggestAccounts;
+ private final GroupCache groupCache;
@Inject
SuggestServiceImpl(final Provider<ReviewDb> schema,
- final AuthConfig authConfig,
+ final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final AccountCache accountCache,
final GroupControl.Factory groupControlFactory,
final IdentifiedUser.GenericFactory userFactory,
final Provider<CurrentUser> currentUser,
- @GerritServerConfig final Config cfg) {
+ @GerritServerConfig final Config cfg,
+ final GroupCache groupCache) {
super(schema, currentUser);
- this.authConfig = authConfig;
+ this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.accountCache = accountCache;
this.groupControlFactory = groupControlFactory;
@@ -76,28 +79,29 @@
this.currentUser = currentUser;
this.suggestAccounts =
cfg.getEnum("suggest", null, "accounts", SuggestAccountsEnum.ALL);
+ this.groupCache = groupCache;
}
public void suggestProjectNameKey(final String query, final int limit,
final AsyncCallback<List<Project.NameKey>> callback) {
- run(callback, new Action<List<Project.NameKey>>() {
- public List<Project.NameKey> run(final ReviewDb db) 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);
+ final int max = 10;
+ final int n = limit <= 0 ? max : Math.min(limit, max);
- final CurrentUser user = currentUser.get();
- final List<Project.NameKey> r = new ArrayList<Project.NameKey>();
- for (final Project p : db.projects().suggestByName(a, b, n)) {
- final ProjectState e = projectCache.get(p.getNameKey());
- if (e != null && e.controlFor(user).isVisible()) {
- r.add(p.getNameKey());
- }
- }
- return r;
+ final List<Project.NameKey> r = new ArrayList<Project.NameKey>(n);
+ for (final Project.NameKey nameKey : projectCache.byName(query)) {
+ final ProjectControl ctl;
+ try {
+ ctl = projectControlFactory.validateFor(nameKey);
+ } catch (NoSuchProjectException e) {
+ continue;
}
- });
+
+ r.add(ctl.getProject().getNameKey());
+ if (r.size() == n) {
+ break;
+ }
+ }
+ callback.onSuccess(r);
}
public void suggestAccount(final String query, final Boolean active,
@@ -154,10 +158,10 @@
map.put(account.getId(), info);
break;
case SAME_GROUP: {
- Set<AccountGroup.Id> usersGroups = groupsOf(account);
- usersGroups.removeAll(authConfig.getRegisteredGroups());
- usersGroups.remove(authConfig.getBatchUsersGroup());
- for (AccountGroup.Id myGroup : currentUser.get().getEffectiveGroups()) {
+ Set<AccountGroup.UUID> usersGroups = groupsOf(account);
+ usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
+ usersGroups.remove(AccountGroup.REGISTERED_USERS);
+ for (AccountGroup.UUID myGroup : currentUser.get().getEffectiveGroups()) {
if (usersGroups.contains(myGroup)) {
map.put(account.getId(), info);
break;
@@ -166,10 +170,10 @@
break;
}
case VISIBLE_GROUP: {
- Set<AccountGroup.Id> usersGroups = groupsOf(account);
- usersGroups.removeAll(authConfig.getRegisteredGroups());
- usersGroups.remove(authConfig.getBatchUsersGroup());
- for (AccountGroup.Id usersGroup : usersGroups) {
+ Set<AccountGroup.UUID> usersGroups = groupsOf(account);
+ usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
+ usersGroups.remove(AccountGroup.REGISTERED_USERS);
+ for (AccountGroup.UUID usersGroup : usersGroups) {
try {
if (groupControlFactory.controlFor(usersGroup).isVisible()) {
map.put(account.getId(), info);
@@ -188,33 +192,34 @@
}
}
- private Set<Id> groupsOf(Account account) {
+ private Set<AccountGroup.UUID> groupsOf(Account account) {
IdentifiedUser user = userFactory.create(account.getId());
- return new HashSet<AccountGroup.Id>(user.getEffectiveGroups());
+ return new HashSet<AccountGroup.UUID>(user.getEffectiveGroups());
}
public void suggestAccountGroup(final String query, final int limit,
- final AsyncCallback<List<AccountGroupName>> callback) {
- run(callback, new Action<List<AccountGroupName>>() {
- public List<AccountGroupName> run(final ReviewDb db) throws OrmException {
+ final AsyncCallback<List<GroupReference>> callback) {
+ run(callback, new Action<List<GroupReference>>() {
+ public List<GroupReference> run(final ReviewDb db) 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);
- Set<AccountGroup.Id> memberOf = currentUser.get().getEffectiveGroups();
- List<AccountGroupName> names = new ArrayList<AccountGroupName>(n);
+ List<GroupReference> r = new ArrayList<GroupReference>(n);
for (AccountGroupName group : db.accountGroupNames()
.suggestByName(a, b, n)) {
try {
- if (memberOf.contains(group.getId())
- || groupControlFactory.controlFor(group.getId()).isVisible()) {
- names.add(group);
+ 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 names;
+ return r;
}
});
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
index f638d48..4f62330 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
@@ -22,6 +22,7 @@
import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupCache;
import com.google.inject.Inject;
import java.util.ArrayList;
@@ -36,13 +37,16 @@
}
private final ReviewDb db;
+ private final GroupCache groupCache;
private final IdentifiedUser user;
private AgreementInfo info;
@Inject
- AgreementInfoFactory(final ReviewDb db, final IdentifiedUser user) {
+ AgreementInfoFactory(final ReviewDb db, final GroupCache groupCache,
+ final IdentifiedUser user) {
this.db = db;
+ this.groupCache = groupCache;
this.user = user;
}
@@ -55,9 +59,14 @@
final List<AccountGroupAgreement> groupAccepted =
new ArrayList<AccountGroupAgreement>();
- for (final AccountGroup.Id groupId : user.getEffectiveGroups()) {
+ for (final AccountGroup.UUID groupUUID : user.getEffectiveGroups()) {
+ AccountGroup group = groupCache.get(groupUUID);
+ if (group == null) {
+ continue;
+ }
+
final List<AccountGroupAgreement> temp =
- db.accountGroupAgreements().byGroup(groupId).toList();
+ db.accountGroupAgreements().byGroup(group.getId()).toList();
Collections.reverse(temp);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java
index 874cc74..6639ac5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc.account;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
+import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -44,7 +45,8 @@
}
@Override
- public AccountGroup.Id call() throws OrmException, NameAlreadyUsedException {
+ public AccountGroup.Id call() throws OrmException, NameAlreadyUsedException,
+ PermissionDeniedException {
final PerformCreateGroup performCreateGroup = performCreateGroupFactory.create();
final Account.Id me = user.getAccountId();
return performCreateGroup.createGroup(groupName, null, false, null, Collections.singleton(me), null);
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 a564907..f89ef09 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
@@ -16,6 +16,7 @@
import com.google.gerrit.common.data.GroupAdminService;
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.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
@@ -46,6 +47,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -87,28 +89,32 @@
this.groupDetailFactory = groupDetailFactory;
}
- public void visibleGroups(final AsyncCallback<List<AccountGroup>> callback) {
- run(callback, new Action<List<AccountGroup>>() {
- public List<AccountGroup> run(ReviewDb db) throws OrmException {
+ public void visibleGroups(final AsyncCallback<GroupList> callback) {
+ run(callback, new Action<GroupList>() {
+ public GroupList run(ReviewDb db) throws OrmException {
final IdentifiedUser user = identifiedUser.get();
- final List<AccountGroup> result;
- if (user.isAdministrator()) {
- result = db.accountGroups().all().toList();
+ final List<AccountGroup> list;
+ if (user.getCapabilities().canAdministrateServer()) {
+ list = db.accountGroups().all().toList();
} else {
- result = new ArrayList<AccountGroup>();
+ list = new ArrayList<AccountGroup>();
for(final AccountGroup group : db.accountGroups().all().toList()) {
final GroupControl c = groupControlFactory.controlFor(group);
if (c.isVisible()) {
- result.add(c.getAccountGroup());
+ list.add(c.getAccountGroup());
}
}
}
- Collections.sort(result, new Comparator<AccountGroup>() {
+ Collections.sort(list, new Comparator<AccountGroup>() {
public int compare(final AccountGroup a, final AccountGroup b) {
return a.getName().compareTo(b.getName());
}
});
- return result;
+
+ GroupList res = new GroupList();
+ res.setGroups(list);
+ res.setCanCreateGroup(user.getCapabilities().canCreateGroup());
+ return res;
}
});
}
@@ -118,8 +124,14 @@
createGroupFactory.create(newName).to(callback);
}
- public void groupDetail(final AccountGroup.Id groupId,
- final AsyncCallback<GroupDetail> callback) {
+ public void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID groupUUID,
+ AsyncCallback<GroupDetail> callback) {
+ if (groupId == null && groupUUID != null) {
+ AccountGroup g = groupCache.get(groupUUID);
+ if (g != null) {
+ groupId = g.getId();
+ }
+ }
groupDetailFactory.create(groupId).to(callback);
}
@@ -281,7 +293,7 @@
Collections.singleton(new AccountGroupIncludeAudit(m,
getAccountId())));
db.accountGroupIncludes().insert(Collections.singleton(m));
- groupIncludeCache.evictInclude(a.getId());
+ groupIncludeCache.evictInclude(a.getGroupUUID());
}
return groupDetailFactory.create(groupId).call();
@@ -361,6 +373,7 @@
}
final Account.Id me = getAccountId();
+ final Set<AccountGroup.Id> groupsToEvict = new HashSet<AccountGroup.Id>();
for (final AccountGroupInclude.Key k : keys) {
final AccountGroupInclude m =
db.accountGroupIncludes().get(k);
@@ -385,9 +398,12 @@
Collections.singleton(audit));
}
db.accountGroupIncludes().delete(Collections.singleton(m));
- groupIncludeCache.evictInclude(m.getIncludeId());
+ groupsToEvict.add(k.getIncludeId());
}
}
+ for (AccountGroup group : db.accountGroups().get(groupsToEvict)) {
+ groupIncludeCache.evictInclude(group.getGroupUUID());
+ }
return VoidResult.INSTANCE;
}
});
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
index b3e993e..088fae2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
@@ -42,10 +42,10 @@
@Override
public List<AccountGroup> call() throws Exception {
- final Set<AccountGroup.Id> effective = user.getEffectiveGroups();
+ final Set<AccountGroup.UUID> effective = user.getEffectiveGroups();
final int cnt = effective.size();
final List<AccountGroup> groupList = new ArrayList<AccountGroup>(cnt);
- for (final AccountGroup.Id groupId : effective) {
+ for (final AccountGroup.UUID groupId : effective) {
groupList.add(groupCache.get(groupId));
}
Collections.sort(groupList, new Comparator<AccountGroup>() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
index d62f0c0..c8bf519 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
@@ -21,14 +21,19 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.git.RenameGroupOp;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
class RenameGroup extends Handler<GroupDetail> {
interface Factory {
@@ -39,6 +44,8 @@
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
private final GroupDetailFactory.Factory groupDetailFactory;
+ private final RenameGroupOp.Factory renameGroupOpFactory;
+ private final IdentifiedUser currentUser;
private final AccountGroup.Id groupId;
private final String newName;
@@ -47,11 +54,15 @@
RenameGroup(final ReviewDb db, final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
final GroupDetailFactory.Factory groupDetailFactory,
+ final RenameGroupOp.Factory renameGroupOpFactory,
+ final IdentifiedUser currentUser,
@Assisted final AccountGroup.Id groupId, @Assisted final String newName) {
this.db = db;
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.groupDetailFactory = groupDetailFactory;
+ this.renameGroupOpFactory = renameGroupOpFactory;
+ this.currentUser = currentUser;
this.groupId = groupId;
this.newName = newName;
}
@@ -94,6 +105,10 @@
groupCache.evict(group);
groupCache.evictAfterRename(old);
+ renameGroupOpFactory.create( //
+ currentUser.newCommitterIdent(new Date(), TimeZone.getDefault()), //
+ group.getGroupUUID(), //
+ old.get(), newName).start(0, TimeUnit.MILLISECONDS);
return groupDetailFactory.create(groupId).call();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
index d2f59f5..8eb1bbe 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChange.java
@@ -27,6 +27,7 @@
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -71,7 +72,8 @@
@Override
public ChangeDetail call() throws NoSuchChangeException, OrmException,
- EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
+ EmailException, NoSuchEntityException, InvalidChangeOperationException,
+ PatchSetInfoNotAvailableException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
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 4016641..2705991 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
@@ -19,10 +19,10 @@
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.ChangeInfo;
+import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
@@ -30,6 +30,7 @@
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
@@ -61,6 +62,7 @@
private final FunctionState.Factory functionState;
private final PatchSetDetailFactory.Factory patchSetDetail;
private final AccountInfoCacheFactory aic;
+ private final AnonymousUser anonymousUser;
private final ReviewDb db;
private final Change.Id changeId;
@@ -74,12 +76,14 @@
final PatchSetDetailFactory.Factory patchSetDetail, final ReviewDb db,
final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
+ final AnonymousUser anonymousUser,
@Assisted final Change.Id id) {
this.approvalTypes = approvalTypes;
this.functionState = functionState;
this.patchSetDetail = patchSetDetail;
this.db = db;
this.changeControlFactory = changeControlFactory;
+ this.anonymousUser = anonymousUser;
this.aic = accountInfoCacheFactory.create();
this.changeId = id;
@@ -99,7 +103,7 @@
detail = new ChangeDetail();
detail.setChange(change);
- detail.setAllowsAnonymous(control.forAnonymousUser().isVisible());
+ detail.setAllowsAnonymous(control.forUser(anonymousUser).isVisible());
detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon());
detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore());
@@ -108,6 +112,21 @@
detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet());
+ if (detail.getChange().getStatus().isOpen()) {
+ List<SubmitRecord> submitRecords = control.canSubmit(db, patch.getId());
+ 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);
+ }
+ }
+ detail.setSubmitRecords(submitRecords);
+ }
+
loadPatchSets();
loadMessages();
if (change.currentPatchSetId() != null) {
@@ -138,26 +157,9 @@
final FunctionState fs =
functionState.create(detail.getChange(), psId, allApprovals);
- final Set<ApprovalCategory.Id> missingApprovals =
- new HashSet<ApprovalCategory.Id>();
-
- final Set<ApprovalCategory.Id> currentActions =
- new HashSet<ApprovalCategory.Id>();
-
for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(at.getCategory()).run(at, fs);
- if (!fs.isValid(at)) {
- missingApprovals.add(at.getCategory().getId());
- }
}
- for (final ApprovalType at : approvalTypes.getActionTypes()) {
- if (CategoryFunction.forCategory(at.getCategory()).isValid(
- control.getCurrentUser(), at, fs)) {
- currentActions.add(at.getCategory().getId());
- }
- }
- detail.setMissingApprovals(missingApprovals);
- detail.setCurrentActions(currentActions);
}
final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() //
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 779475e..cbddad1 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,75 +14,63 @@
package com.google.gerrit.httpd.rpc.changedetail;
-import com.google.gerrit.common.data.AccountInfoCache;
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.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.CanSubmitResult;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail> {
interface Factory {
PatchSetPublishDetailFactory create(PatchSet.Id patchSetId);
}
- private final ProjectCache projectCache;
private final PatchSetInfoFactory infoFactory;
- private final ApprovalTypes approvalTypes;
private final ReviewDb db;
private final ChangeControl.Factory changeControlFactory;
+ private final ApprovalTypes approvalTypes;
private final AccountInfoCacheFactory aic;
private final IdentifiedUser user;
private final PatchSet.Id patchSetId;
- private AccountInfoCache accounts;
private PatchSetInfo patchSetInfo;
private Change change;
private List<PatchLineComment> drafts;
- private Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed;
- private Map<ApprovalCategory.Id, PatchSetApproval> given;
@Inject
PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
- final ProjectCache projectCache, final ApprovalTypes approvalTypes,
final ReviewDb db,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final ChangeControl.Factory changeControlFactory,
+ final ApprovalTypes approvalTypes,
final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
- this.projectCache = projectCache;
this.infoFactory = infoFactory;
- this.approvalTypes = approvalTypes;
this.db = db;
this.changeControlFactory = changeControlFactory;
+ this.approvalTypes = approvalTypes;
this.aic = accountInfoCacheFactory.create();
this.user = user;
@@ -98,65 +86,91 @@
patchSetInfo = infoFactory.get(patchSetId);
drafts = db.patchComments().draft(patchSetId, user.getAccountId()).toList();
- allowed = new HashMap<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>>();
- given = new HashMap<ApprovalCategory.Id, PatchSetApproval>();
- if (change.getStatus().isOpen()
- && patchSetId.equals(change.currentPatchSetId())) {
- computeAllowed();
- for (final PatchSetApproval a : db.patchSetApprovals().byPatchSetUser(
- patchSetId, user.getAccountId())) {
- given.put(a.getCategoryId(), a);
- }
- }
-
aic.want(change.getOwner());
- accounts = aic.create();
PatchSetPublishDetail detail = new PatchSetPublishDetail();
- detail.setAccounts(accounts);
detail.setPatchSetInfo(patchSetInfo);
detail.setChange(change);
detail.setDrafts(drafts);
- detail.setAllowed(allowed);
- detail.setGiven(given);
- final CanSubmitResult canSubmitResult = control.canSubmit(patchSetId);
- detail.setSubmitAllowed(canSubmitResult == CanSubmitResult.OK);
+ List<PermissionRange> allowed = Collections.emptyList();
+ List<PatchSetApproval> given = Collections.emptyList();
- return detail;
- }
+ if (change.getStatus().isOpen()
+ && patchSetId.equals(change.currentPatchSetId())) {
+ // TODO Push this selection of labels down into the Prolog interpreter.
+ // Ideally we discover the labels the user can apply here based on doing
+ // a findall() over the space of labels they can apply combined against
+ // the submit rule, thereby skipping any mutually exclusive cases. However
+ // those are not common, so it might just be reasonable to take this
+ // simple approach.
- private void computeAllowed() {
- final Set<AccountGroup.Id> am = user.getEffectiveGroups();
- final ProjectState pe = projectCache.get(change.getProject());
- for (ApprovalCategory.Id category : approvalTypes.getApprovalCategories()) {
- RefControl rc = pe.controlFor(user).controlForRef(change.getDest());
- List<RefRight> categoryRights = rc.getApplicableRights(category);
- computeAllowed(am, categoryRights, category);
- }
- }
-
- private void computeAllowed(final Set<AccountGroup.Id> am,
- final List<RefRight> list, ApprovalCategory.Id category) {
-
- Set<ApprovalCategoryValue.Id> s = allowed.get(category);
- if (s == null) {
- s = new HashSet<ApprovalCategoryValue.Id>();
- allowed.put(category, s);
- }
-
- for (final RefRight r : list) {
- if (!am.contains(r.getAccountGroupId())) {
- continue;
- }
- final ApprovalType at =
- approvalTypes.getApprovalType(r.getApprovalCategoryId());
- for (short m = r.getMinValue(); m <= r.getMaxValue(); m++) {
- final ApprovalCategoryValue v = at.getValue(m);
- if (v != null) {
- s.add(v.getId());
+ Map<String, PermissionRange> rangeByName =
+ new HashMap<String, PermissionRange>();
+ for (PermissionRange r : control.getLabelRanges()) {
+ if (r.isLabel()) {
+ rangeByName.put(r.getLabel(), r);
}
}
+ allowed = new ArrayList<PermissionRange>();
+
+ given = db.patchSetApprovals() //
+ .byPatchSetUser(patchSetId, user.getAccountId()) //
+ .toList();
+
+ boolean couldSubmit = false;
+ List<SubmitRecord> submitRecords = control.canSubmit(db, patchSetId);
+ for (SubmitRecord rec : submitRecords) {
+ if (rec.status == SubmitRecord.Status.OK) {
+ couldSubmit = true;
+ }
+
+ if (rec.labels != null) {
+ int ok = 0;
+
+ for (SubmitRecord.Label lbl : rec.labels) {
+ boolean canMakeOk = false;
+ PermissionRange range = rangeByName.get(lbl.label);
+ if (range != null) {
+ if (!allowed.contains(range)) {
+ allowed.add(range);
+ }
+
+ ApprovalType at = approvalTypes.byLabel(lbl.label);
+ if (at == null || at.getMax().getValue() == range.getMax()) {
+ canMakeOk = true;
+ }
+ }
+
+ switch (lbl.status) {
+ case OK:
+ ok++;
+ break;
+
+ case NEED:
+ if (canMakeOk) {
+ ok++;
+ }
+ break;
+ }
+ }
+
+ if (rec.status == SubmitRecord.Status.NOT_READY
+ && ok == rec.labels.size()) {
+ couldSubmit = true;
+ }
+ }
+ }
+
+ if (couldSubmit && control.getRefControl().canSubmit()) {
+ detail.setCanSubmit(true);
+ }
}
+
+ detail.setLabels(allowed);
+ detail.setGiven(given);
+ detail.setAccounts(aic.create());
+
+ return detail;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java
index f951439..fa8785a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChange.java
@@ -25,6 +25,7 @@
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -69,7 +70,8 @@
@Override
public ChangeDetail call() throws NoSuchChangeException, OrmException,
- EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
+ EmailException, NoSuchEntityException, InvalidChangeOperationException,
+ PatchSetInfoNotAvailableException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
index 721656c..ec003f1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/SubmitAction.java
@@ -14,8 +14,8 @@
package com.google.gerrit.httpd.rpc.changedetail;
-import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Change;
@@ -26,14 +26,14 @@
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.CanSubmitResult;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.util.List;
+
class SubmitAction extends Handler<ChangeDetail> {
interface Factory {
SubmitAction create(PatchSet.Id patchSetId);
@@ -41,8 +41,6 @@
private final ReviewDb db;
private final MergeQueue merger;
- private final ApprovalTypes approvalTypes;
- private final FunctionState.Factory functionState;
private final IdentifiedUser user;
private final ChangeDetailFactory.Factory changeDetailFactory;
private final ChangeControl.Factory changeControlFactory;
@@ -51,16 +49,14 @@
private final PatchSet.Id patchSetId;
@Inject
- SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
- final FunctionState.Factory fs, final IdentifiedUser user,
+ SubmitAction(final ReviewDb db, final MergeQueue mq,
+ final IdentifiedUser user,
final ChangeDetailFactory.Factory changeDetailFactory,
final ChangeControl.Factory changeControlFactory,
final MergeOp.Factory opFactory,
@Assisted final PatchSet.Id patchSetId) {
this.db = db;
this.merger = mq;
- this.approvalTypes = at;
- this.functionState = fs;
this.user = user;
this.changeControlFactory = changeControlFactory;
this.changeDetailFactory = changeDetailFactory;
@@ -78,13 +74,50 @@
final ChangeControl changeControl =
changeControlFactory.validateFor(changeId);
- CanSubmitResult err =
- changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
- if (err == CanSubmitResult.OK) {
- ChangeUtil.submit(patchSetId, user, db, opFactory, merger);
- return changeDetailFactory.create(changeId).call();
- } else {
- throw new IllegalStateException(err.getMessage());
+ List<SubmitRecord> result = changeControl.canSubmit(db, patchSetId);
+ if (result.isEmpty()) {
+ throw new IllegalStateException("Cannot submit");
+ }
+
+ switch (result.get(0).status) {
+ case OK:
+ ChangeUtil.submit(patchSetId, user, db, opFactory, merger);
+ return changeDetailFactory.create(changeId).call();
+
+ case NOT_READY: {
+ for (SubmitRecord.Label lbl : result.get(0).labels) {
+ switch (lbl.status) {
+ case OK:
+ break;
+
+ case REJECT:
+ throw new IllegalStateException("Blocked by " + lbl.label);
+
+ case NEED:
+ throw new IllegalStateException("Needs " + lbl.label);
+
+ case IMPOSSIBLE:
+ throw new IllegalStateException("Cannnot submit, check project access");
+
+ default:
+ throw new IllegalArgumentException("Unknown status " + lbl.status);
+ }
+ }
+ throw new IllegalStateException("Cannot submit");
+ }
+
+ case CLOSED:
+ throw new IllegalStateException("Change is closed");
+
+ case RULE_ERROR:
+ if (result.get(0).errorMessage != null) {
+ throw new IllegalStateException(result.get(0).errorMessage);
+ } else {
+ throw new IllegalStateException("Internal rule error");
+ }
+
+ default:
+ throw new IllegalStateException("Uknown status " + result.get(0).status);
}
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewerHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewerHandler.java
new file mode 100644
index 0000000..4765eac
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewerHandler.java
@@ -0,0 +1,57 @@
+// 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.patch;
+
+import com.google.gerrit.common.data.ReviewerResult;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.server.patch.AddReviewer;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.Collection;
+
+class AddReviewerHandler extends Handler<ReviewerResult> {
+ interface Factory {
+ AddReviewerHandler create(Change.Id changeId, Collection<String> nameOrEmails);
+ }
+
+ private final AddReviewer.Factory addReviewerFactory;
+ private final ChangeDetailFactory.Factory changeDetailFactory;
+
+ private final Change.Id changeId;
+ private final Collection<String> reviewers;
+
+ @Inject
+ AddReviewerHandler(final AddReviewer.Factory addReviewerFactory,
+ final ChangeDetailFactory.Factory changeDetailFactory,
+ @Assisted final Change.Id changeId,
+ @Assisted final Collection<String> nameOrEmails) {
+
+ this.addReviewerFactory = addReviewerFactory;
+ this.changeDetailFactory = changeDetailFactory;
+
+ this.changeId = changeId;
+ this.reviewers = nameOrEmails;
+ }
+
+ @Override
+ public ReviewerResult call() throws Exception {
+ ReviewerResult result = addReviewerFactory.create(changeId, reviewers).call();
+ result.setChange(changeDetailFactory.create(changeId).call());
+ return result;
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index da52596..394b43f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -58,9 +58,9 @@
private final ApprovalTypes approvalTypes;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
- private final AddReviewer.Factory addReviewerFactory;
+ private final AddReviewerHandler.Factory addReviewerHandlerFactory;
private final ChangeControl.Factory changeControlFactory;
- private final RemoveReviewer.Factory removeReviewerFactory;
+ private final RemoveReviewerHandler.Factory removeReviewerHandlerFactory;
private final FunctionState.Factory functionStateFactory;
private final PublishComments.Factory publishCommentsFactory;
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
@@ -71,8 +71,8 @@
final Provider<CurrentUser> currentUser,
final ApprovalTypes approvalTypes,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
- final AddReviewer.Factory addReviewerFactory,
- final RemoveReviewer.Factory removeReviewerFactory,
+ final AddReviewerHandler.Factory addReviewerHandlerFactory,
+ final RemoveReviewerHandler.Factory removeReviewerHandlerFactory,
final ChangeControl.Factory changeControlFactory,
final FunctionState.Factory functionStateFactory,
final PatchScriptFactory.Factory patchScriptFactoryFactory,
@@ -82,8 +82,8 @@
this.approvalTypes = approvalTypes;
this.accountInfoCacheFactory = accountInfoCacheFactory;
- this.addReviewerFactory = addReviewerFactory;
- this.removeReviewerFactory = removeReviewerFactory;
+ this.addReviewerHandlerFactory = addReviewerHandlerFactory;
+ this.removeReviewerHandlerFactory = removeReviewerHandlerFactory;
this.changeControlFactory = changeControlFactory;
this.functionStateFactory = functionStateFactory;
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
@@ -156,12 +156,12 @@
public void addReviewers(final Change.Id id, final List<String> reviewers,
final AsyncCallback<ReviewerResult> callback) {
- addReviewerFactory.create(id, reviewers).to(callback);
+ addReviewerHandlerFactory.create(id, reviewers).to(callback);
}
public void removeReviewer(final Change.Id id, final Account.Id reviewerId,
final AsyncCallback<ReviewerResult> callback) {
- removeReviewerFactory.create(id, reviewerId).to(callback);
+ removeReviewerHandlerFactory.create(id, reviewerId).to(callback);
}
public void userApprovals(final Set<Change.Id> cids, final Account.Id aid,
@@ -187,11 +187,13 @@
for (final PatchSetApproval ca : db.patchSetApprovals()
.byPatchSetUser(ps_id, aid)) {
final ApprovalCategory.Id category = ca.getCategoryId();
- if (change.getStatus().isOpen()) {
- fs.normalize(approvalTypes.getApprovalType(category), ca);
+ if (ApprovalCategory.SUBMIT.equals(category)) {
+ continue;
}
- if (ca.getValue() == 0
- || ApprovalCategory.SUBMIT.equals(category)) {
+ if (change.getStatus().isOpen()) {
+ fs.normalize(approvalTypes.byId(category), ca);
+ }
+ if (ca.getValue() == 0) {
continue;
}
psas.put(category, ca);
@@ -231,11 +233,13 @@
for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(ps_id)) {
final ApprovalCategory.Id category = ca.getCategoryId();
- if (change.getStatus().isOpen()) {
- fs.normalize(approvalTypes.getApprovalType(category), ca);
+ if (ApprovalCategory.SUBMIT.equals(category)) {
+ continue;
}
- if (ca.getValue() == 0
- || ApprovalCategory.SUBMIT.equals(category)) {
+ if (change.getStatus().isOpen()) {
+ fs.normalize(approvalTypes.byId(category), ca);
+ }
+ if (ca.getValue() == 0) {
continue;
}
boolean keep = true;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
index 7d6d05e..3e94b4c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
@@ -28,8 +28,8 @@
install(new FactoryModule() {
@Override
protected void configure() {
- factory(AddReviewer.Factory.class);
- factory(RemoveReviewer.Factory.class);
+ factory(AddReviewerHandler.Factory.class);
+ factory(RemoveReviewerHandler.Factory.class);
factory(PatchScriptFactory.Factory.class);
factory(SaveDraft.Factory.class);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/RemoveReviewer.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/RemoveReviewer.java
deleted file mode 100644
index 342a674..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/RemoveReviewer.java
+++ /dev/null
@@ -1,95 +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.httpd.rpc.patch;
-
-import com.google.gerrit.common.data.ReviewerResult;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.PatchSetApproval;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implement the remote logic that removes a reviewer from a change.
- */
-class RemoveReviewer extends Handler<ReviewerResult> {
- interface Factory {
- RemoveReviewer create(Change.Id changeId, Account.Id reviewerId);
- }
-
- private final Account.Id reviewerId;
- private final ChangeControl.Factory changeControlFactory;
- private final ReviewDb db;
- private final Change.Id changeId;
- private final ChangeDetailFactory.Factory changeDetailFactory;
-
- @Inject
- RemoveReviewer(final ReviewDb db, final ChangeControl.Factory changeControlFactory,
- final ChangeDetailFactory.Factory changeDetailFactory,
- @Assisted Change.Id changeId, @Assisted Account.Id reviewerId) {
- this.db = db;
- this.changeControlFactory = changeControlFactory;
- this.changeId = changeId;
- this.reviewerId = reviewerId;
- this.changeDetailFactory = changeDetailFactory;
- }
-
- @Override
- public ReviewerResult call() throws Exception {
- ReviewerResult result = new ReviewerResult();
- ChangeControl ctl = changeControlFactory.validateFor(changeId);
- boolean permitted = true;
-
- List<PatchSetApproval> toDelete = new ArrayList<PatchSetApproval>();
- for (PatchSetApproval psa : db.patchSetApprovals().byChange(changeId)) {
- if (psa.getAccountId().equals(reviewerId)) {
- if (ctl.canRemoveReviewer(psa)) {
- toDelete.add(psa);
- } else {
- permitted = false;
- break;
- }
- }
- }
-
- if (permitted) {
- try {
- db.patchSetApprovals().delete(toDelete);
- } catch (OrmException ex) {
- result.addError(new ReviewerResult.Error(
- ReviewerResult.Error.Type.COULD_NOT_REMOVE,
- "Could not remove reviewer " + reviewerId));
- }
- } else {
- result.addError(new ReviewerResult.Error(
- ReviewerResult.Error.Type.COULD_NOT_REMOVE,
- "Not allowed to remove reviewer " + reviewerId));
- }
-
- // Note: call setChange() after the deletion has been made or it will still
- // contain the reviewer we want to delete.
- result.setChange(changeDetailFactory.create(changeId).call());
- return result;
- }
-
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/RemoveReviewerHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/RemoveReviewerHandler.java
new file mode 100644
index 0000000..8849e8e
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/RemoveReviewerHandler.java
@@ -0,0 +1,59 @@
+// 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.httpd.rpc.patch;
+
+import com.google.gerrit.common.data.ReviewerResult;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.server.patch.RemoveReviewer;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.Collections;
+
+/**
+ * Implement the remote logic that removes a reviewer from a change.
+ */
+class RemoveReviewerHandler extends Handler<ReviewerResult> {
+ interface Factory {
+ RemoveReviewerHandler create(Change.Id changeId, Account.Id reviewerId);
+ }
+
+ private final RemoveReviewer.Factory removeReviewerFactory;
+ private final Account.Id reviewerId;
+ private final Change.Id changeId;
+ private final ChangeDetailFactory.Factory changeDetailFactory;
+
+ @Inject
+ RemoveReviewerHandler(final RemoveReviewer.Factory removeReviewerFactory,
+ final ChangeDetailFactory.Factory changeDetailFactory,
+ @Assisted Change.Id changeId, @Assisted Account.Id reviewerId) {
+ this.removeReviewerFactory = removeReviewerFactory;
+ this.changeId = changeId;
+ this.reviewerId = reviewerId;
+ this.changeDetailFactory = changeDetailFactory;
+ }
+
+ @Override
+ public ReviewerResult call() throws Exception {
+ ReviewerResult result = removeReviewerFactory.create(
+ changeId, Collections.singleton(reviewerId)).call();
+ result.setChange(changeDetailFactory.create(changeId).call());
+ return result;
+ }
+
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
deleted file mode 100644
index 358b542..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
+++ /dev/null
@@ -1,207 +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.httpd.rpc.project;
-
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.common.data.ApprovalTypes;
-import com.google.gerrit.common.data.ProjectDetail;
-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.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.NoSuchRefException;
-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.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-
-import java.util.Collections;
-
-import javax.annotation.Nullable;
-
-class AddRefRight extends Handler<ProjectDetail> {
- interface Factory {
- AddRefRight create(@Assisted Project.NameKey projectName,
- @Assisted ApprovalCategory.Id categoryId,
- @Assisted("groupName") String groupName,
- @Nullable @Assisted("refPattern") String refPattern,
- @Assisted("min") short min, @Assisted("max") short max);
- }
-
- private final ProjectDetailFactory.Factory projectDetailFactory;
- private final ProjectControl.Factory projectControlFactory;
- private final ProjectCache projectCache;
- private final GroupCache groupCache;
- private final ReviewDb db;
- private final ApprovalTypes approvalTypes;
-
- private final Project.NameKey projectName;
- private final ApprovalCategory.Id categoryId;
- private final AccountGroup.NameKey groupName;
- private final String refPattern;
- private final short min;
- private final short max;
-
- @Inject
- AddRefRight(final ProjectDetailFactory.Factory projectDetailFactory,
- final ProjectControl.Factory projectControlFactory,
- final ProjectCache projectCache, final GroupCache groupCache,
- final ReviewDb db, final ApprovalTypes approvalTypes,
-
- @Assisted final Project.NameKey projectName,
- @Assisted final ApprovalCategory.Id categoryId,
- @Assisted("groupName") final String groupName,
- @Nullable @Assisted("refPattern") final String refPattern,
- @Assisted("min") final short min, @Assisted("max") final short max) {
- this.projectDetailFactory = projectDetailFactory;
- this.projectControlFactory = projectControlFactory;
- this.projectCache = projectCache;
- this.groupCache = groupCache;
- this.approvalTypes = approvalTypes;
- this.db = db;
-
- this.projectName = projectName;
- this.categoryId = categoryId;
- this.groupName = new AccountGroup.NameKey(groupName);
- this.refPattern = refPattern != null ? refPattern.trim() : null;
-
- if (min <= max) {
- this.min = min;
- this.max = max;
- } else {
- this.min = max;
- this.max = min;
- }
- }
-
- @Override
- public ProjectDetail call() throws NoSuchProjectException, OrmException,
- NoSuchGroupException, InvalidNameException, NoSuchRefException {
- final ProjectControl projectControl =
- projectControlFactory.controlFor(projectName);
-
- final ApprovalType at = approvalTypes.getApprovalType(categoryId);
- if (at == null || at.getValue(min) == null || at.getValue(max) == null) {
- throw new IllegalArgumentException("Invalid category " + categoryId
- + " or range " + min + ".." + max);
- }
-
- String refPattern = this.refPattern;
- if (refPattern == null || refPattern.isEmpty()) {
- if (categoryId.equals(ApprovalCategory.SUBMIT)
- || categoryId.equals(ApprovalCategory.PUSH_HEAD)) {
- // Explicitly related to a branch head.
- refPattern = Constants.R_HEADS + "*";
-
- } else if (!at.getCategory().isAction()) {
- // Non actions are approval votes on a change, assume these apply
- // to branch heads only.
- refPattern = Constants.R_HEADS + "*";
-
- } else if (categoryId.equals(ApprovalCategory.PUSH_TAG)) {
- // Explicitly related to the tag namespace.
- refPattern = Constants.R_TAGS + "*";
-
- } else if (categoryId.equals(ApprovalCategory.READ)
- || categoryId.equals(ApprovalCategory.OWN)) {
- // Currently these are project-wide rights, so apply that way.
- refPattern = RefRight.ALL;
-
- } else {
- // Assume project wide for the default.
- refPattern = RefRight.ALL;
- }
- }
-
- boolean exclusive = refPattern.startsWith("-");
- if (exclusive) {
- refPattern = refPattern.substring(1);
- }
-
- while (refPattern.startsWith("/")) {
- refPattern = refPattern.substring(1);
- }
-
- if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
- String example = RefControl.shortestExample(refPattern);
-
- if (!example.startsWith(Constants.R_REFS)) {
- refPattern = RefRight.REGEX_PREFIX + Constants.R_HEADS
- + refPattern.substring(RefRight.REGEX_PREFIX.length());
- example = RefControl.shortestExample(refPattern);
- }
-
- if (!Repository.isValidRefName(example)) {
- throw new InvalidNameException();
- }
-
- } else {
- if (!refPattern.startsWith(Constants.R_REFS)) {
- refPattern = Constants.R_HEADS + refPattern;
- }
-
- if (refPattern.endsWith("/*")) {
- final String prefix = refPattern.substring(0, refPattern.length() - 2);
- if (!"refs".equals(prefix) && !Repository.isValidRefName(prefix)) {
- throw new InvalidNameException();
- }
- } else {
- if (!Repository.isValidRefName(refPattern)) {
- throw new InvalidNameException();
- }
- }
- }
-
- if (!projectControl.controlForRef(refPattern).isOwner()) {
- throw new NoSuchRefException(refPattern);
- }
-
- if (exclusive) {
- refPattern = "-" + refPattern;
- }
-
- final AccountGroup group = groupCache.get(groupName);
- if (group == null) {
- throw new NoSuchGroupException(groupName);
- }
- final RefRight.Key key =
- new RefRight.Key(projectName, new RefRight.RefPattern(refPattern),
- categoryId, group.getId());
- RefRight rr = db.refRights().get(key);
- if (rr == null) {
- rr = new RefRight(key);
- rr.setMinValue(min);
- rr.setMaxValue(max);
- db.refRights().insert(Collections.singleton(rr));
- } else {
- rr.setMinValue(min);
- rr.setMaxValue(max);
- db.refRights().update(Collections.singleton(rr));
- }
- projectCache.evictAll();
- return projectDetailFactory.create(projectName).call();
- }
-}
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
new file mode 100644
index 0000000..09cfbb4
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -0,0 +1,224 @@
+// 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.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.AccountGroup;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.account.GroupCache;
+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.client.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 org.eclipse.jgit.lib.Repository;
+
+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> {
+ interface Factory {
+ ChangeProjectAccess create(@Assisted Project.NameKey projectName,
+ @Assisted ObjectId base, @Assisted List<AccessSection> sectionList,
+ @Nullable @Assisted String message);
+ }
+
+ 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 MetaDataUpdate.User metaDataUpdateFactory,
+
+ @Assisted final Project.NameKey projectName,
+ @Assisted final ObjectId base, @Assisted List<AccessSection> sectionList,
+ @Nullable @Assisted String message) {
+ 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.isAccessSection(name)) {
+ if (!projectControl.controlForRef(name).isOwner()) {
+ continue;
+ }
+
+ if (name.startsWith(AccessSection.REGEX_PREFIX)) {
+ if (!Repository.isValidRefName(RefControl.shortestExample(name))) {
+ throw new InvalidNameException();
+ }
+
+ } else if (name.equals(AccessSection.ALL)) {
+ // This is a special case we have to allow, it fails below.
+
+ } else if (name.endsWith("/*")) {
+ String prefix = name.substring(0, name.length() - 2);
+ if (!Repository.isValidRefName(prefix)) {
+ throw new InvalidNameException();
+ }
+
+ } else if (!Repository.isValidRefName(name)) {
+ throw new InvalidNameException();
+ }
+
+ 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());
+ }
+ }
+}
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 88c85b8f..2b0f856 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
@@ -17,16 +17,21 @@
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
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.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.client.OrmConcurrencyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.Collections;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+
+import java.io.IOException;
class ChangeProjectSettings extends Handler<ProjectDetail> {
interface Factory {
@@ -36,8 +41,8 @@
private final ProjectDetailFactory.Factory projectDetailFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
- private final ReviewDb db;
- private final GitRepositoryManager repoManager;
+ private final GitRepositoryManager mgr;
+ private final MetaDataUpdate.User metaDataUpdateFactory;
private final Project update;
@@ -45,14 +50,14 @@
ChangeProjectSettings(
final ProjectDetailFactory.Factory projectDetailFactory,
final ProjectControl.Factory projectControlFactory,
- final ProjectCache projectCache, final ReviewDb db,
- final GitRepositoryManager grm,
+ final ProjectCache projectCache, final GitRepositoryManager mgr,
+ final MetaDataUpdate.User metaDataUpdateFactory,
@Assisted final Project update) {
this.projectDetailFactory = projectDetailFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
- this.db = db;
- this.repoManager = grm;
+ this.mgr = mgr;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
this.update = update;
}
@@ -60,20 +65,34 @@
@Override
public ProjectDetail call() throws NoSuchProjectException, OrmException {
final Project.NameKey projectName = update.getNameKey();
- final ProjectControl projectControl =
- projectControlFactory.ownerFor(projectName);
+ projectControlFactory.ownerFor(projectName);
- final Project proj = db.projects().get(projectName);
- if (proj == null) {
+ final MetaDataUpdate md;
+ try {
+ md = metaDataUpdateFactory.create(projectName);
+ } catch (RepositoryNotFoundException notFound) {
throw new NoSuchProjectException(projectName);
}
+ try {
+ // TODO We really should take advantage of the Git commit DAG and
+ // ensure the current version matches the old version the caller read.
+ //
+ ProjectConfig config = ProjectConfig.read(md);
+ config.getProject().copySettingsFrom(update);
- proj.copySettingsFrom(update);
- db.projects().update(Collections.singleton(proj));
- projectCache.evict(proj);
-
- if (!projectControl.getProjectState().isSpecialWildProject()) {
- repoManager.setProjectDescription(projectName, update.getDescription());
+ md.setMessage("Modified project settings\n");
+ if (config.commit(md)) {
+ mgr.setProjectDescription(projectName, update.getDescription());
+ projectCache.evict(config.getProject());
+ } else {
+ throw new OrmConcurrencyException("Cannot update " + projectName);
+ }
+ } catch (ConfigInvalidException err) {
+ throw new OrmException("Cannot read project " + projectName, err);
+ } catch (IOException err) {
+ throw new OrmException("Cannot update project " + projectName, err);
+ } finally {
+ md.close();
}
return projectDetailFactory.create(projectName).call();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
deleted file mode 100644
index ae8b98b..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
+++ /dev/null
@@ -1,91 +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.httpd.rpc.project;
-
-import com.google.gerrit.common.data.ProjectDetail;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.NoSuchRefException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.Collections;
-import java.util.Set;
-
-class DeleteRefRights extends Handler<ProjectDetail> {
- interface Factory {
- DeleteRefRights create(@Assisted Project.NameKey projectName,
- @Assisted Set<RefRight.Key> toRemove);
- }
-
- private final ProjectDetailFactory.Factory projectDetailFactory;
- private final ProjectControl.Factory projectControlFactory;
- private final ProjectCache projectCache;
- private final ReviewDb db;
-
- private final Project.NameKey projectName;
- private final Set<RefRight.Key> toRemove;
-
- @Inject
- DeleteRefRights(final ProjectDetailFactory.Factory projectDetailFactory,
- final ProjectControl.Factory projectControlFactory,
- final ProjectCache projectCache, final ReviewDb db,
-
- @Assisted final Project.NameKey projectName,
- @Assisted final Set<RefRight.Key> toRemove) {
- this.projectDetailFactory = projectDetailFactory;
- this.projectControlFactory = projectControlFactory;
- this.projectCache = projectCache;
- this.db = db;
-
- this.projectName = projectName;
- this.toRemove = toRemove;
- }
-
- @Override
- public ProjectDetail call() throws NoSuchProjectException, OrmException,
- NoSuchRefException {
- final ProjectControl projectControl =
- projectControlFactory.controlFor(projectName);
-
- for (final RefRight.Key k : toRemove) {
- if (!projectName.equals(k.getProjectNameKey())) {
- throw new IllegalArgumentException("All keys must be from same project");
- }
- String refPattern = k.getRefPattern();
- if (refPattern.startsWith("-")) {
- refPattern = refPattern.substring(1);
- }
- if (!projectControl.controlForRef(refPattern).isOwner()) {
- throw new NoSuchRefException(refPattern);
- }
- }
-
- for (final RefRight.Key k : toRemove) {
- final RefRight m = db.refRights().get(k);
- if (m != null) {
- db.refRights().delete(Collections.singleton(m));
- }
- }
- projectCache.evictAll();
- return projectDetailFactory.create(projectName).call();
- }
-}
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
new file mode 100644
index 0000000..440366b
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -0,0 +1,205 @@
+// 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.common.data.AccessSection;
+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.NoSuchGroupException;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.config.AllProjectsName;
+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.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+class ProjectAccessFactory extends Handler<ProjectAccess> {
+ interface Factory {
+ ProjectAccessFactory create(@Assisted Project.NameKey name);
+ }
+
+ private final GroupCache groupCache;
+ private final ProjectCache projectCache;
+ private final ProjectControl.Factory projectControlFactory;
+ private final GroupControl.Factory groupControlFactory;
+ private final MetaDataUpdate.Server metaDataUpdateFactory;
+ private final AllProjectsName allProjectsName;
+
+ private final Project.NameKey projectName;
+ private ProjectControl pc;
+
+ @Inject
+ ProjectAccessFactory(final GroupCache groupCache,
+ final ProjectCache projectCache,
+ final ProjectControl.Factory projectControlFactory,
+ final GroupControl.Factory groupControlFactory,
+ final MetaDataUpdate.Server metaDataUpdateFactory,
+ final AllProjectsName allProjectsName,
+
+ @Assisted final Project.NameKey name) {
+ this.groupCache = groupCache;
+ this.projectCache = projectCache;
+ this.projectControlFactory = projectControlFactory;
+ this.groupControlFactory = groupControlFactory;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.allProjectsName = allProjectsName;
+
+ this.projectName = name;
+ }
+
+ @Override
+ public ProjectAccess call() throws NoSuchProjectException, IOException,
+ ConfigInvalidException {
+ pc = open();
+
+ // Load the current configuration from the repository, ensuring its the most
+ // recent version available. If it differs from what was in the project
+ // state, force a cache flush now.
+ //
+ ProjectConfig config;
+ MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
+ try {
+ config = ProjectConfig.read(md);
+
+ if (config.updateGroupNames(groupCache)) {
+ md.setMessage("Update group names\n");
+ if (config.commit(md)) {
+ projectCache.evict(config.getProject());
+ pc = open();
+ }
+ } else if (config.getRevision() != null
+ && !config.getRevision().equals(
+ pc.getProjectState().getConfig().getRevision())) {
+ projectCache.evict(config.getProject());
+ pc = open();
+ }
+ } finally {
+ md.close();
+ }
+
+ List<AccessSection> local = new ArrayList<AccessSection>();
+ Set<String> ownerOf = new HashSet<String>();
+ Map<AccountGroup.UUID, Boolean> visibleGroups =
+ new HashMap<AccountGroup.UUID, Boolean>();
+
+ for (AccessSection section : config.getAccessSections()) {
+ String name = section.getName();
+ if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
+ if (pc.isOwner()) {
+ local.add(section);
+ ownerOf.add(name);
+ }
+
+ } else if (AccessSection.isAccessSection(name)) {
+ RefControl rc = pc.controlForRef(name);
+ if (rc.isOwner()) {
+ local.add(section);
+ ownerOf.add(name);
+
+ } else if (rc.isVisible()) {
+ // Filter the section to only add rules describing groups that
+ // are visible to the current-user. This includes any group the
+ // user is a member of, as well as groups they own or that
+ // are visible to all users.
+
+ AccessSection dst = null;
+ for (Permission srcPerm : section.getPermissions()) {
+ Permission dstPerm = null;
+
+ for (PermissionRule srcRule : srcPerm.getRules()) {
+ AccountGroup.UUID group = srcRule.getGroup().getUUID();
+ if (group == null) {
+ continue;
+ }
+
+ Boolean canSeeGroup = visibleGroups.get(group);
+ if (canSeeGroup == null) {
+ try {
+ canSeeGroup = groupControlFactory.controlFor(group).isVisible();
+ } catch (NoSuchGroupException e) {
+ canSeeGroup = Boolean.FALSE;
+ }
+ visibleGroups.put(group, canSeeGroup);
+ }
+
+ if (canSeeGroup) {
+ if (dstPerm == null) {
+ if (dst == null) {
+ dst = new AccessSection(name);
+ local.add(dst);
+ }
+ dstPerm = dst.getPermission(srcPerm.getName(), true);
+ }
+ dstPerm.add(srcRule);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (ownerOf.isEmpty() && pc.isOwnerAnyRef()) {
+ // Special case: If the section list is empty, this project has no current
+ // access control information. Rely on what ProjectControl determines
+ // is ownership, which probably means falling back to site administrators.
+ ownerOf.add(AccessSection.ALL);
+ }
+
+ final ProjectAccess detail = new ProjectAccess();
+ detail.setRevision(config.getRevision().name());
+
+ if (projectName.equals(allProjectsName)) {
+ if (pc.isOwner()) {
+ ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
+ }
+ detail.setInheritsFrom(null);
+
+ } else if (config.getProject().getParent() != null) {
+ detail.setInheritsFrom(config.getProject().getParent());
+
+ } else {
+ detail.setInheritsFrom(allProjectsName);
+ }
+
+ detail.setLocal(local);
+ detail.setOwnerOf(ownerOf);
+ return detail;
+ }
+
+ private ProjectControl open() throws NoSuchProjectException {
+ return projectControlFactory.validateFor( //
+ projectName, //
+ ProjectControl.OWNER | ProjectControl.VISIBLE);
+ }
+}
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 9b06dd9..0f9ffff 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
@@ -14,46 +14,48 @@
package com.google.gerrit.httpd.rpc.project;
+import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ListBranchesResult;
+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.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
+import org.eclipse.jgit.lib.ObjectId;
+
import java.util.List;
import java.util.Set;
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
+ private final ChangeProjectAccess.Factory changeProjectAccessFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
private final DeleteBranches.Factory deleteBranchesFactory;
private final ListBranches.Factory listBranchesFactory;
private final VisibleProjects.Factory visibleProjectsFactory;
+ private final ProjectAccessFactory.Factory projectAccessFactory;
private final ProjectDetailFactory.Factory projectDetailFactory;
- private final AddRefRight.Factory addRefRightFactory;
- private final DeleteRefRights.Factory deleteRefRightsFactory;
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
+ final ChangeProjectAccess.Factory changeProjectAccessFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
final ListBranches.Factory listBranchesFactory,
final VisibleProjects.Factory visibleProjectsFactory,
- final ProjectDetailFactory.Factory projectDetailFactory,
- final AddRefRight.Factory addRefRightFactory,
- final DeleteRefRights.Factory deleteRefRightsFactory) {
+ final ProjectAccessFactory.Factory projectAccessFactory,
+ final ProjectDetailFactory.Factory projectDetailFactory) {
this.addBranchFactory = addBranchFactory;
+ this.changeProjectAccessFactory = changeProjectAccessFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
this.deleteBranchesFactory = deleteBranchesFactory;
this.listBranchesFactory = listBranchesFactory;
this.visibleProjectsFactory = visibleProjectsFactory;
+ this.projectAccessFactory = projectAccessFactory;
this.projectDetailFactory = projectDetailFactory;
- this.addRefRightFactory = addRefRightFactory;
- this.deleteRefRightsFactory = deleteRefRightsFactory;
}
@Override
@@ -68,24 +70,23 @@
}
@Override
+ public void projectAccess(final Project.NameKey projectName,
+ final AsyncCallback<ProjectAccess> callback) {
+ projectAccessFactory.create(projectName).to(callback);
+ }
+
+ @Override
public void changeProjectSettings(final Project update,
final AsyncCallback<ProjectDetail> callback) {
changeProjectSettingsFactory.create(update).to(callback);
}
@Override
- public void deleteRight(final Project.NameKey projectName,
- final Set<RefRight.Key> toRemove, final AsyncCallback<ProjectDetail> callback) {
- deleteRefRightsFactory.create(projectName, toRemove).to(callback);
- }
-
- @Override
- public void addRight(final Project.NameKey projectName,
- final ApprovalCategory.Id categoryId, final String groupName,
- final String refPattern, final short min, final short max,
- final AsyncCallback<ProjectDetail> callback) {
- addRefRightFactory.create(projectName, categoryId, groupName, refPattern,
- min, max).to(callback);
+ public void changeProjectAccess(Project.NameKey projectName,
+ String baseRevision, String msg, List<AccessSection> sections,
+ AsyncCallback<ProjectAccess> cb) {
+ ObjectId base = ObjectId.fromString(baseRevision);
+ changeProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
}
@Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
index ef632c4..1eb940b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
@@ -14,50 +14,28 @@
package com.google.gerrit.httpd.rpc.project;
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.common.data.ApprovalTypes;
-import com.google.gerrit.common.data.InheritedRefRight;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
class ProjectDetailFactory extends Handler<ProjectDetail> {
interface Factory {
ProjectDetailFactory create(@Assisted Project.NameKey name);
}
- private final ApprovalTypes approvalTypes;
- private final GroupCache groupCache;
private final ProjectControl.Factory projectControlFactory;
private final Project.NameKey projectName;
- private Map<AccountGroup.Id, AccountGroup> groups;
@Inject
- ProjectDetailFactory(final ApprovalTypes approvalTypes,
- final GroupCache groupCache,
- final ProjectControl.Factory projectControlFactory,
+ ProjectDetailFactory(final ProjectControl.Factory projectControlFactory,
@Assisted final Project.NameKey name) {
- this.approvalTypes = approvalTypes;
- this.groupCache = groupCache;
this.projectControlFactory = projectControlFactory;
this.projectName = name;
@@ -72,88 +50,13 @@
final ProjectDetail detail = new ProjectDetail();
detail.setProject(projectState.getProject());
- groups = new HashMap<AccountGroup.Id, AccountGroup>();
- final List<InheritedRefRight> refRights = new ArrayList<InheritedRefRight>();
-
- for (final RefRight r : projectState.getInheritedRights()) {
- RefControl rc = pc.controlForRef(r.getRefPattern());
- boolean isOwner = rc.isOwner();
-
- if (!isOwner && !rc.isVisible()) {
- continue;
- }
-
- InheritedRefRight refRight = new InheritedRefRight(r, true, isOwner);
- if (!refRights.contains(refRight)) {
- refRights.add(refRight);
- wantGroup(r.getAccountGroupId());
- }
- }
-
- for (final RefRight r : projectState.getLocalRights()) {
- RefControl rc = pc.controlForRef(r.getRefPattern());
- boolean isOwner = rc.isOwner();
-
- if (!isOwner && !rc.isVisible()) {
- continue;
- }
-
- refRights.add(new InheritedRefRight(r, false, isOwner));
- wantGroup(r.getAccountGroupId());
- }
-
- loadGroups();
-
- Collections.sort(refRights, new Comparator<InheritedRefRight>() {
- @Override
- public int compare(final InheritedRefRight a, final InheritedRefRight b) {
- final RefRight right1 = a.getRight();
- final RefRight right2 = b.getRight();
- int rc = categoryOf(right1).compareTo(categoryOf(right2));
- if (rc == 0) {
- rc = right1.getRefPattern().compareTo(right2.getRefPattern());
- }
- if (rc == 0) {
- rc = groupOf(right1).compareTo(groupOf(right2));
- }
- return rc;
- }
-
- private String categoryOf(final RefRight r) {
- final ApprovalType type =
- approvalTypes.getApprovalType(r.getApprovalCategoryId());
- if (type == null) {
- return r.getApprovalCategoryId().get();
- }
- return type.getCategory().getName();
- }
-
- private String groupOf(final RefRight r) {
- return groups.get(r.getAccountGroupId()).getName();
- }
- });
-
final boolean userIsOwner = pc.isOwner();
final boolean userIsOwnerAnyRef = pc.isOwnerAnyRef();
- detail.setRights(refRights);
- detail.setGroups(groups);
detail.setCanModifyAccess(userIsOwnerAnyRef);
detail.setCanModifyAgreements(userIsOwner);
detail.setCanModifyDescription(userIsOwner);
detail.setCanModifyMergeType(userIsOwner);
return detail;
}
-
- private void wantGroup(final AccountGroup.Id id) {
- groups.put(id, null);
- }
-
- private void loadGroups() {
- final Set<AccountGroup.Id> toGet = groups.keySet();
- groups = new HashMap<AccountGroup.Id, AccountGroup>();
- for (AccountGroup.Id groupId : toGet) {
- groups.put(groupId, groupCache.get(groupId));
- }
- }
}
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 7932c79..68b3625 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
@@ -29,12 +29,12 @@
@Override
protected void configure() {
factory(AddBranch.Factory.class);
- factory(AddRefRight.Factory.class);
+ factory(ChangeProjectAccess.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);
- factory(DeleteRefRights.Factory.class);
factory(ListBranches.Factory.class);
factory(VisibleProjects.Factory.class);
+ factory(ProjectAccessFactory.Factory.class);
factory(ProjectDetailFactory.Factory.class);
}
});
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
index 2588350..31ba77e 100644
--- 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
@@ -17,11 +17,9 @@
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
-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.gwtorm.client.OrmException;
import com.google.inject.Inject;
import java.util.ArrayList;
@@ -35,33 +33,26 @@
}
private final ProjectControl.Factory projectControlFactory;
- private final CurrentUser user;
- private final ReviewDb db;
+ private final ProjectCache projectCache;
@Inject
VisibleProjects(final ProjectControl.Factory projectControlFactory,
- final CurrentUser user, final ReviewDb db) {
+ final ProjectCache projectCache) {
this.projectControlFactory = projectControlFactory;
- this.user = user;
- this.db = db;
+ this.projectCache = projectCache;
}
@Override
- public List<Project> call() throws OrmException {
- final List<Project> result;
- if (user.isAdministrator()) {
- result = db.projects().all().toList();
- } else {
- result = new ArrayList<Project>();
- for (Project p : db.projects().all().toList()) {
- try {
- ProjectControl c = projectControlFactory.controlFor(p.getNameKey());
- if (c.isVisible() || c.isOwner()) {
- result.add(p);
- }
- } catch (NoSuchProjectException e) {
- continue;
+ public List<Project> call() {
+ 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>() {
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index 3c9c979..a5b0159 100644
--- a/gerrit-launcher/pom.xml
+++ b/gerrit-launcher/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index a097d75..d9ea7d3 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -120,7 +120,17 @@
try {
String cn = name;
if (cn.equals(cn.toLowerCase())) {
- cn = cn.substring(0, 1).toUpperCase() + cn.substring(1);
+ StringBuilder buf = new StringBuilder();
+ buf.append(Character.toUpperCase(cn.charAt(0)));
+ for (int i = 1; i < cn.length(); i++) {
+ if (cn.charAt(i) == '-' && i + 1 < cn.length()) {
+ i++;
+ buf.append(Character.toUpperCase(cn.charAt(i)));
+ } else {
+ buf.append(cn.charAt(i));
+ }
+ }
+ cn = buf.toString();
}
clazz = Class.forName(pkg + "." + cn, true, loader);
} catch (ClassNotFoundException cnfe) {
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index a46aa4f..f630be64 100644
--- a/gerrit-main/pom.xml
+++ b/gerrit-main/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-main</artifactId>
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index 5bbae03..738d6ce 100644
--- a/gerrit-patch-commonsnet/pom.xml
+++ b/gerrit-patch-commonsnet/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index f30eace..f2d1cfd 100644
--- a/gerrit-patch-jgit/pom.xml
+++ b/gerrit-patch-jgit/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-patch-jgit</artifactId>
@@ -49,4 +49,20 @@
<scope>provided</scope>
</dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index f658f52..54f16a7 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-pgm</artifactId>
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 8c6879d..96c97b0 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
@@ -31,6 +31,7 @@
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.MasterNodeStartup;
+import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.gerrit.sshd.commands.SlaveCommandModule;
@@ -185,6 +186,7 @@
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<Module>();
+ modules.add(SchemaVersionCheck.module());
modules.add(new LogFileCompressor.Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
if (httpd) {
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 71512f2..7b8c286 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
@@ -24,13 +24,10 @@
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.GerritPersonIdentProvider;
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.config.ApprovalTypesProvider;
-import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.FactoryModule;
@@ -38,6 +35,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.schema.SchemaVersionCheck;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.AbstractModule;
@@ -47,7 +45,6 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
@@ -94,18 +91,15 @@
gitInjector = dbInjector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
- bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
+ install(SchemaVersionCheck.module());
bind(ApprovalTypes.class).toProvider(ApprovalTypesProvider.class).in(
Scopes.SINGLETON);
bind(String.class).annotatedWith(CanonicalWebUrl.class)
.toProvider(CanonicalWebUrlProvider.class).in(Scopes.SINGLETON);
- bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
- .toProvider(GerritPersonIdentProvider.class).in(Scopes.SINGLETON);
bind(CachePool.class);
install(AccountCacheImpl.module());
install(GroupCacheImpl.module());
- install(new AuthConfigModule());
install(new FactoryModule() {
@Override
protected void configure() {
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 7127027..68ac33d 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
@@ -31,9 +31,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitProjectImporter;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.schema.SchemaUpdater;
import com.google.gerrit.server.schema.UpdateUI;
import com.google.gerrit.server.util.HostPlatform;
@@ -62,9 +60,6 @@
@Option(name = "--batch", usage = "Batch mode; skip interactive prompting")
private boolean batchMode;
- @Option(name = "--import-projects", usage = "Import git repositories as projects")
- private boolean importProjects;
-
@Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init")
private boolean noAutoStart;
@@ -73,7 +68,6 @@
ErrorLogFile.errorOnlyConsole();
final SiteInit init = createSiteInit();
- init.flags.importProjects = importProjects;
init.flags.autoStart = !noAutoStart && init.site.isNew;
final SiteRun run;
@@ -83,7 +77,6 @@
run = createSiteRun(init);
run.upgradeSchema();
- run.importGit();
} catch (Exception failure) {
if (init.flags.deleteOnFailure) {
recursiveDelete(getSitePath());
@@ -166,7 +159,6 @@
final SchemaUpdater schemaUpdater;
final SchemaFactory<ReviewDb> schema;
final GitRepositoryManager repositoryManager;
- final GitProjectImporter gitProjectImporter;
final Browser browser;
@Inject
@@ -174,14 +166,13 @@
final SchemaUpdater schemaUpdater,
final SchemaFactory<ReviewDb> schema,
final GitRepositoryManager repositoryManager,
- final GitProjectImporter gitProjectImporter, final Browser browser) {
+ final Browser browser) {
this.ui = ui;
this.site = site;
this.flags = flags;
this.schemaUpdater = schemaUpdater;
this.schema = schema;
this.repositoryManager = repositoryManager;
- this.gitProjectImporter = gitProjectImporter;
this.browser = browser;
}
@@ -241,23 +232,6 @@
}
}
- void importGit() throws OrmException, IOException {
- if (flags.importProjects) {
- gitProjectImporter.run(new GitProjectImporter.Messages() {
- @Override
- public void info(String msg) {
- System.err.println(msg);
- System.err.flush();
- }
-
- @Override
- public void warning(String msg) {
- info(msg);
- }
- });
- }
- }
-
void start() throws Exception {
if (flags.autoStart) {
if (HostPlatform.isWin32()) {
@@ -317,9 +291,6 @@
protected void configure() {
bind(ConsoleUI.class).toInstance(init.ui);
bind(InitFlags.class).toInstance(init.flags);
-
- bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
- bind(GitProjectImporter.class);
}
});
return createDbInjector(SINGLE_USER).createChildInjector(modules);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
new file mode 100644
index 0000000..803b702
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
@@ -0,0 +1,93 @@
+// 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.pgm;
+
+import com.google.gerrit.pgm.util.AbstractProgram;
+
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.HaltException;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologClassLoader;
+import com.googlecode.prolog_cafe.lang.PrologMain;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+
+import org.kohsuke.args4j.Option;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+public class PrologShell extends AbstractProgram {
+ @Option(name = "-s", multiValued = true, metaVar = "FILE.pl", usage = "file to load")
+ private List<String> fileName = new ArrayList<String>();
+
+ @Override
+ public int run() {
+ banner();
+
+ BufferingPrologControl pcl = new BufferingPrologControl();
+ pcl.setPrologClassLoader(new PrologClassLoader(getClass().getClassLoader()));
+ pcl.setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
+ pcl.setEnabled(Prolog.Feature.IO, true);
+ pcl.setEnabled(Prolog.Feature.STATISTICS_RUNTIME, true);
+
+ pcl.initialize(Prolog.BUILTIN);
+ pcl.execute(Prolog.BUILTIN, "set_prolog_flag",
+ SymbolTerm.intern("print_stack_trace"),
+ SymbolTerm.intern("on"));
+
+ for (String file : fileName) {
+ String path;
+ try {
+ path = new File(file).getCanonicalPath();
+ } catch (IOException e) {
+ path = new File(file).getAbsolutePath();
+ }
+ pcl.execute(Prolog.BUILTIN, "consult", SymbolTerm.create(path));
+ System.err.println();
+ System.err.flush();
+ }
+
+ try {
+ pcl.execute(Prolog.BUILTIN, "cafeteria");
+ write("% halt\n");
+ return 0;
+ } catch (HaltException halt) {
+ write("% halt(" + halt.getStatus() + ")\n");
+ return halt.getStatus();
+ }
+ }
+
+ private void banner() {
+ System.err.format("Gerrit Code Review %s - Interactive Prolog Shell",
+ com.google.gerrit.common.Version.getVersion());
+ System.err.println();
+ System.err.println("based on " + PrologMain.VERSION);
+ System.err.println(" " + PrologMain.COPYRIGHT);
+ System.err.println("(type Ctrl-D or \"halt.\" to exit,"
+ + " \"['path/to/file.pl'].\" to load a file)");
+ System.err.println();
+ System.err.flush();
+ }
+
+ private void write(String msg) {
+ System.out.flush();
+ System.err.flush();
+ System.out.println(msg);
+ System.out.flush();
+ }
+}
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
new file mode 100644
index 0000000..19b1ab9
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java
@@ -0,0 +1,115 @@
+// 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.rules.PrologCompiler;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+
+import org.eclipse.jgit.lib.Repository;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * Gets rules.pl at refs/meta/config and compiles into jar file called
+ * rules-(sha1 of rules.pl).jar in (site-path)/cache/rules
+ */
+public class Rulec extends SiteProgram {
+ @Option(name = "--all", usage = "recompile all rules")
+ private boolean all;
+
+ @Option(name = "--quiet", usage = "supress some messsages")
+ private boolean quiet;
+
+ @Argument(index = 0, multiValued = true, metaVar = "PROJECT", usage = "project to compile rules for")
+ private List<String> projectNames = new ArrayList<String>();
+
+ private Injector dbInjector;
+
+ private final LifecycleManager manager = new LifecycleManager();
+
+ @Inject
+ private GitRepositoryManager gitManager;
+
+ @Inject
+ private PrologCompiler.Factory jarFactory;
+
+ @Override
+ public int run() throws Exception {
+ dbInjector = createDbInjector(SINGLE_USER);
+ manager.add(dbInjector);
+ manager.start();
+ dbInjector.createChildInjector(new FactoryModule() {
+ @Override
+ protected void configure() {
+ factory(PrologCompiler.Factory.class);
+ }
+ }).injectMembers(this);
+
+ LinkedHashSet<Project.NameKey> names = new LinkedHashSet<Project.NameKey>();
+ for (String name : projectNames) {
+ names.add(new Project.NameKey(name));
+ }
+ if (all) {
+ names.addAll(gitManager.list());
+ }
+
+ boolean error = false;
+ for (Project.NameKey project : names) {
+ Repository git = gitManager.openRepository(project);
+ try {
+ switch (jarFactory.create(git).call()) {
+ case NO_RULES:
+ if (!all || projectNames.contains(project.get())) {
+ System.err.println("error: No rules.pl in " + project.get());
+ error = true;
+ }
+ break;
+
+ case COMPILED:
+ if (!quiet) {
+ System.out.format("Compiled %-60s ... SUCCESS", project.get());
+ System.out.println();
+ }
+ break;
+ }
+ } catch (CompileException err) {
+ if (showStackTrace) {
+ err.printStackTrace();
+ } else {
+ System.err.println("fatal: " + err.getMessage());
+ }
+ error = true;
+ } finally {
+ git.close();
+ }
+ }
+
+ return !error ? 0 : 1;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
index beeed24..8bf5f98 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
@@ -17,7 +17,6 @@
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
@@ -26,7 +25,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -56,7 +55,6 @@
private List<Change> todo;
private Injector dbInjector;
- private Injector gitInjector;
@Inject
private TrackingFooters footers;
@@ -74,17 +72,11 @@
}
dbInjector = createDbInjector(MULTI_USER);
- gitInjector = dbInjector.createChildInjector(new LifecycleModule() {
- @Override
- protected void configure() {
- bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
- listener().to(LocalDiskRepositoryManager.Lifecycle.class);
- }
- });
-
- manager.add(dbInjector, gitInjector);
+ manager.add(
+ dbInjector,
+ dbInjector.createChildInjector(SchemaVersionCheck.module()));
manager.start();
- gitInjector.injectMembers(this);
+ dbInjector.injectMembers(this);
final ReviewDb db = database.open();
try {
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 6870ca4..ee7c794 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
@@ -22,10 +22,10 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
import com.google.gerrit.sshd.CommandExecutorQueueProvider;
-import com.google.gerrit.sshd.QueueProvider;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -142,13 +142,7 @@
}
private WorkQueue.Executor getExecutor() {
- WorkQueue.Executor executor;
- if (userProvider.get().isBatchUser()) {
- executor = queue.getBatchQueue();
- } else {
- executor = queue.getInteractiveQueue();
- }
- return executor;
+ return queue.getQueue(userProvider.get().getCapabilities().getQueueType());
}
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index a5c7d9b..a4007d5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -18,6 +18,7 @@
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.reviewdb.AuthType;
+import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -80,5 +81,9 @@
break;
}
}
+
+ if (auth.getSecure("registerEmailPrivateKey") == null) {
+ auth.setSecure("registerEmailPrivateKey", SignedToken.generateRandomKey());
+ }
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
index 992c616..5d71b48 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
@@ -30,9 +30,6 @@
/** Recursively delete the site path if initialization fails. */
public boolean deleteOnFailure;
- /** Run the Git project importer after initialization. */
- public boolean importProjects;
-
/** Run the daemon (and open the web UI in a browser) after initialization. */
public boolean autoStart;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
index 2d95577..f0cd31f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
@@ -25,14 +25,11 @@
/** Initialize the GitRepositoryManager configuration section. */
@Singleton
class InitGitManager implements InitStep {
- private final InitFlags flags;
private final ConsoleUI ui;
private final Section gerrit;
@Inject
- InitGitManager(final InitFlags flags, final ConsoleUI ui,
- final Section.Factory sections) {
- this.flags = flags;
+ InitGitManager(final ConsoleUI ui, final Section.Factory sections) {
this.ui = ui;
this.gerrit = sections.get("gerrit");
}
@@ -44,11 +41,7 @@
if (d == null) {
throw die("gerrit.basePath is required");
}
- if (d.exists()) {
- if (!flags.importProjects && d.list() != null && d.list().length > 0) {
- flags.importProjects = ui.yesno(true, "Import existing repositories");
- }
- } else if (!d.mkdirs()) {
+ if (!d.exists() && !d.mkdirs()) {
throw die("Cannot create " + d);
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
index 005904c..02ed991 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
@@ -126,7 +126,7 @@
}
String password(final String username, final String password) {
- final String ov = flags.sec.getString(section, null, password);
+ final String ov = getSecure(password);
String user = flags.sec.getString(section, null, username);
if (user == null) {
@@ -149,15 +149,23 @@
final String nv = ui.password("%s's password", user);
if (!eq(ov, nv)) {
- if (nv != null) {
- flags.sec.setString(section, null, password, nv);
- } else {
- flags.sec.unset(section, null, password);
- }
+ setSecure(password, nv);
}
return nv;
}
+ String getSecure(String name) {
+ return flags.sec.getString(section, null, name);
+ }
+
+ void setSecure(String name, String value) {
+ if (value != null) {
+ flags.sec.setString(section, null, name, value);
+ } else {
+ flags.sec.unset(section, null, name);
+ }
+ }
+
private static boolean eq(final String a, final String b) {
if (a == null && b == null) {
return true;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java
index 5d40923..ecd0a16 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java
@@ -32,7 +32,7 @@
private boolean running = true;
@Option(name = "--show-stack-trace", usage = "display stack trace on failure")
- private boolean showStackTrace;
+ protected boolean showStackTrace;
@Option(name = "--help", usage = "display this help text", aliases = {"-h"})
private boolean help;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index 8e3306b..340168c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DatabaseModule;
+import com.google.gerrit.server.schema.SchemaModule;
import com.google.gwtorm.client.OrmException;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
@@ -162,6 +163,7 @@
});
modules.add(new GerritServerConfigModule());
modules.add(new DatabaseModule());
+ modules.add(new SchemaModule());
try {
return Guice.createInjector(PRODUCTION, modules);
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
index 9aba73e..4538bce 100755
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
@@ -54,70 +54,22 @@
get_config() {
if test -f "$GERRIT_CONFIG" ; then
- if type git >/dev/null 2>&1 ; then
- if test "x$1" = x--int ; then
- # Git might not be able to expand "8g" properly. If it gives
- # us 0 back retry for the raw string and expand ourselves.
- #
- n=`git config --file "$GERRIT_CONFIG" --int "$2"`
- if test x0 = "x$n" ; then
- n=`git config --file "$GERRIT_CONFIG" --get "$2"`
- case "$n" in
- *g) n=`expr ${n%%g} \* 1024`m ;;
- *k) n=`expr ${n%%k} \* 1024` ;;
- *) : ;;
- esac
- fi
- echo "$n"
- else
- git config --file "$GERRIT_CONFIG" $1 "$2"
- fi
-
- else
- # This is a very crude parser for the git configuration file.
- # Its not perfect but it can at least pull some basic values
- # from a reasonably standard format.
+ if test "x$1" = x--int ; then
+ # Git might not be able to expand "8g" properly. If it gives
+ # us 0 back retry for the raw string and expand ourselves.
#
- s=`echo "$2" | cut -d. -f1`
- k=`echo "$2" | cut -d. -f2`
- i=0
- while read n ; do
+ n=`git config --file "$GERRIT_CONFIG" --int "$2"`
+ if test x0 = "x$n" ; then
+ n=`git config --file "$GERRIT_CONFIG" --get "$2"`
case "$n" in
- '['$s']') i=1 ;;
- '['*']' ) i=0 ;;
+ *g) n=`expr ${n%%g} \* 1024`m ;;
+ *k) n=`expr ${n%%k} \* 1024` ;;
+ *) : ;;
esac
- test $i || continue
-
- case "$n" in
- *[' ']$k[' ']*=*) : ;;
- [' ']$k=*) : ;;
- $k[' ']*=*) : ;;
- $k=*) : ;;
- *) continue ;;
- esac
-
- n=${n#*=}
-
- if test "x$1" = x--bool ; then
- case "$n" in
- true|on|1|yes) n=true ;;
- false|off|0|no) n=false ;;
- *)
- echo >&2 "error: $2=$n not supported, assuming false."
- n=false
- ;;
- esac
- fi
-
- if test "x$1" = x--int ; then
- case "$n" in
- *g) n=`expr ${n%%g} \* 1024`m ;;
- *k) n=`expr ${n%%k} \* 1024` ;;
- *) : ;;
- esac
- fi
- echo "$n"
- done <"$GERRIT_CONFIG"
+ fi
+ echo "$n"
+ else
+ git config --file "$GERRIT_CONFIG" $1 "$2"
fi
fi
}
@@ -174,6 +126,16 @@
GERRIT_INSTALL_TRACE_FILE=etc/gerrit.config
##################################################
+# No git in PATH? Needed for gerrit.confg parsing
+##################################################
+if type git >/dev/null 2>&1 ; then
+ : OK
+else
+ echo >&2 "** ERROR: Cannot find git in PATH"
+ exit 1
+fi
+
+##################################################
# Try to determine GERRIT_SITE if not set
##################################################
if test -z "$GERRIT_SITE" ; then
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index 060ffdd..3ac4deb 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-prettify</artifactId>
@@ -56,4 +56,20 @@
<scope>provided</scope>
</dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index d81b068..8c43204 100644
--- a/gerrit-reviewdb/pom.xml
+++ b/gerrit-reviewdb/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-reviewdb</artifactId>
@@ -38,4 +38,20 @@
<artifactId>gwtorm</artifactId>
</dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
index 38a2359..ccfdbd5 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountDiffPreference.java
@@ -102,6 +102,9 @@
@Column(id = 11)
protected boolean skipUncommented;
+ @Column(id = 12)
+ protected boolean expandAllComments;
+
protected AccountDiffPreference() {
}
@@ -120,6 +123,7 @@
this.showTabs = p.showTabs;
this.skipDeleted = p.skipDeleted;
this.skipUncommented = p.skipUncommented;
+ this.expandAllComments = p.expandAllComments;
this.context = p.context;
}
@@ -209,4 +213,12 @@
public void setSkipUncommented(boolean skip) {
skipUncommented = skip;
}
+
+ public boolean isExpandAllComments() {
+ return expandAllComments;
+ }
+
+ public void setExpandAllComments(boolean expand) {
+ expandAllComments = expand;
+ }
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java
index d2aceaa..5d8a4b9 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java
@@ -46,6 +46,39 @@
}
}
+ /** Globally unique identifier. */
+ public static class UUID extends
+ StringKey<com.google.gwtorm.client.Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ @Column(id = 1, length = 40)
+ protected String uuid;
+
+ protected UUID() {
+ }
+
+ public UUID(final String n) {
+ uuid = n;
+ }
+
+ @Override
+ public String get() {
+ return uuid;
+ }
+
+ @Override
+ protected void set(String newValue) {
+ uuid = newValue;
+ }
+
+ /** Parse an AccountGroup.UUID out of a string representation. */
+ public static UUID parse(final String str) {
+ final UUID r = new UUID();
+ r.fromString(str);
+ return r;
+ }
+ }
+
/** Distinguished name, within organization directory server. */
public static class ExternalNameKey extends
StringKey<com.google.gwtorm.client.Key<?>> {
@@ -140,6 +173,18 @@
LDAP;
}
+ /** Common UUID assigned to the "Project Owners" placeholder group. */
+ public static final AccountGroup.UUID PROJECT_OWNERS =
+ new AccountGroup.UUID("global:Project-Owners");
+
+ /** Common UUID assigned to the "Anonymous Users" group. */
+ public static final AccountGroup.UUID ANONYMOUS_USERS =
+ new AccountGroup.UUID("global:Anonymous-Users");
+
+ /** Common UUID assigned to the "Registered Users" group. */
+ public static final AccountGroup.UUID REGISTERED_USERS =
+ new AccountGroup.UUID("global:Registered-Users");
+
/** Unique name of this group within the system. */
@Column(id = 1)
protected NameKey name;
@@ -176,15 +221,20 @@
@Column(id = 8)
protected boolean emailOnlyAuthors;
+ /** Globally unique identifier name for this group. */
+ @Column(id = 9)
+ protected UUID groupUUID;
+
protected AccountGroup() {
}
public AccountGroup(final AccountGroup.NameKey newName,
- final AccountGroup.Id newId) {
+ final AccountGroup.Id newId, final AccountGroup.UUID uuid) {
name = newName;
groupId = newId;
ownerGroupId = groupId;
visibleToAll = false;
+ groupUUID = uuid;
setType(Type.INTERNAL);
}
@@ -251,4 +301,12 @@
public void setEmailOnlyAuthors(boolean emailOnlyAuthors) {
this.emailOnlyAuthors = emailOnlyAuthors;
}
+
+ public AccountGroup.UUID getGroupUUID() {
+ return groupUUID;
+ }
+
+ public void setGroupUUID(AccountGroup.UUID uuid) {
+ groupUUID = uuid;
+ }
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java
index 2530654..7eb7ed2 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java
@@ -25,6 +25,9 @@
@PrimaryKey("groupId")
AccountGroup get(AccountGroup.Id id) throws OrmException;
+ @Query("WHERE groupUUID = ?")
+ ResultSet<AccountGroup> byUUID(AccountGroup.UUID uuid) throws OrmException;
+
@Query("WHERE externalName = ?")
ResultSet<AccountGroup> byExternalName(AccountGroup.ExternalNameKey name)
throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ApprovalCategory.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ApprovalCategory.java
index 29ea97e..d7e5024 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ApprovalCategory.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ApprovalCategory.java
@@ -24,33 +24,6 @@
public static final ApprovalCategory.Id SUBMIT =
new ApprovalCategory.Id("SUBM");
- /** Id of the special "Read" action (and category). */
- public static final ApprovalCategory.Id READ =
- new ApprovalCategory.Id("READ");
-
- /** Id of the special "Own" category; manages a project. */
- public static final ApprovalCategory.Id OWN = new ApprovalCategory.Id("OWN");
-
- /** Id of the special "Push Annotated Tag" action (and category). */
- public static final ApprovalCategory.Id PUSH_TAG =
- new ApprovalCategory.Id("pTAG");
- public static final short PUSH_TAG_SIGNED = 1;
- public static final short PUSH_TAG_ANNOTATED = 2;
-
- /** Id of the special "Push Branch" action (and category). */
- public static final ApprovalCategory.Id PUSH_HEAD =
- new ApprovalCategory.Id("pHD");
- public static final short PUSH_HEAD_UPDATE = 1;
- public static final short PUSH_HEAD_CREATE = 2;
- public static final short PUSH_HEAD_REPLACE = 3;
-
- /** Id of the special "Forge Identity" category. */
- public static final ApprovalCategory.Id FORGE_IDENTITY =
- new ApprovalCategory.Id("FORG");
- public static final short FORGE_AUTHOR = 1;
- public static final short FORGE_COMMITTER = 2;
- public static final short FORGE_SERVER = 3;
-
public static class Id extends StringKey<Key<?>> {
private static final long serialVersionUID = 1L;
@@ -73,15 +46,6 @@
protected void set(String newValue) {
id = newValue;
}
-
- /** True if the right can be assigned on the wild project. */
- public boolean canBeOnWildProject() {
- if (OWN.equals(this)) {
- return false;
- } else {
- return true;
- }
- }
}
/** Internal short unique identifier for this category. */
@@ -96,16 +60,7 @@
@Column(id = 3, length = 4, notNull = false)
protected String abbreviatedName;
- /**
- * Order of this category within the Approvals table when presented.
- * <p>
- * If < 0 (e.g. -1) this category is not shown in the Approvals table but is
- * instead considered to be an action that the user might be able to perform,
- * e.g. "Submit".
- * <p>
- * If >= 0 this category is shown in the Approvals table, sorted along with
- * its siblings by <code>position, name</code>.
- */
+ /** Order of this category within the Approvals table when presented. */
@Column(id = 4)
protected short position;
@@ -117,6 +72,9 @@
@Column(id = 6)
protected boolean copyMinScore;
+ /** Computed name derived from {@link #name}. */
+ protected String labelName;
+
protected ApprovalCategory() {
}
@@ -136,6 +94,26 @@
public void setName(final String n) {
name = n;
+ labelName = null;
+ }
+
+ /** Clean version of {@link #getName()}, e.g. "Code Review" is "Code-Review". */
+ public String getLabelName() {
+ if (labelName == null) {
+ StringBuilder r = new StringBuilder();
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (('0' <= c && c <= '9') //
+ || ('a' <= c && c <= 'z') //
+ || ('A' <= c && c <= 'Z')) {
+ r.append(c);
+ } else if (c == ' ') {
+ r.append('-');
+ }
+ }
+ labelName = r.toString();
+ }
+ return labelName;
}
public String getAbbreviatedName() {
@@ -154,14 +132,6 @@
position = p;
}
- public boolean isAction() {
- return position < 0;
- }
-
- public boolean isRange() {
- return !isAction();
- }
-
public String getFunctionName() {
return functionName;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java
index 409547a..b9a6967 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Project.java
@@ -21,7 +21,7 @@
public final class Project {
/** Project name key */
public static class NameKey extends
- StringKey<com.google.gwtorm.client.Key<?>> {
+ StringKey<com.google.gwtorm.client.Key<?>> implements Comparable<NameKey> {
private static final long serialVersionUID = 1L;
@Column(id = 1)
@@ -44,6 +44,24 @@
name = newValue;
}
+ @Override
+ public int compareTo(NameKey other) {
+ return get().compareTo(other.get());
+ }
+
+ @Override
+ public int hashCode() {
+ return get().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object b) {
+ if (b instanceof NameKey) {
+ return get().equals(((NameKey) b).get());
+ }
+ return false;
+ }
+
/** Parse a Project.NameKey out of a string representation. */
public static NameKey parse(final String str) {
final NameKey r = new NameKey();
@@ -53,65 +71,37 @@
}
public static enum SubmitType {
- FAST_FORWARD_ONLY('F'),
+ FAST_FORWARD_ONLY,
- MERGE_IF_NECESSARY('M'),
+ MERGE_IF_NECESSARY,
- MERGE_ALWAYS('A'),
+ MERGE_ALWAYS,
- CHERRY_PICK('C');
-
- private final char code;
-
- private SubmitType(final char c) {
- code = c;
- }
-
- public char getCode() {
- return code;
- }
-
- public static SubmitType forCode(final char c) {
- for (final SubmitType s : SubmitType.values()) {
- if (s.code == c) {
- return s;
- }
- }
- return null;
- }
+ CHERRY_PICK;
}
- @Column(id = 1)
protected NameKey name;
- @Column(id = 2, length = Integer.MAX_VALUE, notNull = false)
protected String description;
- @Column(id = 3)
protected boolean useContributorAgreements;
- @Column(id = 4)
protected boolean useSignedOffBy;
- @Column(id = 5)
- protected char submitType;
+ protected SubmitType submitType;
- @Column(id = 6, notNull = false, name = "parent_name")
protected NameKey parent;
- @Column(id = 7)
protected boolean requireChangeID;
- @Column(id = 8)
protected boolean useContentMerge;
protected Project() {
}
- public Project(final Project.NameKey newName) {
- name = newName;
- useContributorAgreements = true;
- setSubmitType(SubmitType.MERGE_IF_NECESSARY);
+ public Project(Project.NameKey nameKey) {
+ name = nameKey;
+ submitType = SubmitType.MERGE_IF_NECESSARY;
}
public Project.NameKey getNameKey() {
@@ -119,7 +109,7 @@
}
public String getName() {
- return name.get();
+ return name != null ? name.get() : null;
}
public String getDescription() {
@@ -163,11 +153,11 @@
}
public SubmitType getSubmitType() {
- return SubmitType.forCode(submitType);
+ return submitType;
}
public void setSubmitType(final SubmitType type) {
- submitType = type.getCode();
+ submitType = type;
}
public void copySettingsFrom(final Project update) {
@@ -183,7 +173,11 @@
return parent;
}
- public void setParent(final Project.NameKey parentProjectName) {
- parent = parentProjectName;
+ public String getParentName() {
+ return parent != null ? parent.get() : null;
+ }
+
+ public void setParentName(String n) {
+ parent = n != null ? new NameKey(n) : null;
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectAccess.java
deleted file mode 100644
index b9adada..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectAccess.java
+++ /dev/null
@@ -1,33 +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.reviewdb;
-
-import com.google.gwtorm.client.Access;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.PrimaryKey;
-import com.google.gwtorm.client.Query;
-import com.google.gwtorm.client.ResultSet;
-
-public interface ProjectAccess extends Access<Project, Project.NameKey> {
- @PrimaryKey("name")
- Project get(Project.NameKey name) throws OrmException;
-
- @Query("ORDER BY name")
- ResultSet<Project> all() throws OrmException;
-
- @Query("WHERE name.name >= ? AND name.name <= ? ORDER BY name LIMIT ?")
- ResultSet<Project> suggestByName(String nameA, String nameB, int limit)
- throws OrmException;
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
deleted file mode 100644
index 97ee219..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
+++ /dev/null
@@ -1,235 +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.reviewdb;
-
-import com.google.gwtorm.client.Column;
-import com.google.gwtorm.client.CompoundKey;
-import com.google.gwtorm.client.StringKey;
-
-import java.util.Comparator;
-
-/** Grant to use an {@link ApprovalCategory} in the scope of a git ref. */
-public final class RefRight {
- /** Pattern that matches all references in a project. */
- public static final String ALL = "refs/*";
-
- /** Prefix that triggers a regular expression pattern. */
- public static final String REGEX_PREFIX = "^";
-
- public static class RefPattern extends
- StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- @Column(id = 1)
- protected String pattern;
-
- protected RefPattern() {
- }
-
- public RefPattern(final String pattern) {
- this.pattern = pattern;
- }
-
- @Override
- public String get() {
- return pattern;
- }
-
- @Override
- protected void set(String pattern) {
- this.pattern = pattern;
- }
- }
-
- public static class Key extends CompoundKey<Project.NameKey> {
- private static final long serialVersionUID = 1L;
-
- @Column(id = 1)
- protected Project.NameKey projectName;
-
- @Column(id = 2)
- protected RefPattern refPattern;
-
- @Column(id = 3)
- protected ApprovalCategory.Id categoryId;
-
- @Column(id = 4)
- protected AccountGroup.Id groupId;
-
- protected Key() {
- projectName = new Project.NameKey();
- refPattern = new RefPattern();
- categoryId = new ApprovalCategory.Id();
- groupId = new AccountGroup.Id();
- }
-
- public Key(final Project.NameKey projectName, final RefPattern refPattern,
- final ApprovalCategory.Id categoryId, final AccountGroup.Id groupId) {
- this.projectName = projectName;
- this.refPattern = refPattern;
- this.categoryId = categoryId;
- this.groupId = groupId;
- }
-
- @Override
- public Project.NameKey getParentKey() {
- return projectName;
- }
-
- public Project.NameKey getProjectNameKey() {
- return projectName;
- }
-
- public String getRefPattern() {
- return refPattern.get();
- }
-
- public void setGroupId(AccountGroup.Id groupId) {
- this.groupId = groupId;
- }
-
- @Override
- public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {refPattern, categoryId,
- groupId};
- }
- }
-
- @Column(id = 1, name = Column.NONE)
- protected Key key;
-
- @Column(id = 2)
- protected short minValue;
-
- @Column(id = 3)
- protected short maxValue;
-
- protected RefRight() {
- }
-
- public RefRight(RefRight.Key key) {
- this.key = key;
- }
-
- public RefRight(final RefRight refRight, final AccountGroup.Id groupId) {
- this(new RefRight.Key(refRight.getKey().projectName,
- refRight.getKey().refPattern, refRight.getKey().categoryId, groupId));
- setMinValue(refRight.getMinValue());
- setMaxValue(refRight.getMaxValue());
- }
-
- public RefRight.Key getKey() {
- return key;
- }
-
- public String getRefPattern() {
- if (isExclusive()) {
- return key.refPattern.get().substring(1);
- }
- return key.refPattern.get();
- }
-
- public String getRefPatternForDisplay() {
- return key.refPattern.get();
- }
-
- public Project.NameKey getProjectNameKey() {
- return getKey().getProjectNameKey();
- }
-
- public boolean isExclusive() {
- return key.refPattern.get().startsWith("-");
- }
-
- public ApprovalCategory.Id getApprovalCategoryId() {
- return key.categoryId;
- }
-
- public AccountGroup.Id getAccountGroupId() {
- return key.groupId;
- }
-
- public short getMinValue() {
- return minValue;
- }
-
- public void setMinValue(final short m) {
- minValue = m;
- }
-
- public short getMaxValue() {
- return maxValue;
- }
-
- public void setMaxValue(final short m) {
- maxValue = m;
- }
-
- @Override
- public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("{group :");
- s.append(getAccountGroupId().get());
- s.append(", proj :");
- s.append(getProjectNameKey().get());
- s.append(", cat :");
- s.append(getApprovalCategoryId().get());
- s.append(", pattern :");
- s.append(getRefPatternForDisplay());
- s.append(", min :");
- s.append(getMinValue());
- s.append(", max :");
- s.append(getMaxValue());
- s.append("}");
- return s.toString();
- }
-
- @Override
- public int hashCode() {
- return getKey().hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof RefRight) {
- RefRight a = this;
- RefRight b = (RefRight) o;
- return a.getKey().equals(b.getKey())
- && a.getMinValue() == b.getMinValue()
- && a.getMaxValue() == b.getMaxValue();
- }
- return false;
- }
-
- public static final Comparator<RefRight> REF_PATTERN_ORDER =
- new Comparator<RefRight>() {
-
- @Override
- public int compare(RefRight a, RefRight b) {
- int aLength = a.getRefPattern().length();
- int bLength = b.getRefPattern().length();
- if (bLength == aLength) {
- ApprovalCategory.Id aCat = a.getApprovalCategoryId();
- ApprovalCategory.Id bCat = b.getApprovalCategoryId();
- if (aCat.get().equals(bCat.get())) {
- return a.getRefPattern().compareTo(b.getRefPattern());
- }
- return a.getApprovalCategoryId().get()
- .compareTo(b.getApprovalCategoryId().get());
- }
- return bLength - aLength;
- }
- };
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRightAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRightAccess.java
deleted file mode 100644
index a42ff2c..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRightAccess.java
+++ /dev/null
@@ -1,33 +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.reviewdb;
-
-import com.google.gwtorm.client.Access;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.PrimaryKey;
-import com.google.gwtorm.client.Query;
-import com.google.gwtorm.client.ResultSet;
-
-public interface RefRightAccess extends Access<RefRight, RefRight.Key> {
- @PrimaryKey("key")
- RefRight get(RefRight.Key refRight) throws OrmException;
-
- @Query("WHERE key.projectName = ?")
- ResultSet<RefRight> byProject(Project.NameKey project) throws OrmException;
-
- @Query("WHERE key.categoryId = ? AND key.groupId = ?")
- ResultSet<RefRight> byCategoryGroup(ApprovalCategory.Id cat,
- AccountGroup.Id group) throws OrmException;
-}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
index 49e07ff..b75b91b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
@@ -24,7 +24,6 @@
* <p>
* Root entities that are at the top level of some important data graph:
* <ul>
- * <li>{@link Project}: Configuration for a single Git repository.</li>
* <li>{@link Account}: Per-user account registration, preferences, identity.</li>
* <li>{@link Change}: All review information about a single proposed change.</li>
* <li>{@link SystemConfig}: Server-wide settings, managed by administrator.</li>
@@ -94,9 +93,6 @@
AccountPatchReviewAccess accountPatchReviews();
@Relation
- ProjectAccess projects();
-
- @Relation
ChangeAccess changes();
@Relation
@@ -115,9 +111,6 @@
PatchLineCommentAccess patchComments();
@Relation
- RefRightAccess refRights();
-
- @Relation
TrackingIdAccess trackingIds();
/** Create the next unique id for an {@link Account}. */
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
index 6ff23ed..e366a47 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
@@ -52,39 +52,43 @@
@Column(id = 1)
protected Key singleton;
- /** Private key to sign account identification cookies. */
- @Column(id = 2, length = 36)
- public transient String registerEmailPrivateKey;
-
/**
* Local filesystem location of header/footer/CSS configuration files
*/
@Column(id = 3, notNull = false)
public transient String sitePath;
- /** Identity of the administration group; those with full access. */
- @Column(id = 4)
+
+ // DO NOT LOOK BELOW THIS LINE. These fields have all been deleted,
+ // but survive to support schema upgrade code.
+
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 2, length = 36, notNull = false)
+ public transient String registerEmailPrivateKey;
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 4, notNull = false)
public AccountGroup.Id adminGroupId;
-
- /** Identity of the anonymous group, which permits anyone. */
- @Column(id = 5)
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 10, notNull = false)
+ public AccountGroup.UUID adminGroupUUID;
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 5, notNull = false)
public AccountGroup.Id anonymousGroupId;
-
- /** Identity of the registered users group, which permits anyone. */
- @Column(id = 6)
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 6, notNull = false)
public AccountGroup.Id registeredGroupId;
-
- /** Identity of the project */
- @Column(id = 7)
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 7, notNull = false)
public Project.NameKey wildProjectName;
-
- /** Identity of the batch users group */
- @Column(id = 8)
- public AccountGroup.Id batchUsersGroupId;
-
- /** Identity of the owner group, which permits any project owner. */
- @Column(id = 9)
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 9, notNull = false)
public AccountGroup.Id ownerGroupId;
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 8, notNull = false)
+ public AccountGroup.Id batchUsersGroupId;
+ /** DEPRECATED DO NOT USE */
+ @Column(id = 11, notNull = false)
+ public AccountGroup.UUID batchUsersGroupUUID;
protected SystemConfig() {
}
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
index d33d24d..9784a4d 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
@@ -158,14 +158,6 @@
-- *********************************************************************
--- RefRightAccess
--- @PrimaryKey covers: byProject
--- covers: byCategoryGroup
-CREATE INDEX ref_rights_byCatGroup
-ON ref_rights (category_id, group_id);
-
-
--- *********************************************************************
-- TrackingIdAccess
--
CREATE INDEX tracking_ids_byTrkId
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
index 8e3cead..db6894d 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
@@ -240,14 +240,6 @@
-- *********************************************************************
--- RefRightAccess
--- @PrimaryKey covers: byProject
--- covers: byCategoryGroup
-CREATE INDEX ref_rights_byCatGroup
-ON ref_rights (category_id, group_id);
-
-
--- *********************************************************************
-- TrackingIdAccess
--
CREATE INDEX tracking_ids_byTrkId
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index e2370c5..3302e36 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-server</artifactId>
@@ -158,5 +158,63 @@
<groupId>dk.brics.automaton</groupId>
<artifactId>automaton</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>com.googlecode.prolog-cafe</groupId>
+ <artifactId>PrologCafe</artifactId>
+ </dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>prolog-to-java</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <property name="gensrc" location="${project.build.directory}/generated-sources"/>
+
+ <java classname="com.googlecode.prolog_cafe.compiler.Compiler"
+ fork="true"
+ failonerror="true"
+ classpathref="maven.compile.classpath">
+ <arg value="--show-stack-trace"/>
+ <arg value="-O"/>
+ <arg value="-am"/><arg value="${gensrc}/prolog-am"/>
+ <arg value="-s" /><arg value="${gensrc}/prolog-java"/>
+ <arg value="src/main/prolog/gerrit_common.pl"/>
+ </java>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.build.directory}/generated-sources/prolog-java</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
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 1a4a863..8205946 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
@@ -433,8 +433,10 @@
Entry<ApprovalCategory.Id, ApprovalCategoryValue.Id> approval) {
ApprovalAttribute a = new ApprovalAttribute();
a.type = approval.getKey().get();
- final ApprovalType at = approvalTypes.getApprovalType(approval.getKey());
- a.description = at.getCategory().getName();
+ ApprovalType at = approvalTypes.byId(approval.getKey());
+ if (at != null) {
+ a.description = at.getCategory().getName();
+ }
a.value = Short.toString(approval.getValue().get());
return a;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
new file mode 100644
index 0000000..f9aac59
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
@@ -0,0 +1,318 @@
+// 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.rules;
+
+import com.google.gerrit.common.Version;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.compiler.Compiler;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+/**
+ * Helper class for Rulec: does the actual prolog -> java src -> class -> jar work
+ * Finds rules.pl in refs/meta/config branch
+ * Creates rules-(sha1 of rules.pl).jar in (site-path)/cache/rules
+ */
+public class PrologCompiler implements Callable<PrologCompiler.Status> {
+ public interface Factory {
+ PrologCompiler create(Repository git);
+ }
+
+ public static enum Status {
+ NO_RULES, COMPILED;
+ }
+
+ private final File ruleDir;
+ private final Repository git;
+
+ @Inject
+ PrologCompiler(@GerritServerConfig Config config, SitePaths site,
+ @Assisted Repository gitRepository) {
+ File cacheDir = site.resolve(config.getString("cache", null, "directory"));
+ ruleDir = cacheDir != null ? new File(cacheDir, "rules") : null;
+ git = gitRepository;
+ }
+
+ public Status call() throws IOException, CompileException {
+ ObjectId metaConfig = git.resolve(GitRepositoryManager.REF_CONFIG);
+ if (metaConfig == null) {
+ return Status.NO_RULES;
+ }
+
+ ObjectId rulesId = git.resolve(metaConfig.name() + ":rules.pl");
+ if (rulesId == null) {
+ return Status.NO_RULES;
+ }
+
+ if (ruleDir == null) {
+ throw new CompileException("Caching not enabled");
+ }
+ if (!ruleDir.isDirectory() && !ruleDir.mkdir()) {
+ throw new IOException("Cannot create " + ruleDir);
+ }
+
+ File tempDir = File.createTempFile("GerritCodeReview_", ".rulec");
+ if (!tempDir.delete() || !tempDir.mkdir()) {
+ throw new IOException("Cannot create " + tempDir);
+ }
+ try {
+ // Try to make the directory accessible only by this process.
+ // This may help to prevent leaking rule data to outsiders.
+ tempDir.setReadable(true, true);
+ tempDir.setWritable(true, true);
+ tempDir.setExecutable(true, true);
+
+ compileProlog(rulesId, tempDir);
+ compileJava(tempDir);
+
+ File jarFile = new File(ruleDir, "rules-" + rulesId.getName() + ".jar");
+ List<String> classFiles = getRelativePaths(tempDir, ".class");
+ createJar(jarFile, classFiles, tempDir, metaConfig, rulesId);
+
+ return Status.COMPILED;
+ } finally {
+ deleteAllFiles(tempDir);
+ }
+ }
+
+ /** Creates a copy of rules.pl and compiles it into Java sources. */
+ private void compileProlog(ObjectId prolog, File tempDir)
+ throws IOException, CompileException {
+ File tempRules = copyToTempFile(prolog, tempDir);
+ try {
+ Compiler comp = new Compiler();
+ comp.prologToJavaSource(tempRules.getPath(), tempDir.getPath());
+ } finally {
+ tempRules.delete();
+ }
+ }
+
+ private File copyToTempFile(ObjectId blobId, File tempDir)
+ throws IOException, FileNotFoundException, MissingObjectException {
+ // Any leak of tmp caused by this method failing will be cleaned
+ // up by our caller when tempDir is recursively deleted.
+ File tmp = File.createTempFile("rules", ".pl", tempDir);
+ FileOutputStream out = new FileOutputStream(tmp);
+ try {
+ git.open(blobId).copyTo(out);
+ } finally {
+ out.close();
+ }
+ return tmp;
+ }
+
+ /** Compile java src into java .class files */
+ private void compileJava(File tempDir) throws IOException, CompileException {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ throw new CompileException("JDK required (running inside of JRE)");
+ }
+
+ DiagnosticCollector<JavaFileObject> diagnostics =
+ new DiagnosticCollector<JavaFileObject>();
+ StandardJavaFileManager fileManager =
+ compiler.getStandardFileManager(diagnostics, null, null);
+ try {
+ Iterable<? extends JavaFileObject> compilationUnits = fileManager
+ .getJavaFileObjectsFromFiles(getAllFiles(tempDir, ".java"));
+ ArrayList<String> options = new ArrayList<String>();
+ String classpath = getMyClasspath();
+ if (classpath != null) {
+ options.add("-classpath");
+ options.add(classpath);
+ }
+ options.add("-d");
+ options.add(tempDir.getPath());
+ JavaCompiler.CompilationTask task = compiler.getTask(
+ null,
+ fileManager,
+ diagnostics,
+ options,
+ null,
+ compilationUnits);
+ if (!task.call()) {
+ Locale myLocale = Locale.getDefault();
+ StringBuilder msg = new StringBuilder();
+ msg.append("Cannot compile to Java bytecode:");
+ for (Diagnostic<? extends JavaFileObject> err : diagnostics.getDiagnostics()) {
+ msg.append('\n');
+ msg.append(err.getKind());
+ msg.append(": ");
+ if (err.getSource() != null) {
+ msg.append(err.getSource().getName());
+ }
+ msg.append(':');
+ msg.append(err.getLineNumber());
+ msg.append(": ");
+ msg.append(err.getMessage(myLocale));
+ }
+ throw new CompileException(msg.toString());
+ }
+ } finally {
+ fileManager.close();
+ }
+ }
+
+ private String getMyClasspath() {
+ StringBuilder cp = new StringBuilder();
+ appendClasspath(cp, getClass().getClassLoader());
+ return 0 < cp.length() ? cp.toString() : null;
+ }
+
+ private void appendClasspath(StringBuilder cp, ClassLoader classLoader) {
+ if (classLoader.getParent() != null) {
+ appendClasspath(cp, classLoader.getParent());
+ }
+ if (classLoader instanceof URLClassLoader) {
+ for (URL url : ((URLClassLoader) classLoader).getURLs()) {
+ if ("file".equals(url.getProtocol())) {
+ if (0 < cp.length()) {
+ cp.append(File.pathSeparatorChar);
+ }
+ cp.append(url.getPath());
+ }
+ }
+ }
+ }
+
+ /** Takes compiled prolog .class files, puts them into the jar file. */
+ private void createJar(File archiveFile, List<String> toBeJared,
+ File tempDir, ObjectId metaConfig, ObjectId rulesId) throws IOException {
+ long now = System.currentTimeMillis();
+ File tmpjar = File.createTempFile(".rulec_", ".jar", archiveFile.getParentFile());
+ try {
+ Manifest mf = new Manifest();
+ mf.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ mf.getMainAttributes().putValue("Built-by", "Gerrit Code Review " + Version.getVersion());
+ if (git.getDirectory() != null) {
+ mf.getMainAttributes().putValue("Source-Repository", git.getDirectory().getPath());
+ }
+ mf.getMainAttributes().putValue("Source-Commit", metaConfig.name());
+ mf.getMainAttributes().putValue("Source-Blob", rulesId.name());
+
+ FileOutputStream stream = new FileOutputStream(tmpjar);
+ JarOutputStream out = new JarOutputStream(stream, mf);
+ byte buffer[] = new byte[10240];
+ try {
+ for (String path : toBeJared) {
+ JarEntry jarAdd = new JarEntry(path);
+ File f = new File(tempDir, path);
+ jarAdd.setTime(now);
+ out.putNextEntry(jarAdd);
+ if (f.isFile()) {
+ FileInputStream in = new FileInputStream(f);
+ try {
+ while (true) {
+ int nRead = in.read(buffer, 0, buffer.length);
+ if (nRead <= 0) {
+ break;
+ }
+ out.write(buffer, 0, nRead);
+ }
+ } finally {
+ in.close();
+ }
+ }
+ out.closeEntry();
+ }
+ } finally {
+ out.close();
+ }
+
+ if (!tmpjar.renameTo(archiveFile)) {
+ throw new IOException("Cannot replace " + archiveFile);
+ }
+ } finally {
+ tmpjar.delete();
+ }
+ }
+
+ private List<File> getAllFiles(File dir, String extension) {
+ ArrayList<File> fileList = new ArrayList<File>();
+ getAllFiles(dir, extension, fileList);
+ return fileList;
+ }
+
+ private void getAllFiles(File dir, String extension, List<File> fileList) {
+ for (File f : dir.listFiles()) {
+ if (f.getName().endsWith(extension)) {
+ fileList.add(f);
+ }
+ if (f.isDirectory()) {
+ getAllFiles(f, extension, fileList);
+ }
+ }
+ }
+
+ private List<String> getRelativePaths(File dir, String extension) {
+ ArrayList<String> pathList = new ArrayList<String>();
+ getRelativePaths(dir, extension, "", pathList);
+ return pathList;
+ }
+
+ private void getRelativePaths(File dir, String extension, String path, List<String> pathList) {
+ for (File f : dir.listFiles()) {
+ if (f.getName().endsWith(extension)) {
+ pathList.add(path + f.getName());
+ }
+ if (f.isDirectory()) {
+ getRelativePaths(f, extension, path + f.getName() + "/", pathList);
+ }
+ }
+ }
+
+ private void deleteAllFiles(File dir) {
+ for (File f : dir.listFiles()) {
+ if (f.isDirectory()) {
+ deleteAllFiles(f);
+ } else {
+ f.delete();
+ }
+ }
+ dir.delete();
+ }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
new file mode 100644
index 0000000..d01e160
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
@@ -0,0 +1,83 @@
+// 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.rules;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.assistedinject.Assisted;
+
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+
+import java.util.EnumSet;
+
+/**
+ * Per-thread Prolog interpreter.
+ * <p>
+ * This class is not thread safe.
+ * <p>
+ * A single copy of the Prolog interpreter, for the current thread.
+ */
+public class PrologEnvironment extends BufferingPrologControl {
+ static final int MAX_ARITY = 8;
+
+ public static interface Factory {
+ /**
+ * Construct a new Prolog interpreter.
+ *
+ * @param src the machine to template the new environment from.
+ * @return the new interpreter.
+ */
+ PrologEnvironment create(PrologMachineCopy src);
+ }
+
+ private final Injector injector;
+
+ @Inject
+ PrologEnvironment(Injector i, @Assisted PrologMachineCopy src) {
+ super(src);
+ injector = i;
+ setMaxArity(MAX_ARITY);
+ setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
+ }
+
+ /** Get the global Guice Injector that configured the environment. */
+ public Injector getInjector() {
+ return injector;
+ }
+
+ /**
+ * Lookup a stored value in the interpreter's hash manager.
+ *
+ * @param <T> type of stored Java object.
+ * @param sv unique key.
+ * @return the value; null if not stored.
+ */
+ public <T> T get(StoredValue<T> sv) {
+ return sv.getOrNull(engine);
+ }
+
+ /**
+ * Set a stored value on the interpreter's hash manager.
+ *
+ * @param <T> type of stored Java object.
+ * @param sv unique key.
+ * @param obj the value to store under {@code sv}.
+ */
+ public <T> void set(StoredValue<T> sv, T obj) {
+ sv.set(engine, obj);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java
similarity index 72%
copy from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
copy to gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java
index 0977ee9..d361790 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java
@@ -12,15 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.rules;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.google.gerrit.server.config.FactoryModule;
-public class Schema_49 extends SchemaVersion {
-
- @Inject
- Schema_49(Provider<Schema_48> prior) {
- super(prior);
+public class PrologModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ factory(PrologEnvironment.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
new file mode 100644
index 0000000..690d5ca
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
@@ -0,0 +1,225 @@
+// 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.rules;
+
+import static com.googlecode.prolog_cafe.lang.PrologMachineCopy.save;
+
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologClassLoader;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.StringReader;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages a cache of compiled Prolog rules.
+ * <p>
+ * Rules are loaded from the {@code site_path/cache/rules/rules-SHA1.jar}, where
+ * {@code SHA1} is the SHA1 of the Prolog {@code rules.pl} in a project's
+ * {@link GitRepositoryManager#REF_CONFIG} branch.
+ */
+@Singleton
+public class RulesCache {
+ /** Maximum size of a dynamic Prolog script, in bytes. */
+ private static final int SRC_LIMIT = 128 * 1024;
+
+ /** Default size of the internal Prolog database within each interpreter. */
+ private static final int DB_MAX = 256;
+
+ private static final String[] PACKAGE_LIST = {
+ Prolog.BUILTIN,
+ "gerrit",
+ };
+
+ private final Map<ObjectId, MachineRef> machineCache =
+ new HashMap<ObjectId, MachineRef>();
+
+ private final ReferenceQueue<PrologMachineCopy> dead =
+ new ReferenceQueue<PrologMachineCopy>();
+
+ private static final class MachineRef extends WeakReference<PrologMachineCopy> {
+ final ObjectId key;
+
+ MachineRef(ObjectId key, PrologMachineCopy pcm,
+ ReferenceQueue<PrologMachineCopy> queue) {
+ super(pcm, queue);
+ this.key = key;
+ }
+ }
+
+ private final File cacheDir;
+ private final File rulesDir;
+ private final GitRepositoryManager gitMgr;
+ private final ClassLoader systemLoader;
+ private final PrologMachineCopy defaultMachine;
+
+ @Inject
+ protected RulesCache(@GerritServerConfig Config config, SitePaths site,
+ GitRepositoryManager gm) {
+ cacheDir = site.resolve(config.getString("cache", null, "directory"));
+ rulesDir = cacheDir != null ? new File(cacheDir, "rules") : null;
+ gitMgr = gm;
+
+ systemLoader = getClass().getClassLoader();
+ defaultMachine = save(newEmptyMachine(systemLoader));
+ }
+
+ /**
+ * Locate a cached Prolog machine state, or create one if not available.
+ *
+ * @return a Prolog machine, after loading the specified rules.
+ * @throws CompileException the machine cannot be created.
+ */
+ public synchronized PrologMachineCopy loadMachine(
+ Project.NameKey project,
+ ObjectId rulesId)
+ throws CompileException {
+ if (project == null || rulesId == null) {
+ return defaultMachine;
+ }
+
+ Reference<? extends PrologMachineCopy> ref = machineCache.get(rulesId);
+ if (ref != null) {
+ PrologMachineCopy pmc = ref.get();
+ if (pmc != null) {
+ return pmc;
+ }
+
+ machineCache.remove(rulesId);
+ ref.enqueue();
+ }
+
+ gc();
+
+ PrologMachineCopy pcm = createMachine(project, rulesId);
+ MachineRef newRef = new MachineRef(rulesId, pcm, dead);
+ machineCache.put(rulesId, newRef);
+ return pcm;
+ }
+
+ private void gc() {
+ Reference<?> ref;
+ while ((ref = dead.poll()) != null) {
+ ObjectId key = ((MachineRef) ref).key;
+ if (machineCache.get(key) == ref) {
+ machineCache.remove(key);
+ }
+ }
+ }
+
+ private PrologMachineCopy createMachine(Project.NameKey project,
+ ObjectId rulesId) throws CompileException {
+ // If the rules are available as a complied JAR on local disk, prefer
+ // that over dynamic consult as the bytecode will be faster.
+ //
+ if (rulesDir != null) {
+ File jarFile = new File(rulesDir, "rules-" + rulesId.getName() + ".jar");
+ if (jarFile.isFile()) {
+ URL[] cp = new URL[] {toURL(jarFile)};
+ return save(newEmptyMachine(new URLClassLoader(cp, systemLoader)));
+ }
+ }
+
+ // Dynamically consult the rules into the machine's internal database.
+ //
+ String rules = read(project, rulesId);
+ BufferingPrologControl ctl = newEmptyMachine(systemLoader);
+ PushbackReader in = new PushbackReader(
+ new StringReader(rules),
+ Prolog.PUSHBACK_SIZE);
+
+ if (!ctl.execute(
+ Prolog.BUILTIN, "consult_stream",
+ SymbolTerm.intern("rules.pl"),
+ new JavaObjectTerm(in))) {
+ throw new CompileException("Cannot consult rules of " + project);
+ }
+ return save(ctl);
+ }
+
+ private String read(Project.NameKey project, ObjectId rulesId)
+ throws CompileException {
+ Repository git;
+ try {
+ git = gitMgr.openRepository(project);
+ } catch (RepositoryNotFoundException e) {
+ throw new CompileException("Cannot open repository " + project, e);
+ }
+ try {
+ ObjectLoader ldr = git.open(rulesId, Constants.OBJ_BLOB);
+ byte[] raw = ldr.getCachedBytes(SRC_LIMIT);
+ return RawParseUtils.decode(raw);
+ } catch (LargeObjectException e) {
+ throw new CompileException("rules of " + project + " are too large", e);
+ } catch (RuntimeException e) {
+ throw new CompileException("Cannot load rules of " + project, e);
+ } catch (IOException e) {
+ throw new CompileException("Cannot load rules of " + project, e);
+ } finally {
+ git.close();
+ }
+ }
+
+ private static BufferingPrologControl newEmptyMachine(ClassLoader cl) {
+ BufferingPrologControl ctl = new BufferingPrologControl();
+ ctl.setMaxArity(PrologEnvironment.MAX_ARITY);
+ ctl.setMaxDatabaseSize(DB_MAX);
+ ctl.setPrologClassLoader(new PrologClassLoader(cl));
+ ctl.setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
+
+ // Bootstrap the interpreter and ensure there is clean state.
+ ctl.initialize(PACKAGE_LIST);
+ return ctl;
+ }
+
+ private static URL toURL(File jarFile) throws CompileException {
+ try {
+ return jarFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ throw new CompileException("Cannot create URL for " + jarFile, e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
new file mode 100644
index 0000000..dbd7fe3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
@@ -0,0 +1,85 @@
+// 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.rules;
+
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.SystemException;
+import com.googlecode.prolog_cafe.lang.Term;
+
+/**
+ * Defines a value cached in a {@link PrologEnvironment}.
+ *
+ * @see StoredValues
+ */
+public class StoredValue<T> {
+ /** Construct a new unique key that does not match any other key. */
+ public static <T> StoredValue<T> create() {
+ return new StoredValue<T>(new JavaObjectTerm(new Object()));
+ }
+
+ /** Construct a key based on a Java Class object, useful for singletons. */
+ public static <T> StoredValue<T> create(Class<T> clazz) {
+ return new StoredValue<T>(new JavaObjectTerm(clazz));
+ }
+
+ private final Term key;
+
+ /**
+ * Initialize a stored value key using a Prolog term.
+ *
+ * @param key unique identity of the stored value. This will be the hash key
+ * in the interpreter's hash manager.
+ */
+ public StoredValue(Term key) {
+ this.key = key;
+ }
+
+ /** Look up the value in the engine, or return null. */
+ @SuppressWarnings("unchecked")
+ public T getOrNull(Prolog engine) {
+ Term r = engine.getHashManager().get(key);
+ return r != null && r.isJavaObject() ? (T) r.toJava() : null;
+ }
+
+ /** Get the value from the engine, or throw SystemException. */
+ public T get(Prolog engine) {
+ T r = getOrNull(engine);
+ if (r == null) {
+ String msg;
+ if (key.isJavaObject() && key.toJava() instanceof Class<?>) {
+ msg = "No " + ((Class<?>) key.toJava()).getName() + " avaliable";
+ } else {
+ msg = key.toString();
+ }
+ throw new SystemException(msg);
+ }
+ return r;
+ }
+
+ public void set(Prolog engine, T obj) {
+ engine.getHashManager().put(key, new JavaObjectTerm(obj));
+ }
+
+ /** Perform {@link #getOrNull(Prolog)} on the environment's interpreter. */
+ public T get(PrologEnvironment env) {
+ return env.get(this);
+ }
+
+ /** Set the value into the environment's interpreter. */
+ public void set(PrologEnvironment env, T obj) {
+ env.set(this, obj);
+ }
+}
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
new file mode 100644
index 0000000..cef129b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -0,0 +1,32 @@
+// 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.rules;
+
+import static com.google.gerrit.rules.StoredValue.create;
+
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.project.ChangeControl;
+
+public final class StoredValues {
+ public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
+ public static final StoredValue<Change> CHANGE = create(Change.class);
+ public static final StoredValue<PatchSet.Id> PATCH_SET_ID = create(PatchSet.Id.class);
+ public static final StoredValue<ChangeControl> CHANGE_CONTROL = create(ChangeControl.class);
+
+ private StoredValues() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
index 1bd2066..c1263ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
@@ -17,25 +17,23 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.Inject;
-import com.google.inject.Singleton;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/** An anonymous user who has not yet authenticated. */
-@Singleton
public class AnonymousUser extends CurrentUser {
@Inject
- AnonymousUser(final AuthConfig auth) {
- super(AccessPath.UNKNOWN, auth);
+ AnonymousUser(CapabilityControl.Factory capabilityControlFactory) {
+ super(capabilityControlFactory, AccessPath.UNKNOWN);
}
@Override
- public Set<AccountGroup.Id> getEffectiveGroups() {
- return authConfig.getAnonymousGroups();
+ public Set<AccountGroup.UUID> getEffectiveGroups() {
+ return Collections.singleton(AccountGroup.ANONYMOUS_USERS);
}
@Override
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 6886d2c..a3dcfea 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
@@ -34,6 +34,7 @@
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.EmailException;
@@ -212,7 +213,7 @@
final IdentifiedUser user, final String message, final ReviewDb db,
final AbandonedSender.Factory abandonedSenderFactory,
final ChangeHookRunner hooks) throws NoSuchChangeException,
- EmailException, OrmException {
+ InvalidChangeOperationException, EmailException, OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final PatchSet patch = db.patchSets().get(patchSetId);
if (patch == null) {
@@ -245,22 +246,14 @@
}
});
- if (updatedChange != null) {
- db.changeMessages().insert(Collections.singleton(cmsg));
+ updatedChange(db, updatedChange, cmsg,
+ "Change is no longer open or patchset is not latest");
- final List<PatchSetApproval> approvals =
- db.patchSetApprovals().byChange(changeId).toList();
- for (PatchSetApproval a : approvals) {
- a.cache(updatedChange);
- }
- db.patchSetApprovals().update(approvals);
-
- // Email the reviewers
- final AbandonedSender cm = abandonedSenderFactory.create(updatedChange);
- cm.setFrom(user.getAccountId());
- cm.setChangeMessage(cmsg);
- cm.send();
- }
+ // Email the reviewers
+ final AbandonedSender cm = abandonedSenderFactory.create(updatedChange);
+ cm.setFrom(user.getAccountId());
+ cm.setChangeMessage(cmsg);
+ cm.send();
hooks.doChangeAbandonedHook(updatedChange, user.getAccount(), message);
}
@@ -371,7 +364,7 @@
final IdentifiedUser user, final String message, final ReviewDb db,
final AbandonedSender.Factory abandonedSenderFactory,
final ChangeHookRunner hooks) throws NoSuchChangeException,
- EmailException, OrmException {
+ InvalidChangeOperationException, EmailException, OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final PatchSet patch = db.patchSets().get(patchSetId);
if (patch == null) {
@@ -404,26 +397,35 @@
}
});
- if (updatedChange != null) {
- db.changeMessages().insert(Collections.singleton(cmsg));
+ updatedChange(db, updatedChange, cmsg,
+ "Change is not abandoned or patchset is not latest");
- final List<PatchSetApproval> approvals =
- db.patchSetApprovals().byChange(changeId).toList();
- for (PatchSetApproval a : approvals) {
- a.cache(updatedChange);
- }
- db.patchSetApprovals().update(approvals);
-
- // Email the reviewers
- final AbandonedSender cm = abandonedSenderFactory.create(updatedChange);
- cm.setFrom(user.getAccountId());
- cm.setChangeMessage(cmsg);
- cm.send();
- }
+ // Email the reviewers
+ final AbandonedSender cm = abandonedSenderFactory.create(updatedChange);
+ cm.setFrom(user.getAccountId());
+ cm.setChangeMessage(cmsg);
+ cm.send();
hooks.doChangeRestoreHook(updatedChange, user.getAccount(), message);
}
+ private static void updatedChange(final ReviewDb db, final Change change,
+ final ChangeMessage cmsg, final String err) throws NoSuchChangeException,
+ InvalidChangeOperationException, OrmException {
+ if (change == null) {
+ throw new InvalidChangeOperationException(err);
+ }
+
+ db.changeMessages().insert(Collections.singleton(cmsg));
+
+ final List<PatchSetApproval> approvals =
+ db.patchSetApprovals().byChange(change.getId()).toList();
+ for (PatchSetApproval a : approvals) {
+ a.cache(change);
+ }
+ db.patchSetApprovals().update(approvals);
+ }
+
public static String sortKey(long lastUpdated, int id){
// The encoding uses minutes since Wed Oct 1 00:00:00 2008 UTC.
// We overrun approximately 4,085 years later, so ~6093.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 0d8d19a..b9404b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -17,7 +17,7 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.servlet.RequestScoped;
import java.util.Collection;
@@ -32,12 +32,16 @@
* @see IdentifiedUser
*/
public abstract class CurrentUser {
+ private final CapabilityControl.Factory capabilityControlFactory;
private final AccessPath accessPath;
- protected final AuthConfig authConfig;
- protected CurrentUser(final AccessPath accessPath, final AuthConfig authConfig) {
+ private CapabilityControl capabilities;
+
+ protected CurrentUser(
+ CapabilityControl.Factory capabilityControlFactory,
+ AccessPath accessPath) {
+ this.capabilityControlFactory = capabilityControlFactory;
this.accessPath = accessPath;
- this.authConfig = authConfig;
}
/** How this user is accessing the Gerrit Code Review application. */
@@ -56,7 +60,7 @@
*
* @return active groups for this user.
*/
- public abstract Set<AccountGroup.Id> getEffectiveGroups();
+ public abstract Set<AccountGroup.UUID> getEffectiveGroups();
/** Set of changes starred by this user. */
public abstract Set<Change.Id> getStarredChanges();
@@ -64,12 +68,18 @@
/** Filters selecting changes the user wants to monitor. */
public abstract Collection<AccountProjectWatch> getNotificationFilters();
- /** Is the user a non-interactive user? */
- public boolean isBatchUser() {
- return getEffectiveGroups().contains(authConfig.getBatchUsersGroup());
+ /** Unique name of the user on this server, if one has been assigned. */
+ public String getUserName() {
+ return null;
}
- public final boolean isAdministrator() {
- return getEffectiveGroups().contains(authConfig.getAdministratorsGroup());
+ /** Capabilities available to this user account. */
+ public CapabilityControl getCapabilities() {
+ CapabilityControl ctl = capabilities;
+ if (ctl == null) {
+ ctl = capabilityControlFactory.create(this);
+ capabilities = ctl;
+ }
+ return ctl;
}
}
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 e46957d..722e3d7 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
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.StarredChange;
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.GroupIncludeCache;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
@@ -43,11 +44,14 @@
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.LinkedList;
+import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Set;
@@ -60,6 +64,7 @@
/** Create an IdentifiedUser, ignoring any per-request state. */
@Singleton
public static class GenericFactory {
+ private final CapabilityControl.Factory capabilityControlFactory;
private final AuthConfig authConfig;
private final Provider<String> canonicalUrl;
private final Realm realm;
@@ -67,10 +72,13 @@
private final GroupIncludeCache groupIncludeCache;
@Inject
- GenericFactory(final AuthConfig authConfig,
+ GenericFactory(
+ CapabilityControl.Factory capabilityControlFactory,
+ final AuthConfig authConfig,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
final GroupIncludeCache groupIncludeCache) {
+ this.capabilityControlFactory = capabilityControlFactory;
this.authConfig = authConfig;
this.canonicalUrl = canonicalUrl;
this.realm = realm;
@@ -83,14 +91,16 @@
}
public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
- return new IdentifiedUser(AccessPath.UNKNOWN, authConfig, canonicalUrl,
- realm, accountCache, groupIncludeCache, null, db, id);
+ return new IdentifiedUser(capabilityControlFactory, AccessPath.UNKNOWN,
+ authConfig, canonicalUrl, realm, accountCache, groupIncludeCache,
+ null, db, id);
}
public IdentifiedUser create(AccessPath accessPath,
Provider<SocketAddress> remotePeerProvider, Account.Id id) {
- return new IdentifiedUser(accessPath, authConfig, canonicalUrl, realm,
- accountCache, groupIncludeCache, remotePeerProvider, null, id);
+ return new IdentifiedUser(capabilityControlFactory, accessPath,
+ authConfig, canonicalUrl, realm, accountCache, groupIncludeCache,
+ remotePeerProvider, null, id);
}
}
@@ -102,6 +112,7 @@
*/
@Singleton
public static class RequestFactory {
+ private final CapabilityControl.Factory capabilityControlFactory;
private final AuthConfig authConfig;
private final Provider<String> canonicalUrl;
private final Realm realm;
@@ -112,13 +123,16 @@
private final Provider<ReviewDb> dbProvider;
@Inject
- RequestFactory(final AuthConfig authConfig,
+ RequestFactory(
+ CapabilityControl.Factory capabilityControlFactory,
+ final AuthConfig authConfig,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
final GroupIncludeCache groupIncludeCache,
final @RemotePeer Provider<SocketAddress> remotePeerProvider,
final Provider<ReviewDb> dbProvider) {
+ this.capabilityControlFactory = capabilityControlFactory;
this.authConfig = authConfig;
this.canonicalUrl = canonicalUrl;
this.realm = realm;
@@ -131,18 +145,42 @@
public IdentifiedUser create(final AccessPath accessPath,
final Account.Id id) {
- return new IdentifiedUser(accessPath, authConfig, canonicalUrl, realm,
- accountCache, groupIncludeCache, remotePeerProvider, dbProvider, id);
+ return new IdentifiedUser(capabilityControlFactory, accessPath,
+ authConfig, canonicalUrl, realm, accountCache, groupIncludeCache,
+ 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 final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
private final GroupIncludeCache groupIncludeCache;
+ private final AuthConfig authConfig;
@Nullable
private final Provider<SocketAddress> remotePeerProvider;
@@ -154,21 +192,24 @@
private AccountState state;
private Set<String> emailAddresses;
- private Set<AccountGroup.Id> effectiveGroups;
+ private Set<AccountGroup.UUID> effectiveGroups;
private Set<Change.Id> starredChanges;
private Collection<AccountProjectWatch> notificationFilters;
- private IdentifiedUser(final AccessPath accessPath,
+ private IdentifiedUser(
+ CapabilityControl.Factory capabilityControlFactory,
+ final AccessPath accessPath,
final AuthConfig authConfig, final Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
final GroupIncludeCache groupIncludeCache,
@Nullable final Provider<SocketAddress> remotePeerProvider,
@Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
- super(accessPath, authConfig);
+ super(capabilityControlFactory, accessPath);
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
+ this.authConfig = authConfig;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
this.accountId = id;
@@ -187,6 +228,7 @@
}
/** @return the user's user name; null if one has not been selected/assigned. */
+ @Override
public String getUserName() {
return state().getUserName();
}
@@ -217,14 +259,14 @@
}
@Override
- public Set<AccountGroup.Id> getEffectiveGroups() {
+ public Set<AccountGroup.UUID> getEffectiveGroups() {
if (effectiveGroups == null) {
- Set<AccountGroup.Id> seedGroups;
+ Set<AccountGroup.UUID> seedGroups;
if (authConfig.isIdentityTrustable(state().getExternalIds())) {
seedGroups = realm.groups(state());
} else {
- seedGroups = authConfig.getRegisteredGroups();
+ seedGroups = registeredGroups;
}
effectiveGroups = getIncludedGroups(seedGroups);
@@ -233,14 +275,14 @@
return effectiveGroups;
}
- private Set<AccountGroup.Id> getIncludedGroups(Set<AccountGroup.Id> seedGroups) {
- Set<AccountGroup.Id> includes = new HashSet<AccountGroup.Id> (seedGroups);
- Queue<AccountGroup.Id> groupQueue = new LinkedList<AccountGroup.Id> (seedGroups);
+ private Set<AccountGroup.UUID> getIncludedGroups(Set<AccountGroup.UUID> seedGroups) {
+ Set<AccountGroup.UUID> includes = new HashSet<AccountGroup.UUID> (seedGroups);
+ Queue<AccountGroup.UUID> groupQueue = new LinkedList<AccountGroup.UUID> (seedGroups);
while (groupQueue.size() > 0) {
- AccountGroup.Id id = groupQueue.remove();
+ AccountGroup.UUID id = groupQueue.remove();
- for (final AccountGroup.Id groupId : groupIncludeCache.getByInclude(id)) {
+ for (final AccountGroup.UUID groupId : groupIncludeCache.getByInclude(id)) {
if (includes.add(groupId)) {
groupQueue.add(groupId);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
index 26dec09..a58f7b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
@@ -17,14 +17,13 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Set;
/** Identity of a peer daemon process that isn't this JVM. */
@@ -36,22 +35,18 @@
PeerDaemonUser create(@Assisted SocketAddress peer);
}
- private final Set<AccountGroup.Id> effectiveGroups;
private final SocketAddress peer;
@Inject
- protected PeerDaemonUser(AuthConfig authConfig, @Assisted SocketAddress peer) {
- super(AccessPath.SSH_COMMAND, authConfig);
-
- final HashSet<AccountGroup.Id> g = new HashSet<AccountGroup.Id>();
- g.add(authConfig.getAdministratorsGroup());
- this.effectiveGroups = Collections.unmodifiableSet(g);
+ protected PeerDaemonUser(CapabilityControl.Factory capabilityControlFactory,
+ @Assisted SocketAddress peer) {
+ super(capabilityControlFactory, AccessPath.SSH_COMMAND);
this.peer = peer;
}
@Override
- public Set<AccountGroup.Id> getEffectiveGroups() {
- return effectiveGroups;
+ public Set<AccountGroup.UUID> getEffectiveGroups() {
+ return Collections.emptySet();
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
index 5c44bfe..ae7f5ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
@@ -17,7 +17,7 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.account.CapabilityControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -28,19 +28,19 @@
public class ReplicationUser extends CurrentUser {
/** Magic set of groups enabling read of any project and reference. */
- public static final Set<AccountGroup.Id> EVERYTHING_VISIBLE =
- Collections.unmodifiableSet(new HashSet<AccountGroup.Id>(0));
+ public static final Set<AccountGroup.UUID> EVERYTHING_VISIBLE =
+ Collections.unmodifiableSet(new HashSet<AccountGroup.UUID>(0));
public interface Factory {
- ReplicationUser create(@Assisted Set<AccountGroup.Id> authGroups);
+ ReplicationUser create(@Assisted Set<AccountGroup.UUID> authGroups);
}
- private final Set<AccountGroup.Id> effectiveGroups;
+ private final Set<AccountGroup.UUID> effectiveGroups;
@Inject
- protected ReplicationUser(AuthConfig authConfig,
- @Assisted Set<AccountGroup.Id> authGroups) {
- super(AccessPath.REPLICATION, authConfig);
+ protected ReplicationUser(CapabilityControl.Factory capabilityControlFactory,
+ @Assisted Set<AccountGroup.UUID> authGroups) {
+ super(capabilityControlFactory, AccessPath.REPLICATION);
if (authGroups == EVERYTHING_VISIBLE) {
effectiveGroups = EVERYTHING_VISIBLE;
@@ -53,12 +53,12 @@
}
}
- private static Set<AccountGroup.Id> copy(Set<AccountGroup.Id> groups) {
- return Collections.unmodifiableSet(new HashSet<AccountGroup.Id>(groups));
+ private static Set<AccountGroup.UUID> copy(Set<AccountGroup.UUID> groups) {
+ return Collections.unmodifiableSet(new HashSet<AccountGroup.UUID>(groups));
}
@Override
- public Set<AccountGroup.Id> getEffectiveGroups() {
+ public Set<AccountGroup.UUID> getEffectiveGroups() {
return effectiveGroups;
}
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 52ccc66..aea13c7 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
@@ -22,7 +22,6 @@
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.AuthConfig;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -90,18 +89,13 @@
static class ByIdLoader extends EntryCreator<Account.Id, AccountState> {
private final SchemaFactory<ReviewDb> schema;
- private final Set<AccountGroup.Id> registered;
- private final Set<AccountGroup.Id> anonymous;
private final GroupCache groupCache;
private final Cache<String, Account.Id> byName;
@Inject
- ByIdLoader(SchemaFactory<ReviewDb> sf, AuthConfig auth,
- GroupCache groupCache,
+ ByIdLoader(SchemaFactory<ReviewDb> sf, GroupCache groupCache,
@Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
this.schema = sf;
- this.registered = auth.getRegisteredGroups();
- this.anonymous = auth.getAnonymousGroups();
this.groupCache = groupCache;
this.byName = byUsername;
}
@@ -133,21 +127,18 @@
Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
who).toList());
- Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
+ Set<AccountGroup.UUID> internalGroups = new HashSet<AccountGroup.UUID>();
for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
final AccountGroup.Id groupId = g.getAccountGroupId();
final AccountGroup group = groupCache.get(groupId);
if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
- internalGroups.add(groupId);
+ internalGroups.add(group.getGroupUUID());
}
}
- if (internalGroups.isEmpty()) {
- internalGroups = registered;
- } else {
- internalGroups.addAll(registered);
- internalGroups = Collections.unmodifiableSet(internalGroups);
- }
+ internalGroups.add(AccountGroup.REGISTERED_USERS);
+ internalGroups.add(AccountGroup.ANONYMOUS_USERS);
+ internalGroups = Collections.unmodifiableSet(internalGroups);
return new AccountState(account, internalGroups, externalIds);
}
@@ -156,6 +147,8 @@
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);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 5cb8f36..9af8171 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -15,6 +15,9 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.reviewdb.Account;
@@ -25,6 +28,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -51,6 +55,7 @@
private final Realm realm;
private final IdentifiedUser.GenericFactory userFactory;
private final ChangeUserName.Factory changeUserNameFactory;
+ private final ProjectCache projectCache;
private final AtomicBoolean firstAccount;
@Inject
@@ -58,7 +63,8 @@
final AccountCache byIdCache, final AccountByEmailCache byEmailCache,
final AuthConfig authConfig, final Realm accountMapper,
final IdentifiedUser.GenericFactory userFactory,
- final ChangeUserName.Factory changeUserNameFactory) throws OrmException {
+ final ChangeUserName.Factory changeUserNameFactory,
+ final ProjectCache projectCache) throws OrmException {
this.schema = schema;
this.byIdCache = byIdCache;
this.byEmailCache = byEmailCache;
@@ -66,6 +72,7 @@
this.realm = accountMapper;
this.userFactory = userFactory;
this.changeUserNameFactory = changeUserNameFactory;
+ this.projectCache = projectCache;
firstAccount = new AtomicBoolean();
final ReviewDb db = schema.open();
@@ -275,9 +282,16 @@
// is going to be the site's administrator and just make them that
// to bootstrap the authentication database.
//
- final AccountGroup.Id admin = authConfig.getAdministratorsGroup();
+ Permission admin = projectCache.getAllProjects()
+ .getConfig()
+ .getAccessSection(AccessSection.GLOBAL_CAPABILITIES)
+ .getPermission(GlobalCapability.ADMINISTRATE_SERVER);
+
+ final AccountGroup.UUID uuid = admin.getRules().get(0).getGroup().getUUID();
+ final AccountGroup g = db.accountGroups().byUUID(uuid).iterator().next();
+ final AccountGroup.Id adminId = g.getId();
final AccountGroupMember m =
- new AccountGroupMember(new AccountGroupMember.Key(newId, admin));
+ new AccountGroupMember(new AccountGroupMember.Key(newId, adminId));
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m, newId)));
db.accountGroupMembers().insert(Collections.singleton(m));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
index 9393227..1b036d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -26,11 +26,11 @@
public class AccountState {
private final Account account;
- private final Set<AccountGroup.Id> internalGroups;
+ private final Set<AccountGroup.UUID> internalGroups;
private final Collection<AccountExternalId> externalIds;
public AccountState(final Account account,
- final Set<AccountGroup.Id> actualGroups,
+ final Set<AccountGroup.UUID> actualGroups,
final Collection<AccountExternalId> externalIds) {
this.account = account;
this.internalGroups = actualGroups;
@@ -89,7 +89,7 @@
}
/** The set of groups maintained directly within the Gerrit database. */
- public Set<AccountGroup.Id> getInternalGroups() {
+ public Set<AccountGroup.UUID> getInternalGroups() {
return internalGroups;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java
index b3ca18a..2a54029 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthRequest.java
@@ -57,6 +57,7 @@
private String displayName;
private String emailAddress;
private String userName;
+ private boolean skipAuthentication;
public AuthRequest(final String externalId) {
this.externalId = externalId;
@@ -108,4 +109,12 @@
public void setUserName(final String user) {
userName = user;
}
+
+ public boolean isSkipAuthentication() {
+ return skipAuthentication;
+ }
+
+ public void setSkipAuthentication(boolean skip) {
+ skipAuthentication = skip;
+ }
}
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
new file mode 100644
index 0000000..39719ad
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -0,0 +1,256 @@
+// 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.account;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupReference;
+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.AccountGroup;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.git.QueueProvider;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Access control management for server-wide capabilities. */
+public class CapabilityControl {
+ public static interface Factory {
+ public CapabilityControl create(CurrentUser user);
+ }
+
+ private final ProjectState state;
+ private final CurrentUser user;
+ private Map<String, List<PermissionRule>> permissions;
+
+ private Boolean canAdministrateServer;
+
+ @Inject
+ CapabilityControl(ProjectCache projectCache, @Assisted CurrentUser currentUser) {
+ state = projectCache.getAllProjects();
+ user = currentUser;
+ }
+
+ /** Identity of the user the control will compute for. */
+ public CurrentUser getCurrentUser() {
+ return user;
+ }
+
+ /** @return true if the user can administer this server. */
+ public boolean canAdministrateServer() {
+ if (canAdministrateServer == null) {
+ canAdministrateServer = user instanceof PeerDaemonUser
+ || canPerform(GlobalCapability.ADMINISTRATE_SERVER);
+ }
+ return canAdministrateServer;
+ }
+
+ /** @return true if the user can create an account for another user. */
+ public boolean canCreateAccount() {
+ return canPerform(GlobalCapability.CREATE_ACCOUNT)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can create a group. */
+ public boolean canCreateGroup() {
+ return canPerform(GlobalCapability.CREATE_GROUP)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can create a group. */
+ public boolean canCreateProject() {
+ return canPerform(GlobalCapability.CREATE_PROJECT)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can kill any running task. */
+ public boolean canKillTask() {
+ return canPerform(GlobalCapability.KILL_TASK)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can view the server caches. */
+ public boolean canViewCaches() {
+ return canPerform(GlobalCapability.VIEW_CACHES)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can flush the server's caches. */
+ public boolean canFlushCaches() {
+ return canPerform(GlobalCapability.FLUSH_CACHES)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can view open connections. */
+ public boolean canViewConnections() {
+ return canPerform(GlobalCapability.VIEW_CONNECTIONS)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can view the entire queue. */
+ public boolean canViewQueue() {
+ return canPerform(GlobalCapability.VIEW_QUEUE)
+ || canAdministrateServer();
+ }
+
+ /** @return true if the user can force replication to any configured destination. */
+ public boolean canStartReplication() {
+ return canPerform(GlobalCapability.START_REPLICATION)
+ || canAdministrateServer();
+ }
+
+ /** @return which priority queue the user's tasks should be submitted to. */
+ public QueueProvider.QueueType getQueueType() {
+ // If a non-generic group (that is not Anonymous Users or Registered Users)
+ // grants us INTERACTIVE permission, use the INTERACTIVE queue even if
+ // BATCH was otherwise granted. This allows site administrators to grant
+ // INTERACTIVE to Registered Users, and BATCH to 'CI Servers' and have
+ // the 'CI Servers' actually use the BATCH queue while everyone else gets
+ // to use the INTERACTIVE queue without additional grants.
+ //
+ List<PermissionRule> rules = access(GlobalCapability.PRIORITY);
+ boolean batch = false;
+ for (PermissionRule r : rules) {
+ switch (r.getAction()) {
+ case INTERACTIVE:
+ if (!isGenericGroup(r.getGroup())) {
+ return QueueProvider.QueueType.INTERACTIVE;
+ }
+ break;
+
+ case BATCH:
+ batch = true;
+ break;
+ }
+ }
+
+ if (batch) {
+ // If any of our groups matched to the BATCH queue, use it.
+ return QueueProvider.QueueType.BATCH;
+ } else {
+ return QueueProvider.QueueType.INTERACTIVE;
+ }
+ }
+
+ private static boolean isGenericGroup(GroupReference group) {
+ return AccountGroup.ANONYMOUS_USERS.equals(group.getUUID())
+ || AccountGroup.REGISTERED_USERS.equals(group.getUUID());
+ }
+
+ /** True if the user has this permission. Works only for non labels. */
+ public boolean canPerform(String permissionName) {
+ return !access(permissionName).isEmpty();
+ }
+
+ /** The range of permitted values associated with a label permission. */
+ public PermissionRange getRange(String permission) {
+ if (GlobalCapability.hasRange(permission)) {
+ return toRange(permission, access(permission));
+ }
+ return null;
+ }
+
+ private static PermissionRange toRange(String permissionName,
+ List<PermissionRule> ruleList) {
+ int min = 0;
+ int max = 0;
+ for (PermissionRule rule : ruleList) {
+ min = Math.min(min, rule.getMin());
+ max = Math.max(max, rule.getMax());
+ }
+ return new PermissionRange(permissionName, min, max);
+ }
+
+ /** Rules for the given permission, or the empty list. */
+ private List<PermissionRule> access(String permissionName) {
+ List<PermissionRule> r = permissions().get(permissionName);
+ return r != null ? r : Collections.<PermissionRule> emptyList();
+ }
+
+ /** All rules that pertain to this user. */
+ private Map<String, List<PermissionRule>> permissions() {
+ if (permissions == null) {
+ permissions = indexPermissions();
+ }
+ return permissions;
+ }
+
+ private Map<String, List<PermissionRule>> indexPermissions() {
+ Map<String, List<PermissionRule>> res =
+ new HashMap<String, List<PermissionRule>>();
+
+ AccessSection section = state.getConfig()
+ .getAccessSection(AccessSection.GLOBAL_CAPABILITIES);
+ if (section == null) {
+ section = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
+ }
+
+ for (Permission permission : section.getPermissions()) {
+ for (PermissionRule rule : permission.getRules()) {
+ if (matchGroup(rule.getGroup().getUUID())) {
+ if (rule.getAction() != PermissionRule.Action.DENY) {
+ List<PermissionRule> r = res.get(permission.getName());
+ if (r == null) {
+ r = new ArrayList<PermissionRule>(2);
+ res.put(permission.getName(), r);
+ }
+ r.add(rule);
+ }
+ }
+ }
+ }
+
+ configureDefaults(res, section);
+ return res;
+ }
+
+ private boolean matchGroup(AccountGroup.UUID uuid) {
+ Set<AccountGroup.UUID> userGroups = getCurrentUser().getEffectiveGroups();
+ return userGroups.contains(uuid);
+ }
+
+ private static final GroupReference anonymous = new GroupReference(
+ AccountGroup.ANONYMOUS_USERS,
+ "Anonymous Users");
+
+ private static void configureDefaults(
+ Map<String, List<PermissionRule>> res,
+ AccessSection section) {
+ configureDefault(res, section, GlobalCapability.QUERY_LIMIT, anonymous);
+ }
+
+ private static void configureDefault(Map<String, List<PermissionRule>> res,
+ AccessSection section, String capName, GroupReference group) {
+ if (section.getPermission(capName) == null) {
+ PermissionRange.WithDefaults range = GlobalCapability.getRange(capName);
+ if (range != null) {
+ PermissionRule rule = new PermissionRule(group);
+ rule.setRange(range.getDefaultMin(), range.getDefaultMax());
+ res.put(capName, Collections.singletonList(rule));
+ }
+ }
+ }
+}
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 a836f54..77afb13 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
@@ -51,7 +51,7 @@
}
@Override
- public Set<AccountGroup.Id> groups(final AccountState who) {
+ public Set<AccountGroup.UUID> groups(final AccountState who) {
return who.getInternalGroups();
}
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 978d9c2..6dce197 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
@@ -24,6 +24,8 @@
public AccountGroup get(AccountGroup.NameKey name);
+ public AccountGroup get(AccountGroup.UUID uuid);
+
public Collection<AccountGroup> get(AccountGroup.ExternalNameKey externalName);
public void evict(AccountGroup group);
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 d948aef..4d6dbc1 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
@@ -20,7 +20,6 @@
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.AuthConfig;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -29,12 +28,14 @@
import com.google.inject.name.Named;
import java.util.Collection;
+import java.util.List;
/** Tracks group objects in memory for efficient access. */
@Singleton
public class GroupCacheImpl implements GroupCache {
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";
public static Module module() {
@@ -49,6 +50,10 @@
new TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>>() {};
core(byName, BYNAME_NAME).populateWith(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) //
@@ -62,15 +67,18 @@
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;
@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) {
this.byId = byId;
this.byName = byName;
+ this.byUUID = byUUID;
this.byExternalName = byExternalName;
}
@@ -81,6 +89,7 @@
public void evict(final AccountGroup group) {
byId.remove(group.getId());
byName.remove(group.getNameKey());
+ byUUID.remove(group.getGroupUUID());
byExternalName.remove(group.getExternalNameKey());
}
@@ -92,6 +101,10 @@
return byName.get(name);
}
+ public AccountGroup get(final AccountGroup.UUID uuid) {
+ return byUUID.get(uuid);
+ }
+
public Collection<AccountGroup> get(
final AccountGroup.ExternalNameKey externalName) {
return byExternalName.get(externalName);
@@ -99,12 +112,10 @@
static class ByIdLoader extends EntryCreator<AccountGroup.Id, AccountGroup> {
private final SchemaFactory<ReviewDb> schema;
- private final AccountGroup.Id administrators;
@Inject
- ByIdLoader(final SchemaFactory<ReviewDb> sf, final AuthConfig authConfig) {
+ ByIdLoader(final SchemaFactory<ReviewDb> sf) {
schema = sf;
- administrators = authConfig.getAdministratorsGroup();
}
@Override
@@ -126,9 +137,8 @@
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);
+ final AccountGroup g = new AccountGroup(name, key, null);
g.setType(AccountGroup.Type.SYSTEM);
- g.setOwnerGroupId(administrators);
return g;
}
}
@@ -160,6 +170,32 @@
}
}
+ static class ByUUIDLoader extends
+ EntryCreator<AccountGroup.UUID, AccountGroup> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByUUIDLoader(final SchemaFactory<ReviewDb> sf) {
+ schema = sf;
+ }
+
+ @Override
+ public AccountGroup createEntry(final AccountGroup.UUID uuid)
+ throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ List<AccountGroup> r = db.accountGroups().byUUID(uuid).toList();
+ if (r.size() == 1) {
+ return r.get(0);
+ } else {
+ return null;
+ }
+ } finally {
+ db.close();
+ }
+ }
+ }
+
static class ByExternalNameLoader extends
EntryCreator<AccountGroup.ExternalNameKey, Collection<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
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 602b593..d26a37f 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
@@ -18,6 +18,7 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -39,11 +40,20 @@
if (group == null) {
throw new NoSuchGroupException(groupId);
}
- return new GroupControl(user.get(), group);
+ return new GroupControl(groupCache, user.get(), group);
+ }
+
+ public GroupControl controlFor(final AccountGroup.UUID groupId)
+ throws NoSuchGroupException {
+ final AccountGroup group = groupCache.get(groupId);
+ if (group == null) {
+ throw new NoSuchGroupException(groupId);
+ }
+ return new GroupControl(groupCache, user.get(), group);
}
public GroupControl controlFor(final AccountGroup group) {
- return new GroupControl(user.get(), group);
+ return new GroupControl(groupCache, user.get(), group);
}
public GroupControl validateFor(final AccountGroup.Id groupId)
@@ -56,10 +66,13 @@
}
}
+ private final GroupCache groupCache;
private final CurrentUser user;
private final AccountGroup group;
+ private Boolean isOwner;
- GroupControl(final CurrentUser who, final AccountGroup gc) {
+ GroupControl(GroupCache g, CurrentUser who, AccountGroup gc) {
+ groupCache = g;
user = who;
group = gc;
}
@@ -74,36 +87,50 @@
/** Can this user see this group exists? */
public boolean isVisible() {
- return group.isVisibleToAll() || isOwner();
+ return group.isVisibleToAll()
+ || user.getEffectiveGroups().contains(group.getGroupUUID())
+ || isOwner();
}
public boolean isOwner() {
- final AccountGroup.Id owner = group.getOwnerGroupId();
- return getCurrentUser().getEffectiveGroups().contains(owner)
- || getCurrentUser().isAdministrator();
+ if (isOwner == null) {
+ AccountGroup g = groupCache.get(group.getOwnerGroupId());
+ AccountGroup.UUID ownerUUID = g != null ? g.getGroupUUID() : null;
+ isOwner = getCurrentUser().getEffectiveGroups().contains(ownerUUID)
+ || getCurrentUser().getCapabilities().canAdministrateServer();
+ }
+ return isOwner;
}
- public boolean canAddMember(final Account.Id id) {
+ public boolean canAddMember(Account.Id id) {
return isOwner();
}
- public boolean canRemoveMember(final Account.Id id) {
+ public boolean canRemoveMember(Account.Id id) {
return isOwner();
}
public boolean canSeeMember(Account.Id id) {
- return isVisible();
+ if (user instanceof IdentifiedUser
+ && ((IdentifiedUser) user).getAccountId().equals(id)) {
+ return true;
+ }
+ return canSeeMembers();
}
- public boolean canAddGroup(final AccountGroup.Id id) {
+ public boolean canAddGroup(AccountGroup.Id id) {
return isOwner();
}
- public boolean canRemoveGroup(final AccountGroup.Id id) {
+ public boolean canRemoveGroup(AccountGroup.Id id) {
return isOwner();
}
public boolean canSeeGroup(AccountGroup.Id id) {
- return isVisible();
+ return canSeeMembers();
+ }
+
+ private boolean canSeeMembers() {
+ return group.isVisibleToAll() || isOwner();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java
index 3088806..e5f73a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java
@@ -20,8 +20,8 @@
/** Tracks group inclusions in memory for efficient access. */
public interface GroupIncludeCache {
- public Collection<AccountGroup.Id> getByInclude(AccountGroup.Id groupId);
+ public Collection<AccountGroup.UUID> getByInclude(AccountGroup.UUID groupId);
- public void evictInclude(AccountGroup.Id groupId);
+ public void evictInclude(AccountGroup.UUID groupId);
}
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 76e9231..830d01c 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
@@ -21,16 +21,17 @@
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gwtorm.client.SchemaFactory;
-
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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/** Tracks group inclusions in memory for efficient access. */
@Singleton
@@ -41,8 +42,8 @@
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<AccountGroup.Id, Collection<AccountGroup.Id>>> byInclude =
- new TypeLiteral<Cache<AccountGroup.Id, Collection<AccountGroup.Id>>>() {};
+ final TypeLiteral<Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>>> byInclude =
+ new TypeLiteral<Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>>>() {};
core(byInclude, BYINCLUDE_NAME).populateWith(ByIncludeLoader.class);
bind(GroupIncludeCacheImpl.class);
@@ -51,23 +52,24 @@
};
}
- private final Cache<AccountGroup.Id, Collection<AccountGroup.Id>> byInclude;
+ private final Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>> byInclude;
@Inject
GroupIncludeCacheImpl(
- @Named(BYINCLUDE_NAME) Cache<AccountGroup.Id, Collection<AccountGroup.Id>> byInclude) {
+ @Named(BYINCLUDE_NAME) Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>> byInclude) {
this.byInclude = byInclude;
}
- public Collection<AccountGroup.Id> getByInclude(final AccountGroup.Id groupId) {
+ public Collection<AccountGroup.UUID> getByInclude(AccountGroup.UUID groupId) {
return byInclude.get(groupId);
}
- public void evictInclude(AccountGroup.Id groupId) {
+ public void evictInclude(AccountGroup.UUID groupId) {
byInclude.remove(groupId);
}
- static class ByIncludeLoader extends EntryCreator<AccountGroup.Id, Collection<AccountGroup.Id>> {
+ static class ByIncludeLoader extends
+ EntryCreator<AccountGroup.UUID, Collection<AccountGroup.UUID>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -76,14 +78,23 @@
}
@Override
- public Collection<AccountGroup.Id> createEntry(final AccountGroup.Id key) throws Exception {
+ public Collection<AccountGroup.UUID> createEntry(final AccountGroup.UUID key) throws Exception {
final ReviewDb db = schema.open();
try {
- ArrayList<AccountGroup.Id> groupArray = new ArrayList<AccountGroup.Id> ();
- for (AccountGroupInclude agi : db.accountGroupIncludes().byInclude(key)) {
- groupArray.add(agi.getGroupId());
+ List<AccountGroup> group = db.accountGroups().byUUID(key).toList();
+ if (group.size() != 1) {
+ return Collections.emptyList();
}
+ Set<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
+ for (AccountGroupInclude agi : db.accountGroupIncludes().byInclude(group.get(0).getId())) {
+ ids.add(agi.getGroupId());
+ }
+
+ Set<AccountGroup.UUID> groupArray = new HashSet<AccountGroup.UUID> ();
+ for (AccountGroup g : db.accountGroups().get(ids)) {
+ groupArray.add(g.getGroupUUID());
+ }
return Collections.unmodifiableCollection(groupArray);
} finally {
db.close();
@@ -91,7 +102,7 @@
}
@Override
- public Collection<AccountGroup.Id> missing(final AccountGroup.Id key) {
+ 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/GroupUUID.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupUUID.java
new file mode 100644
index 0000000..9eec1df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupUUID.java
@@ -0,0 +1,35 @@
+// 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.account;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.security.MessageDigest;
+
+public class GroupUUID {
+ public static AccountGroup.UUID make(String groupName, PersonIdent creator) {
+ MessageDigest md = Constants.newMessageDigest();
+ md.update(Constants.encode("group " + groupName + "\n"));
+ md.update(Constants.encode("creator " + creator.toExternalString() + "\n"));
+ return new AccountGroup.UUID(ObjectId.fromRaw(md.digest()).name());
+ }
+
+ private GroupUUID() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
index aab1cda..0a6b800 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
+import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupInclude;
@@ -23,14 +24,18 @@
import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
+import org.eclipse.jgit.lib.PersonIdent;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
public class PerformCreateGroup {
@@ -43,14 +48,18 @@
private final AccountCache accountCache;
private final GroupIncludeCache groupIncludeCache;
private final IdentifiedUser currentUser;
+ private final PersonIdent serverIdent;
@Inject
PerformCreateGroup(final ReviewDb db, final AccountCache accountCache,
- final GroupIncludeCache groupIncludeCache, final IdentifiedUser currentUser) {
+ final GroupIncludeCache groupIncludeCache,
+ final IdentifiedUser currentUser,
+ @GerritPersonIdent final PersonIdent serverIdent) {
this.db = db;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
this.currentUser = currentUser;
+ this.serverIdent = serverIdent;
}
/**
@@ -71,17 +80,28 @@
* error
* @throws NameAlreadyUsedException is thrown in case a group with the given
* name already exists
+ * @throws PermissionDeniedException user cannot create a group.
*/
public AccountGroup.Id createGroup(final String groupName,
final String groupDescription, final boolean visibleToAll,
final AccountGroup.Id ownerGroupId,
final Collection<? extends Account.Id> initialMembers,
final Collection<? extends AccountGroup.Id> initialGroups)
- throws OrmException, NameAlreadyUsedException {
+ throws OrmException, NameAlreadyUsedException, PermissionDeniedException {
+ if (!currentUser.getCapabilities().canCreateGroup()) {
+ throw new PermissionDeniedException(String.format(
+ "%s does not have \"Create Group\" capability.",
+ currentUser.getUserName()));
+ }
+
final AccountGroup.Id groupId =
new AccountGroup.Id(db.nextAccountGroupId());
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
- final AccountGroup group = new AccountGroup(nameKey, groupId);
+ final AccountGroup.UUID uuid = GroupUUID.make(groupName,
+ currentUser.newCommitterIdent(
+ serverIdent.getWhen(),
+ serverIdent.getTimeZone()));
+ final AccountGroup group = new AccountGroup(nameKey, groupId, uuid);
group.setVisibleToAll(visibleToAll);
if (ownerGroupId != null) {
group.setOwnerGroupId(ownerGroupId);
@@ -149,8 +169,9 @@
db.accountGroupIncludes().insert(includeList);
db.accountGroupIncludesAudit().insert(includesAudit);
- for (AccountGroup.Id includeId : groups) {
- groupIncludeCache.evictInclude(includeId);
+ for (AccountGroup group : db.accountGroups().get(
+ new HashSet<AccountGroup.Id>(groups))) {
+ groupIncludeCache.evictInclude(group.getGroupUUID());
}
}
}
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 e42d4ae..072f796 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
@@ -27,7 +27,7 @@
public void onCreateAccount(AuthRequest who, Account account);
- public Set<AccountGroup.Id> groups(AccountState who);
+ public Set<AccountGroup.UUID> groups(AccountState who);
/**
* Locate an account whose local username is the given account name.
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 a9ea853..4a24b32 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
@@ -134,7 +134,7 @@
}
}
- Set<AccountGroup.Id> queryForGroups(final DirContext ctx,
+ Set<AccountGroup.UUID> queryForGroups(final DirContext ctx,
final String username, LdapQuery.Result account)
throws NamingException, AccountException {
final LdapSchema schema = getSchema(ctx);
@@ -179,12 +179,12 @@
}
}
- final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
+ 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.getId());
+ actual.add(group.getGroupUUID());
}
}
}
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 810df28..d41fe82 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
@@ -32,8 +32,8 @@
@Override
protected void configure() {
- final TypeLiteral<Cache<String, Set<AccountGroup.Id>>> groups =
- new TypeLiteral<Cache<String, Set<AccountGroup.Id>>>() {};
+ 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);
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 de33b44..e008e6c 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
@@ -68,14 +68,14 @@
private final Cache<String, Account.Id> usernameCache;
private final Set<Account.FieldName> readOnlyAccountFields;
- private final Cache<String, Set<AccountGroup.Id>> membershipCache;
+ private final Cache<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.Id>> membershipCache,
+ @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) {
this.helper = helper;
@@ -193,7 +193,7 @@
final Helper.LdapSchema schema = helper.getSchema(ctx);
final LdapQuery.Result m = helper.findAccount(schema, ctx, username);
- if (authConfig.getAuthType() == AuthType.LDAP) {
+ if (authConfig.getAuthType() == AuthType.LDAP && !who.isSkipAuthentication()) {
// We found the user account, but we need to verify
// the password matches it before we can continue.
//
@@ -241,8 +241,8 @@
}
@Override
- public Set<AccountGroup.Id> groups(final AccountState who) {
- final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>();
+ public Set<AccountGroup.UUID> groups(final AccountState who) {
+ final HashSet<AccountGroup.UUID> r = new HashSet<AccountGroup.UUID>();
r.addAll(membershipCache.get(findId(who.getExternalIds())));
r.addAll(who.getInternalGroups());
return r;
@@ -324,7 +324,7 @@
}
}
- static class MemberLoader extends EntryCreator<String, Set<AccountGroup.Id>> {
+ static class MemberLoader extends EntryCreator<String, Set<AccountGroup.UUID>> {
private final Helper helper;
@Inject
@@ -333,7 +333,7 @@
}
@Override
- public Set<AccountGroup.Id> createEntry(final String username)
+ public Set<AccountGroup.UUID> createEntry(final String username)
throws Exception {
final DirContext ctx = helper.open();
try {
@@ -348,7 +348,7 @@
}
@Override
- public Set<AccountGroup.Id> missing(final String key) {
+ public Set<AccountGroup.UUID> missing(final String key) {
return Collections.emptySet();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_20.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsName.java
similarity index 67%
rename from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_20.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsName.java
index 4d8dca7..bb5d9d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_20.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsName.java
@@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.server.config;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.google.gerrit.reviewdb.Project;
-class Schema_20 extends SchemaVersion {
- @Inject
- Schema_20(Provider<Schema_19> prior) {
- super(prior);
+/** Special name of the project that all projects derive from. */
+@SuppressWarnings("serial")
+public class AllProjectsName extends Project.NameKey {
+ public AllProjectsName(String name) {
+ super(name);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java
similarity index 62%
rename from gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java
index ee8df40..73074b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllProjectsNameProvider.java
@@ -14,20 +14,26 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.SystemConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
-public class WildProjectNameProvider implements Provider<Project.NameKey> {
- private final Project.NameKey name;
+import org.eclipse.jgit.lib.Config;
+
+public class AllProjectsNameProvider implements Provider<AllProjectsName> {
+ public static final String DEFAULT = "All-Projects";
+
+ private final AllProjectsName name;
@Inject
- WildProjectNameProvider(final SystemConfig config) {
- name = config.wildProjectName;
+ AllProjectsNameProvider(@GerritServerConfig Config cfg) {
+ String n = cfg.getString("gerrit", null, "allProjects");
+ if (n == null || n.isEmpty()) {
+ n = DEFAULT;
+ }
+ name = new AllProjectsName(n);
}
- public Project.NameKey get() {
+ public AllProjectsName get() {
return name;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java
index db5bceb..0afaa05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java
@@ -39,8 +39,7 @@
@Override
public ApprovalTypes get() {
- List<ApprovalType> approvalTypes = new ArrayList<ApprovalType>(2);
- List<ApprovalType> actionTypes = new ArrayList<ApprovalType>(2);
+ List<ApprovalType> types = new ArrayList<ApprovalType>(2);
try {
final ReviewDb db = schema.open();
@@ -48,12 +47,7 @@
for (final ApprovalCategory c : db.approvalCategories().all()) {
final List<ApprovalCategoryValue> values =
db.approvalCategoryValues().byCategory(c.getId()).toList();
- final ApprovalType type = new ApprovalType(c, values);
- if (type.getCategory().isAction()) {
- actionTypes.add(type);
- } else {
- approvalTypes.add(type);
- }
+ types.add(new ApprovalType(c, values));
}
} finally {
db.close();
@@ -62,8 +56,6 @@
throw new ProvisionException("Cannot query approval categories", e);
}
- approvalTypes = Collections.unmodifiableList(approvalTypes);
- actionTypes = Collections.unmodifiableList(actionTypes);
- return new ApprovalTypes(approvalTypes, actionTypes);
+ return new ApprovalTypes(Collections.unmodifiableList(types));
}
}
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 16a6a7c..f61a7c6 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
@@ -16,9 +16,7 @@
import com.google.gerrit.common.auth.openid.OpenIdProviderPattern;
import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AuthType;
-import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.inject.Inject;
@@ -29,9 +27,8 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
+import java.util.concurrent.TimeUnit;
/** Authentication related settings from {@code gerrit.config}. */
@Singleton
@@ -45,15 +42,10 @@
private final boolean cookieSecure;
private final SignedToken emailReg;
- private final AccountGroup.Id administratorGroup;
- private final Set<AccountGroup.Id> anonymousGroups;
- private final Set<AccountGroup.Id> registeredGroups;
- private final AccountGroup.Id batchUsersGroup;
-
private final boolean allowGoogleAccountUpgrade;
@Inject
- AuthConfig(@GerritServerConfig final Config cfg, final SystemConfig s)
+ AuthConfig(@GerritServerConfig final Config cfg)
throws XsrfException {
authType = toType(cfg);
httpHeader = cfg.getString("auth", null, "httpheader");
@@ -62,15 +54,17 @@
allowedOpenIDs = toPatterns(cfg, "allowedOpenID");
cookiePath = cfg.getString("auth", null, "cookiepath");
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
- emailReg = new SignedToken(5 * 24 * 60 * 60, s.registerEmailPrivateKey);
- final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>(2);
- r.add(s.anonymousGroupId);
- r.add(s.registeredGroupId);
- registeredGroups = Collections.unmodifiableSet(r);
- anonymousGroups = Collections.singleton(s.anonymousGroupId);
- administratorGroup = s.adminGroupId;
- batchUsersGroup = s.batchUsersGroupId;
+ String key = cfg.getString("auth", null, "registerEmailPrivateKey");
+ if (key != null && !key.isEmpty()) {
+ int age = (int) ConfigUtil.getTimeUnit(cfg,
+ "auth", null, "maxRegisterEmailTokenAge",
+ TimeUnit.SECONDS.convert(5, TimeUnit.DAYS),
+ TimeUnit.SECONDS);
+ emailReg = new SignedToken(age, key);
+ } else {
+ emailReg = null;
+ }
if (authType == AuthType.OPENID) {
allowGoogleAccountUpgrade =
@@ -126,26 +120,6 @@
return allowGoogleAccountUpgrade;
}
- /** Identity of the magic group with full powers. */
- public AccountGroup.Id getAdministratorsGroup() {
- return administratorGroup;
- }
-
- /** Identity of the group whose service is degraded to lower priority. */
- public AccountGroup.Id getBatchUsersGroup() {
- return batchUsersGroup;
- }
-
- /** Groups that all users, including anonymous users, belong to. */
- public Set<AccountGroup.Id> getAnonymousGroups() {
- return anonymousGroups;
- }
-
- /** Groups that all users who have created an account belong to. */
- public Set<AccountGroup.Id> getRegisteredGroups() {
- return registeredGroups;
- }
-
/** OpenID identities which the server permits for authentication. */
public List<OpenIdProviderPattern> getAllowedOpenIDs() {
return allowedOpenIDs;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java
index 33ea3fb..e5d4ba8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java
@@ -16,14 +16,12 @@
import static com.google.inject.Scopes.SINGLETON;
-import com.google.gerrit.reviewdb.SystemConfig;
import com.google.inject.AbstractModule;
/** Creates {@link AuthConfig} from {@link GerritServerConfig}. */
public class AuthConfigModule extends AbstractModule {
@Override
protected void configure() {
- bind(SystemConfig.class).toProvider(SystemConfigProvider.class).in(SINGLETON);
bind(AuthConfig.class).in(SINGLETON);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index f24dcc5..3c102ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -310,10 +310,10 @@
* @return the actual groups resolved from the database. If no groups are
* found, returns an empty {@code Set}, never {@code null}.
*/
- public static Set<AccountGroup.Id> groupsFor(
+ public static Set<AccountGroup.UUID> groupsFor(
SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log,
String groupNotFoundWarning) {
- final Set<AccountGroup.Id> result = new HashSet<AccountGroup.Id>();
+ final Set<AccountGroup.UUID> result = new HashSet<AccountGroup.UUID>();
try {
final ReviewDb db = dbfactory.open();
try {
@@ -322,9 +322,16 @@
db.accountGroupNames().get(new AccountGroup.NameKey(name));
if (group == null) {
log.warn(MessageFormat.format(groupNotFoundWarning, name));
- } else {
- result.add(group.getId());
+ continue;
}
+
+ AccountGroup ag = db.accountGroups().get(group.getId());
+ if (ag == null) {
+ log.warn(MessageFormat.format(groupNotFoundWarning, name));
+ continue;
+ }
+
+ result.add(ag.getGroupUUID());
}
} finally {
db.close();
@@ -345,7 +352,7 @@
* @return the actual groups resolved from the database. If no groups are
* found, returns an empty {@code Set}, never {@code null}.
*/
- public static Set<AccountGroup.Id> groupsFor(
+ public static Set<AccountGroup.UUID> groupsFor(
SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log) {
return groupsFor(dbfactory, groupNames, log,
"Group \"{0}\" not in database, skipping.");
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 d6cc95d..a564e56 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
@@ -20,17 +20,16 @@
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.AuthType;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.rules.PrologModule;
+import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.FileTypeRegistry;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.account.AccountByEmailCacheImpl;
import com.google.gerrit.server.account.AccountCacheImpl;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
+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.GroupCacheImpl;
@@ -41,8 +40,7 @@
import com.google.gerrit.server.cache.CachePool;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.git.ChangeMergeQueue;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.PushReplication;
@@ -72,7 +70,6 @@
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.RuntimeConstants;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
import java.util.Properties;
@@ -136,19 +133,14 @@
break;
}
- bind(Project.NameKey.class).annotatedWith(WildProjectName.class)
- .toProvider(WildProjectNameProvider.class).in(SINGLETON);
bind(ApprovalTypes.class).toProvider(ApprovalTypesProvider.class).in(
SINGLETON);
bind(EmailExpander.class).toProvider(EmailExpanderProvider.class).in(
SINGLETON);
- bind(AnonymousUser.class);
-
- bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class).toProvider(
- GerritPersonIdentProvider.class);
bind(IdGenerator.class);
bind(CachePool.class);
+ bind(RulesCache.class);
install(AccountByEmailCacheImpl.module());
install(AccountCacheImpl.module());
install(GroupCacheImpl.module());
@@ -157,13 +149,15 @@
install(ProjectCacheImpl.module());
install(TagCache.module());
install(new AccessControlModule());
+ install(new GitModule());
+ install(new PrologModule());
factory(AccountInfoCacheFactory.Factory.class);
+ factory(CapabilityControl.Factory.class);
factory(GroupInfoCacheFactory.Factory.class);
factory(ProjectState.Factory.class);
factory(RefControl.Factory.class);
- bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(WorkQueue.class);
bind(ToolsCatalog.class);
@@ -191,7 +185,6 @@
install(new LifecycleModule() {
@Override
protected void configure() {
- listener().to(LocalDiskRepositoryManager.Lifecycle.class);
listener().to(CachePool.Lifecycle.class);
listener().to(WorkQueue.Lifecycle.class);
listener().to(VelocityLifecycle.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 f6dad7d..a050cfd 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,6 +17,7 @@
import static com.google.inject.Scopes.SINGLETON;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountResolver;
@@ -24,6 +25,7 @@
import com.google.gerrit.server.account.PerformCreateGroup;
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.ReceiveCommits;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender;
@@ -34,7 +36,9 @@
import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.mail.RevertedSender;
+import com.google.gerrit.server.patch.AddReviewer;
import com.google.gerrit.server.patch.PublishComments;
+import com.google.gerrit.server.patch.RemoveReviewer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
@@ -49,9 +53,11 @@
bind(ReviewDb.class).toProvider(RequestScopedReviewDbProvider.class).in(
RequestScoped.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
+ bind(MetaDataUpdate.User.class).in(RequestScoped.class);
bind(AccountResolver.class);
bind(ChangeQueryRewriter.class);
+ bind(AnonymousUser.class).in(RequestScoped.class);
bind(ChangeControl.Factory.class).in(SINGLETON);
bind(GroupControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON);
@@ -64,11 +70,13 @@
// Not really per-request, but dammit, I don't know where else to
// easily park this stuff.
//
+ factory(AddReviewer.Factory.class);
factory(AddReviewerSender.Factory.class);
factory(CreateChangeSender.Factory.class);
factory(PublishComments.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
factory(AbandonedSender.Factory.class);
+ factory(RemoveReviewer.Factory.class);
factory(RevertedSender.Factory.class);
factory(CommentSender.Factory.class);
factory(MergedSender.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 9af6d62..1c13b9e 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
@@ -22,21 +22,17 @@
import org.eclipse.jgit.lib.Config;
import java.util.Collections;
-import java.util.HashSet;
public class GitReceivePackGroupsProvider extends GroupSetProvider {
@Inject
public GitReceivePackGroupsProvider(@GerritServerConfig Config config,
- AuthConfig authConfig, SchemaFactory<ReviewDb> db) {
+ SchemaFactory<ReviewDb> db) {
super(config, db, "receive", null, "allowGroup");
// If no group was set, default to "registered users"
//
if (groupIds.isEmpty()) {
- HashSet<AccountGroup.Id> all = new HashSet<AccountGroup.Id>();
- all.addAll(authConfig.getRegisteredGroups());
- all.removeAll(authConfig.getAnonymousGroups());
- groupIds = Collections.unmodifiableSet(all);
+ groupIds = Collections.singleton(AccountGroup.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 bfb09a5..65e6900 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
@@ -27,15 +27,15 @@
public class GitUploadPackGroupsProvider extends GroupSetProvider {
@Inject
public GitUploadPackGroupsProvider(@GerritServerConfig Config config,
- AuthConfig authConfig, SchemaFactory<ReviewDb> db) {
+ SchemaFactory<ReviewDb> db) {
super(config, db, "upload", null, "allowGroup");
// If no group was set, default to "registered users" and "anonymous"
//
if (groupIds.isEmpty()) {
- HashSet<AccountGroup.Id> all = new HashSet<AccountGroup.Id>();
- all.addAll(authConfig.getRegisteredGroups());
- all.addAll(authConfig.getAnonymousGroups());
+ HashSet<AccountGroup.UUID> all = new HashSet<AccountGroup.UUID>();
+ all.add(AccountGroup.REGISTERED_USERS);
+ all.add(AccountGroup.ANONYMOUS_USERS);
groupIds = Collections.unmodifiableSet(all);
}
}
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 373fdb5..6ff977f 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
@@ -30,11 +30,11 @@
import java.util.Set;
public abstract class GroupSetProvider implements
- Provider<Set<AccountGroup.Id>> {
+ Provider<Set<AccountGroup.UUID>> {
private static final Logger log =
LoggerFactory.getLogger(GroupSetProvider.class);
- protected Set<AccountGroup.Id> groupIds;
+ protected Set<AccountGroup.UUID> groupIds;
@Inject
protected GroupSetProvider(@GerritServerConfig Config config,
@@ -44,7 +44,7 @@
}
@Override
- public Set<AccountGroup.Id> get() {
+ public Set<AccountGroup.UUID> get() {
return groupIds;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroups.java
deleted file mode 100644
index 7db2cd3..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroups.java
+++ /dev/null
@@ -1,30 +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.config;
-
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-/**
- * Marker on a {@code Set<AccountGroup.Id>} for the configured groups with
- * permission to create projects.
- */
-@Retention(RUNTIME)
-@BindingAnnotation
-public @interface ProjectCreatorGroups {
-}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java
deleted file mode 100644
index 381c914..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectCreatorGroupsProvider.java
+++ /dev/null
@@ -1,47 +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.config;
-
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gwtorm.client.SchemaFactory;
-import com.google.inject.Inject;
-
-import org.eclipse.jgit.lib.Config;
-
-import java.util.Collections;
-
-/**
- * Provider of the group(s) which are allowed to create new projects. Currently
- * only supports {@code createGroup} declarations in the {@code "*"} repository,
- * like so:
- *
- * <pre>
- * [repository "*"]
- * createGroup = Registered Users
- * createGroup = Administrators
- * </pre>
- */
-public class ProjectCreatorGroupsProvider extends GroupSetProvider {
- @Inject
- public ProjectCreatorGroupsProvider(@GerritServerConfig final Config config,
- final SystemConfig systemConfig, final SchemaFactory<ReviewDb> db) {
- super(config, db, "repository", "*", "createGroup");
-
- if (groupIds.isEmpty()) {
- groupIds = Collections.singleton(systemConfig.adminGroupId);
- }
- }
-}
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 c457d73..05ea11d 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,15 +14,12 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
-import java.util.Set;
-
/**
* Provider of the group(s) which should become owners of a newly created
* project. Currently only supports {@code ownerGroup} declarations in the
@@ -37,12 +34,7 @@
public class ProjectOwnerGroupsProvider extends GroupSetProvider {
@Inject
public ProjectOwnerGroupsProvider(
- @ProjectCreatorGroups final Set<AccountGroup.Id> creatorGroups,
@GerritServerConfig final Config config, final SchemaFactory<ReviewDb> db) {
super(config, db, "repository", "*", "ownerGroup");
-
- if (groupIds.isEmpty()) {
- groupIds = creatorGroups;
- }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePathFromSystemConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePathFromSystemConfigProvider.java
deleted file mode 100644
index 5b5dcc1..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePathFromSystemConfigProvider.java
+++ /dev/null
@@ -1,37 +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.config;
-
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.io.File;
-
-/** Provides {@link java.io.File} annotated with {@link SitePath}. */
-public class SitePathFromSystemConfigProvider implements Provider<File> {
- private final File path;
-
- @Inject
- SitePathFromSystemConfigProvider(final SystemConfig config) {
- final String p = config.sitePath;
- path = new File(p != null && p.length() > 0 ? p : ".").getAbsoluteFile();
- }
-
- @Override
- public File get() {
- return path;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectName.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectName.java
deleted file mode 100644
index 3df550a..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectName.java
+++ /dev/null
@@ -1,33 +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.config;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.google.gerrit.reviewdb.Project;
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-
-/**
- * Marker on a {@link Project.NameKey} for the current wildcard project.
- * <p>
- * This is the name of the project whose rights inherit into every other project
- * in the system.
- */
-@Retention(RUNTIME)
-@BindingAnnotation
-public @interface WildProjectName {
-}
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 573e6bb..56ac0c1 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
@@ -27,7 +27,6 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.google.inject.internal.Nullable;
import org.eclipse.jgit.lib.ObjectId;
@@ -35,6 +34,8 @@
import java.util.Collection;
import java.util.Map;
+import javax.annotation.Nullable;
+
@Singleton
public class EventFactory {
private final AccountCache accountCache;
@@ -212,7 +213,7 @@
a.by = asAccountAttribute(approval.getAccountId());
a.grantedOn = approval.getGranted().getTime() / 1000L;
- ApprovalType at = approvalTypes.getApprovalType(approval.getCategoryId());
+ ApprovalType at = approvalTypes.byId(approval.getCategoryId());
if (at != null) {
a.description = at.getCategory().getName();
}
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 976ec2e..fb88ad6 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
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.git.GitRepositoryManager.REFS_NOTES_REVIEW;
+import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
@@ -197,10 +198,13 @@
} else if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
submit = a;
} else {
- formatter.appendApproval(
- approvalTypes.getApprovalType(a.getCategoryId()).getCategory(),
- a.getValue(),
- accountCache.get(a.getAccountId()).getAccount());
+ ApprovalType type = approvalTypes.byId(a.getCategoryId());
+ if (type != null) {
+ formatter.appendApproval(
+ type.getCategory(),
+ a.getValue(),
+ accountCache.get(a.getAccountId()).getAccount());
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModule.java
similarity index 63%
copy from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/git/GitModule.java
index 0977ee9..7947d30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModule.java
@@ -12,15 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.server.git;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.google.gerrit.server.config.FactoryModule;
-public class Schema_49 extends SchemaVersion {
-
- @Inject
- Schema_49(Provider<Schema_48> prior) {
- super(prior);
+/** Configures the Git support. */
+public class GitModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ factory(RenameGroupOp.Factory.class);
+ factory(MetaDataUpdate.InternalFactory.class);
+ bind(MetaDataUpdate.Server.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
deleted file mode 100644
index 3f2ff0d..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
+++ /dev/null
@@ -1,121 +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.git;
-
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.Project.SubmitType;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.SchemaFactory;
-import com.google.inject.Inject;
-
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.util.FS;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/** Imports all projects found within the repository manager. */
-public class GitProjectImporter {
- public interface Messages {
- void info(String msg);
- void warning(String msg);
- }
-
- private final LocalDiskRepositoryManager repositoryManager;
- private final SchemaFactory<ReviewDb> schema;
- private Messages messages;
-
- @Inject
- GitProjectImporter(final LocalDiskRepositoryManager repositoryManager,
- final SchemaFactory<ReviewDb> schema) {
- this.repositoryManager = repositoryManager;
- this.schema = schema;
- }
-
- public void run(final Messages msg) throws OrmException, IOException {
- messages = msg;
- messages.info("Scanning " + repositoryManager.getBasePath());
- final ReviewDb db = schema.open();
- try {
- final HashSet<String> have = new HashSet<String>();
- for (Project p : db.projects().all()) {
- have.add(p.getName());
- }
- importProjects(repositoryManager.getBasePath(), "", db, have);
- } finally {
- db.close();
- }
- }
-
- private void importProjects(final File dir, final String prefix,
- final ReviewDb db, final Set<String> have) throws OrmException,
- IOException {
- final File[] ls = dir.listFiles();
- if (ls == null) {
- return;
- }
-
- for (File f : ls) {
- String name = f.getName();
- if (".".equals(name) || "..".equals(name)) {
- continue;
- }
-
- if (FileKey.isGitRepository(f, FS.DETECTED)) {
- if (name.equals(".git")) {
- if ("".equals(prefix)) {
- // If the git base path is itself a git repository working
- // directory, this is a bit nonsensical for Gerrit Code Review.
- // Skip the path and do the next one.
- messages.warning("Skipping " + f.getAbsolutePath());
- continue;
- }
- name = prefix.substring(0, prefix.length() - 1);
-
- } else if (name.endsWith(".git")) {
- name = prefix + name.substring(0, name.length() - 4);
-
- } else {
- name = prefix + name;
- if (!have.contains(name)) {
- messages.warning("Importing non-standard name '" + name + "'");
- }
- }
-
- if (have.contains(name)) {
- continue;
- }
-
- final Project.NameKey nameKey = new Project.NameKey(name);
- final Project p = new Project(nameKey);
-
- p.setDescription(repositoryManager.getProjectDescription(nameKey));
- p.setSubmitType(SubmitType.MERGE_IF_NECESSARY);
- p.setUseContributorAgreements(false);
- p.setUseSignedOffBy(false);
- p.setUseContentMerge(false);
- p.setRequireChangeID(false);
- db.projects().insert(Collections.singleton(p));
-
- } else if (f.isDirectory()) {
- importProjects(f, prefix + f.getName() + "/", db, have);
- }
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index a19b722..3e3d929 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -21,7 +21,7 @@
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
-
+import java.util.SortedSet;
/**
* Manages Git repositories for the Gerrit server process.
@@ -37,6 +37,9 @@
/** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
public static final String REF_REJECT_COMMITS = "refs/meta/reject-commits";
+ /** Configuration settings for a project {@code refs/meta/config} */
+ public static final String REF_CONFIG = "refs/meta/config";
+
/**
* Get (or open) a repository by name.
*
@@ -61,6 +64,9 @@
public abstract Repository createRepository(Project.NameKey name)
throws RepositoryNotFoundException;
+ /** @return set of all known projects, sorted by natural NameKey order. */
+ public abstract SortedSet<Project.NameKey> list();
+
/**
* Read the {@code GIT_DIR/description} file for gitweb.
* <p>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 7e98348..268eed3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -23,10 +23,12 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.LockFile;
import org.eclipse.jgit.storage.file.WindowCache;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
@@ -39,6 +41,9 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
/** Manages Git repositories stored on the local filesystem. */
@Singleton
@@ -129,10 +134,19 @@
}
loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
}
- return RepositoryCache.open(loc, false);
+
+ Repository db = RepositoryCache.open(loc, false);
+ db.create(true /* bare */);
+
+ StoredConfig config = db.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
+ config.save();
+
+ return db;
} catch (IOException e1) {
final RepositoryNotFoundException e2;
- e2 = new RepositoryNotFoundException("Cannot open repository " + name);
+ e2 = new RepositoryNotFoundException("Cannot create repository " + name);
e2.initCause(e1);
throw e2;
}
@@ -142,41 +156,48 @@
throws RepositoryNotFoundException, IOException {
final Repository e = openRepository(name);
try {
- final File d = new File(e.getDirectory(), "description");
-
- String description;
- try {
- description = RawParseUtils.decode(IO.readFully(d));
- } catch (FileNotFoundException err) {
- return null;
- }
-
- if (description != null) {
- description = description.trim();
- if (description.isEmpty()) {
- description = null;
- }
- if (UNNAMED.equals(description)) {
- description = null;
- }
- }
- return description;
+ return getProjectDescription(e);
} finally {
e.close();
}
}
+ private String getProjectDescription(final Repository e) throws IOException {
+ final File d = new File(e.getDirectory(), "description");
+
+ String description;
+ try {
+ description = RawParseUtils.decode(IO.readFully(d));
+ } catch (FileNotFoundException err) {
+ return null;
+ }
+
+ if (description != null) {
+ description = description.trim();
+ if (description.isEmpty()) {
+ description = null;
+ }
+ if (UNNAMED.equals(description)) {
+ description = null;
+ }
+ }
+ return description;
+ }
+
public void setProjectDescription(final Project.NameKey name,
final String description) {
// Update git's description file, in case gitweb is being used
//
try {
- final Repository e;
- final LockFile f;
-
- e = openRepository(name);
+ final Repository e = openRepository(name);
try {
- f = new LockFile(new File(e.getDirectory(), "description"), FS.DETECTED);
+ final String old = getProjectDescription(e);
+ if ((old == null && description == null)
+ || (old != null && old.equals(description))) {
+ return;
+ }
+
+ final LockFile f = new LockFile(new File(e.getDirectory(), "description"), FS.DETECTED);
if (f.lock()) {
String d = description;
if (d != null) {
@@ -204,6 +225,7 @@
final String name = nameKey.get();
if (name.length() == 0) return true; // no empty paths
+ if (name.charAt(name.length() -1) == '/') return true; // no suffix
if (name.indexOf('\\') >= 0) return true; // no windows/dos stlye paths
if (name.charAt(0) == '/') return true; // no absolute paths
@@ -216,4 +238,46 @@
return false; // is a reasonable name
}
+
+ @Override
+ public SortedSet<Project.NameKey> list() {
+ SortedSet<Project.NameKey> names = new TreeSet<Project.NameKey>();
+ scanProjects(basePath, "", names);
+ return Collections.unmodifiableSortedSet(names);
+ }
+
+ private void scanProjects(final File dir, final String prefix,
+ final SortedSet<Project.NameKey> names) {
+ final File[] ls = dir.listFiles();
+ if (ls == null) {
+ return;
+ }
+
+ for (File f : ls) {
+ String fileName = f.getName();
+ if (FileKey.isGitRepository(f, FS.DETECTED)) {
+ String projectName;
+ if (fileName.equals(Constants.DOT_GIT)) {
+ projectName = prefix.substring(0, prefix.length() - 1);
+
+ } else if (fileName.endsWith(Constants.DOT_GIT_EXT)) {
+ int newLen = fileName.length() - Constants.DOT_GIT_EXT.length();
+ projectName = prefix + fileName.substring(0, newLen);
+
+ } else {
+ projectName = prefix + fileName;
+ }
+
+ Project.NameKey nameKey = new Project.NameKey(projectName);
+ if (isUnreasonableName(nameKey)) {
+ log.warn("Ignoring unreasonably named repository " + f.getAbsolutePath());
+ } else {
+ names.add(nameKey);
+ }
+
+ } else if (f.isDirectory()) {
+ scanProjects(f, prefix + f.getName() + "/", names);
+ }
+ }
+ }
}
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 f29c52e..88676bd 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
@@ -808,7 +808,7 @@
tag = "Tested-by";
} else {
final ApprovalType at =
- approvalTypes.getApprovalType(a.getCategoryId());
+ approvalTypes.byId(a.getCategoryId());
if (at == null) {
// A deprecated/deleted approval type, ignore it.
continue;
@@ -885,6 +885,17 @@
private void updateBranch() throws MergeException {
if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
+ if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
+ try {
+ ProjectConfig cfg = new ProjectConfig(destProject.getNameKey());
+ cfg.load(db, mergeTip);
+ } catch (Exception e) {
+ throw new MergeException("Submit would store invalid"
+ + " project configuration " + mergeTip.name() + " for "
+ + destProject.getName(), e);
+ }
+ }
+
branchUpdate.setForceUpdate(false);
branchUpdate.setNewObjectId(mergeTip);
branchUpdate.setRefLogMessage("merged", true);
@@ -898,6 +909,14 @@
branchUpdate.getOldObjectId(),
mergeTip);
}
+
+ if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
+ projectCache.evict(destProject);
+ ProjectState ps = projectCache.get(destProject.getNameKey());
+ repoManager.setProjectDescription(destProject.getNameKey(), //
+ ps.getProject().getDescription());
+ }
+
replication.scheduleUpdate(destBranch.getParentKey(), branchUpdate
.getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
new file mode 100644
index 0000000..568c8cf9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -0,0 +1,128 @@
+// 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;
+
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+/** Helps with the updating of a {@link VersionedMetaData}. */
+public class MetaDataUpdate {
+ public static class User {
+ private final InternalFactory factory;
+ private final GitRepositoryManager mgr;
+ private final PersonIdent serverIdent;
+ private final PersonIdent userIdent;
+
+ @Inject
+ User(InternalFactory factory, GitRepositoryManager mgr,
+ @GerritPersonIdent PersonIdent serverIdent, IdentifiedUser currentUser) {
+ this.factory = factory;
+ this.mgr = mgr;
+ this.serverIdent = serverIdent;
+ this.userIdent = currentUser.newCommitterIdent( //
+ serverIdent.getWhen(), //
+ serverIdent.getTimeZone());
+ }
+
+ public PersonIdent getUserPersonIdent() {
+ return userIdent;
+ }
+
+ public MetaDataUpdate create(Project.NameKey name)
+ throws RepositoryNotFoundException {
+ MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
+ md.getCommitBuilder().setAuthor(userIdent);
+ md.getCommitBuilder().setCommitter(serverIdent);
+ return md;
+ }
+ }
+
+ public static class Server {
+ private final InternalFactory factory;
+ private final GitRepositoryManager mgr;
+ private final PersonIdent serverIdent;
+
+ @Inject
+ Server(InternalFactory factory, GitRepositoryManager mgr,
+ @GerritPersonIdent PersonIdent serverIdent) {
+ this.factory = factory;
+ this.mgr = mgr;
+ this.serverIdent = serverIdent;
+ }
+
+ public MetaDataUpdate create(Project.NameKey name)
+ throws RepositoryNotFoundException {
+ MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
+ md.getCommitBuilder().setAuthor(serverIdent);
+ md.getCommitBuilder().setCommitter(serverIdent);
+ return md;
+ }
+ }
+
+ interface InternalFactory {
+ MetaDataUpdate create(@Assisted Project.NameKey projectName,
+ @Assisted Repository db);
+ }
+
+ private final ReplicationQueue replication;
+ private final Project.NameKey projectName;
+ private final Repository db;
+ private final CommitBuilder commit;
+
+ @Inject
+ public MetaDataUpdate(ReplicationQueue replication,
+ @Assisted Project.NameKey projectName, @Assisted Repository db) {
+ this.replication = replication;
+ this.projectName = projectName;
+ this.db = db;
+ this.commit = new CommitBuilder();
+ }
+
+ /** Set the commit message used when committing the update. */
+ public void setMessage(String message) {
+ getCommitBuilder().setMessage(message);
+ }
+
+ /** Close the cached Repository handle. */
+ public void close() {
+ getRepository().close();
+ }
+
+ Project.NameKey getProjectName() {
+ return projectName;
+ }
+
+ Repository getRepository() {
+ return db;
+ }
+
+ public CommitBuilder getCommitBuilder() {
+ return commit;
+ }
+
+ void replicate(String ref) {
+ if (replication.isEnabled()) {
+ replication.scheduleUpdate(projectName, ref);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NoReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NoReplication.java
new file mode 100644
index 0000000..ceb70d2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NoReplication.java
@@ -0,0 +1,37 @@
+// 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;
+
+import com.google.gerrit.reviewdb.Project;
+
+/** A disabled {@link ReplicationQueue}. */
+public final class NoReplication implements ReplicationQueue {
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public void scheduleUpdate(Project.NameKey project, String ref) {
+ }
+
+ @Override
+ public void scheduleFullSync(Project.NameKey project, String urlMatch) {
+ }
+
+ @Override
+ public void replicateNewProject(Project.NameKey project, String head) {
+ }
+}
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
new file mode 100644
index 0000000..164fb05
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -0,0 +1,499 @@
+// 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.git;
+
+import static com.google.gerrit.common.data.AccessSection.isAccessSection;
+import static com.google.gerrit.common.data.Permission.isPermission;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+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.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.Project.SubmitType;
+import com.google.gerrit.server.account.GroupCache;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ProjectConfig extends VersionedMetaData {
+ private static final String PROJECT_CONFIG = "project.config";
+ private static final String GROUP_LIST = "groups";
+
+ private static final String PROJECT = "project";
+ private static final String KEY_DESCRIPTION = "description";
+
+ private static final String ACCESS = "access";
+ private static final String KEY_INHERIT_FROM = "inheritFrom";
+ private static final String KEY_GROUP_PERMISSIONS = "exclusiveGroupPermissions";
+
+ private static final String CAPABILITY = "capability";
+
+ private static final String RECEIVE = "receive";
+ private static final String KEY_REQUIRE_SIGNED_OFF_BY = "requireSignedOffBy";
+ private static final String KEY_REQUIRE_CHANGE_ID = "requireChangeId";
+ private static final String KEY_REQUIRE_CONTRIBUTOR_AGREEMENT =
+ "requireContributorAgreement";
+
+ private static final String SUBMIT = "submit";
+ private static final String KEY_ACTION = "action";
+ private static final String KEY_MERGE_CONTENT = "mergeContent";
+
+ private static final SubmitType defaultSubmitAction =
+ SubmitType.MERGE_IF_NECESSARY;
+
+ private Project.NameKey projectName;
+ private Project project;
+ private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
+ private Map<String, AccessSection> accessSections;
+ private List<ValidationError> validationErrors;
+ private ObjectId rulesId;
+
+ public static ProjectConfig read(MetaDataUpdate update) throws IOException,
+ ConfigInvalidException {
+ ProjectConfig r = new ProjectConfig(update.getProjectName());
+ r.load(update);
+ return r;
+ }
+
+ public static ProjectConfig read(MetaDataUpdate update, ObjectId id)
+ throws IOException, ConfigInvalidException {
+ ProjectConfig r = new ProjectConfig(update.getProjectName());
+ r.load(update, id);
+ return r;
+ }
+
+ public ProjectConfig(Project.NameKey projectName) {
+ this.projectName = projectName;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public AccessSection getAccessSection(String name) {
+ return getAccessSection(name, false);
+ }
+
+ public AccessSection getAccessSection(String name, boolean create) {
+ AccessSection as = accessSections.get(name);
+ if (as == null && create) {
+ as = new AccessSection(name);
+ accessSections.put(name, as);
+ }
+ return as;
+ }
+
+ public Collection<AccessSection> getAccessSections() {
+ return sort(accessSections.values());
+ }
+
+ public void remove(AccessSection section) {
+ if (section != null) {
+ accessSections.remove(section.getName());
+ }
+ }
+
+ public void replace(AccessSection section) {
+ for (Permission permission : section.getPermissions()) {
+ for (PermissionRule rule : permission.getRules()) {
+ rule.setGroup(resolve(rule.getGroup()));
+ }
+ }
+
+ accessSections.put(section.getName(), section);
+ }
+
+ public GroupReference resolve(AccountGroup group) {
+ return resolve(GroupReference.forGroup(group));
+ }
+
+ public GroupReference resolve(GroupReference group) {
+ if (group != null) {
+ GroupReference ref = groupsByUUID.get(group.getUUID());
+ if (ref != null) {
+ return ref;
+ }
+ groupsByUUID.put(group.getUUID(), group);
+ }
+ return group;
+ }
+
+ /** @return the group reference, if the group is used by at least one rule. */
+ public GroupReference getGroup(AccountGroup.UUID uuid) {
+ return groupsByUUID.get(uuid);
+ }
+
+ /**
+ * @return the project's rules.pl ObjectId, if present in the branch.
+ * Null if it doesn't exist.
+ */
+ public ObjectId getRulesId() {
+ return rulesId;
+ }
+
+ /**
+ * Check all GroupReferences use current group name, repairing stale ones.
+ *
+ * @param groupCache 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) {
+ boolean dirty = false;
+ for (GroupReference ref : groupsByUUID.values()) {
+ AccountGroup g = groupCache.get(ref.getUUID());
+ if (g != null && !g.getName().equals(ref.getName())) {
+ dirty = true;
+ ref.setName(g.getName());
+ }
+ }
+ return dirty;
+ }
+
+ /**
+ * Get the validation errors, if any were discovered during load.
+ *
+ * @return list of errors; empty list if there are no errors.
+ */
+ public List<ValidationError> getValidationErrors() {
+ if (validationErrors != null) {
+ return Collections.unmodifiableList(validationErrors);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ protected String getRefName() {
+ return GitRepositoryManager.REF_CONFIG;
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ Map<String, GroupReference> groupsByName = readGroupList();
+
+ rulesId = getObjectId("rules.pl");
+ Config rc = readConfig(PROJECT_CONFIG);
+ project = new Project(projectName);
+
+ Project p = project;
+ p.setDescription(rc.getString(PROJECT, null, KEY_DESCRIPTION));
+ if (p.getDescription() == null) {
+ p.setDescription("");
+ }
+ p.setParentName(rc.getString(ACCESS, null, KEY_INHERIT_FROM));
+
+ p.setUseContributorAgreements(getBoolean(rc, RECEIVE, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, false));
+ p.setUseSignedOffBy(getBoolean(rc, RECEIVE, KEY_REQUIRE_SIGNED_OFF_BY, false));
+ p.setRequireChangeID(getBoolean(rc, RECEIVE, KEY_REQUIRE_CHANGE_ID, false));
+
+ p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
+ p.setUseContentMerge(getBoolean(rc, SUBMIT, KEY_MERGE_CONTENT, false));
+
+ accessSections = new HashMap<String, AccessSection>();
+ for (String refName : rc.getSubsections(ACCESS)) {
+ if (isAccessSection(refName)) {
+ AccessSection as = getAccessSection(refName, true);
+
+ for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) {
+ for (String n : varName.split("[, \t]{1,}")) {
+ if (isPermission(n)) {
+ as.getPermission(n, true).setExclusiveGroup(true);
+ }
+ }
+ }
+
+ for (String varName : rc.getNames(ACCESS, refName)) {
+ if (isPermission(varName)) {
+ Permission perm = as.getPermission(varName, true);
+ loadPermissionRules(rc, ACCESS, refName, varName, groupsByName,
+ perm, perm.isLabel());
+ }
+ }
+ }
+ }
+
+ AccessSection capability = null;
+ for (String varName : rc.getNames(CAPABILITY)) {
+ if (GlobalCapability.isCapability(varName)) {
+ if (capability == null) {
+ capability = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
+ accessSections.put(AccessSection.GLOBAL_CAPABILITIES, capability);
+ }
+ Permission perm = capability.getPermission(varName, true);
+ loadPermissionRules(rc, CAPABILITY, null, varName, groupsByName, perm,
+ GlobalCapability.hasRange(varName));
+ }
+ }
+ }
+
+ private void loadPermissionRules(Config rc, String section,
+ String subsection, String varName,
+ Map<String, GroupReference> groupsByName, Permission perm,
+ boolean useRange) {
+ for (String ruleString : rc.getStringList(section, subsection, varName)) {
+ PermissionRule rule;
+ try {
+ rule = PermissionRule.fromString(ruleString, useRange);
+ } catch (IllegalArgumentException notRule) {
+ error(new ValidationError(PROJECT_CONFIG, "Invalid rule in "
+ + section
+ + (subsection != null ? "." + subsection : "")
+ + "." + varName + ": "
+ + notRule.getMessage()));
+ continue;
+ }
+
+ GroupReference ref = groupsByName.get(rule.getGroup().getName());
+ if (ref == null) {
+ // The group wasn't mentioned in the groups table, so there is
+ // no valid UUID for it. Pool the reference anyway so at least
+ // all rules in the same file share the same GroupReference.
+ //
+ ref = rule.getGroup();
+ groupsByName.put(ref.getName(), ref);
+ error(new ValidationError(PROJECT_CONFIG,
+ "group \"" + ref.getName() + "\" not in " + GROUP_LIST));
+ }
+
+ rule.setGroup(ref);
+ perm.add(rule);
+ }
+ }
+
+ private Map<String, GroupReference> readGroupList() throws IOException {
+ groupsByUUID = new HashMap<AccountGroup.UUID, GroupReference>();
+ Map<String, GroupReference> groupsByName =
+ new HashMap<String, GroupReference>();
+
+ BufferedReader br = new BufferedReader(new StringReader(readUTF8(GROUP_LIST)));
+ String s;
+ for (int lineNumber = 1; (s = br.readLine()) != null; lineNumber++) {
+ if (s.isEmpty() || s.startsWith("#")) {
+ continue;
+ }
+
+ int tab = s.indexOf('\t');
+ if (tab < 0) {
+ error(new ValidationError(GROUP_LIST, lineNumber, "missing tab delimiter"));
+ continue;
+ }
+
+ AccountGroup.UUID uuid = new AccountGroup.UUID(s.substring(0, tab).trim());
+ String name = s.substring(tab + 1).trim();
+ GroupReference ref = new GroupReference(uuid, name);
+
+ groupsByUUID.put(uuid, ref);
+ groupsByName.put(name, ref);
+ }
+ return groupsByName;
+ }
+
+ @Override
+ protected void onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException {
+ if (commit.getMessage() == null || "".equals(commit.getMessage())) {
+ commit.setMessage("Updated project configuration\n");
+ }
+
+ Config rc = readConfig(PROJECT_CONFIG);
+ Project p = project;
+
+ if (p.getDescription() != null && !p.getDescription().isEmpty()) {
+ rc.setString(PROJECT, null, KEY_DESCRIPTION, p.getDescription());
+ } else {
+ rc.unset(PROJECT, null, KEY_DESCRIPTION);
+ }
+ set(rc, ACCESS, null, KEY_INHERIT_FROM, p.getParentName());
+
+ set(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, p.isUseContributorAgreements());
+ set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, p.isUseSignedOffBy());
+ set(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, p.isRequireChangeID());
+
+ set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
+ set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.isUseContentMerge());
+
+ Set<AccountGroup.UUID> keepGroups = new HashSet<AccountGroup.UUID>();
+ AccessSection capability = accessSections.get(AccessSection.GLOBAL_CAPABILITIES);
+ if (capability != null) {
+ Set<String> have = new HashSet<String>();
+ for (Permission permission : sort(capability.getPermissions())) {
+ have.add(permission.getName().toLowerCase());
+
+ boolean needRange = GlobalCapability.hasRange(permission.getName());
+ List<String> rules = new ArrayList<String>();
+ for (PermissionRule rule : sort(permission.getRules())) {
+ GroupReference group = rule.getGroup();
+ if (group.getUUID() != null) {
+ keepGroups.add(group.getUUID());
+ }
+ rules.add(rule.asString(needRange));
+ }
+ rc.setStringList(CAPABILITY, null, permission.getName(), rules);
+ }
+ for (String varName : rc.getNames(CAPABILITY)) {
+ if (GlobalCapability.isCapability(varName)
+ && !have.contains(varName.toLowerCase())) {
+ rc.unset(CAPABILITY, null, varName);
+ }
+ }
+ } else {
+ rc.unsetSection(CAPABILITY, null);
+ }
+
+ for (AccessSection as : sort(accessSections.values())) {
+ String refName = as.getName();
+ if (AccessSection.GLOBAL_CAPABILITIES.equals(refName)) {
+ continue;
+ }
+
+ StringBuilder doNotInherit = new StringBuilder();
+ for (Permission perm : sort(as.getPermissions())) {
+ if (perm.getExclusiveGroup()) {
+ if (0 < doNotInherit.length()) {
+ doNotInherit.append(' ');
+ }
+ doNotInherit.append(perm.getName());
+ }
+ }
+ if (0 < doNotInherit.length()) {
+ rc.setString(ACCESS, refName, KEY_GROUP_PERMISSIONS, doNotInherit.toString());
+ } else {
+ rc.unset(ACCESS, refName, KEY_GROUP_PERMISSIONS);
+ }
+
+ Set<String> have = new HashSet<String>();
+ for (Permission permission : sort(as.getPermissions())) {
+ have.add(permission.getName().toLowerCase());
+
+ boolean needRange = permission.isLabel();
+ List<String> rules = new ArrayList<String>();
+ for (PermissionRule rule : sort(permission.getRules())) {
+ GroupReference group = rule.getGroup();
+ if (group.getUUID() != null) {
+ keepGroups.add(group.getUUID());
+ }
+ rules.add(rule.asString(needRange));
+ }
+ rc.setStringList(ACCESS, refName, permission.getName(), rules);
+ }
+
+ for (String varName : rc.getNames(ACCESS, refName)) {
+ if (isPermission(varName) && !have.contains(varName.toLowerCase())) {
+ rc.unset(ACCESS, refName, varName);
+ }
+ }
+ }
+
+ for (String name : rc.getSubsections(ACCESS)) {
+ if (isAccessSection(name) && !accessSections.containsKey(name)) {
+ rc.unsetSection(ACCESS, name);
+ }
+ }
+ groupsByUUID.keySet().retainAll(keepGroups);
+
+ saveConfig(PROJECT_CONFIG, rc);
+ saveGroupList();
+ }
+
+ private void saveGroupList() throws IOException {
+ if (groupsByUUID.isEmpty()) {
+ saveFile(GROUP_LIST, null);
+ return;
+ }
+
+ final int uuidLen = 40;
+ StringBuilder buf = new StringBuilder();
+ buf.append(pad(uuidLen, "# UUID"));
+ buf.append('\t');
+ buf.append("Group Name");
+ buf.append('\n');
+
+ buf.append('#');
+ buf.append('\n');
+
+ for (GroupReference g : sort(groupsByUUID.values())) {
+ if (g.getUUID() != null && g.getName() != null) {
+ buf.append(pad(uuidLen, g.getUUID().get()));
+ buf.append('\t');
+ buf.append(g.getName());
+ buf.append('\n');
+ }
+ }
+ saveUTF8(GROUP_LIST, buf.toString());
+ }
+
+ private boolean getBoolean(Config rc, String section, String name,
+ boolean defaultValue) {
+ try {
+ return rc.getBoolean(section, name, defaultValue);
+ } catch (IllegalArgumentException err) {
+ error(new ValidationError(PROJECT_CONFIG, err.getMessage()));
+ return defaultValue;
+ }
+ }
+
+ private <E extends Enum<?>> E getEnum(Config rc, String section,
+ String subsection, String name, E defaultValue) {
+ try {
+ return rc.getEnum(section, subsection, name, defaultValue);
+ } catch (IllegalArgumentException err) {
+ error(new ValidationError(PROJECT_CONFIG, err.getMessage()));
+ return defaultValue;
+ }
+ }
+
+ private void error(ValidationError error) {
+ if (validationErrors == null) {
+ validationErrors = new ArrayList<ValidationError>(4);
+ }
+ validationErrors.add(error);
+ }
+
+ private static String pad(int len, String src) {
+ if (len <= src.length()) {
+ return src;
+ }
+
+ StringBuilder r = new StringBuilder(len);
+ r.append(src);
+ while (r.length() < len) {
+ r.append(' ');
+ }
+ return r.toString();
+ }
+
+ private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
+ ArrayList<T> r = new ArrayList<T>(m);
+ Collections.sort(r);
+ return r;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushAllProjectsOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushAllProjectsOp.java
index 8985f29..b5f991c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushAllProjectsOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushAllProjectsOp.java
@@ -15,10 +15,7 @@
package com.google.gerrit.server.git;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.config.WildProjectName;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.SchemaFactory;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -37,20 +34,16 @@
private static final Logger log =
LoggerFactory.getLogger(PushAllProjectsOp.class);
- private final SchemaFactory<ReviewDb> schema;
+ private final ProjectCache projectCache;
private final ReplicationQueue replication;
- private final Project.NameKey wildProject;
private final String urlMatch;
@Inject
- public PushAllProjectsOp(final WorkQueue wq,
- final SchemaFactory<ReviewDb> sf, final ReplicationQueue rq,
- @WildProjectName final Project.NameKey wp,
- @Assisted @Nullable final String urlMatch) {
+ public PushAllProjectsOp(final WorkQueue wq, final ProjectCache projectCache,
+ final ReplicationQueue rq, @Assisted @Nullable final String urlMatch) {
super(wq);
- this.schema = sf;
+ this.projectCache = projectCache;
this.replication = rq;
- this.wildProject = wp;
this.urlMatch = urlMatch;
}
@@ -63,17 +56,10 @@
public void run() {
try {
- final ReviewDb db = schema.open();
- try {
- for (final Project project : db.projects().all()) {
- if (!project.getNameKey().equals(wildProject)) {
- replication.scheduleFullSync(project.getNameKey(), urlMatch);
- }
- }
- } finally {
- db.close();
+ for (final Project.NameKey nameKey : projectCache.all()) {
+ replication.scheduleFullSync(nameKey, urlMatch);
}
- } catch (OrmException e) {
+ } catch (RuntimeException e) {
log.error("Cannot enumerate known projects", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
index fd7649a..cb005aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
@@ -356,7 +356,7 @@
String[] authGroupNames =
cfg.getStringList("remote", rc.getName(), "authGroup");
- final Set<AccountGroup.Id> authGroups;
+ final Set<AccountGroup.UUID> authGroups;
if (authGroupNames.length > 0) {
authGroups = ConfigUtil.groupsFor(db, authGroupNames, //
log, "Group \"{0}\" not in database, removing from authGroup");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
similarity index 72%
copy from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
index 0977ee9..b539416 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
@@ -12,15 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.server.git;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_49 extends SchemaVersion {
-
- @Inject
- Schema_49(Provider<Schema_48> prior) {
- super(prior);
+public interface QueueProvider {
+ public static enum QueueType {
+ INTERACTIVE, BATCH;
}
+
+ public WorkQueue.Executor getQueue(QueueType type);
}
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 96dd95d..b27800d 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
@@ -18,17 +18,13 @@
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.errors.NoSuchAccountException;
-import com.google.gerrit.reviewdb.AbstractAgreement;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountAgreement;
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupAgreement;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
-import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetAncestor;
import com.google.gerrit.reviewdb.PatchSetApproval;
@@ -48,7 +44,9 @@
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
+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.server.project.RefControl;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
@@ -113,20 +111,6 @@
ReceiveCommits create(ProjectControl projectControl, Repository repository);
}
- public static class Capable {
- public static final Capable OK = new Capable("OK");
-
- private final String message;
-
- Capable(String msg) {
- message = msg;
- }
-
- public String getMessage() {
- return message;
- }
- }
-
private final Set<Account.Id> reviewerId = new HashSet<Account.Id>();
private final Set<Account.Id> ccId = new HashSet<Account.Id>();
@@ -140,6 +124,8 @@
private final ReplicationQueue replication;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ChangeHookRunner hooks;
+ private final GitRepositoryManager repoManager;
+ private final ProjectCache projectCache;
private final String canonicalWebUrl;
private final PersonIdent gerritIdent;
private final TrackingFooters trackingFooters;
@@ -175,6 +161,8 @@
final ReplicationQueue replication,
final PatchSetInfoFactory patchSetInfoFactory,
final ChangeHookRunner hooks,
+ final ProjectCache projectCache,
+ final GitRepositoryManager repoManager,
final TagCache tagCache,
@CanonicalWebUrl @Nullable final String canonicalWebUrl,
@GerritPersonIdent final PersonIdent gerritIdent,
@@ -192,6 +180,8 @@
this.replication = replication;
this.patchSetInfoFactory = patchSetInfoFactory;
this.hooks = hooks;
+ this.projectCache = projectCache;
+ this.repoManager = repoManager;
this.canonicalWebUrl = canonicalWebUrl;
this.gerritIdent = gerritIdent;
this.trackingFooters = trackingFooters;
@@ -309,9 +299,9 @@
/** Determine if the user can upload commits. */
public Capable canUpload() {
- if (!projectControl.canPushToAtLeastOneRef()) {
- String reqName = project.getName();
- return new Capable("Upload denied for project '" + reqName + "'");
+ Capable result = projectControl.canPushToAtLeastOneRef();
+ if (result != Capable.OK) {
+ return result;
}
// Don't permit receive-pack to be executed if a refs/for/branch_name
@@ -336,16 +326,7 @@
return new Capable("One or more refs/for/ names blocks change upload");
}
- if (project.isUseContributorAgreements()) {
- try {
- return verifyActiveContributorAgreement();
- } catch (OrmException e) {
- log.error("Cannot query database for agreements", e);
- return new Capable("Cannot verify contribution agreement");
- }
- } else {
- return Capable.OK;
- }
+ return Capable.OK;
}
@Override
@@ -388,6 +369,13 @@
break;
}
+ if (isConfig(c)) {
+ projectCache.evict(project);
+ ProjectState ps = projectCache.get(project.getNameKey());
+ repoManager.setProjectDescription(project.getNameKey(), //
+ ps.getProject().getDescription());
+ }
+
if (!c.getRefName().startsWith(NEW_CHANGE)) {
// We only schedule direct refs updates for replication.
// Change refs are scheduled when they are created.
@@ -410,122 +398,6 @@
}
}
- private Capable verifyActiveContributorAgreement() throws OrmException {
- AbstractAgreement bestAgreement = null;
- ContributorAgreement bestCla = null;
-
- OUTER: for (AccountGroup.Id groupId : currentUser.getEffectiveGroups()) {
- final List<AccountGroupAgreement> temp =
- db.accountGroupAgreements().byGroup(groupId).toList();
-
- Collections.reverse(temp);
-
- for (final AccountGroupAgreement a : temp) {
- final ContributorAgreement cla =
- db.contributorAgreements().get(a.getAgreementId());
- if (cla == null) {
- continue;
- }
-
- bestAgreement = a;
- bestCla = cla;
- break OUTER;
- }
- }
-
- if (bestAgreement == null) {
- final List<AccountAgreement> temp =
- db.accountAgreements().byAccount(currentUser.getAccountId()).toList();
-
- Collections.reverse(temp);
-
- for (final AccountAgreement a : temp) {
- final ContributorAgreement cla =
- db.contributorAgreements().get(a.getAgreementId());
- if (cla == null) {
- continue;
- }
-
- bestAgreement = a;
- bestCla = cla;
- break;
- }
- }
-
- if (bestCla != null && !bestCla.isActive()) {
- final StringBuilder msg = new StringBuilder();
- msg.append(bestCla.getShortName());
- msg.append(" contributor agreement is expired.\n");
- if (canonicalWebUrl != null) {
- msg.append("\nPlease complete a new agreement");
- msg.append(":\n\n ");
- msg.append(canonicalWebUrl);
- msg.append("#");
- msg.append(PageLinks.SETTINGS_AGREEMENTS);
- msg.append("\n");
- }
- msg.append("\n");
- return new Capable(msg.toString());
- }
-
- if (bestCla != null && bestCla.isRequireContactInformation()) {
- boolean fail = false;
- fail |= missing(currentUser.getAccount().getFullName());
- fail |= missing(currentUser.getAccount().getPreferredEmail());
- fail |= !currentUser.getAccount().isContactFiled();
-
- if (fail) {
- final StringBuilder msg = new StringBuilder();
- msg.append(bestCla.getShortName());
- msg.append(" contributor agreement requires");
- msg.append(" current contact information.\n");
- if (canonicalWebUrl != null) {
- msg.append("\nPlease review your contact information");
- msg.append(":\n\n ");
- msg.append(canonicalWebUrl);
- msg.append("#");
- msg.append(PageLinks.SETTINGS_CONTACT);
- msg.append("\n");
- }
- msg.append("\n");
- return new Capable(msg.toString());
- }
- }
-
- if (bestAgreement != null) {
- switch (bestAgreement.getStatus()) {
- case VERIFIED:
- return Capable.OK;
- case REJECTED:
- return new Capable(bestCla.getShortName()
- + " contributor agreement was rejected."
- + "\n (rejected on " + bestAgreement.getReviewedOn()
- + ")\n");
- case NEW:
- return new Capable(bestCla.getShortName()
- + " contributor agreement is still pending review.\n");
- }
- }
-
- final StringBuilder msg = new StringBuilder();
- msg.append(" A Contributor Agreement must be completed before uploading");
- if (canonicalWebUrl != null) {
- msg.append(":\n\n ");
- msg.append(canonicalWebUrl);
- msg.append("#");
- msg.append(PageLinks.SETTINGS_AGREEMENTS);
- msg.append("\n");
- } else {
- msg.append(".");
- }
- msg.append("\n");
- return new Capable(msg.toString());
- }
-
- private static boolean missing(final String value) {
- return value == null || value.trim().equals("");
- }
-
private Account.Id toAccountId(final String nameOrEmail) throws OrmException,
NoSuchAccountException {
final Account a = accountResolver.findByNameOrEmail(nameOrEmail);
@@ -567,24 +439,70 @@
switch (cmd.getType()) {
case CREATE:
parseCreate(cmd);
- continue;
+ break;
case UPDATE:
parseUpdate(cmd);
- continue;
+ break;
case DELETE:
parseDelete(cmd);
- continue;
+ break;
case UPDATE_NONFASTFORWARD:
parseRewind(cmd);
+ break;
+
+ default:
+ reject(cmd);
continue;
}
- // Everything else is bogus as far as we are concerned.
- //
- reject(cmd);
+ if (cmd.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED){
+ continue;
+ }
+
+ if (isConfig(cmd)) {
+ if (!projectControl.isOwner()) {
+ reject(cmd, "not project owner");
+ continue;
+ }
+
+ switch (cmd.getType()) {
+ case CREATE:
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ try {
+ ProjectConfig cfg = new ProjectConfig(project.getNameKey());
+ cfg.load(repo, cmd.getNewId());
+ if (!cfg.getValidationErrors().isEmpty()) {
+ rp.sendError("Invalid project configuration:");
+ for (ValidationError err : cfg.getValidationErrors()) {
+ rp.sendError(" " + err.getMessage());
+ }
+ reject(cmd, "invalid project configuration");
+ log.error("User " + currentUser.getUserName()
+ + " tried to push invalid project configuration "
+ + cmd.getNewId().name() + " for " + project.getName());
+ continue;
+ }
+ } catch (Exception e) {
+ reject(cmd, "invalid project configuration");
+ log.error("User " + currentUser.getUserName()
+ + " tried to push invalid project configuration "
+ + cmd.getNewId().name() + " for " + project.getName(), e);
+ continue;
+ }
+ break;
+
+ case DELETE:
+ break;
+
+ default:
+ reject(cmd);
+ continue;
+ }
+ }
}
}
@@ -1300,15 +1218,18 @@
oldCC.add(a.getAccountId());
}
- final ApprovalType type =
- approvalTypes.getApprovalType(a.getCategoryId());
- if (a.getPatchSetId().equals(priorPatchSet)
- && type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
- // If there was a negative vote on the prior patch set, carry it
- // into this patch set.
- //
- db.patchSetApprovals().insert(
- Collections.singleton(new PatchSetApproval(ps.getId(), a)));
+ // ApprovalCategory.SUBMIT is still in db but not relevant in git-store
+ if (!ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
+ final ApprovalType type =
+ approvalTypes.byId(a.getCategoryId());
+ if (a.getPatchSetId().equals(priorPatchSet)
+ && type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
+ // If there was a negative vote on the prior patch set, carry it
+ // into this patch set.
+ //
+ db.patchSetApprovals().insert(
+ Collections.singleton(new PatchSetApproval(ps.getId(), a)));
+ }
}
if (!haveAuthor && authorId != null && a.getAccountId().equals(authorId)) {
@@ -1634,10 +1555,35 @@
// Check for banned commits to prevent them from entering the tree again.
if (rejectCommits.contains(c)) {
- reject(newChange, "contains banned commit " + c.getName());
+ reject(cmd, "contains banned commit " + c.getName());
return false;
}
+ // If this is the special project configuration branch, validate the config.
+ if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+ try {
+ ProjectConfig cfg = new ProjectConfig(project.getNameKey());
+ cfg.load(repo, cmd.getNewId());
+ if (!cfg.getValidationErrors().isEmpty()) {
+ rp.sendError("Invalid project configuration:");
+ for (ValidationError err : cfg.getValidationErrors()) {
+ rp.sendError(" " + err.getMessage());
+ }
+ reject(cmd, "invalid project configuration");
+ log.error("User " + currentUser.getUserName()
+ + " tried to push invalid project configuration "
+ + cmd.getNewId().name() + " for " + project.getName());
+ return false;
+ }
+ } catch (Exception e) {
+ reject(cmd, "invalid project configuration");
+ log.error("User " + currentUser.getUserName()
+ + " tried to push invalid project configuration "
+ + cmd.getNewId().name() + " for " + project.getName(), e);
+ return false;
+ }
+ }
+
return true;
}
@@ -1932,4 +1878,8 @@
private static boolean isHead(final ReceiveCommand cmd) {
return cmd.getRefName().startsWith(Constants.R_HEADS);
}
+
+ private static boolean isConfig(final ReceiveCommand cmd) {
+ return cmd.getRefName().equals(GitRepositoryManager.REF_CONFIG);
+ }
}
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
new file mode 100644
index 0000000..cedcb0b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
@@ -0,0 +1,153 @@
+// 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;
+
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.Project.NameKey;
+import com.google.gerrit.server.project.ProjectCache;
+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.PersonIdent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class RenameGroupOp extends DefaultQueueOp {
+ public interface Factory {
+ RenameGroupOp create(@Assisted("author") PersonIdent author,
+ @Assisted AccountGroup.UUID uuid, @Assisted("oldName") String oldName,
+ @Assisted("newName") String newName);
+ }
+
+ private static final int MAX_TRIES = 10;
+ private static final Logger log =
+ LoggerFactory.getLogger(RenameGroupOp.class);
+
+ private final ProjectCache projectCache;
+ private final MetaDataUpdate.Server metaDataUpdateFactory;
+
+ private final PersonIdent author;
+ private final AccountGroup.UUID uuid;
+ private final String oldName;
+ private final String newName;
+ private final List<Project.NameKey> retryOn;
+
+ private boolean tryingAgain;
+
+ @Inject
+ public RenameGroupOp(WorkQueue workQueue, ProjectCache projectCache,
+ MetaDataUpdate.Server metaDataUpdateFactory,
+
+ @Assisted("author") PersonIdent author, @Assisted AccountGroup.UUID uuid,
+ @Assisted("oldName") String oldName, @Assisted("newName") String newName) {
+ super(workQueue);
+ this.projectCache = projectCache;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+
+ this.author = author;
+ this.uuid = uuid;
+ this.oldName = oldName;
+ this.newName = newName;
+ this.retryOn = new ArrayList<Project.NameKey>();
+ }
+
+ @Override
+ public void run() {
+ Iterable<NameKey> names = tryingAgain ? retryOn : projectCache.all();
+ for (Project.NameKey projectName : names) {
+ ProjectConfig config = projectCache.get(projectName).getConfig();
+ GroupReference ref = config.getGroup(uuid);
+ if (ref == null || newName.equals(ref.getName())) {
+ continue;
+ }
+
+ try {
+ MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
+ try {
+ rename(md);
+ } finally {
+ md.close();
+ }
+ } catch (RepositoryNotFoundException noProject) {
+ continue;
+ } catch (ConfigInvalidException err) {
+ log.error("Cannot rename group " + oldName + " in " + projectName, err);
+ } catch (IOException err) {
+ log.error("Cannot rename group " + oldName + " in " + projectName, err);
+ }
+ }
+
+ // If one or more projects did not update, wait 5 minutes
+ // and give it another attempt.
+ if (!retryOn.isEmpty() && !tryingAgain) {
+ tryingAgain = true;
+ start(5, TimeUnit.MINUTES);
+ }
+ }
+
+ private void rename(MetaDataUpdate md) throws IOException,
+ ConfigInvalidException {
+ boolean success = false;
+ for (int attempts = 0; !success && attempts < MAX_TRIES; attempts++) {
+ ProjectConfig config = ProjectConfig.read(md);
+
+ // The group isn't referenced, or its name has been fixed already.
+ //
+ GroupReference ref = config.getGroup(uuid);
+ if (ref == null || newName.equals(ref.getName())) {
+ projectCache.evict(config.getProject());
+ return;
+ }
+
+ ref.setName(newName);
+ md.getCommitBuilder().setAuthor(author);
+ md.setMessage("Rename group " + oldName + " to " + newName + "\n");
+ if (config.commit(md)) {
+ projectCache.evict(config.getProject());
+ success = true;
+
+ } else {
+ try {
+ Thread.sleep(25 /* milliseconds */);
+ } catch (InterruptedException wakeUp) {
+ continue;
+ }
+ }
+ }
+
+ if (!success) {
+ if (tryingAgain) {
+ log.warn("Could not rename group " + oldName + " to " + newName
+ + " in " + md.getProjectName().get());
+ } else {
+ retryOn.add(md.getProjectName());
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Rename Group " + oldName;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteHeaderFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteHeaderFormatter.java
index d54dcec..8997e2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteHeaderFormatter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteHeaderFormatter.java
@@ -51,8 +51,7 @@
void appendApproval(ApprovalCategory category,
short value, Account user) {
- // TODO: use category.getLabel() when available
- sb.append(category.getName().replace(' ', '-'));
+ sb.append(category.getLabelName());
sb.append(value < 0 ? "-" : "+").append(Math.abs(value)).append(": ");
appendUserData(user);
sb.append("\n");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java
new file mode 100644
index 0000000..e1ab41d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ValidationError.java
@@ -0,0 +1,41 @@
+// 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;
+
+/** Indicates a problem with Git based data. */
+public class ValidationError {
+ private final String message;
+
+ public ValidationError(String file, String message) {
+ this(file + ": " + message);
+ }
+
+ public ValidationError(String file, int line, String message) {
+ this(file + ":" + line + ": " + message);
+ }
+
+ public ValidationError(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return "ValidationError[" + message + "]";
+ }
+}
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
new file mode 100644
index 0000000..655daa3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -0,0 +1,331 @@
+// 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.git;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.IOException;
+
+/**
+ * Support for metadata stored within a version controlled branch.
+ * <p>
+ * Implementors are responsible for supplying implementations of the onLoad and
+ * onSave methods to read from the repository, or format an update that can
+ * later be written back to the repository.
+ */
+public abstract class VersionedMetaData {
+ private RevCommit revision;
+ private ObjectReader reader;
+ private ObjectInserter inserter;
+ private DirCache newTree;
+
+ /** @return name of the reference storing this configuration. */
+ protected abstract String getRefName();
+
+ protected abstract void onLoad() throws IOException, ConfigInvalidException;
+
+ protected abstract void onSave(CommitBuilder commit) throws IOException,
+ ConfigInvalidException;
+
+ /** @return revision of the metadata that was loaded. */
+ public ObjectId getRevision() {
+ return revision != null ? revision.copy() : null;
+ }
+
+ /** Initialize in-memory as though the repository branch doesn't exist. */
+ public void createInMemory() {
+ try {
+ revision = null;
+ onLoad();
+ } catch (IOException err) {
+ throw new RuntimeException("Unexpected IOException", err);
+ } catch (ConfigInvalidException err) {
+ throw new RuntimeException("Unexpected ConfigInvalidException", err);
+ }
+ }
+
+ /**
+ * Load the current version from the branch.
+ * <p>
+ * The repository is not held after the call completes, allowing the
+ * application to retain this object for long periods of time.
+ *
+ * @param db repository to access.
+ * @throws IOException
+ * @throws ConfigInvalidException
+ */
+ public void load(Repository db) throws IOException, ConfigInvalidException {
+ Ref ref = db.getRef(getRefName());
+ load(db, ref != null ? ref.getObjectId() : null);
+ }
+
+ /**
+ * Load a specific version from the repository.
+ * <p>
+ * This method is primarily useful for applying updates to a specific revision
+ * that was shown to an end-user in the user interface. If there are conflicts
+ * with another user's concurrent changes, these will be automatically
+ * detected at commit time.
+ * <p>
+ * The repository is not held after the call completes, allowing the
+ * application to retain this object for long periods of time.
+ *
+ * @param db repository to access.
+ * @param id revision to load.
+ * @throws IOException
+ * @throws ConfigInvalidException
+ */
+ public void load(Repository db, ObjectId id) throws IOException,
+ ConfigInvalidException {
+ if (id != null) {
+ reader = db.newObjectReader();
+ try {
+ revision = new RevWalk(reader).parseCommit(id);
+ onLoad();
+ } finally {
+ reader.release();
+ reader = null;
+ }
+ } else {
+ // The branch does not yet exist.
+ revision = null;
+ onLoad();
+ }
+ }
+
+ public void load(MetaDataUpdate update) throws IOException,
+ ConfigInvalidException {
+ load(update.getRepository());
+ }
+
+ public void load(MetaDataUpdate update, ObjectId id) throws IOException,
+ ConfigInvalidException {
+ load(update.getRepository(), id);
+ }
+
+ /**
+ * 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.
+ * @throws IOException if there is a storage problem and the update cannot be
+ * executed as requested.
+ */
+ public boolean commit(MetaDataUpdate update) throws IOException {
+ final Repository db = update.getRepository();
+ final CommitBuilder commit = update.getCommitBuilder();
+
+ reader = db.newObjectReader();
+ inserter = db.newObjectInserter();
+ try {
+ final RevWalk rw = new RevWalk(reader);
+ final RevTree src = revision != null ? rw.parseTree(revision) : null;
+ final ObjectId res = writeTree(src, commit);
+
+ if (res.equals(src)) {
+ // If there are no changes to the content, don't create the commit.
+ return true;
+ }
+
+ commit.setTreeId(res);
+ if (revision != null) {
+ commit.setParentId(revision);
+ }
+
+ RefUpdate ru = db.updateRef(getRefName());
+ if (revision != null) {
+ ru.setExpectedOldObjectId(revision);
+ } else {
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ }
+ ru.setNewObjectId(inserter.insert(commit));
+ ru.disableRefLog();
+ inserter.flush();
+
+ switch (ru.update(rw)) {
+ case NEW:
+ case FAST_FORWARD:
+ revision = rw.parseCommit(ru.getNewObjectId());
+ update.replicate(ru.getName());
+ return true;
+
+ case LOCK_FAILURE:
+ return false;
+
+ default:
+ throw new IOException("Cannot update " + ru.getName() + " in "
+ + db.getDirectory() + ": " + ru.getResult());
+ }
+ } catch (ConfigInvalidException e) {
+ throw new IOException("Cannot update " + getRefName() + " in "
+ + db.getDirectory() + ": " + e.getMessage(), e);
+ } finally {
+ inserter.release();
+ inserter = null;
+
+ reader.release();
+ reader = null;
+ }
+ }
+
+ private ObjectId writeTree(RevTree srcTree, CommitBuilder commit)
+ throws IOException, MissingObjectException, IncorrectObjectTypeException,
+ UnmergedPathException, ConfigInvalidException {
+ try {
+ newTree = readTree(srcTree);
+ onSave(commit);
+ return newTree.writeTree(inserter);
+ } finally {
+ newTree = null;
+ }
+ }
+
+ private DirCache readTree(RevTree tree) throws IOException,
+ MissingObjectException, IncorrectObjectTypeException {
+ DirCache dc = DirCache.newInCore();
+ if (tree != null) {
+ DirCacheBuilder b = dc.builder();
+ b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, tree);
+ b.finish();
+ }
+ return dc;
+ }
+
+ protected Config readConfig(String fileName) throws IOException,
+ ConfigInvalidException {
+ Config rc = new Config();
+ String text = readUTF8(fileName);
+ if (!text.isEmpty()) {
+ try {
+ rc.fromText(text);
+ } catch (ConfigInvalidException err) {
+ throw new ConfigInvalidException("Invalid config file " + fileName
+ + " in commit" + revision.name(), err);
+ }
+ }
+ return rc;
+ }
+
+ protected String readUTF8(String fileName) throws IOException {
+ byte[] raw = readFile(fileName);
+ return raw.length != 0 ? RawParseUtils.decode(raw) : "";
+ }
+
+ protected byte[] readFile(String fileName) throws IOException {
+ if (revision == null) {
+ return new byte[] {};
+ }
+
+ TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree());
+ if (tw != null) {
+ ObjectLoader obj = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
+ return obj.getCachedBytes(Integer.MAX_VALUE);
+
+ } else {
+ return new byte[] {};
+ }
+ }
+
+ protected ObjectId getObjectId(String fileName) throws IOException {
+ if (revision == null) {
+ return null;
+ }
+
+ TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree());
+ if (tw != null) {
+ return tw.getObjectId(0);
+ }
+
+ return null;
+ }
+
+ protected static void set(Config rc, String section, String subsection,
+ String name, String value) {
+ if (value != null) {
+ rc.setString(section, subsection, name, value);
+ } else {
+ rc.unset(section, subsection, name);
+ }
+ }
+
+ protected static void set(Config rc, String section, String subsection,
+ String name, boolean value) {
+ if (value) {
+ rc.setBoolean(section, subsection, name, value);
+ } else {
+ rc.unset(section, subsection, name);
+ }
+ }
+
+ protected static <E extends Enum<?>> void set(Config rc, String section,
+ String subsection, String name, E value, E defaultValue) {
+ if (value != defaultValue) {
+ rc.setEnum(section, subsection, name, value);
+ } else {
+ rc.unset(section, subsection, name);
+ }
+ }
+
+ protected void saveConfig(String fileName, Config cfg) throws IOException {
+ saveUTF8(fileName, cfg.toText());
+ }
+
+ protected void saveUTF8(String fileName, String text) throws IOException {
+ saveFile(fileName, text != null ? Constants.encode(text) : null);
+ }
+
+ protected void saveFile(String fileName, byte[] raw) throws IOException {
+ DirCacheEditor editor = newTree.editor();
+ if (raw != null && 0 < raw.length) {
+ final ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, raw);
+ editor.add(new PathEdit(fileName) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.REGULAR_FILE);
+ ent.setObjectId(blobId);
+ }
+ });
+ } else {
+ editor.add(new DeletePath(fileName));
+ }
+ editor.finish();
+ }
+}
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 f5a7870..4b5d279 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
@@ -69,8 +69,8 @@
/** Is the from user in an email squelching group? */
final IdentifiedUser user = args.identifiedUserFactory.create(id);
- final Set<AccountGroup.Id> gids = user.getEffectiveGroups();
- for (final AccountGroup.Id gid : gids) {
+ final Set<AccountGroup.UUID> gids = user.getEffectiveGroups();
+ for (final AccountGroup.UUID gid : gids) {
if (args.groupCache.get(gid).isEmailOnlyAuthors()) {
emailOnlyAuthors = true;
break;
@@ -273,11 +273,11 @@
}
/** Get the groups which own the project. */
- protected Set<AccountGroup.Id> getProjectOwners() {
+ protected Set<AccountGroup.UUID> getProjectOwners() {
final ProjectState r;
r = args.projectCache.get(change.getProject());
- return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
+ return r != null ? r.getOwners() : Collections.<AccountGroup.UUID> emptySet();
}
/** TO or CC all vested parties (change owner, patch set uploader, author). */
@@ -336,7 +336,7 @@
}
for (AccountProjectWatch w : args.db.get().accountProjectWatches()
- .byProject(args.wildProject)) {
+ .byProject(args.allProjectsName)) {
if (!projectWatchers.contains(w.getAccountId())) {
add(matching, w);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index c14ff1b..8bb68df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -20,6 +20,7 @@
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
+import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -34,10 +35,13 @@
public CreateChangeSender create(Change change);
}
+ private final GroupCache groupCache;
+
@Inject
public CreateChangeSender(EmailArguments ea, SshInfo sshInfo,
- @Assisted Change c) {
+ GroupCache groupCache, @Assisted Change c) {
super(ea, sshInfo, c);
+ this.groupCache = groupCache;
}
@Override
@@ -52,10 +56,13 @@
// Try to mark interested owners with a TO and not a BCC line.
//
final Set<Account.Id> owners = new HashSet<Account.Id>();
- for (AccountGroup.Id g : getProjectOwners()) {
- for (AccountGroupMember m : args.db.get().accountGroupMembers()
- .byGroup(g)) {
- owners.add(m.getAccountId());
+ for (AccountGroup.UUID uuid : getProjectOwners()) {
+ AccountGroup group = groupCache.get(uuid);
+ if (group != null) {
+ for (AccountGroupMember m : args.db.get().accountGroupMembers()
+ .byGroup(group.getId())) {
+ owners.add(m.getAccountId());
+ }
}
}
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 4a99eb4..ae4aff0 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
@@ -14,15 +14,14 @@
package com.google.gerrit.server.mail;
-import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -45,7 +44,7 @@
final PatchSetInfoFactory patchSetInfoFactory;
final IdentifiedUser.GenericFactory identifiedUserFactory;
final Provider<String> urlProvider;
- final Project.NameKey wildProject;
+ final AllProjectsName allProjectsName;
final ChangeQueryBuilder.Factory queryBuilder;
final Provider<ChangeQueryRewriter> queryRewriter;
@@ -59,7 +58,7 @@
EmailSender emailSender, PatchSetInfoFactory patchSetInfoFactory,
GenericFactory identifiedUserFactory,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
- @WildProjectName Project.NameKey wildProject,
+ AllProjectsName allProjectsName,
ChangeQueryBuilder.Factory queryBuilder,
Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db,
SitePaths site) {
@@ -73,7 +72,7 @@
this.patchSetInfoFactory = patchSetInfoFactory;
this.identifiedUserFactory = identifiedUserFactory;
this.urlProvider = urlProvider;
- this.wildProject = wildProject;
+ this.allProjectsName = allProjectsName;
this.queryBuilder = queryBuilder;
this.queryRewriter = queryRewriter;
this.db = db;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java
similarity index 89%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java
index efe93f5..4cb3198 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java
@@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.httpd.rpc.patch;
-import com.google.gerrit.common.data.ReviewerResult;
+package com.google.gerrit.server.patch;
+
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
+import com.google.gerrit.common.data.ReviewerResult;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
@@ -38,16 +37,16 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Callable;
-class AddReviewer extends Handler<ReviewerResult> {
- interface Factory {
+public class AddReviewer implements Callable<ReviewerResult> {
+ public interface Factory {
AddReviewer create(Change.Id changeId, Collection<String> nameOrEmails);
}
private final AddReviewerSender.Factory addReviewerSenderFactory;
private final AccountResolver accountResolver;
private final ChangeControl.Factory changeControlFactory;
- private final ChangeDetailFactory.Factory changeDetailFactory;
private final ReviewDb db;
private final IdentifiedUser currentUser;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
@@ -62,7 +61,6 @@
final ChangeControl.Factory changeControlFactory, final ReviewDb db,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final IdentifiedUser currentUser, final ApprovalTypes approvalTypes,
- final ChangeDetailFactory.Factory changeDetailFactory,
@Assisted final Change.Id changeId,
@Assisted final Collection<String> nameOrEmails) {
this.addReviewerSenderFactory = addReviewerSenderFactory;
@@ -71,7 +69,6 @@
this.changeControlFactory = changeControlFactory;
this.identifiedUserFactory = identifiedUserFactory;
this.currentUser = currentUser;
- this.changeDetailFactory = changeDetailFactory;
final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
addReviewerCategoryId =
@@ -94,16 +91,19 @@
ReviewerResult.Error.Type.ACCOUNT_NOT_FOUND, nameOrEmail));
continue;
}
+
if (!account.isActive()) {
result.addError(new ReviewerResult.Error(
- ReviewerResult.Error.Type.ACCOUNT_INACTIVE, nameOrEmail));
+ ReviewerResult.Error.Type.ACCOUNT_INACTIVE,
+ formatUser(account, nameOrEmail)));
continue;
}
final IdentifiedUser user = identifiedUserFactory.create(account.getId());
if (!control.forUser(user).isVisible()) {
result.addError(new ReviewerResult.Error(
- ReviewerResult.Error.Type.CHANGE_NOT_VISIBLE, nameOrEmail));
+ ReviewerResult.Error.Type.CHANGE_NOT_VISIBLE,
+ formatUser(account, nameOrEmail)));
continue;
}
@@ -143,10 +143,17 @@
cm.send();
}
- result.setChange(changeDetailFactory.create(changeId).call());
return result;
}
+ private String formatUser(Account account, String nameOrEmail) {
+ if (nameOrEmail.matches("^[1-9][0-9]*$")) {
+ return RemoveReviewer.formatUser(account, nameOrEmail);
+ } else {
+ return nameOrEmail;
+ }
+ }
+
private boolean exists(final PatchSet.Id patchSetId,
final Account.Id reviewerId) throws OrmException {
return db.patchSetApprovals().byPatchSetUser(patchSetId, reviewerId)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
index f3e2890..244648a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -104,7 +105,8 @@
}
@Override
- public VoidResult call() throws NoSuchChangeException, OrmException {
+ public VoidResult call() throws NoSuchChangeException,
+ InvalidChangeOperationException, OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl ctl = changeControlFactory.validateFor(changeId);
change = ctl.getChange();
@@ -119,6 +121,8 @@
final boolean isCurrent = patchSetId.equals(change.currentPatchSetId());
if (isCurrent && change.getStatus().isOpen()) {
publishApprovals();
+ } else if (! approvals.isEmpty()) {
+ throw new InvalidChangeOperationException("Change is closed");
} else {
publishMessageOnly();
}
@@ -171,7 +175,9 @@
final short o = a.getValue();
a.setValue(want.get());
a.cache(change);
- functionState.normalize(types.getApprovalType(a.getCategoryId()), a);
+ if (!ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
+ functionState.normalize(types.byId(a.getCategoryId()), a);
+ }
if (o != a.getValue()) {
// Value changed, ensure we update the database.
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/RemoveReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/RemoveReviewer.java
new file mode 100644
index 0000000..884cb54
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/RemoveReviewer.java
@@ -0,0 +1,130 @@
+// 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.patch;
+
+import com.google.gerrit.common.data.ReviewerResult;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+public class RemoveReviewer implements Callable<ReviewerResult> {
+ private static final Logger log =
+ LoggerFactory.getLogger(RemoveReviewer.class);
+
+ public interface Factory {
+ RemoveReviewer create(Change.Id changeId, Set<Account.Id> reviewerId);
+ }
+
+ private final ChangeControl.Factory changeControlFactory;
+ private final ReviewDb db;
+ private final AccountCache accountCache;
+ private final Change.Id changeId;
+ private final Set<Account.Id> ids;
+
+ @Inject
+ RemoveReviewer(ReviewDb db, ChangeControl.Factory changeControlFactory,
+ AccountCache accountCache, @Assisted Change.Id changeId,
+ @Assisted Set<Account.Id> ids) {
+ this.db = db;
+ this.changeControlFactory = changeControlFactory;
+ this.accountCache = accountCache;
+ this.changeId = changeId;
+ this.ids = ids;
+ }
+
+ @Override
+ public ReviewerResult call() throws Exception {
+ ReviewerResult result = new ReviewerResult();
+ ChangeControl ctl = changeControlFactory.validateFor(changeId);
+ Set<Account.Id> rejected = new HashSet<Account.Id>();
+
+ List<PatchSetApproval> current = db.patchSetApprovals().byChange(changeId).toList();
+ for (PatchSetApproval psa : current) {
+ Account.Id who = psa.getAccountId();
+ if (ids.contains(who) && !ctl.canRemoveReviewer(psa) && rejected.add(who)) {
+ result.addError(new ReviewerResult.Error(
+ ReviewerResult.Error.Type.REMOVE_NOT_PERMITTED,
+ formatUser(who)));
+ }
+ }
+
+ List<PatchSetApproval> toDelete = new ArrayList<PatchSetApproval>();
+ for (PatchSetApproval psa : current) {
+ Account.Id who = psa.getAccountId();
+ if (ids.contains(who) && !rejected.contains(who)) {
+ toDelete.add(psa);
+ }
+ }
+
+ try {
+ db.patchSetApprovals().delete(toDelete);
+ } catch (OrmException err) {
+ log.warn("Cannot remove reviewers from change "+changeId, err);
+ Set<Account.Id> failed = new HashSet<Account.Id>();
+ for (PatchSetApproval psa : toDelete) {
+ failed.add(psa.getAccountId());
+ }
+ for (Account.Id who : failed) {
+ result.addError(new ReviewerResult.Error(
+ ReviewerResult.Error.Type.COULD_NOT_REMOVE,
+ formatUser(who)));
+ }
+ }
+
+ return result;
+ }
+
+ private String formatUser(Account.Id who) {
+ AccountState state = accountCache.get(who);
+ if (state != null) {
+ return formatUser(state.getAccount(), who);
+ } else {
+ return who.toString();
+ }
+ }
+
+ static String formatUser(Account a, Object fallback) {
+ if (a.getFullName() != null && !a.getFullName().isEmpty()) {
+ return a.getFullName();
+ }
+
+ if (a.getPreferredEmail() != null && !a.getPreferredEmail().isEmpty()) {
+ return a.getPreferredEmail();
+ }
+
+ if (a.getUserName() != null && a.getUserName().isEmpty()) {
+ return a.getUserName();
+ }
+
+ return fallback.toString();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
index 1e2e7f4..09a2a91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
@@ -22,8 +22,6 @@
import com.google.gerrit.server.config.GitReceivePackGroupsProvider;
import com.google.gerrit.server.config.GitUploadPackGroups;
import com.google.gerrit.server.config.GitUploadPackGroupsProvider;
-import com.google.gerrit.server.config.ProjectCreatorGroups;
-import com.google.gerrit.server.config.ProjectCreatorGroupsProvider;
import com.google.gerrit.server.config.ProjectOwnerGroups;
import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
import com.google.inject.TypeLiteral;
@@ -33,19 +31,15 @@
public class AccessControlModule extends FactoryModule {
@Override
protected void configure() {
- bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
- .annotatedWith(ProjectCreatorGroups.class) //
- .toProvider(ProjectCreatorGroupsProvider.class).in(SINGLETON);
-
- bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
.annotatedWith(ProjectOwnerGroups.class) //
.toProvider(ProjectOwnerGroupsProvider.class).in(SINGLETON);
- bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
.annotatedWith(GitUploadPackGroups.class) //
.toProvider(GitUploadPackGroupsProvider.class).in(SINGLETON);
- bind(new TypeLiteral<Set<AccountGroup.Id>>() {}) //
+ bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
.annotatedWith(GitReceivePackGroups.class) //
.toProvider(GitReceivePackGroupsProvider.class).in(SINGLETON);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CanSubmitResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CanSubmitResult.java
deleted file mode 100644
index 0c14b80..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CanSubmitResult.java
+++ /dev/null
@@ -1,43 +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.project;
-
-/**
- * Result from {@code ChangeControl.canSubmit()}.
- *
- * @see ChangeControl#canSubmit(com.google.gerrit.reviewdb.PatchSet.Id,
- * com.google.gerrit.reviewdb.ReviewDb,
- * com.google.gerrit.common.data.ApprovalTypes,
- * com.google.gerrit.server.workflow.FunctionState.Factory)
- */
-public class CanSubmitResult {
- /** Magic constant meaning submitting is possible. */
- public static final CanSubmitResult OK = new CanSubmitResult("OK");
-
- private final String errorMessage;
-
- CanSubmitResult(String error) {
- this.errorMessage = error;
- }
-
- public String getMessage() {
- return errorMessage;
- }
-
- @Override
- public String toString() {
- return "CanSubmitResult[" + getMessage() + "]";
- }
-}
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 3291f1a..26a0135 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
@@ -14,29 +14,42 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.common.data.ApprovalTypes;
-import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.workflow.CategoryFunction;
-import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+import com.googlecode.prolog_cafe.lang.VariableTerm;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** Access control management for a user accessing a single change. */
public class ChangeControl {
+ private static final Logger log = LoggerFactory
+ .getLogger(ChangeControl.class);
+
public static class GenericFactory {
private final ProjectControl.GenericFactory projectControl;
@@ -117,10 +130,6 @@
this.change = c;
}
- public ChangeControl forAnonymousUser() {
- return new ChangeControl(getRefControl().forAnonymousUser(), getChange());
- }
-
public ChangeControl forUser(final CurrentUser who) {
return new ChangeControl(getRefControl().forUser(who), getChange());
}
@@ -155,7 +164,7 @@
return isOwner() // owner (aka creator) of the change can abandon
|| getRefControl().isOwner() // branch owner can abandon
|| getProjectControl().isOwner() // project owner can abandon
- || getCurrentUser().isAdministrator() // site administers are god
+ || getCurrentUser().getCapabilities().canAdministrateServer() // site administers are god
;
}
@@ -164,8 +173,14 @@
return canAbandon(); // Anyone who can abandon the change can restore it back
}
- public short normalize(ApprovalCategory.Id category, short score) {
- return getRefControl().normalize(category, score);
+ /** All value ranges of any allowed label permission. */
+ public List<PermissionRange> getLabelRanges() {
+ return getRefControl().getLabelRanges();
+ }
+
+ /** The range of permitted values associated with a label permission. */
+ public PermissionRange getRange(String permission) {
+ return getRefControl().getRange(permission);
}
/** Can this user add a patch set to this change? */
@@ -204,7 +219,7 @@
//
if (getRefControl().isOwner() // branch owner
|| getProjectControl().isOwner() // project owner
- || getCurrentUser().isAdministrator()) {
+ || getCurrentUser().getCapabilities().canAdministrateServer()) {
return true;
}
}
@@ -212,62 +227,175 @@
return false;
}
- /** @return {@link CanSubmitResult#OK}, or a result with an error message. */
- public CanSubmitResult canSubmit(final PatchSet.Id patchSetId) {
+ public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet.Id patchSetId) {
if (change.getStatus().isClosed()) {
- return new CanSubmitResult("Change " + change.getId() + " is closed");
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.CLOSED;
+ return Collections.singletonList(rec);
}
+
if (!patchSetId.equals(change.currentPatchSetId())) {
- return new CanSubmitResult("Patch set " + patchSetId + " is not current");
+ return ruleError("Patch set " + patchSetId + " is not current");
}
- if (!getRefControl().canSubmit()) {
- return new CanSubmitResult("User does not have permission to submit");
+
+ PrologEnvironment env;
+ try {
+ env = getProjectControl().getProjectState().newPrologEnvironment();
+ } catch (CompileException err) {
+ return logRuleError("Cannot consult rules.pl for "
+ + getProject().getName(), err);
}
- if (!(getCurrentUser() instanceof IdentifiedUser)) {
- return new CanSubmitResult("User is not signed-in");
+
+ env.set(StoredValues.REVIEW_DB, db);
+ env.set(StoredValues.CHANGE, change);
+ env.set(StoredValues.PATCH_SET_ID, patchSetId);
+ env.set(StoredValues.CHANGE_CONTROL, this);
+
+ Term submitRule = env.once(
+ "gerrit", "locate_submit_rule",
+ new VariableTerm());
+ if (submitRule == null) {
+ return logRuleError("No user:submit_rule found for "
+ + getProject().getName());
}
- return CanSubmitResult.OK;
+
+ List<Term> results = new ArrayList<Term>();
+ try {
+ for (Term[] template : env.all(
+ "gerrit", "can_submit",
+ submitRule,
+ new VariableTerm())) {
+ results.add(template[1]);
+ }
+ } catch (PrologException err) {
+ return logRuleError("Exception calling " + submitRule + " on change "
+ + change.getId() + " of " + getProject().getName(), err);
+ } catch (RuntimeException err) {
+ return logRuleError("Exception calling " + submitRule + " on change "
+ + change.getId() + " of " + getProject().getName(), err);
+ }
+
+ if (results.isEmpty()) {
+ // This should never occur. A well written submit rule will always produce
+ // at least one result informing the caller of the labels that are
+ // required for this change to be submittable. Each label will indicate
+ // whether or not that is actually possible given the permissions.
+ log.error("Submit rule " + submitRule + " for change " + change.getId()
+ + " of " + getProject().getName() + " has no solution.");
+ return ruleError("Project submit rule has no solution");
+ }
+
+ // Convert the results from Prolog Cafe's format to Gerrit's common format.
+ // can_submit/1 terminates when an ok(P) record is found. Therefore walk
+ // the results backwards, using only that ok(P) record if it exists. This
+ // skips partial results that occur early in the output. Later after the loop
+ // the out collection is reversed to restore it to the original ordering.
+ //
+ List<SubmitRecord> out = new ArrayList<SubmitRecord>(results.size());
+ for (int resultIdx = results.size() - 1; 0 <= resultIdx; resultIdx--) {
+ Term submitRecord = results.get(resultIdx);
+ SubmitRecord rec = new SubmitRecord();
+ out.add(rec);
+
+ if (!submitRecord.isStructure() || 1 != submitRecord.arity()) {
+ return logInvalidResult(submitRule, submitRecord);
+ }
+
+ if ("ok".equals(submitRecord.name())) {
+ rec.status = SubmitRecord.Status.OK;
+
+ } else if ("not_ready".equals(submitRecord.name())) {
+ rec.status = SubmitRecord.Status.NOT_READY;
+
+ } else {
+ return logInvalidResult(submitRule, submitRecord);
+ }
+
+ // Unpack the one argument. This should also be a structure with one
+ // argument per label that needs to be reported on to the caller.
+ //
+ submitRecord = submitRecord.arg(0);
+
+ if (!submitRecord.isStructure()) {
+ return logInvalidResult(submitRule, submitRecord);
+ }
+
+ rec.labels = new ArrayList<SubmitRecord.Label> (submitRecord.arity());
+
+ for (Term state : ((StructureTerm) submitRecord).args()) {
+ if (!state.isStructure() || 2 != state.arity() || !"label".equals(state.name())) {
+ return logInvalidResult(submitRule, submitRecord);
+ }
+
+ SubmitRecord.Label lbl = new SubmitRecord.Label();
+ rec.labels.add(lbl);
+
+ lbl.label = state.arg(0).name();
+ Term status = state.arg(1);
+
+ if ("ok".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.OK;
+ appliedBy(lbl, status);
+
+ } else if ("reject".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.REJECT;
+ appliedBy(lbl, status);
+
+ } else if ("need".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.NEED;
+
+ } else if ("impossible".equals(status.name())) {
+ lbl.status = SubmitRecord.Label.Status.IMPOSSIBLE;
+
+ } else {
+ return logInvalidResult(submitRule, submitRecord);
+ }
+ }
+
+ if (rec.status == SubmitRecord.Status.OK) {
+ break;
+ }
+ }
+ Collections.reverse(out);
+
+ return out;
}
- /** @return {@link CanSubmitResult#OK}, or a result with an error message. */
- public CanSubmitResult canSubmit(final PatchSet.Id patchSetId, final ReviewDb db,
- final ApprovalTypes approvalTypes,
- FunctionState.Factory functionStateFactory)
- throws OrmException {
-
- CanSubmitResult result = canSubmit(patchSetId);
- if (result != CanSubmitResult.OK) {
- return result;
- }
-
- final List<PatchSetApproval> allApprovals =
- new ArrayList<PatchSetApproval>(db.patchSetApprovals().byPatchSet(
- patchSetId).toList());
- final PatchSetApproval myAction =
- ChangeUtil.createSubmitApproval(patchSetId,
- (IdentifiedUser) getCurrentUser(), db);
-
- final ApprovalType actionType =
- approvalTypes.getApprovalType(myAction.getCategoryId());
- if (actionType == null || !actionType.getCategory().isAction()) {
- return new CanSubmitResult("Invalid action " + myAction.getCategoryId());
- }
-
- final FunctionState fs =
- functionStateFactory.create(change, patchSetId, allApprovals);
- for (ApprovalType c : approvalTypes.getApprovalTypes()) {
- CategoryFunction.forCategory(c.getCategory()).run(c, fs);
- }
- if (!CategoryFunction.forCategory(actionType.getCategory()).isValid(
- getCurrentUser(), actionType, fs)) {
- return new CanSubmitResult(actionType.getCategory().getName()
- + " not permitted");
- }
- fs.normalize(actionType, myAction);
- if (myAction.getValue() <= 0) {
- return new CanSubmitResult(actionType.getCategory().getName()
- + " not permitted");
- }
- return CanSubmitResult.OK;
+ private List<SubmitRecord> logInvalidResult(Term rule, Term record) {
+ return logRuleError("Submit rule " + rule + " for change " + change.getId()
+ + " of " + getProject().getName() + " output invalid result: " + record);
}
-}
+
+ private List<SubmitRecord> logRuleError(String err, Exception e) {
+ log.error(err, e);
+ return ruleError("Error evaluating project rules, check server log");
+ }
+
+ private List<SubmitRecord> logRuleError(String err) {
+ log.error(err);
+ return ruleError("Error evaluating project rules, check server log");
+ }
+
+ private List<SubmitRecord> ruleError(String err) {
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.RULE_ERROR;
+ rec.errorMessage = err;
+ return Collections.singletonList(rec);
+ }
+
+ private void appliedBy(SubmitRecord.Label label, Term status) {
+ if (status.isStructure() && status.arity() == 1) {
+ Term who = status.arg(0);
+ if (isUser(who)) {
+ label.appliedBy = new Account.Id(((IntegerTerm) who.arg(0)).intValue());
+ }
+ }
+ }
+
+ private static boolean isUser(Term who) {
+ return who.isStructure()
+ && who.arity() == 1
+ && who.name().equals("user")
+ && who.arg(0).isInteger();
+ }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/InvalidChangeOperationException.java
similarity index 67%
copy from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/project/InvalidChangeOperationException.java
index 0977ee9..e5c6ba5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/InvalidChangeOperationException.java
@@ -12,15 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.schema;
+package com.google.gerrit.server.project;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-public class Schema_49 extends SchemaVersion {
+/** Indicates the change operation is not currently valid. */
+public class InvalidChangeOperationException extends Exception {
+ private static final long serialVersionUID = 1L;
- @Inject
- Schema_49(Provider<Schema_48> prior) {
- super(prior);
+ public InvalidChangeOperationException(String msg) {
+ super(msg);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
index 35b5ee5..cff554c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
@@ -18,6 +18,9 @@
/** Cache of project information, including access rights. */
public interface ProjectCache {
+ /** @return the parent state for all projects on this server. */
+ public ProjectState getAllProjects();
+
/**
* Get the cached data for a project by its unique name.
*
@@ -29,6 +32,17 @@
/** Invalidate the cached information about the given project. */
public void evict(Project p);
- /** Invalidate the cached information about all projects. */
- public void evictAll();
+ /** @return sorted iteration of projects. */
+ public abstract Iterable<Project.NameKey> all();
+
+ /**
+ * Filter the set of registered project names by common prefix.
+ *
+ * @param prefix common prefix.
+ * @return sorted iteration of projects sharing the same prefix.
+ */
+ public abstract Iterable<Project.NameKey> byName(String prefix);
+
+ /** Notify the cache that a new project was constructed. */
+ public void onCreateProject(Project.NameKey newProjectName);
}
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 48eef87..ff68720 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
@@ -15,45 +15,106 @@
package com.google.gerrit.server.project;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.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.client.SchemaFactory;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ProjectConfig;
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 java.util.Collection;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+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 String CACHE_NAME = "projects";
+ private static final String CACHE_LIST = "project_list";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
+ final TypeLiteral<Cache<Project.NameKey, ProjectState>> nameType =
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
- core(type, CACHE_NAME).populateWith(Loader.class);
+ core(nameType, CACHE_NAME).populateWith(Loader.class);
+
+ final TypeLiteral<Cache<ListKey, SortedSet<Project.NameKey>>> listType =
+ new TypeLiteral<Cache<ListKey, SortedSet<Project.NameKey>>>() {};
+ core(listType, CACHE_LIST).populateWith(Lister.class);
+
bind(ProjectCacheImpl.class);
bind(ProjectCache.class).to(ProjectCacheImpl.class);
}
};
}
+ private final AllProjectsName allProjectsName;
private final Cache<Project.NameKey, ProjectState> byName;
+ private final Cache<ListKey,SortedSet<Project.NameKey>> list;
+ private final Lock listLock;
+ private volatile long generation;
@Inject
ProjectCacheImpl(
- @Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName) {
+ final AllProjectsName allProjectsName,
+ @Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName,
+ @Named(CACHE_LIST) final Cache<ListKey, SortedSet<Project.NameKey>> list,
+ @GerritServerConfig final Config serverConfig) {
+ this.allProjectsName = allProjectsName;
this.byName = byName;
+ this.list = list;
+ this.listLock = new ReentrantLock(true /* fair */);
+
+ long checkFrequencyMillis = TimeUnit.MILLISECONDS.convert(
+ ConfigUtil.getTimeUnit(serverConfig,
+ "cache", "projects", "checkFrequency",
+ 5, TimeUnit.MINUTES), TimeUnit.MINUTES);
+ if (10 < checkFrequencyMillis) {
+ // Start with generation 1 (to avoid magic 0 below).
+ generation = 1;
+ Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ // This is not exactly thread-safe, but is OK for our use.
+ // The only thread that writes the volatile is this task.
+ generation = generation + 1;
+ }
+ }, checkFrequencyMillis, checkFrequencyMillis, TimeUnit.MILLISECONDS);
+ } else {
+ // Magic generation 0 triggers ProjectState to always
+ // check on each needsRefresh() request we make to it.
+ generation = 0;
+ }
+ }
+
+ @Override
+ public ProjectState getAllProjects() {
+ ProjectState state = get(allProjectsName);
+ if (state == null) {
+ // This should never occur, the server must have this
+ // project to process anything.
+ throw new IllegalStateException("Missing project " + allProjectsName);
+ }
+ return state;
}
/**
@@ -63,7 +124,12 @@
* @return the cached data; null if no such project exists.
*/
public ProjectState get(final Project.NameKey projectName) {
- return byName.get(projectName);
+ ProjectState state = byName.get(projectName);
+ if (state != null && state.needsRefresh(generation)) {
+ byName.remove(projectName);
+ state = byName.get(projectName);
+ }
+ return state;
}
/** Invalidate the cached information about the given project. */
@@ -73,38 +139,120 @@
}
}
- /** Invalidate the cached information about all projects. */
- public void evictAll() {
- byName.removeAll();
+ @Override
+ public void onCreateProject(Project.NameKey newProjectName) {
+ listLock.lock();
+ try {
+ SortedSet<Project.NameKey> n = list.get(ListKey.ALL);
+ n = new TreeSet<Project.NameKey>(n);
+ n.add(newProjectName);
+ list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
+ } finally {
+ listLock.unlock();
+ }
+ }
+
+ @Override
+ public Iterable<Project.NameKey> all() {
+ return list.get(ListKey.ALL);
+ }
+
+ @Override
+ public Iterable<Project.NameKey> byName(final String pfx) {
+ return new Iterable<Project.NameKey>() {
+ @Override
+ public Iterator<Project.NameKey> iterator() {
+ return new Iterator<Project.NameKey>() {
+ private Project.NameKey next;
+ private Iterator<Project.NameKey> itr =
+ list.get(ListKey.ALL).tailSet(new Project.NameKey(pfx)).iterator();
+
+ @Override
+ public boolean hasNext() {
+ if (next != null) {
+ return true;
+ }
+
+ if (!itr.hasNext()) {
+ return false;
+ }
+
+ Project.NameKey r = itr.next();
+ if (r.get().startsWith(pfx)) {
+ next = r;
+ return true;
+ } else {
+ itr = Collections.<Project.NameKey> emptyList().iterator();
+ return false;
+ }
+ }
+
+ @Override
+ public Project.NameKey next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ Project.NameKey r = next;
+ next = null;
+ return r;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
}
static class Loader extends EntryCreator<Project.NameKey, ProjectState> {
private final ProjectState.Factory projectStateFactory;
- private final SchemaFactory<ReviewDb> schema;
+ private final GitRepositoryManager mgr;
@Inject
- Loader(ProjectState.Factory psf, SchemaFactory<ReviewDb> sf) {
+ Loader(ProjectState.Factory psf, GitRepositoryManager g) {
projectStateFactory = psf;
- schema = sf;
+ mgr = g;
}
@Override
public ProjectState createEntry(Project.NameKey key) throws Exception {
- final ReviewDb db = schema.open();
try {
- final Project p = db.projects().get(key);
- if (p == null) {
- return null;
+ Repository git = mgr.openRepository(key);
+ try {
+ final ProjectConfig cfg = new ProjectConfig(key);
+ cfg.load(git);
+ return projectStateFactory.create(cfg);
+ } finally {
+ git.close();
}
- final Collection<RefRight> rights =
- Collections.unmodifiableCollection(db.refRights().byProject(
- p.getNameKey()).toList());
-
- return projectStateFactory.create(p, rights);
- } finally {
- db.close();
+ } catch (RepositoryNotFoundException notFound) {
+ return null;
}
}
}
+
+ static class ListKey {
+ static final ListKey ALL = new ListKey();
+
+ private ListKey() {
+ }
+ }
+
+ static class Lister extends EntryCreator<ListKey, SortedSet<Project.NameKey>> {
+ private final GitRepositoryManager mgr;
+
+ @Inject
+ Lister(GitRepositoryManager mgr) {
+ this.mgr = mgr;
+ }
+
+ @Override
+ public SortedSet<Project.NameKey> createEntry(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 2a55019..e9247af 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
@@ -14,28 +14,51 @@
package com.google.gerrit.server.project;
-import static com.google.gerrit.common.CollectionsUtil.*;
+import static com.google.gerrit.common.CollectionsUtil.isAnyIncludedIn;
+
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.reviewdb.AbstractAgreement;
+import com.google.gerrit.reviewdb.AccountAgreement;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.AccountGroupAgreement;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ReplicationUser;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
+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 javax.annotation.Nullable;
+
/** Access control management for a user accessing a project's data. */
public class ProjectControl {
+ private static final Logger log =
+ LoggerFactory.getLogger(ProjectControl.class);
+
public static final int VISIBLE = 1 << 0;
public static final int OWNER = 1 << 1;
@@ -103,32 +126,36 @@
ProjectControl create(CurrentUser who, ProjectState ps);
}
- private final SystemConfig systemConfig;
- private final Set<AccountGroup.Id> uploadGroups;
- private final Set<AccountGroup.Id> receiveGroups;
+ private final Set<AccountGroup.UUID> uploadGroups;
+ private final Set<AccountGroup.UUID> receiveGroups;
+ private final String canonicalWebUrl;
private final RefControl.Factory refControlFactory;
+ private final SchemaFactory<ReviewDb> schema;
private final CurrentUser user;
private final ProjectState state;
+ private final GroupCache groupCache;
+
+
+ private Collection<AccessSection> access;
@Inject
- ProjectControl(final SystemConfig systemConfig,
- @GitUploadPackGroups Set<AccountGroup.Id> uploadGroups,
- @GitReceivePackGroups Set<AccountGroup.Id> receiveGroups,
+ ProjectControl(@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
+ @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
+ final SchemaFactory<ReviewDb> schema, final GroupCache groupCache,
+ @CanonicalWebUrl @Nullable final String canonicalWebUrl,
final RefControl.Factory refControlFactory,
@Assisted CurrentUser who, @Assisted ProjectState ps) {
- this.systemConfig = systemConfig;
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
+ this.schema = schema;
+ this.groupCache = groupCache;
+ this.canonicalWebUrl = canonicalWebUrl;
this.refControlFactory = refControlFactory;
user = who;
state = ps;
}
- public ProjectControl forAnonymousUser() {
- return state.controlForAnonymousUser();
- }
-
public ProjectControl forUser(final CurrentUser who) {
return state.controlFor(who);
}
@@ -160,18 +187,18 @@
/** Can this user see this project exists? */
public boolean isVisible() {
return visibleForReplication()
- || canPerformOnAnyRef(ApprovalCategory.READ, (short) 1);
+ || canPerformOnAnyRef(Permission.READ);
}
public boolean canAddRefs() {
- return (canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, ApprovalCategory.PUSH_HEAD_CREATE)
+ return (canPerformOnAnyRef(Permission.CREATE)
|| isOwnerAnyRef());
}
/** Can this user see all the refs in this projects? */
public boolean allRefsAreVisible() {
return visibleForReplication()
- || canPerformOnAllRefs(ApprovalCategory.READ, (short) 1);
+ || canPerformOnAllRefs(Permission.READ);
}
/** Is this project completely visible for replication? */
@@ -182,65 +209,221 @@
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
public boolean isOwner() {
- return controlForRef(RefRight.ALL).isOwner()
- || getCurrentUser().isAdministrator();
+ return controlForRef(AccessSection.ALL).isOwner()
+ || getCurrentUser().getCapabilities().canAdministrateServer();
}
/** Does this user have ownership on at least one reference name? */
public boolean isOwnerAnyRef() {
- return canPerformOnAnyRef(ApprovalCategory.OWN, (short) 1)
- || getCurrentUser().isAdministrator();
+ return canPerformOnAnyRef(Permission.OWNER)
+ || getCurrentUser().getCapabilities().canAdministrateServer();
}
/** @return true if the user can upload to at least one reference */
- public boolean canPushToAtLeastOneRef() {
- return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2)
- || canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, (short) 1)
- || canPerformOnAnyRef(ApprovalCategory.PUSH_TAG, (short) 1);
+ public Capable canPushToAtLeastOneRef() {
+ if (! canPerformOnAnyRef(Permission.PUSH) &&
+ ! canPerformOnAnyRef(Permission.PUSH_TAG)) {
+ String pName = state.getProject().getName();
+ return new Capable("Upload denied for project '" + pName + "'");
+ }
+ Project project = state.getProject();
+ if (project.isUseContributorAgreements()) {
+ try {
+ return verifyActiveContributorAgreement();
+ } catch (OrmException e) {
+ log.error("Cannot query database for agreements", e);
+ return new Capable("Cannot verify contribution agreement");
+ }
+ }
+ return Capable.OK;
}
- // TODO (anatol.pomazau): Try to merge this method with similar RefRightsForPattern#canPerform
- private boolean canPerformOnAnyRef(ApprovalCategory.Id actionId,
- short requireValue) {
- final Set<AccountGroup.Id> groups = getEffectiveUserGroups();
+ private Capable verifyActiveContributorAgreement() throws OrmException {
+ if (! (user instanceof IdentifiedUser)) {
+ return new Capable("Must be logged in to verify Contributor Agreement");
+ }
+ final IdentifiedUser iUser = (IdentifiedUser) user;
+ final ReviewDb db = schema.open();
- for (final RefRight pr : state.getAllRights(actionId, true)) {
- if (groups.contains(pr.getAccountGroupId())
- && pr.getMaxValue() >= requireValue) {
- return true;
+ AbstractAgreement bestAgreement = null;
+ ContributorAgreement bestCla = null;
+ try {
+
+ OUTER: for (AccountGroup.UUID groupUUID : iUser.getEffectiveGroups()) {
+ AccountGroup group = groupCache.get(groupUUID);
+ if (group == null) {
+ continue;
+ }
+
+ final List<AccountGroupAgreement> temp =
+ db.accountGroupAgreements().byGroup(group.getId()).toList();
+
+ Collections.reverse(temp);
+
+ for (final AccountGroupAgreement a : temp) {
+ final ContributorAgreement cla =
+ db.contributorAgreements().get(a.getAgreementId());
+ if (cla == null) {
+ continue;
+ }
+
+ bestAgreement = a;
+ bestCla = cla;
+ break OUTER;
+ }
+ }
+
+ if (bestAgreement == null) {
+ final List<AccountAgreement> temp =
+ db.accountAgreements().byAccount(iUser.getAccountId()).toList();
+
+ Collections.reverse(temp);
+
+ for (final AccountAgreement a : temp) {
+ final ContributorAgreement cla =
+ db.contributorAgreements().get(a.getAgreementId());
+ if (cla == null) {
+ continue;
+ }
+
+ bestAgreement = a;
+ bestCla = cla;
+ break;
+ }
+ }
+ } finally {
+ db.close();
+ }
+
+
+ if (bestCla != null && !bestCla.isActive()) {
+ final StringBuilder msg = new StringBuilder();
+ msg.append(bestCla.getShortName());
+ msg.append(" contributor agreement is expired.\n");
+ if (canonicalWebUrl != null) {
+ msg.append("\nPlease complete a new agreement");
+ msg.append(":\n\n ");
+ msg.append(canonicalWebUrl);
+ msg.append("#");
+ msg.append(PageLinks.SETTINGS_AGREEMENTS);
+ msg.append("\n");
+ }
+ msg.append("\n");
+ return new Capable(msg.toString());
+ }
+
+ if (bestCla != null && bestCla.isRequireContactInformation()) {
+ boolean fail = false;
+ fail |= missing(iUser.getAccount().getFullName());
+ fail |= missing(iUser.getAccount().getPreferredEmail());
+ fail |= !iUser.getAccount().isContactFiled();
+
+ if (fail) {
+ final StringBuilder msg = new StringBuilder();
+ msg.append(bestCla.getShortName());
+ msg.append(" contributor agreement requires");
+ msg.append(" current contact information.\n");
+ if (canonicalWebUrl != null) {
+ msg.append("\nPlease review your contact information");
+ msg.append(":\n\n ");
+ msg.append(canonicalWebUrl);
+ msg.append("#");
+ msg.append(PageLinks.SETTINGS_CONTACT);
+ msg.append("\n");
+ }
+ msg.append("\n");
+ return new Capable(msg.toString());
}
}
- return false;
+ if (bestAgreement != null) {
+ switch (bestAgreement.getStatus()) {
+ case VERIFIED:
+ return Capable.OK;
+ case REJECTED:
+ return new Capable(bestCla.getShortName()
+ + " contributor agreement was rejected."
+ + "\n (rejected on " + bestAgreement.getReviewedOn()
+ + ")\n");
+ case NEW:
+ return new Capable(bestCla.getShortName()
+ + " contributor agreement is still pending review.\n");
+ }
+ }
+
+ final StringBuilder msg = new StringBuilder();
+ msg.append(" A Contributor Agreement must be completed before uploading");
+ if (canonicalWebUrl != null) {
+ msg.append(":\n\n ");
+ msg.append(canonicalWebUrl);
+ msg.append("#");
+ msg.append(PageLinks.SETTINGS_AGREEMENTS);
+ msg.append("\n");
+ } else {
+ msg.append(".");
+ }
+ msg.append("\n");
+ return new Capable(msg.toString());
+ }
+
+ private static boolean missing(final String value) {
+ return value == null || value.trim().equals("");
}
/**
* @return the effective groups of the current user for this project
*/
- private Set<AccountGroup.Id> getEffectiveUserGroups() {
- final Set<AccountGroup.Id> userGroups = user.getEffectiveGroups();
+ private Set<AccountGroup.UUID> getEffectiveUserGroups() {
+ final Set<AccountGroup.UUID> userGroups = user.getEffectiveGroups();
if (isOwner()) {
- final Set<AccountGroup.Id> userGroupsOnProject =
- new HashSet<AccountGroup.Id>(userGroups.size() + 1);
+ final Set<AccountGroup.UUID> userGroupsOnProject =
+ new HashSet<AccountGroup.UUID>(userGroups.size() + 1);
userGroupsOnProject.addAll(userGroups);
- userGroupsOnProject.add(systemConfig.ownerGroupId);
+ userGroupsOnProject.add(AccountGroup.PROJECT_OWNERS);
return Collections.unmodifiableSet(userGroupsOnProject);
} else {
return userGroups;
}
}
- private boolean canPerformOnAllRefs(ApprovalCategory.Id actionId,
- short requireValue) {
+ private boolean canPerformOnAnyRef(String permissionName) {
+ final Set<AccountGroup.UUID> groups = getEffectiveUserGroups();
+
+ for (AccessSection section : access()) {
+ Permission permission = section.getPermission(permissionName);
+ if (permission == null) {
+ continue;
+ }
+
+ for (PermissionRule rule : permission.getRules()) {
+ if (rule.getDeny()) {
+ continue;
+ }
+
+ // Being in a group that was granted this permission is only an
+ // approximation. There might be overrides and doNotInherit
+ // that would render this to be false.
+ //
+ if (groups.contains(rule.getGroup().getUUID())
+ && controlForRef(section.getName()).canPerform(permissionName)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean canPerformOnAllRefs(String permission) {
boolean canPerform = false;
- final Set<String> patterns = allRefPatterns(actionId);
- if (patterns.contains(RefRight.ALL)) {
+ Set<String> patterns = allRefPatterns(permission);
+ if (patterns.contains(AccessSection.ALL)) {
// Only possible if granted on the pattern that
// matches every possible reference. Check all
// patterns also have the permission.
//
for (final String pattern : patterns) {
- if (controlForRef(pattern).canPerform(actionId, requireValue)) {
+ if (controlForRef(pattern).canPerform(permission)) {
canPerform = true;
} else {
return false;
@@ -250,14 +433,24 @@
return canPerform;
}
- private Set<String> allRefPatterns(ApprovalCategory.Id actionId) {
- final Set<String> all = new HashSet<String>();
- for (final RefRight pr : state.getAllRights(actionId, true)) {
- all.add(pr.getRefPattern());
+ private Set<String> allRefPatterns(String permissionName) {
+ Set<String> all = new HashSet<String>();
+ for (AccessSection section : access()) {
+ Permission permission = section.getPermission(permissionName);
+ if (permission != null) {
+ all.add(section.getName());
+ }
}
return all;
}
+ Collection<AccessSection> access() {
+ if (access == null) {
+ access = state.getAllAccessSections();
+ }
+ return access;
+ }
+
public boolean canRunUploadPack() {
return isAnyIncludedIn(uploadGroups, getEffectiveUserGroups());
}
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 0b8e83a..376d1a7 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,200 +14,178 @@
package com.google.gerrit.server.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.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ProjectConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/** Cached information on a project. */
public class ProjectState {
public interface Factory {
- ProjectState create(Project project, Collection<RefRight> localRights);
+ ProjectState create(ProjectConfig config);
}
- private final AnonymousUser anonymousUser;
- private final Project.NameKey wildProject;
+ private final boolean isAllProjects;
private final ProjectCache projectCache;
private final ProjectControl.AssistedFactory projectControlFactory;
+ private final PrologEnvironment.Factory envFactory;
+ private final GitRepositoryManager gitMgr;
+ private final RulesCache rulesCache;
- private final Project project;
- private final Collection<RefRight> localRights;
- private final Set<AccountGroup.Id> localOwners;
+ private final ProjectConfig config;
+ private final Set<AccountGroup.UUID> localOwners;
- private volatile Collection<RefRight> inheritedRights;
+ /** Prolog rule state. */
+ private volatile PrologMachineCopy rulesMachine;
+
+ /** Last system time the configuration's revision was examined. */
+ private volatile long lastCheckTime;
+
@Inject
- protected ProjectState(final AnonymousUser anonymousUser,
+ protected ProjectState(
final ProjectCache projectCache,
- @WildProjectName final Project.NameKey wildProject,
+ final AllProjectsName allProjectsName,
final ProjectControl.AssistedFactory projectControlFactory,
- @Assisted final Project project,
- @Assisted Collection<RefRight> rights) {
- this.anonymousUser = anonymousUser;
+ final PrologEnvironment.Factory envFactory,
+ final GitRepositoryManager gitMgr,
+ final RulesCache rulesCache,
+ @Assisted final ProjectConfig config) {
this.projectCache = projectCache;
- this.wildProject = wildProject;
+ this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName);
this.projectControlFactory = projectControlFactory;
+ this.envFactory = envFactory;
+ this.gitMgr = gitMgr;
+ this.rulesCache = rulesCache;
+ this.config = config;
- if (wildProject.equals(project.getNameKey())) {
- rights = new ArrayList<RefRight>(rights);
- for (Iterator<RefRight> itr = rights.iterator(); itr.hasNext();) {
- if (!itr.next().getApprovalCategoryId().canBeOnWildProject()) {
- itr.remove();
+ 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());
+ }
}
}
- rights = Collections.unmodifiableCollection(rights);
- }
-
- this.project = project;
- this.localRights = rights;
-
- final HashSet<AccountGroup.Id> groups = new HashSet<AccountGroup.Id>();
- for (final RefRight right : rights) {
- if (ApprovalCategory.OWN.equals(right.getApprovalCategoryId())
- && right.getMaxValue() > 0
- && right.getRefPattern().equals(RefRight.ALL)) {
- groups.add(right.getAccountGroupId());
- }
}
localOwners = Collections.unmodifiableSet(groups);
}
+ boolean needsRefresh(long generation) {
+ if (generation <= 0) {
+ return isRevisionOutOfDate();
+ }
+ if (lastCheckTime != generation) {
+ lastCheckTime = generation;
+ return isRevisionOutOfDate();
+ }
+ return false;
+ }
+
+ private boolean isRevisionOutOfDate() {
+ try {
+ Repository git = gitMgr.openRepository(getProject().getNameKey());
+ try {
+ Ref ref = git.getRef(GitRepositoryManager.REF_CONFIG);
+ if (ref == null || ref.getObjectId() == null) {
+ return true;
+ }
+ return !ref.getObjectId().equals(config.getRevision());
+ } finally {
+ git.close();
+ }
+ } catch (IOException gone) {
+ return true;
+ }
+ }
+
+ /** @return Construct a new PrologEnvironment for the calling thread. */
+ public PrologEnvironment newPrologEnvironment() throws CompileException {
+ PrologMachineCopy pmc = rulesMachine;
+ if (pmc == null) {
+ pmc = rulesCache.loadMachine(
+ getProject().getNameKey(),
+ getConfig().getRulesId());
+ rulesMachine = pmc;
+ }
+ return envFactory.create(pmc);
+ }
+
public Project getProject() {
- return project;
+ return getConfig().getProject();
+ }
+
+ public ProjectConfig getConfig() {
+ return config;
}
/** Get the rights that pertain only to this project. */
- public Collection<RefRight> getLocalRights() {
- return localRights;
+ public Collection<AccessSection> getLocalAccessSections() {
+ return getConfig().getAccessSections();
}
- /**
- * Get the rights that pertain only to this project.
- *
- * @param action the category requested.
- * @return immutable collection of rights for the requested category.
- */
- public Collection<RefRight> getLocalRights(ApprovalCategory.Id action) {
- return filter(getLocalRights(), action);
- }
-
- /** Get the rights this project inherits from the wild project. */
- public Collection<RefRight> getInheritedRights() {
- if (inheritedRights == null) {
- inheritedRights = computeInheritedRights();
- }
- return inheritedRights;
- }
-
- void setInheritedRights(Collection<RefRight> all) {
- inheritedRights = all;
- }
-
- private Collection<RefRight> computeInheritedRights() {
- if (isSpecialWildProject()) {
+ /** Get the rights this project inherits. */
+ public Collection<AccessSection> getInheritedAccessSections() {
+ if (isAllProjects) {
return Collections.emptyList();
}
- List<RefRight> inherited = new ArrayList<RefRight>();
+ List<AccessSection> inherited = new ArrayList<AccessSection>();
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
- Project.NameKey parent = project.getParent();
+ Project.NameKey parent = getProject().getParent();
while (parent != null && seen.add(parent)) {
ProjectState s = projectCache.get(parent);
if (s != null) {
- inherited.addAll(s.getLocalRights());
+ inherited.addAll(s.getLocalAccessSections());
parent = s.getProject().getParent();
} else {
break;
}
}
- // Wild project is the parent, or the root of the tree
+ // The root of the tree is the special "All-Projects" case.
if (parent == null) {
- inherited.addAll(getWildProjectRights());
+ inherited.addAll(projectCache.getAllProjects().getLocalAccessSections());
}
- return Collections.unmodifiableCollection(inherited);
+ return inherited;
}
- private Collection<RefRight> getWildProjectRights() {
- final ProjectState s = projectCache.get(wildProject);
- return s != null ? s.getLocalRights() : Collections.<RefRight> emptyList();
- }
-
- /**
- * Utility class that is needed to filter overridden refrights
- */
- private static class Grant {
- final AccountGroup.Id group;
- final String pattern;
-
- private Grant(AccountGroup.Id group, String pattern) {
- this.group = group;
- this.pattern = pattern;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null)
- return false;
- Grant grant = (Grant) o;
- return group.equals(grant.group) && pattern.equals(grant.pattern);
- }
-
- @Override
- public int hashCode() {
- int result = group.hashCode();
- result = 31 * result + pattern.hashCode();
- return result;
- }
- }
-
- /**
- * Get the rights this project has and inherits from the wild project.
- *
- * @param action the category requested.
- * @param dropOverridden whether to remove inherited permissions in case if we have a
- * local one that matches (action,group,ref)
- * @return immutable collection of rights for the requested category.
- */
- public Collection<RefRight> getAllRights(ApprovalCategory.Id action, boolean dropOverridden) {
- Collection<RefRight> rights = new LinkedList<RefRight>(getLocalRights(action));
- rights.addAll(filter(getInheritedRights(), action));
- if (dropOverridden) {
- Set<Grant> grants = new HashSet<Grant>();
- Iterator<RefRight> iter = rights.iterator();
- while (iter.hasNext()) {
- RefRight right = iter.next();
-
- Grant grant = new Grant(right.getAccountGroupId(), right.getRefPattern());
- if (grants.contains(grant)) {
- iter.remove();
- } else {
- grants.add(grant);
- }
- }
- }
- return Collections.unmodifiableCollection(rights);
- }
-
- /** Is this the special wild project which manages inherited rights? */
- public boolean isSpecialWildProject() {
- return project.getNameKey().equals(wildProject);
+ /** Get both local and inherited access sections. */
+ public Collection<AccessSection> getAllAccessSections() {
+ List<AccessSection> all = new ArrayList<AccessSection>();
+ all.addAll(getLocalAccessSections());
+ all.addAll(getInheritedAccessSections());
+ return all;
}
/**
@@ -216,13 +194,13 @@
* are no local owners the local owners of the nearest parent project
* that has local owners are returned
*/
- public Set<AccountGroup.Id> getOwners() {
- if (!localOwners.isEmpty() || isSpecialWildProject()
- || project.getParent() == null) {
+ public Set<AccountGroup.UUID> getOwners() {
+ Project.NameKey parentName = getProject().getParent();
+ if (!localOwners.isEmpty() || parentName == null || isAllProjects) {
return localOwners;
}
- final ProjectState parent = projectCache.get(project.getParent());
+ ProjectState parent = projectCache.get(parentName);
if (parent != null) {
return parent.getOwners();
}
@@ -237,38 +215,27 @@
* owners) and all groups to which the owner privilege for 'refs/*' is
* assigned for one of the parent projects (the inherited owners).
*/
- public Set<AccountGroup.Id> getAllOwners() {
- final HashSet<AccountGroup.Id> owners = new HashSet<AccountGroup.Id>();
- for (final RefRight right : getAllRights(ApprovalCategory.OWN, true)) {
- if (right.getMaxValue() > 0 && right.getRefPattern().equals(RefRight.ALL)) {
- owners.add(right.getAccountGroupId());
+ public Set<AccountGroup.UUID> getAllOwners() {
+ HashSet<AccountGroup.UUID> owners = new HashSet<AccountGroup.UUID>();
+ owners.addAll(localOwners);
+
+ Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
+ Project.NameKey parent = getProject().getParent();
+
+ while (parent != null && seen.add(parent)) {
+ ProjectState s = projectCache.get(parent);
+ if (s != null) {
+ owners.addAll(s.localOwners);
+ parent = s.getProject().getParent();
+ } else {
+ break;
}
}
- return Collections.unmodifiableSet(owners);
- }
- public ProjectControl controlForAnonymousUser() {
- return controlFor(anonymousUser);
+ return Collections.unmodifiableSet(owners);
}
public ProjectControl controlFor(final CurrentUser user) {
return projectControlFactory.create(user, this);
}
-
- private static Collection<RefRight> filter(Collection<RefRight> all,
- ApprovalCategory.Id actionId) {
- if (all.isEmpty()) {
- return Collections.emptyList();
- }
- final Collection<RefRight> mine = new ArrayList<RefRight>(all.size());
- for (final RefRight right : all) {
- if (right.getApprovalCategoryId().equals(actionId)) {
- mine.add(right);
- }
- }
- if (mine.isEmpty()) {
- return Collections.emptyList();
- }
- return Collections.unmodifiableCollection(mine);
- }
}
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 8ddf585..e6b39c9 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
@@ -14,27 +14,16 @@
package com.google.gerrit.server.project;
-import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_AUTHOR;
-import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_COMMITTER;
-import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_IDENTITY;
-import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_SERVER;
-import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_UPDATE;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANNOTATED;
-import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_SIGNED;
-import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
-
+import com.google.gerrit.common.CollectionsUtil;
+import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ParamertizedString;
+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.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -49,7 +38,6 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -57,8 +45,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
import java.util.regex.Pattern;
@@ -68,18 +54,18 @@
RefControl create(ProjectControl projectControl, String ref);
}
- private final SystemConfig systemConfig;
private final ProjectControl projectControl;
private final String refName;
+ private Map<String, List<PermissionRule>> permissions;
+
+ private Boolean owner;
private Boolean canForgeAuthor;
private Boolean canForgeCommitter;
@Inject
- protected RefControl(final SystemConfig systemConfig,
- @Assisted final ProjectControl projectControl,
+ protected RefControl(@Assisted final ProjectControl projectControl,
@Assisted String ref) {
- this.systemConfig = systemConfig;
if (isRE(ref)) {
ref = shortestExample(ref);
@@ -104,36 +90,35 @@
return getProjectControl().getCurrentUser();
}
- public RefControl forAnonymousUser() {
- return getProjectControl().forAnonymousUser().controlForRef(getRefName());
- }
-
public RefControl forUser(final CurrentUser who) {
return getProjectControl().forUser(who).controlForRef(getRefName());
}
/** Is this user a ref owner? */
public boolean isOwner() {
- if (canPerform(OWN, (short) 1)) {
- return true;
- }
+ if (owner == null) {
+ if (canPerform(Permission.OWNER)) {
+ owner = true;
- // We have to prevent infinite recursion here, the project control
- // calls us to find out if there is ownership of all references in
- // order to determine project level ownership.
- //
- if (getRefName().equals(
- RefRight.ALL.substring(0, RefRight.ALL.length() - 1))) {
- return getCurrentUser().isAdministrator();
- } else {
- return getProjectControl().isOwner();
+ } else if (getRefName().equals(
+ AccessSection.ALL.substring(0, AccessSection.ALL.length() - 1))) {
+ // We have to prevent infinite recursion here, the project control
+ // calls us to find out if there is ownership of all references in
+ // order to determine project level ownership.
+ //
+ owner = getCurrentUser().getCapabilities().canAdministrateServer();
+
+ } else {
+ owner = getProjectControl().isOwner();
+ }
}
+ return owner;
}
/** Can this user see this reference exists? */
public boolean isVisible() {
return getProjectControl().visibleForReplication()
- || canPerform(READ, (short) 1);
+ || canPerform(Permission.READ);
}
/**
@@ -144,27 +129,66 @@
* ref
*/
public boolean canUpload() {
- return canPerform(READ, (short) 2);
+ return getProjectControl()
+ .controlForRef("refs/for/" + getRefName())
+ .canPerform(Permission.PUSH);
}
/** @return true if this user can submit merge patch sets to this ref */
public boolean canUploadMerges() {
- return canPerform(READ, (short) 3);
+ return getProjectControl()
+ .controlForRef("refs/for/" + getRefName())
+ .canPerform(Permission.PUSH_MERGE);
}
/** @return true if this user can submit patch sets to this ref */
public boolean canSubmit() {
- return canPerform(ApprovalCategory.SUBMIT, (short) 1);
+ if (GitRepositoryManager.REF_CONFIG.equals(refName)) {
+ // Always allow project owners to submit configuration changes.
+ // Submitting configuration changes modifies the access control
+ // 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 submitting to the configuration.
+ return getProjectControl().isOwner();
+ }
+ return canPerform(Permission.SUBMIT);
}
/** @return true if the user can update the reference as a fast-forward. */
public boolean canUpdate() {
- return canPerform(PUSH_HEAD, PUSH_HEAD_UPDATE);
+ if (GitRepositoryManager.REF_CONFIG.equals(refName)
+ && !getProjectControl().isOwner()) {
+ // Pushing requires being at least project owner, in addition to push.
+ // Pushing configuration changes modifies the access control
+ // 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;
+ }
+ return canPerform(Permission.PUSH);
}
/** @return true if the user can rewind (force push) the reference. */
public boolean canForceUpdate() {
- return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE) || canDelete();
+ return canPushWithForce() || canDelete();
+ }
+
+ private boolean canPushWithForce() {
+ if (GitRepositoryManager.REF_CONFIG.equals(refName)
+ && !getProjectControl().isOwner()) {
+ // Pushing requires being at least project owner, in addition to push.
+ // Pushing configuration changes modifies the access control
+ // 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;
+ }
+ for (PermissionRule rule : access(Permission.PUSH)) {
+ if (rule.getForce()) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -186,7 +210,7 @@
}
if (object instanceof RevCommit) {
- return owner || canPerform(PUSH_HEAD, PUSH_HEAD_CREATE);
+ return owner || canPerform(Permission.CREATE);
} else if (object instanceof RevTag) {
final RevTag tag = (RevTag) object;
@@ -208,7 +232,7 @@
} else {
valid = false;
}
- if (!valid && !owner && !canPerform(FORGE_IDENTITY, FORGE_COMMITTER)) {
+ if (!valid && !owner && !canForgeCommitter()) {
return false;
}
}
@@ -217,9 +241,9 @@
// than if it doesn't have a PGP signature.
//
if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
- return owner || canPerform(PUSH_TAG, PUSH_TAG_SIGNED);
+ return owner || canPerform(Permission.PUSH_TAG);
} else {
- return owner || canPerform(PUSH_TAG, PUSH_TAG_ANNOTATED);
+ return owner || canPerform(Permission.PUSH_TAG);
}
} else {
@@ -234,12 +258,21 @@
* @return {@code true} if the user specified can delete a Git ref.
*/
public boolean canDelete() {
+ if (GitRepositoryManager.REF_CONFIG.equals(refName)) {
+ // Never allow removal of the refs/meta/config branch.
+ // Deleting the branch would destroy all Gerrit specific
+ // metadata about the project, including its access rules.
+ // If a project is to be removed from Gerrit, its repository
+ // should be removed first.
+ return false;
+ }
+
switch (getCurrentUser().getAccessPath()) {
case WEB_UI:
- return isOwner() || canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE);
+ return isOwner() || canPushWithForce();
case GIT:
- return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE);
+ return canPushWithForce();
default:
return false;
@@ -249,7 +282,7 @@
/** @return true if this user can forge the author line in a commit. */
public boolean canForgeAuthor() {
if (canForgeAuthor == null) {
- canForgeAuthor = canPerform(FORGE_IDENTITY, FORGE_AUTHOR);
+ canForgeAuthor = canPerform(Permission.FORGE_AUTHOR);
}
return canForgeAuthor;
}
@@ -257,314 +290,103 @@
/** @return true if this user can forge the committer line in a commit. */
public boolean canForgeCommitter() {
if (canForgeCommitter == null) {
- canForgeCommitter = canPerform(FORGE_IDENTITY, FORGE_COMMITTER);
+ canForgeCommitter = canPerform(Permission.FORGE_COMMITTER);
}
return canForgeCommitter;
}
/** @return true if this user can forge the server on the committer line. */
public boolean canForgeGerritServerIdentity() {
- return canPerform(FORGE_IDENTITY, FORGE_SERVER);
+ return canPerform(Permission.FORGE_SERVER);
}
- public short normalize(ApprovalCategory.Id category, short score) {
- short minAllowed = 0, maxAllowed = 0;
- for (RefRight r : getApplicableRights(category)) {
- if (getCurrentUser().getEffectiveGroups().contains(r.getAccountGroupId())) {
- minAllowed = (short) Math.min(minAllowed, r.getMinValue());
- maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue());
+ /** All value ranges of any allowed label permission. */
+ public List<PermissionRange> getLabelRanges() {
+ List<PermissionRange> r = new ArrayList<PermissionRange>();
+ for (Map.Entry<String, List<PermissionRule>> e : permissions().entrySet()) {
+ if (Permission.isLabel(e.getKey())) {
+ r.add(toRange(e.getKey(), e.getValue()));
}
}
-
- if (score < minAllowed) {
- score = minAllowed;
- }
- if (score > maxAllowed) {
- score = maxAllowed;
- }
- return score;
+ return r;
}
- /**
- * Convenience holder class used to map a ref pattern to the list of
- * {@code RefRight}s that use it in the database.
- */
- public final static class RefRightsForPattern {
- private final List<RefRight> rights;
- private boolean containsExclusive;
-
- public RefRightsForPattern() {
- rights = new ArrayList<RefRight>();
- containsExclusive = false;
+ /** The range of permitted values associated with a label permission. */
+ public PermissionRange getRange(String permission) {
+ if (Permission.isLabel(permission)) {
+ return toRange(permission, access(permission));
}
+ return null;
+ }
- public void addRight(RefRight right) {
- rights.add(right);
- if (right.isExclusive()) {
- containsExclusive = true;
- }
+ private static PermissionRange toRange(String permissionName, List<PermissionRule> ruleList) {
+ int min = 0;
+ int max = 0;
+ for (PermissionRule rule : ruleList) {
+ min = Math.min(min, rule.getMin());
+ max = Math.max(max, rule.getMax());
}
+ return new PermissionRange(permissionName, min, max);
+ }
- public List<RefRight> getRights() {
- return Collections.unmodifiableList(rights);
- }
+ /** True if the user has this permission. Works only for non labels. */
+ boolean canPerform(String permissionName) {
+ return !access(permissionName).isEmpty();
+ }
- public boolean containsExclusive() {
- return containsExclusive;
- }
+ /** Rules for the given permission, or the empty list. */
+ private List<PermissionRule> access(String permissionName) {
+ List<PermissionRule> r = permissions().get(permissionName);
+ return r != null ? r : Collections.<PermissionRule> emptyList();
+ }
- /**
- * Returns The max allowed value for this ref pattern for all specified
- * groups.
- *
- * @param groups The groups of the user
- * @return The allowed value for this ref for all the specified groups
- */
- private boolean allowedValueForRef(Set<AccountGroup.Id> groups, short level) {
- for (RefRight right : rights) {
- if (groups.contains(right.getAccountGroupId())
- && right.getMaxValue() >= level) {
- return true;
+ /** All rules that pertain to this user, on this reference. */
+ private Map<String, List<PermissionRule>> permissions() {
+ if (permissions == null) {
+ List<AccessSection> sections = new ArrayList<AccessSection>();
+ for (AccessSection section : projectControl.access()) {
+ if (appliesToRef(section)) {
+ sections.add(section);
}
}
- return false;
- }
- }
+ Collections.sort(sections, new MostSpecificComparator(getRefName()));
- boolean canPerform(ApprovalCategory.Id actionId, short level) {
- final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups();
+ Set<SeenRule> seen = new HashSet<SeenRule>();
+ Set<String> exclusiveGroupPermissions = new HashSet<String>();
- List<RefRight> allRights = new ArrayList<RefRight>();
- allRights.addAll(getAllRights(actionId));
+ permissions = new HashMap<String, List<PermissionRule>>();
+ for (AccessSection section : sections) {
+ for (Permission permission : section.getPermissions()) {
+ if (exclusiveGroupPermissions.contains(permission.getName())) {
+ continue;
+ }
- SortedMap<String, RefRightsForPattern> perPatternRights =
- sortedRightsByPattern(allRights);
-
- for (RefRightsForPattern right : perPatternRights.values()) {
- if (right.allowedValueForRef(groups, level)) {
- return true;
- }
- if (right.containsExclusive() && !actionId.equals(OWN)) {
- break;
- }
- }
- return false;
- }
-
- /**
- * Order the Ref Pattern by the most specific. This sort is done by:
- * <ul>
- * <li>1 - The minor value of Levenshtein string distance between the branch
- * name and the regex string shortest example. A shorter distance is a more
- * specific match.
- * <li>2 - Finites first, infinities after.
- * <li>3 - Number of transitions.
- * <li>4 - Length of the expression text.
- * </ul>
- *
- * Levenshtein distance is a measure of the similarity between two strings.
- * The distance is the number of deletions, insertions, or substitutions
- * required to transform one string into another.
- *
- * For example, if given refs/heads/m* and refs/heads/*, the distances are 5
- * and 6. It means that refs/heads/m* is more specific because it's closer to
- * refs/heads/master than refs/heads/*.
- *
- * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the
- * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more
- * transitions, which after all turns it more specific.
- */
- private final Comparator<String> BY_MOST_SPECIFIC_SORT =
- new Comparator<String>() {
- public int compare(final String pattern1, final String pattern2) {
- int cmp = distance(pattern1) - distance(pattern2);
- if (cmp == 0) {
- boolean p1_finite = finite(pattern1);
- boolean p2_finite = finite(pattern2);
-
- if (p1_finite && !p2_finite) {
- cmp = -1;
- } else if (!p1_finite && p2_finite) {
- cmp = 1;
- } else /* if (f1 == f2) */{
- cmp = 0;
+ for (PermissionRule rule : permission.getRules()) {
+ if (matchGroup(rule.getGroup().getUUID())) {
+ SeenRule s = new SeenRule(section, permission, rule);
+ if (seen.add(s) && !rule.getDeny()) {
+ List<PermissionRule> r = permissions.get(permission.getName());
+ if (r == null) {
+ r = new ArrayList<PermissionRule>(2);
+ permissions.put(permission.getName(), r);
+ }
+ r.add(rule);
+ }
}
}
- if (cmp == 0) {
- cmp = transitions(pattern1) - transitions(pattern2);
- }
- if (cmp == 0) {
- cmp = pattern2.length() - pattern1.length();
- }
- return cmp;
- }
- private int distance(String pattern) {
- String example;
- if (isRE(pattern)) {
- example = shortestExample(pattern);
-
- } else if (pattern.endsWith("/*")) {
- example = pattern.substring(0, pattern.length() - 1) + '1';
-
- } else if (pattern.equals(getRefName())) {
- return 0;
-
- } else {
- return Math.max(pattern.length(), getRefName().length());
- }
- return StringUtils.getLevenshteinDistance(example, getRefName());
- }
-
- private boolean finite(String pattern) {
- if (isRE(pattern)) {
- return toRegExp(pattern).toAutomaton().isFinite();
-
- } else if (pattern.endsWith("/*")) {
- return false;
-
- } else {
- return true;
+ if (permission.getExclusiveGroup()) {
+ exclusiveGroupPermissions.add(permission.getName());
}
}
-
- private int transitions(String pattern) {
- if (isRE(pattern)) {
- return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
-
- } else if (pattern.endsWith("/*")) {
- return pattern.length();
-
- } else {
- return pattern.length();
- }
- }
- };
-
- /**
- * Sorts all given rights into a map, ordered by descending length of
- * ref pattern.
- *
- * For example, if given the following rights in argument:
- *
- * ["refs/heads/master", group1, -1, +1],
- * ["refs/heads/master", group2, -2, +2],
- * ["refs/heads/*", group3, -1, +1]
- * ["refs/heads/stable", group2, -1, +1]
- *
- * Then the following map is returned:
- * "refs/heads/master" => {
- * ["refs/heads/master", group1, -1, +1],
- * ["refs/heads/master", group2, -2, +2]
- * }
- * "refs/heads/stable" => {["refs/heads/stable", group2, -1, +1]}
- * "refs/heads/*" => {["refs/heads/*", group3, -1, +1]}
- *
- * @param actionRights
- * @return A sorted map keyed off the ref pattern of all rights.
- */
- private SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
- List<RefRight> actionRights) {
- SortedMap<String, RefRightsForPattern> rights =
- new TreeMap<String, RefRightsForPattern>(BY_MOST_SPECIFIC_SORT);
- for (RefRight actionRight : actionRights) {
- RefRightsForPattern patternRights =
- rights.get(actionRight.getRefPattern());
- if (patternRights == null) {
- patternRights = new RefRightsForPattern();
- rights.put(actionRight.getRefPattern(), patternRights);
- }
- patternRights.addRight(actionRight);
- }
- return rights;
- }
-
- private List<RefRight> getAllRights(ApprovalCategory.Id actionId) {
- final List<RefRight> allRefRights = filter(getProjectState().getAllRights(actionId, true));
- return resolveOwnerGroups(allRefRights);
- }
-
- /**
- * Returns all applicable rights for a given approval category.
- *
- * Applicable rights are defined as the list of {@code RefRight}s which match
- * the ref for which this object was created, stopping the ref wildcard
- * matching when an exclusive ref right was encountered, for the given
- * approval category.
- * @param id The {@link ApprovalCategory.Id}.
- * @return All applicable rights.
- */
- public List<RefRight> getApplicableRights(final ApprovalCategory.Id id) {
- List<RefRight> l = new ArrayList<RefRight>();
- l.addAll(getAllRights(id));
- SortedMap<String, RefRightsForPattern> perPatternRights =
- sortedRightsByPattern(l);
- List<RefRight> applicable = new ArrayList<RefRight>();
- for (RefRightsForPattern patternRights : perPatternRights.values()) {
- applicable.addAll(patternRights.getRights());
- if (patternRights.containsExclusive()) {
- break;
}
}
- return Collections.unmodifiableList(applicable);
+ return permissions;
}
- /**
- * Resolves all refRights which assign privileges to the 'Project Owners'
- * group. All other refRights stay unchanged.
- *
- * @param refRights refRights to be resolved
- * @return the resolved refRights
- */
- private List<RefRight> resolveOwnerGroups(final List<RefRight> refRights) {
- final List<RefRight> resolvedRefRights =
- new ArrayList<RefRight>(refRights.size());
- for (final RefRight refRight : refRights) {
- resolvedRefRights.addAll(resolveOwnerGroups(refRight));
- }
- return resolvedRefRights;
- }
+ private boolean appliesToRef(AccessSection section) {
+ String refPattern = section.getName();
- /**
- * Checks if the given refRight assigns privileges to the 'Project Owners'
- * group.
- * If yes, resolves the 'Project Owners' group to the concrete groups that
- * own the project and creates new refRights for the concrete owner groups
- * which are returned.
- * If no, the given refRight is returned unchanged.
- *
- * @param refRight refRight
- * @return the resolved refRights
- */
- private Set<RefRight> resolveOwnerGroups(final RefRight refRight) {
- final Set<RefRight> resolvedRefRights = new HashSet<RefRight>();
- if (refRight.getAccountGroupId().equals(systemConfig.ownerGroupId)) {
- for (final AccountGroup.Id ownerGroup : getProjectState().getAllOwners()) {
- if (!ownerGroup.equals(systemConfig.ownerGroupId)) {
- resolvedRefRights.add(new RefRight(refRight, ownerGroup));
- }
- }
- } else {
- resolvedRefRights.add(refRight);
- }
- return resolvedRefRights;
- }
-
- private List<RefRight> filter(Collection<RefRight> all) {
- List<RefRight> mine = new ArrayList<RefRight>(all.size());
- for (RefRight right : all) {
- if (matches(right.getRefPattern())) {
- mine.add(right);
- }
- }
- return mine;
- }
-
- private ProjectState getProjectState() {
- return projectControl.getProjectState();
- }
-
- private boolean matches(String refPattern) {
if (isTemplate(refPattern)) {
ParamertizedString template = new ParamertizedString(refPattern);
HashMap<String, String> p = new HashMap<String, String>();
@@ -599,6 +421,18 @@
}
}
+ private boolean matchGroup(AccountGroup.UUID uuid) {
+ Set<AccountGroup.UUID> userGroups = getCurrentUser().getEffectiveGroups();
+
+ if (AccountGroup.PROJECT_OWNERS.equals(uuid)) {
+ ProjectState state = projectControl.getProjectState();
+ return CollectionsUtil.isAnyIncludedIn(state.getAllOwners(), userGroups);
+
+ } else {
+ return userGroups.contains(uuid);
+ }
+ }
+
private static boolean isTemplate(String refPattern) {
return 0 <= refPattern.indexOf("${");
}
@@ -611,7 +445,7 @@
}
private static boolean isRE(String refPattern) {
- return refPattern.startsWith(RefRight.REGEX_PREFIX);
+ return refPattern.startsWith(AccessSection.REGEX_PREFIX);
}
public static String shortestExample(String pattern) {
@@ -630,4 +464,143 @@
}
return new RegExp(refPattern, RegExp.NONE);
}
+
+ /** Tracks whether or not a permission has been overridden. */
+ private static class SeenRule {
+ final String refPattern;
+ final String permissionName;
+ final AccountGroup.UUID group;
+
+ SeenRule(AccessSection section, Permission permission, PermissionRule rule) {
+ refPattern = section.getName();
+ permissionName = permission.getName();
+ group = rule.getGroup().getUUID();
+ }
+
+ @Override
+ public int hashCode() {
+ int hc = refPattern.hashCode();
+ hc = hc * 31 + permissionName.hashCode();
+ if (group != null) {
+ hc = hc * 31 + group.hashCode();
+ }
+ return hc;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof SeenRule) {
+ SeenRule a = this;
+ SeenRule b = (SeenRule) other;
+ return a.refPattern.equals(b.refPattern) //
+ && a.permissionName.equals(b.permissionName) //
+ && eq(a.group, b.group);
+ }
+ return false;
+ }
+
+ private boolean eq(AccountGroup.UUID a, AccountGroup.UUID b) {
+ return a != null && b != null && a.equals(b);
+ }
+ }
+
+ /**
+ * Order the Ref Pattern by the most specific. This sort is done by:
+ * <ul>
+ * <li>1 - The minor value of Levenshtein string distance between the branch
+ * name and the regex string shortest example. A shorter distance is a more
+ * specific match.
+ * <li>2 - Finites first, infinities after.
+ * <li>3 - Number of transitions.
+ * <li>4 - Length of the expression text.
+ * </ul>
+ *
+ * Levenshtein distance is a measure of the similarity between two strings.
+ * The distance is the number of deletions, insertions, or substitutions
+ * required to transform one string into another.
+ *
+ * For example, if given refs/heads/m* and refs/heads/*, the distances are 5
+ * and 6. It means that refs/heads/m* is more specific because it's closer to
+ * refs/heads/master than refs/heads/*.
+ *
+ * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the
+ * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more
+ * transitions, which after all turns it more specific.
+ */
+ private static final class MostSpecificComparator implements
+ Comparator<AccessSection> {
+ private final String refName;
+
+ MostSpecificComparator(String refName) {
+ this.refName = refName;
+ }
+
+ public int compare(AccessSection a, AccessSection b) {
+ return compare(a.getName(), b.getName());
+ }
+
+ private int compare(final String pattern1, final String pattern2) {
+ int cmp = distance(pattern1) - distance(pattern2);
+ if (cmp == 0) {
+ boolean p1_finite = finite(pattern1);
+ boolean p2_finite = finite(pattern2);
+
+ if (p1_finite && !p2_finite) {
+ cmp = -1;
+ } else if (!p1_finite && p2_finite) {
+ cmp = 1;
+ } else /* if (f1 == f2) */{
+ cmp = 0;
+ }
+ }
+ if (cmp == 0) {
+ cmp = transitions(pattern1) - transitions(pattern2);
+ }
+ if (cmp == 0) {
+ cmp = pattern2.length() - pattern1.length();
+ }
+ return cmp;
+ }
+
+ private int distance(String pattern) {
+ String example;
+ if (isRE(pattern)) {
+ example = shortestExample(pattern);
+
+ } else if (pattern.endsWith("/*")) {
+ example = pattern.substring(0, pattern.length() - 1) + '1';
+
+ } else if (pattern.equals(refName)) {
+ return 0;
+
+ } else {
+ return Math.max(pattern.length(), refName.length());
+ }
+ return StringUtils.getLevenshteinDistance(example, refName);
+ }
+
+ private boolean finite(String pattern) {
+ if (isRE(pattern)) {
+ return toRegExp(pattern).toAutomaton().isFinite();
+
+ } else if (pattern.endsWith("/*")) {
+ return false;
+
+ } else {
+ return true;
+ }
+ }
+
+ private int transitions(String pattern) {
+ if (isRE(pattern)) {
+ return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
+
+ } else if (pattern.endsWith("/*")) {
+ return pattern.length();
+
+ } else {
+ return pattern.length();
+ }
+ }
+ }
}
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 577c5af..cbb15f7 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
@@ -24,12 +24,13 @@
import com.google.gerrit.server.CurrentUser;
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.config.AuthConfig;
-import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.IntPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryBuilder;
@@ -98,39 +99,43 @@
final Provider<ReviewDb> dbProvider;
final Provider<ChangeQueryRewriter> rewriter;
final IdentifiedUser.GenericFactory userFactory;
+ final CapabilityControl.Factory capabilityControlFactory;
final ChangeControl.Factory changeControlFactory;
final ChangeControl.GenericFactory changeControlGenericFactory;
final AccountResolver accountResolver;
final GroupCache groupCache;
- final AuthConfig authConfig;
final ApprovalTypes approvalTypes;
- final Project.NameKey wildProjectName;
+ final AllProjectsName allProjectsName;
final PatchListCache patchListCache;
final GitRepositoryManager repoManager;
+ final ProjectCache projectCache;
@Inject
Arguments(Provider<ReviewDb> dbProvider,
Provider<ChangeQueryRewriter> rewriter,
IdentifiedUser.GenericFactory userFactory,
+ CapabilityControl.Factory capabilityControlFactory,
ChangeControl.Factory changeControlFactory,
ChangeControl.GenericFactory changeControlGenericFactory,
AccountResolver accountResolver, GroupCache groupCache,
- AuthConfig authConfig, ApprovalTypes approvalTypes,
- @WildProjectName Project.NameKey wildProjectName,
+ ApprovalTypes approvalTypes,
+ AllProjectsName allProjectsName,
PatchListCache patchListCache,
- GitRepositoryManager repoManager) {
+ GitRepositoryManager repoManager,
+ ProjectCache projectCache) {
this.dbProvider = dbProvider;
this.rewriter = rewriter;
this.userFactory = userFactory;
+ this.capabilityControlFactory = capabilityControlFactory;
this.changeControlFactory = changeControlFactory;
this.changeControlGenericFactory = changeControlGenericFactory;
this.accountResolver = accountResolver;
this.groupCache = groupCache;
- this.authConfig = authConfig;
this.approvalTypes = approvalTypes;
- this.wildProjectName = wildProjectName;
+ this.allProjectsName = allProjectsName;
this.patchListCache = patchListCache;
this.repoManager = repoManager;
+ this.projectCache = projectCache;
}
}
@@ -340,17 +345,18 @@
//
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
if (g != null) {
- return visibleto(new SingleGroupUser(args.authConfig, g.getId()));
+ return visibleto(new SingleGroupUser(args.capabilityControlFactory,
+ g.getGroupUUID()));
}
Collection<AccountGroup> matches =
args.groupCache.get(new AccountGroup.ExternalNameKey(who));
if (matches != null && !matches.isEmpty()) {
- HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
+ HashSet<AccountGroup.UUID> ids = new HashSet<AccountGroup.UUID>();
for (AccountGroup group : matches) {
- ids.add(group.getId());
+ ids.add(group.getGroupUUID());
}
- return visibleto(new SingleGroupUser(args.authConfig, ids));
+ return visibleto(new SingleGroupUser(args.capabilityControlFactory, ids));
}
throw error("No user or group matches \"" + who + "\".");
@@ -508,23 +514,19 @@
// Try to match a project name by substring query.
final List<ProjectPredicate> predicate =
new ArrayList<ProjectPredicate>();
- try {
- for (final Project p : args.dbProvider.get().projects().all()) {
- if (p.getName().toLowerCase().contains(query.toLowerCase())) {
- predicate.add(new ProjectPredicate(args.dbProvider, p.getName()));
- }
+ for (Project.NameKey name : args.projectCache.all()) {
+ if (name.get().toLowerCase().contains(query.toLowerCase())) {
+ predicate.add(new ProjectPredicate(args.dbProvider, name.get()));
}
+ }
- // If two or more projects contains "query" as substring create an
- // OrPredicate holding predicates for all these projects, otherwise if
- // only one contains that, return only that one predicate by itself.
- if (predicate.size() == 1) {
- return predicate.get(0);
- } else if (predicate.size() > 1) {
- return Predicate.or(predicate);
- }
- } catch (OrmException e) {
- throw error("Cannot lookup project.", e);
+ // If two or more projects contains "query" as substring create an
+ // OrPredicate holding predicates for all these projects, otherwise if
+ // only one contains that, return only that one predicate by itself.
+ if (predicate.size() == 1) {
+ return predicate.get(0);
+ } else if (predicate.size() > 1) {
+ return Predicate.or(predicate);
}
throw error("Unsupported query:" + query);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index 717b487..b31bf65 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -38,7 +38,8 @@
new ChangeQueryBuilder.Arguments( //
new InvalidProvider<ReviewDb>(), //
new InvalidProvider<ChangeQueryRewriter>(), //
- null, null, null, null, null, null, null, null, null, null), null));
+ null, null, null, null, null, null, null, //
+ null, null, null, null), null));
private final Provider<ReviewDb> dbProvider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 870be73..69d379cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -79,7 +79,7 @@
Project.NameKey project = change.getDest().getParentKey();
List<Predicate<ChangeData>> list = rules.get(project);
if (list == null) {
- list = rules.get(args.wildProjectName);
+ list = rules.get(args.allProjectsName);
}
if (list != null) {
for (Predicate<ChangeData> p : list) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index e76c278..ece61ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
@@ -33,48 +34,54 @@
private static enum Test {
EQ {
@Override
- public boolean match(short psValue, short expValue) {
+ public boolean match(int psValue, int expValue) {
return psValue == expValue;
}
},
GT_EQ {
@Override
- public boolean match(short psValue, short expValue) {
+ public boolean match(int psValue, int expValue) {
return psValue >= expValue;
}
},
LT_EQ {
@Override
- public boolean match(short psValue, short expValue) {
+ public boolean match(int psValue, int expValue) {
return psValue <= expValue;
}
};
- abstract boolean match(short psValue, short expValue);
+ abstract boolean match(int psValue, int expValue);
}
- private static ApprovalCategory.Id category(ApprovalTypes types, String toFind) {
- if (types.getApprovalType(new ApprovalCategory.Id(toFind)) != null) {
- return new ApprovalCategory.Id(toFind);
+ private static ApprovalCategory category(ApprovalTypes types, String toFind) {
+ if (types.byLabel(toFind) != null) {
+ return types.byLabel(toFind).getCategory();
+ }
+
+ if (types.byId(new ApprovalCategory.Id(toFind)) != null) {
+ return types.byId(new ApprovalCategory.Id(toFind)).getCategory();
}
for (ApprovalType at : types.getApprovalTypes()) {
- String name = at.getCategory().getName();
- if (toFind.equalsIgnoreCase(name)) {
- return at.getCategory().getId();
+ ApprovalCategory category = at.getCategory();
- } else if (toFind.equalsIgnoreCase(name.replace(" ", ""))) {
- return at.getCategory().getId();
+ if (toFind.equalsIgnoreCase(category.getName())) {
+ return category;
+
+ } else if (toFind.equalsIgnoreCase(category.getName().replace(" ", ""))) {
+ return category;
}
}
for (ApprovalType at : types.getApprovalTypes()) {
- if (toFind.equalsIgnoreCase(at.getCategory().getAbbreviatedName())) {
- return at.getCategory().getId();
+ ApprovalCategory category = at.getCategory();
+ if (toFind.equalsIgnoreCase(category.getAbbreviatedName())) {
+ return category;
}
}
- return new ApprovalCategory.Id(toFind);
+ return new ApprovalCategory(new ApprovalCategory.Id(toFind), toFind);
}
private static Test op(String op) {
@@ -92,19 +99,20 @@
}
}
- private static short value(String value) {
+ private static int value(String value) {
if (value.startsWith("+")) {
value = value.substring(1);
}
- return Short.parseShort(value);
+ return Integer.parseInt(value);
}
private final ChangeControl.GenericFactory ccFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<ReviewDb> dbProvider;
private final Test test;
- private final ApprovalCategory.Id category;
- private final short expVal;
+ private final ApprovalCategory category;
+ private final String permissionName;
+ private final int expVal;
LabelPredicate(ChangeControl.GenericFactory ccFactory,
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
@@ -131,13 +139,15 @@
test = Test.EQ;
expVal = 1;
}
+
+ this.permissionName = Permission.forLabel(category.getLabelName());
}
@Override
public boolean match(final ChangeData object) throws OrmException {
for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
- if (p.getCategoryId().equals(category)) {
- short psVal = p.getValue();
+ if (p.getCategoryId().equals(category.getId())) {
+ int psVal = p.getValue();
if (test.match(psVal, expVal)) {
// Double check the value is still permitted for the user.
//
@@ -149,7 +159,7 @@
//
continue;
}
- psVal = cc.normalize(category, psVal);
+ psVal = cc.getRange(permissionName).squash(psVal);
} catch (NoSuchChangeException e) {
// The project has disappeared.
//
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index a975f1b..2d3184d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb;
@@ -64,8 +65,8 @@
private final ChangeQueryBuilder queryBuilder;
private final ChangeQueryRewriter queryRewriter;
private final Provider<ReviewDb> db;
+ private final int maxLimit;
- private int defaultLimit = 500;
private OutputFormat outputFormat = OutputFormat.TEXT;
private boolean includePatchSets;
private boolean includeCurrentPatchSet;
@@ -82,6 +83,9 @@
this.queryBuilder = queryBuilder.create(currentUser);
this.queryRewriter = queryRewriter;
this.db = db;
+ this.maxLimit = currentUser.getCapabilities()
+ .getRange(GlobalCapability.QUERY_LIMIT)
+ .getMax();
}
public void setIncludePatchSets(boolean on) {
@@ -106,6 +110,13 @@
new BufferedWriter( //
new OutputStreamWriter(outputStream, "UTF-8")));
try {
+ if (maxLimit <= 0) {
+ ErrorMessage m = new ErrorMessage();
+ m.message = "query disabled";
+ show(m);
+ return;
+ }
+
try {
final QueryStats stats = new QueryStats();
stats.runTimeMilliseconds = System.currentTimeMillis();
@@ -198,7 +209,7 @@
}
private int limit(Predicate<ChangeData> s) {
- return queryBuilder.hasLimit(s) ? queryBuilder.getLimit(s) : defaultLimit;
+ return queryBuilder.hasLimit(s) ? queryBuilder.getLimit(s) : maxLimit;
}
@SuppressWarnings("unchecked")
@@ -206,13 +217,10 @@
final Predicate<ChangeData> visibleToMe) throws QueryParseException {
Predicate<ChangeData> q = queryBuilder.parse(queryString);
- if (!queryBuilder.hasLimit(q)) {
- q = Predicate.and(q, queryBuilder.limit(defaultLimit));
- }
if (!queryBuilder.hasSortKey(q)) {
q = Predicate.and(q, queryBuilder.sortkey_before("z"));
}
- q = Predicate.and(q, visibleToMe);
+ q = Predicate.and(q, queryBuilder.limit(maxLimit), visibleToMe);
Predicate<ChangeData> s = queryRewriter.rewrite(q);
if (!(s instanceof ChangeDataSource)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
index 2fb6694..ad28b41 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
@@ -19,26 +19,28 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.account.CapabilityControl;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
final class SingleGroupUser extends CurrentUser {
- private final Set<AccountGroup.Id> groups;
+ private final Set<AccountGroup.UUID> groups;
- SingleGroupUser(AuthConfig authConfig, AccountGroup.Id groupId) {
- this(authConfig, Collections.singleton(groupId));
+ SingleGroupUser(CapabilityControl.Factory capabilityControlFactory,
+ AccountGroup.UUID groupId) {
+ this(capabilityControlFactory, Collections.singleton(groupId));
}
- SingleGroupUser(AuthConfig authConfig, Set<AccountGroup.Id> groups) {
- super(AccessPath.UNKNOWN, authConfig);
+ SingleGroupUser(CapabilityControl.Factory capabilityControlFactory,
+ Set<AccountGroup.UUID> groups) {
+ super(capabilityControlFactory, AccessPath.UNKNOWN);
this.groups = groups;
}
@Override
- public Set<AccountGroup.Id> getEffectiveGroups() {
+ public Set<AccountGroup.UUID> getEffectiveGroups() {
return groups;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java
index 55a36d6..6b66e33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java
@@ -26,8 +26,6 @@
public class DatabaseModule extends FactoryModule {
@Override
protected void configure() {
- install(new SchemaVersion.Module());
-
bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).to(
new TypeLiteral<Database<ReviewDb>>() {}).in(SINGLETON);
bind(new TypeLiteral<Database<ReviewDb>>() {}).toProvider(
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 a24471a..08e9b55 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
@@ -14,20 +14,28 @@
package com.google.gerrit.server.schema;
+import com.google.gerrit.common.Version;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.GroupUUID;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.workflow.NoOpFunction;
-import com.google.gerrit.server.workflow.SubmitFunction;
-import com.google.gwtjsonrpc.server.SignedToken;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.NoReplication;
+import com.google.gerrit.server.git.ProjectConfig;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -37,6 +45,11 @@
import com.google.gwtorm.schema.sql.SqlDialect;
import com.google.inject.Inject;
+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.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -44,33 +57,49 @@
/** Creates the current database schema and populates initial code rows. */
public class SchemaCreator {
- private static final Project.NameKey DEFAULT_WILD_NAME =
- new Project.NameKey("-- All Projects --");
-
private final @SitePath
File site_path;
+ private final GitRepositoryManager mgr;
+ private final AllProjectsName allProjectsName;
+ private final PersonIdent serverUser;
+
private final int versionNbr;
private final ScriptRunner index_generic;
private final ScriptRunner index_postgres;
private final ScriptRunner mysql_nextval;
+ private AccountGroup admin;
+ private AccountGroup anonymous;
+ private AccountGroup registered;
+ private AccountGroup owners;
+
@Inject
- public SchemaCreator(final SitePaths site,
- @Current final SchemaVersion version) {
- this(site.site_path, version);
+ public SchemaCreator(SitePaths site,
+ @Current SchemaVersion version,
+ GitRepositoryManager mgr,
+ AllProjectsName allProjectsName,
+ @GerritPersonIdent PersonIdent au) {
+ this(site.site_path, version, mgr, allProjectsName, au);
}
- public SchemaCreator(final @SitePath File site,
- @Current final SchemaVersion version) {
+ public SchemaCreator(@SitePath File site,
+ @Current SchemaVersion version,
+ GitRepositoryManager gitMgr,
+ AllProjectsName ap,
+ @GerritPersonIdent PersonIdent au) {
site_path = site;
+ mgr = gitMgr;
+ allProjectsName = ap;
+ serverUser = au;
versionNbr = version.getVersionNbr();
index_generic = new ScriptRunner("index_generic.sql");
index_postgres = new ScriptRunner("index_postgres.sql");
mysql_nextval = new ScriptRunner("mysql_nextval.sql");
}
- public void create(final ReviewDb db) throws OrmException {
+ public void create(final ReviewDb db) throws OrmException, IOException,
+ ConfigInvalidException {
final JdbcSchema jdbc = (JdbcSchema) db;
final JdbcExecutor e = new JdbcExecutor(jdbc);
try {
@@ -84,15 +113,13 @@
db.schemaVersion().insert(Collections.singleton(sVer));
final SystemConfig sConfig = initSystemConfig(db);
- initOwnerCategory(db);
- initReadCategory(db, sConfig);
initVerifiedCategory(db);
initCodeReviewCategory(db, sConfig);
- initSubmitCategory(db);
- initPushTagCategory(db);
- initPushUpdateBranchCategory(db);
- initForgeIdentityCategory(db, sConfig);
- initWildCardProject(db);
+
+ if (mgr != null) {
+ // TODO This should never be null when initializing a site.
+ initWildCardProject();
+ }
final SqlDialect d = jdbc.getDialect();
if (d instanceof DialectH2) {
@@ -110,19 +137,27 @@
}
}
+ private AccountGroup newGroup(ReviewDb c, String name, AccountGroup.UUID uuid)
+ throws OrmException {
+ if (uuid == null) {
+ uuid = GroupUUID.make(name, serverUser);
+ }
+ return new AccountGroup( //
+ new AccountGroup.NameKey(name), //
+ new AccountGroup.Id(c.nextAccountGroupId()), //
+ uuid);
+ }
+
private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException {
- final AccountGroup admin =
- new AccountGroup(new AccountGroup.NameKey("Administrators"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ admin = newGroup(c, "Administrators", null);
admin.setDescription("Gerrit Site Administrators");
admin.setType(AccountGroup.Type.INTERNAL);
c.accountGroups().insert(Collections.singleton(admin));
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(admin)));
- final AccountGroup anonymous =
- new AccountGroup(new AccountGroup.NameKey("Anonymous Users"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ anonymous =
+ newGroup(c, "Anonymous Users", AccountGroup.ANONYMOUS_USERS);
anonymous.setDescription("Any user, signed-in or not");
anonymous.setOwnerGroupId(admin.getId());
anonymous.setType(AccountGroup.Type.SYSTEM);
@@ -130,9 +165,8 @@
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(anonymous)));
- final AccountGroup registered =
- new AccountGroup(new AccountGroup.NameKey("Registered Users"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ registered =
+ newGroup(c, "Registered Users", AccountGroup.REGISTERED_USERS);
registered.setDescription("Any signed-in user");
registered.setOwnerGroupId(admin.getId());
registered.setType(AccountGroup.Type.SYSTEM);
@@ -140,9 +174,7 @@
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(registered)));
- final AccountGroup batchUsers =
- new AccountGroup(new AccountGroup.NameKey("Non-Interactive Users"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ final AccountGroup batchUsers = newGroup(c, "Non-Interactive Users", null);
batchUsers.setDescription("Users who perform batch actions on Gerrit");
batchUsers.setOwnerGroupId(admin.getId());
batchUsers.setType(AccountGroup.Type.INTERNAL);
@@ -150,9 +182,7 @@
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(batchUsers)));
- final AccountGroup owners =
- new AccountGroup(new AccountGroup.NameKey("Project Owners"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ owners = newGroup(c, "Project Owners", AccountGroup.PROJECT_OWNERS);
owners.setDescription("Any owner of the project");
owners.setOwnerGroupId(admin.getId());
owners.setType(AccountGroup.Type.SYSTEM);
@@ -161,13 +191,6 @@
Collections.singleton(new AccountGroupName(owners)));
final SystemConfig s = SystemConfig.create();
- s.registerEmailPrivateKey = SignedToken.generateRandomKey();
- s.adminGroupId = admin.getId();
- s.anonymousGroupId = anonymous.getId();
- s.registeredGroupId = registered.getId();
- s.batchUsersGroupId = batchUsers.getId();
- s.ownerGroupId = owners.getId();
- s.wildProjectName = DEFAULT_WILD_NAME;
try {
s.sitePath = site_path.getCanonicalPath();
} catch (IOException e) {
@@ -177,13 +200,67 @@
return s;
}
- private void initWildCardProject(final ReviewDb c) throws OrmException {
- final Project p;
+ private void initWildCardProject() throws IOException, ConfigInvalidException {
+ Repository git;
+ try {
+ git = mgr.openRepository(allProjectsName);
+ } catch (RepositoryNotFoundException notFound) {
+ // A repository may be missing if this project existed only to store
+ // inheritable permissions. For example 'All-Projects'.
+ try {
+ git = mgr.createRepository(allProjectsName);
+ } catch (RepositoryNotFoundException err) {
+ final String name = allProjectsName.get();
+ throw new IOException("Cannot create repository " + name, err);
+ }
+ }
+ try {
+ MetaDataUpdate md = new MetaDataUpdate(new NoReplication(), allProjectsName, git);
+ md.getCommitBuilder().setAuthor(serverUser);
+ md.getCommitBuilder().setCommitter(serverUser);
- p = new Project(DEFAULT_WILD_NAME);
- p.setDescription("Rights inherited by all other projects");
- p.setUseContributorAgreements(false);
- c.projects().insert(Collections.singleton(p));
+ ProjectConfig config = ProjectConfig.read(md);
+ Project p = config.getProject();
+ p.setDescription("Rights inherited by all other projects");
+ p.setUseContributorAgreements(false);
+
+ AccessSection cap = config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true);
+ AccessSection all = config.getAccessSection(AccessSection.ALL, true);
+ AccessSection heads = config.getAccessSection(AccessSection.HEADS, true);
+ AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
+
+ cap.getPermission(GlobalCapability.ADMINISTRATE_SERVER, true)
+ .add(rule(config, admin));
+
+ PermissionRule review = rule(config, registered);
+ review.setRange(-1, 1);
+ heads.getPermission(Permission.LABEL + "Code-Review", true).add(review);
+
+ all.getPermission(Permission.READ, true) //
+ .add(rule(config, admin));
+ all.getPermission(Permission.READ, true) //
+ .add(rule(config, anonymous));
+
+ config.getAccessSection("refs/for/" + AccessSection.ALL, true) //
+ .getPermission(Permission.PUSH, true) //
+ .add(rule(config, registered));
+ all.getPermission(Permission.FORGE_AUTHOR, true) //
+ .add(rule(config, registered));
+
+ meta.getPermission(Permission.READ, true) //
+ .add(rule(config, owners));
+
+ md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
+ if (!config.commit(md)) {
+ throw new IOException("Cannot create " + allProjectsName.get());
+ }
+ } finally {
+ git.close();
+ }
+ }
+
+ private PermissionRule rule(ProjectConfig config, AccountGroup group) {
+ return new PermissionRule(config.resolve(group));
}
private void initVerifiedCategory(final ReviewDb c) throws OrmException {
@@ -218,143 +295,6 @@
vals.add(value(cat, -2, "Do not submit"));
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
-
- final RefRight approve =
- new RefRight(new RefRight.Key(DEFAULT_WILD_NAME,
- new RefRight.RefPattern("refs/heads/*"), cat.getId(),
- sConfig.registeredGroupId));
- approve.setMaxValue((short) 1);
- approve.setMinValue((short) -1);
- c.refRights().insert(Collections.singleton(approve));
- }
-
- private void initOwnerCategory(final ReviewDb c) throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> vals;
-
- cat = new ApprovalCategory(ApprovalCategory.OWN, "Owner");
- cat.setPosition((short) -1);
- cat.setFunctionName(NoOpFunction.NAME);
- vals = new ArrayList<ApprovalCategoryValue>();
- vals.add(value(cat, 1, "Administer All Settings"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(vals);
- }
-
- private void initReadCategory(final ReviewDb c, final SystemConfig sConfig)
- throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> vals;
-
- cat = new ApprovalCategory(ApprovalCategory.READ, "Read Access");
- cat.setPosition((short) -1);
- cat.setFunctionName(NoOpFunction.NAME);
- vals = new ArrayList<ApprovalCategoryValue>();
- vals.add(value(cat, 3, "Upload merges permission"));
- vals.add(value(cat, 2, "Upload permission"));
- vals.add(value(cat, 1, "Read access"));
- vals.add(value(cat, -1, "No access"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(vals);
-
- final RefRight.RefPattern pattern = new RefRight.RefPattern(RefRight.ALL);
- {
- final RefRight read =
- new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
- cat.getId(), sConfig.anonymousGroupId));
- read.setMaxValue((short) 1);
- read.setMinValue((short) 1);
- c.refRights().insert(Collections.singleton(read));
- }
- {
- final RefRight read =
- new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
- cat.getId(), sConfig.registeredGroupId));
- read.setMaxValue((short) 2);
- read.setMinValue((short) 1);
- c.refRights().insert(Collections.singleton(read));
- }
- {
- final RefRight read =
- new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
- cat.getId(), sConfig.adminGroupId));
- read.setMaxValue((short) 1);
- read.setMinValue((short) 1);
- c.refRights().insert(Collections.singleton(read));
- }
- }
-
- private void initSubmitCategory(final ReviewDb c) throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> vals;
-
- cat = new ApprovalCategory(ApprovalCategory.SUBMIT, "Submit");
- cat.setPosition((short) -1);
- cat.setFunctionName(SubmitFunction.NAME);
- vals = new ArrayList<ApprovalCategoryValue>();
- vals.add(value(cat, 1, "Submit"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(vals);
- }
-
- private void initPushTagCategory(final ReviewDb c) throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> vals;
-
- cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Tag");
- cat.setPosition((short) -1);
- cat.setFunctionName(NoOpFunction.NAME);
- vals = new ArrayList<ApprovalCategoryValue>();
- vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag"));
- vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED,
- "Create Annotated Tag"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(vals);
- }
-
- private void initPushUpdateBranchCategory(final ReviewDb c)
- throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> vals;
-
- cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch");
- cat.setPosition((short) -1);
- cat.setFunctionName(NoOpFunction.NAME);
- vals = new ArrayList<ApprovalCategoryValue>();
- vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch"));
- vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch"));
- vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE,
- "Force Push Branch; Delete Branch"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(vals);
- }
-
- private void initForgeIdentityCategory(final ReviewDb c,
- final SystemConfig sConfig) throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> values;
-
- cat =
- new ApprovalCategory(ApprovalCategory.FORGE_IDENTITY, "Forge Identity");
- cat.setPosition((short) -1);
- cat.setFunctionName(NoOpFunction.NAME);
- values = new ArrayList<ApprovalCategoryValue>();
- values.add(value(cat, ApprovalCategory.FORGE_AUTHOR,
- "Forge Author Identity"));
- values.add(value(cat, ApprovalCategory.FORGE_COMMITTER,
- "Forge Committer or Tagger Identity"));
- values.add(value(cat, ApprovalCategory.FORGE_SERVER,
- "Forge Gerrit Code Review Server Identity"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(values);
-
- RefRight right =
- new RefRight(new RefRight.Key(sConfig.wildProjectName,
- new RefRight.RefPattern(RefRight.ALL),
- ApprovalCategory.FORGE_IDENTITY, sConfig.registeredGroupId));
- right.setMinValue(ApprovalCategory.FORGE_AUTHOR);
- right.setMaxValue(ApprovalCategory.FORGE_AUTHOR);
- c.refRights().insert(Collections.singleton(right));
}
private static ApprovalCategoryValue value(final ApprovalCategory cat,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
new file mode 100644
index 0000000..ea277d7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
@@ -0,0 +1,51 @@
+// 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.schema;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+/** Validate the schema and connect to Git. */
+public class SchemaModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ install(new SchemaVersion.Module());
+
+ bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
+ .toProvider(GerritPersonIdentProvider.class);
+
+ bind(AllProjectsName.class)
+ .toProvider(AllProjectsNameProvider.class)
+ .in(SINGLETON);
+
+ bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
+ install(new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(LocalDiskRepositoryManager.Lifecycle.class);
+ }
+ });
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java
index f44eff5..3937aaa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java
@@ -23,6 +23,8 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collections;
@@ -49,7 +51,13 @@
final SchemaVersion u = updater.get();
final CurrentSchemaVersion version = getSchemaVersion(db);
if (version == null) {
- creator.create(db);
+ try {
+ creator.create(db);
+ } catch (IOException e) {
+ throw new OrmException("Cannot initialize schema", e);
+ } catch (ConfigInvalidException e) {
+ throw new OrmException("Cannot initialize schema", e);
+ }
} else {
try {
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 978a152..480d719 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. */
- private static final Class<? extends SchemaVersion> C = Schema_52.class;
+ private static final Class<? extends SchemaVersion> C = Schema_57.class;
public static class Module extends AbstractModule {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
similarity index 62%
rename from gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
index ba3e0fc..9a59567 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
@@ -12,37 +12,43 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.schema;
+import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gerrit.server.schema.Current;
-import com.google.gerrit.server.schema.SchemaVersion;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
+import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
-import java.util.List;
+/** Validates the current schema version. */
+public class SchemaVersionCheck implements LifecycleListener {
+ public static Module module () {
+ return new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(SchemaVersionCheck.class);
+ }
+ };
+ }
-/** Loads the {@link SystemConfig} from the database. */
-public class SystemConfigProvider implements Provider<SystemConfig> {
private final SchemaFactory<ReviewDb> schema;
@Current
private final Provider<SchemaVersion> version;
@Inject
- public SystemConfigProvider(final SchemaFactory<ReviewDb> schemaFactory,
- @Current final Provider<SchemaVersion> version) {
+ public SchemaVersionCheck(SchemaFactory<ReviewDb> schemaFactory,
+ @Current Provider<SchemaVersion> version) {
this.schema = schemaFactory;
this.version = version;
}
- @Override
- public SystemConfig get() {
+ public void start() {
try {
final ReviewDb db = schema.open();
try {
@@ -50,33 +56,25 @@
final int eVer = version.get().getVersionNbr();
if (sVer == null) {
- throw new OrmException("Schema not yet initialized."
+ throw new ProvisionException("Schema not yet initialized."
+ " Run init to initialize the schema.");
}
if (sVer.versionNbr != eVer) {
- throw new OrmException("Unsupported schema version "
+ throw new ProvisionException("Unsupported schema version "
+ sVer.versionNbr + "; expected schema version " + eVer
+ ". Run init to upgrade.");
}
-
- final List<SystemConfig> all = db.systemConfig().all().toList();
- switch (all.size()) {
- case 1:
- return all.get(0);
- case 0:
- throw new OrmException("system_config table is empty");
- default:
- throw new OrmException("system_config must have exactly 1 row;"
- + " found " + all.size() + " rows instead");
- }
} finally {
db.close();
}
} catch (OrmException e) {
- throw new ProvisionException("Cannot read system_config", e);
+ throw new ProvisionException("Cannot read schema_version", e);
}
}
+ public void stop() {
+ }
+
private CurrentSchemaVersion getSchemaVersion(final ReviewDb db) {
try {
return db.schemaVersion().get(new CurrentSchemaVersion.Key());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_19.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_19.java
deleted file mode 100644
index 7ad91ff..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_19.java
+++ /dev/null
@@ -1,42 +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.schema;
-
-import com.google.gerrit.reviewdb.CurrentSchemaVersion;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-
-class Schema_19 extends SchemaVersion {
- @Inject
- Schema_19() {
- super(new Provider<SchemaVersion>() {
- public SchemaVersion get() {
- throw new ProvisionException("Cannot upgrade from 18");
- }
- });
- }
-
- @Override
- protected void upgradeFrom(UpdateUI ui, CurrentSchemaVersion curr,
- ReviewDb db, boolean toTargetVersion) throws OrmException {
- throw new OrmException("Cannot upgrade from " + curr.versionNbr
- + "; manually run scripts from"
- + " http://gerrit.googlecode.com/files/schema-upgrades003_019.zip"
- + " and restart.");
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_21.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_21.java
deleted file mode 100644
index ed50f7f..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_21.java
+++ /dev/null
@@ -1,74 +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.schema;
-
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Collections;
-
-class Schema_21 extends SchemaVersion {
- @Inject
- Schema_21(Provider<Schema_20> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- JdbcSchema jdbc = (JdbcSchema) db;
- SystemConfig sc = db.systemConfig().get(new SystemConfig.Key());
-
- Statement s = jdbc.getConnection().createStatement();
- try {
- ResultSet r;
-
- r = s.executeQuery("SELECT name FROM projects WHERE project_id = 0");
- try {
- if (!r.next()) {
- throw new OrmException("Cannot read old wild project");
- }
- sc.wildProjectName = new Project.NameKey(r.getString(1));
- } finally {
- r.close();
- }
-
- if (jdbc.getDialect() instanceof DialectMySQL) {
- try {
- s.execute("DROP FUNCTION nextval_project_id");
- } catch (SQLException se) {
- ui.message("warning: could not delete function nextval_project_id");
- }
-
- } else if (jdbc.getDialect() instanceof DialectH2) {
- s.execute("ALTER TABLE projects DROP CONSTRAINT"
- + " IF EXISTS CONSTRAINT_F3");
- }
- } finally {
- s.close();
- }
-
- db.systemConfig().update(Collections.singleton(sc));
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_22.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_22.java
deleted file mode 100644
index 8e24aa2..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_22.java
+++ /dev/null
@@ -1,74 +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.schema;
-
-import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
-
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.AccountExternalId.Key;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collection;
-
-class Schema_22 extends SchemaVersion {
- @Inject
- Schema_22(Provider<Schema_21> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- Statement s = ((JdbcSchema) db).getConnection().createStatement();
- try {
- ResultSet results =
- s.executeQuery(//
- "SELECT account_id, ssh_user_name"
- + " FROM accounts" //
- + " WHERE ssh_user_name IS NOT NULL"
- + " AND ssh_user_name <> ''");
- Collection<AccountExternalId> ids = new ArrayList<AccountExternalId>();
- while (results.next()) {
- final int accountId = results.getInt(1);
- final String userName = results.getString(2);
-
- final Account.Id account = new Account.Id(accountId);
- final AccountExternalId.Key key = toKey(userName);
- ids.add(new AccountExternalId(account, key));
- }
- db.accountExternalIds().insert(ids);
-
- if (((JdbcSchema) db).getDialect() instanceof DialectH2) {
- s.execute("ALTER TABLE accounts DROP CONSTRAINT"
- + " IF EXISTS CONSTRAINT_AF");
- }
- } finally {
- s.close();
- }
- }
-
- private Key toKey(final String userName) {
- return new AccountExternalId.Key(SCHEME_USERNAME, userName);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_23.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_23.java
deleted file mode 100644
index 413fa4e..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_23.java
+++ /dev/null
@@ -1,61 +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.schema;
-
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupName;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collection;
-
-class Schema_23 extends SchemaVersion {
- @Inject
- Schema_23(Provider<Schema_22> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- Collection<AccountGroupName> names = new ArrayList<AccountGroupName>();
- Statement queryStmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- ResultSet results =
- queryStmt.executeQuery("SELECT group_id, name FROM account_groups");
- while (results.next()) {
- final int id = results.getInt(1);
- final String name = results.getString(2);
-
- final AccountGroup.Id group = new AccountGroup.Id(id);
- final AccountGroup.NameKey key = toKey(name);
- names.add(new AccountGroupName(key, group));
- }
- } finally {
- queryStmt.close();
- }
- db.accountGroupNames().insert(names);
- }
-
- private AccountGroup.NameKey toKey(final String name) {
- return new AccountGroup.NameKey(name);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_24.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_24.java
deleted file mode 100644
index 0172921..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_24.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-class Schema_24 extends SchemaVersion {
- @Inject
- Schema_24(Provider<Schema_23> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java
deleted file mode 100644
index fdfff70..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java
+++ /dev/null
@@ -1,112 +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.schema;
-
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import org.eclipse.jgit.lib.Constants;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-class Schema_25 extends SchemaVersion {
- private Set<ApprovalCategory.Id> nonActions;
-
- @Inject
- Schema_25(Provider<Schema_24> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- nonActions = new HashSet<ApprovalCategory.Id>();
- for (ApprovalCategory c : db.approvalCategories().all()) {
- if (!c.isAction()) {
- nonActions.add(c.getId());
- }
- }
-
- List<RefRight> rights = new ArrayList<RefRight>();
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- ResultSet rs = stmt.executeQuery("SELECT * FROM project_rights");
- try {
- while (rs.next()) {
- rights.add(toRefRight(rs));
- }
- } finally {
- rs.close();
- }
-
- db.refRights().insert(rights);
- stmt.execute("CREATE INDEX ref_rights_byCatGroup"
- + " ON ref_rights (category_id, group_id)");
- } finally {
- stmt.close();
- }
- }
-
- private RefRight toRefRight(ResultSet rs) throws SQLException {
- short min_value = rs.getShort("min_value");
- short max_value = rs.getShort("max_value");
- String category_id = rs.getString("category_id");
- int group_id = rs.getInt("group_id");
- String project_name = rs.getString("project_name");
-
- ApprovalCategory.Id category = new ApprovalCategory.Id(category_id);
- Project.NameKey project = new Project.NameKey(project_name);
- AccountGroup.Id group = new AccountGroup.Id(group_id);
-
- RefRight.RefPattern ref;
- if (category.equals(ApprovalCategory.SUBMIT)
- || category.equals(ApprovalCategory.PUSH_HEAD)
- || nonActions.contains(category)) {
- // Explicitly related to a branch head.
- ref = new RefRight.RefPattern(Constants.R_HEADS + "*");
-
- } else if (category.equals(ApprovalCategory.PUSH_TAG)) {
- // Explicitly related to the tag namespace.
- ref = new RefRight.RefPattern(Constants.R_TAGS + "/*");
-
- } else if (category.equals(ApprovalCategory.READ)
- || category.equals(ApprovalCategory.OWN)) {
- // Currently these are project-wide rights, so apply that way.
- ref = new RefRight.RefPattern(RefRight.ALL);
-
- } else {
- // Assume project wide for the default.
- ref = new RefRight.RefPattern(RefRight.ALL);
- }
-
- RefRight.Key key = new RefRight.Key(project, ref, category, group);
- RefRight r = new RefRight(key);
- r.setMinValue(min_value);
- r.setMaxValue(max_value);
- return r;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_26.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_26.java
deleted file mode 100644
index 9c76af2..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_26.java
+++ /dev/null
@@ -1,47 +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.schema;
-
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.SQLException;
-import java.sql.Statement;
-
-class Schema_26 extends SchemaVersion {
- @Inject
- Schema_26(Provider<Schema_25> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- if (((JdbcSchema) db).getDialect() instanceof DialectMySQL) {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("ALTER TABLE account_group_members_audit" //
- + " MODIFY removed_on TIMESTAMP NULL DEFAULT NULL");
- stmt.execute("UPDATE account_group_members_audit" //
- + " SET removed_on = NULL" //
- + " WHERE removed_by IS NULL;");
- } finally {
- stmt.close();
- }
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_27.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_27.java
deleted file mode 100644
index f8febec..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_27.java
+++ /dev/null
@@ -1,93 +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.schema;
-
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupName;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-class Schema_27 extends SchemaVersion {
- @Inject
- Schema_27(Provider<Schema_26> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException, OrmException {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- final SqlDialect dialect = ((JdbcSchema) db).getDialect();
- if (dialect instanceof DialectPostgreSQL) {
- stmt.execute("ALTER TABLE account_groups"
- + " ALTER COLUMN name TYPE VARCHAR(255)");
- stmt.execute("ALTER TABLE account_group_names"
- + " ALTER COLUMN name TYPE VARCHAR(255)");
-
- } else if (dialect instanceof DialectH2) {
- stmt.execute("ALTER TABLE account_groups"
- + " ALTER COLUMN name VARCHAR(255)");
- stmt.execute("ALTER TABLE account_group_names"
- + " ALTER COLUMN name VARCHAR(255) NOT NULL");
-
- } else if (dialect instanceof DialectMySQL) {
- stmt.execute("ALTER TABLE account_groups MODIFY name VARCHAR(255)");
- stmt.execute("ALTER TABLE account_group_names"
- + " MODIFY name VARCHAR(255)");
-
- } else {
- throw new OrmException("Unsupported dialect " + dialect);
- }
- } finally {
- stmt.close();
- }
-
- // Some groups might be missing their names, our older schema
- // creation logic failed to create the name objects. Do it now.
- //
- Map<AccountGroup.NameKey, AccountGroupName> names =
- db.accountGroupNames().toMap(db.accountGroupNames().all());
-
- List<AccountGroupName> insert = new ArrayList<AccountGroupName>();
- List<AccountGroupName> update = new ArrayList<AccountGroupName>();
-
- for (AccountGroup g : db.accountGroups().all()) {
- AccountGroupName n = names.get(g.getNameKey());
- if (n == null) {
- insert.add(new AccountGroupName(g));
-
- } else if (!g.getId().equals(n.getId())) {
- n.setId(g.getId());
- update.add(n);
- }
- }
-
- db.accountGroupNames().insert(insert);
- db.accountGroupNames().update(update);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_28.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_28.java
deleted file mode 100644
index cddbe4e..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_28.java
+++ /dev/null
@@ -1,101 +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.schema;
-
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gerrit.server.workflow.NoOpFunction;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collections;
-
-class Schema_28 extends SchemaVersion {
- @Inject
- Schema_28(Provider<Schema_27> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- final SystemConfig cfg = db.systemConfig().get(new SystemConfig.Key());
- ApprovalCategory cat;
-
- initForgeIdentityCategory(db, cfg);
-
- // Don't grant FORGE_COMMITTER to existing PUSH_HEAD rights. That
- // is considered a bug that we are fixing with this schema upgrade.
- // Administrators might need to relax permissions manually after the
- // upgrade if that forgery is critical to their workflow.
-
- cat = db.approvalCategories().get(ApprovalCategory.PUSH_TAG);
- if (cat != null && "Push Annotated Tag".equals(cat.getName())) {
- cat.setName("Push Tag");
- db.approvalCategories().update(Collections.singleton(cat));
- }
-
- // Since we deleted Push Tags +3, drop anything using +3 down to +2.
- //
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("UPDATE ref_rights SET max_value = "
- + ApprovalCategory.PUSH_TAG_ANNOTATED + " WHERE max_value >= 3");
- stmt.execute("UPDATE ref_rights SET min_value = "
- + ApprovalCategory.PUSH_TAG_ANNOTATED + " WHERE min_value >= 3");
- } finally {
- stmt.close();
- }
- }
-
- private void initForgeIdentityCategory(final ReviewDb c,
- final SystemConfig sConfig) throws OrmException {
- final ApprovalCategory cat;
- final ArrayList<ApprovalCategoryValue> values;
-
- cat =
- new ApprovalCategory(ApprovalCategory.FORGE_IDENTITY, "Forge Identity");
- cat.setPosition((short) -1);
- cat.setFunctionName(NoOpFunction.NAME);
- values = new ArrayList<ApprovalCategoryValue>();
- values.add(value(cat, ApprovalCategory.FORGE_AUTHOR,
- "Forge Author Identity"));
- values.add(value(cat, ApprovalCategory.FORGE_COMMITTER,
- "Forge Committer or Tagger Identity"));
- c.approvalCategories().insert(Collections.singleton(cat));
- c.approvalCategoryValues().insert(values);
-
- RefRight right =
- new RefRight(new RefRight.Key(sConfig.wildProjectName,
- new RefRight.RefPattern(RefRight.ALL),
- ApprovalCategory.FORGE_IDENTITY, sConfig.registeredGroupId));
- right.setMinValue(ApprovalCategory.FORGE_AUTHOR);
- right.setMaxValue(ApprovalCategory.FORGE_AUTHOR);
- c.refRights().insert(Collections.singleton(right));
- }
-
- private static ApprovalCategoryValue value(final ApprovalCategory cat,
- final int value, final String name) {
- return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
- (short) value), name);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_29.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_29.java
deleted file mode 100644
index 37920bf..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_29.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-class Schema_29 extends SchemaVersion {
- @Inject
- Schema_29(Provider<Schema_28> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_30.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_30.java
deleted file mode 100644
index 7285e32..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_30.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.schema;
-
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.util.Collections;
-
-class Schema_30 extends SchemaVersion {
- @Inject
- Schema_30(Provider<Schema_29> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
- db.approvalCategoryValues().insert(
- Collections.singleton(new ApprovalCategoryValue(
- new ApprovalCategoryValue.Id(ApprovalCategory.FORGE_IDENTITY,
- ApprovalCategory.FORGE_SERVER),
- "Forge Gerrit Code Review Server Identity")));
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_31.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_31.java
deleted file mode 100644
index 62f57e2..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_31.java
+++ /dev/null
@@ -1,41 +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.schema;
-
-import com.google.gerrit.reviewdb.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;
-
-class Schema_31 extends SchemaVersion {
- @Inject
- Schema_31(Provider<Schema_30> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("CREATE INDEX changes_byProject"
- + " ON changes (dest_project_name)");
- } finally {
- stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_32.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_32.java
deleted file mode 100644
index 2af5596..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_32.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_32 extends SchemaVersion {
- @Inject
- Schema_32(Provider<Schema_31> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_33.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_33.java
deleted file mode 100644
index 0c733d7..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_33.java
+++ /dev/null
@@ -1,49 +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.schema;
-
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupName;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.util.Collections;
-
-public class Schema_33 extends SchemaVersion {
- @Inject
- Schema_33(Provider<Schema_32> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
- SystemConfig config = db.systemConfig().all().toList().get(0);
- final AccountGroup batchUsers =
- new AccountGroup(new AccountGroup.NameKey("Non-Interactive Users"),
- new AccountGroup.Id(db.nextAccountGroupId()));
- batchUsers.setDescription("Users who perform batch actions on Gerrit");
- batchUsers.setOwnerGroupId(config.adminGroupId);
- batchUsers.setType(AccountGroup.Type.INTERNAL);
- db.accountGroups().insert(Collections.singleton(batchUsers));
- db.accountGroupNames().insert(
- Collections.singleton(new AccountGroupName(batchUsers)));
-
- config.batchUsersGroupId = batchUsers.getId();
- db.systemConfig().update(Collections.singleton(config));
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java
deleted file mode 100644
index fa94146..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_34.java
+++ /dev/null
@@ -1,123 +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.schema;
-
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.RefRight.RefPattern;
-import com.google.gerrit.server.project.RefControl.RefRightsForPattern;
-import com.google.gwtorm.client.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-public class Schema_34 extends SchemaVersion {
- private static final Comparator<String> DESCENDING_SORT =
- new Comparator<String>() {
-
- @Override
- public int compare(String a, String b) {
- int aLength = a.length();
- int bLength = b.length();
- if (bLength == aLength) {
- return a.compareTo(b);
- }
- return bLength - aLength;
- }
- };
-
- @Inject
- Schema_34(Provider<Schema_33> prior) {
- super(prior);
- }
-
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
- Iterable<Project> projects = db.projects().all();
- boolean showedBanner = false;
-
- List<RefRight> toUpdate = new ArrayList<RefRight>();
- List<RefRight> toDelete = new ArrayList<RefRight>();
- for (Project p : projects) {
- boolean showedProject = false;
- List<RefRight> pr = db.refRights().byProject(p.getNameKey()).toList();
- Map<ApprovalCategory.Id, Map<String, RefRightsForPattern>> r =
- new HashMap<ApprovalCategory.Id, Map<String, RefRightsForPattern>>();
- for (RefRight right : pr) {
- ApprovalCategory.Id cat = right.getApprovalCategoryId();
- if (r.get(cat) == null) {
- Map<String, RefRightsForPattern> m =
- new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
- r.put(cat, m);
- }
- if (r.get(cat).get(right.getRefPattern()) == null) {
- RefRightsForPattern s = new RefRightsForPattern();
- r.get(cat).put(right.getRefPattern(), s);
- }
- r.get(cat).get(right.getRefPattern()).addRight(right);
- }
-
- for (Map<String, RefRightsForPattern> categoryRights : r.values()) {
- for (RefRightsForPattern rrp : categoryRights.values()) {
- RefRight oldRight = rrp.getRights().get(0);
- if (shouldPrompt(oldRight)) {
- if (!showedBanner) {
- ui.message("Entering interactive reference rights migration tool...");
- showedBanner = true;
- }
- if (!showedProject) {
- ui.message("In project " + p.getName());
- showedProject = true;
- }
- ui.message("For category " + oldRight.getApprovalCategoryId());
- boolean isWildcard = oldRight.getRefPattern().endsWith("/*");
- boolean shouldUpdate = ui.yesno(!isWildcard,
- "Should rights for pattern "
- + oldRight.getRefPattern()
- + " be considered exclusive?");
- if (shouldUpdate) {
- RefRight.Key newKey = new RefRight.Key(oldRight.getProjectNameKey(),
- new RefPattern("-" + oldRight.getRefPattern()),
- oldRight.getApprovalCategoryId(),
- oldRight.getAccountGroupId());
- RefRight newRight = new RefRight(newKey);
- newRight.setMaxValue(oldRight.getMaxValue());
- newRight.setMinValue(oldRight.getMinValue());
- toUpdate.add(newRight);
- toDelete.add(oldRight);
- }
- }
- }
- }
- }
- db.refRights().insert(toUpdate);
- db.refRights().delete(toDelete);
- }
-
- private boolean shouldPrompt(RefRight right) {
- return !right.getRefPattern().equals("refs/*")
- && !right.getRefPattern().equals("refs/heads/*")
- && !right.getRefPattern().equals("refs/tags/*");
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_35.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_35.java
deleted file mode 100644
index 12d90c3..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_35.java
+++ /dev/null
@@ -1,41 +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.schema;
-
-import com.google.gerrit.reviewdb.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_35 extends SchemaVersion {
- @Inject
- Schema_35(Provider<Schema_34> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("CREATE INDEX tracking_ids_byTrkId"
- + " ON tracking_ids (tracking_id)");
- } finally {
- stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java
deleted file mode 100644
index ba6b841..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_36.java
+++ /dev/null
@@ -1,51 +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.schema;
-
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_36 extends SchemaVersion {
- @Inject
- Schema_36(Provider<Schema_35> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- if (((JdbcSchema) db).getDialect() instanceof DialectMySQL) {
- stmt.execute("DROP INDEX account_project_watches_ntNew ON account_project_watches");
- stmt.execute("DROP INDEX account_project_watches_ntCmt ON account_project_watches");
- stmt.execute("DROP INDEX account_project_watches_ntSub ON account_project_watches");
- } else {
- stmt.execute("DROP INDEX account_project_watches_ntNew");
- stmt.execute("DROP INDEX account_project_watches_ntCmt");
- stmt.execute("DROP INDEX account_project_watches_ntSub");
- }
- stmt.execute("CREATE INDEX account_project_watches_byProject"
- + " ON account_project_watches (project_name)");
- } finally {
- stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java
deleted file mode 100644
index 871f2e9..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_37.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_37 extends SchemaVersion {
- @Inject
- Schema_37(Provider<Schema_36> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java
deleted file mode 100644
index 59d6fa2..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_38.java
+++ /dev/null
@@ -1,67 +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.schema;
-
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountDiffPreference;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-
-public class Schema_38 extends SchemaVersion {
- @Inject
- Schema_38(Provider<Schema_37> prior) {
- super(prior);
- }
-
- /**
- * Migrate the account.default_context column to account_diff_preferences.context column.
- * <p>
- * Other fields in account_diff_preferences will be filled in with their defaults as
- * defined in the {@link AccountDiffPreference#createDefault(com.google.gerrit.reviewdb.Account.Id)}
- */
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
- SQLException {
- List<AccountDiffPreference> newPrefs =
- new ArrayList<AccountDiffPreference>();
-
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- ResultSet result =
- stmt.executeQuery("SELECT account_id, default_context"
- + " FROM accounts WHERE default_context <> 10");
- while (result.next()) {
- int accountId = result.getInt(1);
- short defaultContext = result.getShort(2);
- AccountDiffPreference diffPref = AccountDiffPreference.createDefault(new Account.Id(accountId));
- diffPref.setContext(defaultContext);
- newPrefs.add(diffPref);
- }
- } finally {
- stmt.close();
- }
-
- db.accountDiffPreferences().insert(newPrefs);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java
deleted file mode 100644
index 39ae226..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_39.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_39 extends SchemaVersion {
- @Inject
- Schema_39(Provider<Schema_38> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java
deleted file mode 100644
index 7d3e4f5..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_40.java
+++ /dev/null
@@ -1,70 +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.schema;
-
-import com.google.gerrit.reviewdb.AccountProjectWatch;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_40 extends SchemaVersion {
- @Inject
- Schema_40(Provider<Schema_39> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException,
- OrmException {
- // Set to "*" the filter field of the previously watched projects
- //
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("UPDATE account_project_watches" //
- + " SET filter = '" + AccountProjectWatch.FILTER_ALL + "'" //
- + " WHERE filter IS NULL OR filter = ''");
-
- // Set the new primary key
- //
- final SqlDialect dialect = ((JdbcSchema) db).getDialect();
- if (dialect instanceof DialectPostgreSQL) {
- stmt.execute("ALTER TABLE account_project_watches "
- + "DROP CONSTRAINT account_project_watches_pkey");
- stmt.execute("ALTER TABLE account_project_watches "
- + "ADD PRIMARY KEY (account_id, project_name, filter)");
-
- } else if ((dialect instanceof DialectH2)
- || (dialect instanceof DialectMySQL)) {
- stmt.execute("ALTER TABLE account_project_watches DROP PRIMARY KEY");
- stmt.execute("ALTER TABLE account_project_watches "
- + "ADD PRIMARY KEY (account_id, project_name, filter)");
-
- } else {
- throw new OrmException("Unsupported dialect " + dialect);
- }
- } finally {
- stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java
deleted file mode 100644
index 508db43..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_41 extends SchemaVersion {
- @Inject
- Schema_41(Provider<Schema_40> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.java
deleted file mode 100644
index 0edb7e5..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_43.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_43 extends SchemaVersion {
- @Inject
- Schema_43(Provider<Schema_42> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.java
deleted file mode 100644
index 4ab1986..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_44.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_44 extends SchemaVersion {
- @Inject
- Schema_44(Provider<Schema_43> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.java
deleted file mode 100644
index e37e87d..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_45.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_45 extends SchemaVersion {
- @Inject
- Schema_45(Provider<Schema_44> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java
deleted file mode 100644
index e7b104c..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java
+++ /dev/null
@@ -1,67 +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.schema;
-
-import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupName;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Collections;
-
-public class Schema_46 extends SchemaVersion {
-
- @Inject
- Schema_46(final Provider<Schema_45> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException,
- OrmException {
- AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
-
- // update system_config
- final Connection connection = ((JdbcSchema) db).getConnection();
- Statement stmt = null;
- try {
- stmt = connection.createStatement();
- stmt.execute("UPDATE system_config SET OWNER_GROUP_ID = " + groupId.get());
- final ResultSet resultSet =
- stmt.executeQuery("SELECT ADMIN_GROUP_ID FROM system_config");
- resultSet.next();
- final int adminGroupId = resultSet.getInt(1);
-
- // create 'Project Owners' group
- AccountGroup.NameKey nameKey = new AccountGroup.NameKey("Project Owners");
- AccountGroup group = new AccountGroup(nameKey, groupId);
- group.setType(AccountGroup.Type.SYSTEM);
- group.setOwnerGroupId(new AccountGroup.Id(adminGroupId));
- group.setDescription("Any owner of the project");
- AccountGroupName gn = new AccountGroupName(group);
- db.accountGroupNames().insert(Collections.singleton(gn));
- db.accountGroups().insert(Collections.singleton(group));
- } finally {
- if (stmt != null) stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.java
deleted file mode 100644
index 124cc02..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_47.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_47 extends SchemaVersion {
- @Inject
- Schema_47(Provider<Schema_46> prior) {
- super(prior);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_48.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_48.java
deleted file mode 100644
index 4e8b94d..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_48.java
+++ /dev/null
@@ -1,54 +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.schema;
-
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.ApprovalCategoryValue;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import java.util.Collections;
-
-public class Schema_48 extends SchemaVersion {
- @Inject
- Schema_48(Provider<Schema_47> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- // Read +3 allows merges to be uploaded
- db.approvalCategoryValues().insert(
- Collections.singleton(new ApprovalCategoryValue(
- new ApprovalCategoryValue.Id(ApprovalCategory.READ, (short) 3),
- "Upload merges permission")));
- // Since we added Read +3, elevate any Read +2 to that level to provide
- // access equivalent to prior schema versions.
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("UPDATE ref_rights SET max_value = 3"
- + " WHERE category_id = '" + ApprovalCategory.READ.get()
- + "' AND max_value = 2");
- } finally {
- stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_50.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_50.java
deleted file mode 100644
index 4a90c1f..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_50.java
+++ /dev/null
@@ -1,25 +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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_50 extends SchemaVersion {
- @Inject
- Schema_50(Provider<Schema_49> prior) {
- super(prior);
- }
-}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_51.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_51.java
deleted file mode 100644
index d111160..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_51.java
+++ /dev/null
@@ -1,41 +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.schema;
-
-import com.google.gerrit.reviewdb.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_51 extends SchemaVersion {
- @Inject
- Schema_51(Provider<Schema_50> prior) {
- super(prior);
- }
-
- @Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
- Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
- try {
- stmt.execute("CREATE INDEX account_group_includes_byInclude"
- + " ON account_group_includes (include_id)");
- } finally {
- stmt.close();
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_52.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_52.java
index 3fbbbe0..e16b95c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_52.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_52.java
@@ -14,12 +14,28 @@
package com.google.gerrit.server.schema;
+import com.google.gerrit.reviewdb.CurrentSchemaVersion;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
public class Schema_52 extends SchemaVersion {
@Inject
- Schema_52(Provider<Schema_51> prior) {
- super(prior);
+ Schema_52() {
+ super(new Provider<SchemaVersion>() {
+ public SchemaVersion get() {
+ throw new ProvisionException("Cannot upgrade from 51");
+ }
+ });
+ }
+
+ @Override
+ protected void upgradeFrom(UpdateUI ui, CurrentSchemaVersion curr,
+ ReviewDb db, boolean toTargetVersion) throws OrmException {
+ throw new OrmException("Cannot upgrade from schema " + curr.versionNbr
+ + "; manually run init from Gerrit Code Review 2.1.7"
+ + " and restart this version to continue.");
}
}
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
new file mode 100644
index 0000000..5e90e8e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -0,0 +1,473 @@
+// 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.schema;
+
+import static com.google.gerrit.common.data.Permission.CREATE;
+import static com.google.gerrit.common.data.Permission.FORGE_AUTHOR;
+import static com.google.gerrit.common.data.Permission.FORGE_COMMITTER;
+import static com.google.gerrit.common.data.Permission.FORGE_SERVER;
+import static com.google.gerrit.common.data.Permission.LABEL;
+import static com.google.gerrit.common.data.Permission.OWNER;
+import static com.google.gerrit.common.data.Permission.PUSH;
+import static com.google.gerrit.common.data.Permission.PUSH_MERGE;
+import static com.google.gerrit.common.data.Permission.PUSH_TAG;
+import static com.google.gerrit.common.data.Permission.READ;
+import static com.google.gerrit.common.data.Permission.SUBMIT;
+
+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.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.GroupUUID;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.NoReplication;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.jdbc.JdbcSchema;
+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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class Schema_53 extends SchemaVersion {
+ private final GitRepositoryManager mgr;
+ private final PersonIdent serverUser;
+
+ private SystemConfig systemConfig;
+ private Map<AccountGroup.Id, GroupReference> groupMap;
+ private Map<ApprovalCategory.Id, ApprovalCategory> categoryMap;
+ private GroupReference projectOwners;
+
+ private Map<Project.NameKey, Project.NameKey> parentsByProject;
+ private Map<Project.NameKey, List<OldRefRight>> rightsByProject;
+
+ private final String OLD_SUBMIT = "SUBM";
+ private final String OLD_READ = "READ";
+ private final String OLD_OWN = "OWN";
+ private final String OLD_PUSH_TAG = "pTAG";
+ private final String OLD_PUSH_HEAD = "pHD";
+ private final String OLD_FORGE_IDENTITY = "FORG";
+
+ @Inject
+ Schema_53(Provider<Schema_52> 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 {
+ systemConfig = db.systemConfig().get(new SystemConfig.Key());
+ categoryMap = db.approvalCategories().toMap(db.approvalCategories().all());
+
+ assignGroupUUIDs(db);
+ readOldRefRights(db);
+ readProjectParents(db);
+ exportProjectConfig(db);
+
+ deleteActionCategories(db);
+ }
+
+ private void deleteActionCategories(ReviewDb db) throws OrmException {
+ List<ApprovalCategory> delete = new ArrayList<ApprovalCategory>();
+ for (ApprovalCategory category : categoryMap.values()) {
+ if (category.getPosition() < 0) {
+ delete.add(category);
+ }
+ }
+ db.approvalCategories().delete(delete);
+ }
+
+ private void assignGroupUUIDs(ReviewDb db) throws OrmException {
+ groupMap = new HashMap<AccountGroup.Id, GroupReference>();
+ List<AccountGroup> groups = db.accountGroups().all().toList();
+ for (AccountGroup g : groups) {
+ if (g.getId().equals(systemConfig.ownerGroupId)) {
+ g.setGroupUUID(AccountGroup.PROJECT_OWNERS);
+ projectOwners = GroupReference.forGroup(g);
+
+ } else if (g.getId().equals(systemConfig.anonymousGroupId)) {
+ g.setGroupUUID(AccountGroup.ANONYMOUS_USERS);
+
+ } else if (g.getId().equals(systemConfig.registeredGroupId)) {
+ g.setGroupUUID(AccountGroup.REGISTERED_USERS);
+
+ } else {
+ g.setGroupUUID(GroupUUID.make(g.getName(), serverUser));
+ }
+ groupMap.put(g.getId(), GroupReference.forGroup(g));
+ }
+ db.accountGroups().update(groups);
+
+ systemConfig.adminGroupUUID = toUUID(systemConfig.adminGroupId);
+ systemConfig.batchUsersGroupUUID = toUUID(systemConfig.batchUsersGroupId);
+ db.systemConfig().update(Collections.singleton(systemConfig));
+ }
+
+ private AccountGroup.UUID toUUID(AccountGroup.Id id) {
+ return groupMap.get(id).getUUID();
+ }
+
+ private void exportProjectConfig(ReviewDb db) throws OrmException,
+ SQLException {
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT * FROM projects ORDER BY name");
+ while (rs.next()) {
+ final String name = rs.getString("name");
+ final Project.NameKey nameKey = new Project.NameKey(name);
+
+ Repository git;
+ try {
+ git = mgr.openRepository(nameKey);
+ } catch (RepositoryNotFoundException notFound) {
+ // A repository may be missing if this project existed only to store
+ // inheritable permissions. For example 'All-Projects'.
+ try {
+ git = mgr.createRepository(nameKey);
+ } catch (RepositoryNotFoundException err) {
+ throw new OrmException("Cannot create repository " + name, err);
+ }
+ }
+ try {
+ MetaDataUpdate md =
+ new MetaDataUpdate(new NoReplication(), nameKey, git);
+ md.getCommitBuilder().setAuthor(serverUser);
+ md.getCommitBuilder().setCommitter(serverUser);
+
+ ProjectConfig config = ProjectConfig.read(md);
+ loadProject(rs, config.getProject());
+ config.getAccessSections().clear();
+ convertRights(config);
+
+ // Grant out read on the config branch by default.
+ //
+ if (config.getProject().getNameKey().equals(systemConfig.wildProjectName)) {
+ AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
+ Permission read = meta.getPermission(READ, true);
+ read.getRule(config.resolve(projectOwners), true);
+ }
+
+ md.setMessage("Import project configuration from SQL\n");
+ if (!config.commit(md)) {
+ throw new OrmException("Cannot export project " + name);
+ }
+ } catch (ConfigInvalidException err) {
+ throw new OrmException("Cannot read project " + name, err);
+ } catch (IOException err) {
+ throw new OrmException("Cannot export project " + name, err);
+ } finally {
+ git.close();
+ }
+ }
+ rs.close();
+ stmt.close();
+ }
+
+ private void loadProject(ResultSet rs, Project project) throws SQLException,
+ OrmException {
+ project.setDescription(rs.getString("description"));
+ project.setUseContributorAgreements("Y".equals(rs
+ .getString("use_contributor_agreements")));
+
+ switch (rs.getString("submit_type").charAt(0)) {
+ case 'F':
+ project.setSubmitType(Project.SubmitType.FAST_FORWARD_ONLY);
+ break;
+ case 'M':
+ project.setSubmitType(Project.SubmitType.MERGE_IF_NECESSARY);
+ break;
+ case 'A':
+ project.setSubmitType(Project.SubmitType.MERGE_ALWAYS);
+ break;
+ case 'C':
+ project.setSubmitType(Project.SubmitType.CHERRY_PICK);
+ break;
+ default:
+ throw new OrmException("Unsupported submit_type="
+ + rs.getString("submit_type") + " on project " + project.getName());
+ }
+
+ project.setUseSignedOffBy("Y".equals(rs.getString("use_signed_off_by")));
+ project.setRequireChangeID("Y".equals(rs.getString("require_change_id")));
+ project.setUseContentMerge("Y".equals(rs.getString("use_content_merge")));
+ project.setParentName(rs.getString("parent_name"));
+ }
+
+ private void readOldRefRights(ReviewDb db) throws SQLException {
+ rightsByProject = new HashMap<Project.NameKey, List<OldRefRight>>();
+
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT * FROM ref_rights");
+ while (rs.next()) {
+ OldRefRight right = new OldRefRight(rs);
+ if (right.group == null || right.category == null) {
+ continue;
+ }
+
+ List<OldRefRight> list;
+
+ list = rightsByProject.get(right.project);
+ if (list == null) {
+ list = new ArrayList<OldRefRight>();
+ rightsByProject.put(right.project, list);
+ }
+ list.add(right);
+ }
+ rs.close();
+ stmt.close();
+ }
+
+ private void readProjectParents(ReviewDb db) throws SQLException {
+ parentsByProject = new HashMap<Project.NameKey, Project.NameKey>();
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT * FROM projects");
+ while (rs.next()) {
+ String name = rs.getString("name");
+ String parent_name = rs.getString("parent_name");
+ if (parent_name == null) {
+ parent_name = systemConfig.wildProjectName.get();
+ }
+ parentsByProject.put(new Project.NameKey(name), //
+ new Project.NameKey(parent_name));
+ }
+ rs.close();
+ stmt.close();
+ }
+
+ private void convertRights(ProjectConfig config) {
+ List<OldRefRight> myRights =
+ rightsByProject.get(config.getProject().getNameKey());
+ if (myRights == null) {
+ return;
+ }
+
+ for (OldRefRight old : myRights) {
+ AccessSection section = config.getAccessSection(old.ref_pattern, true);
+ GroupReference group = config.resolve(old.group);
+
+ if (OLD_SUBMIT.equals(old.category)) {
+ PermissionRule submit = rule(group);
+ submit.setDeny(old.max_value <= 0);
+ add(section, SUBMIT, old.exclusive, submit);
+
+ } else if (OLD_READ.equals(old.category)) {
+ if (old.exclusive) {
+ section.getPermission(READ, true).setExclusiveGroup(true);
+ newChangePermission(config, old.ref_pattern).setExclusiveGroup(true);
+ }
+
+ PermissionRule read = rule(group);
+ read.setDeny(old.max_value <= 0);
+ add(section, READ, old.exclusive, read);
+
+ if (3 <= old.max_value) {
+ newMergePermission(config, old.ref_pattern).add(rule(group));
+ } else if (3 <= inheritedMax(config, old)) {
+ newMergePermission(config, old.ref_pattern).add(deny(group));
+ }
+
+ if (2 <= old.max_value) {
+ newChangePermission(config, old.ref_pattern).add(rule(group));
+ } else if (2 <= inheritedMax(config, old)) {
+ newChangePermission(config, old.ref_pattern).add(deny(group));
+ }
+
+ } else if (OLD_OWN.equals(old.category)) {
+ add(section, OWNER, false, rule(group));
+
+ } else if (OLD_PUSH_TAG.equals(old.category)) {
+ PermissionRule push = rule(group);
+ push.setDeny(old.max_value <= 0);
+ add(section, PUSH_TAG, old.exclusive, push);
+
+ } else if (OLD_PUSH_HEAD.equals(old.category)) {
+ if (old.exclusive) {
+ section.getPermission(PUSH, true).setExclusiveGroup(true);
+ section.getPermission(CREATE, true).setExclusiveGroup(true);
+ }
+
+ PermissionRule push = rule(group);
+ push.setDeny(old.max_value <= 0);
+ push.setForce(3 <= old.max_value);
+ add(section, PUSH, old.exclusive, push);
+
+ if (2 <= old.max_value) {
+ add(section, CREATE, old.exclusive, rule(group));
+ } else if (2 <= inheritedMax(config, old)) {
+ add(section, CREATE, old.exclusive, deny(group));
+ }
+
+ } else if (OLD_FORGE_IDENTITY.equals(old.category)) {
+ if (old.exclusive) {
+ section.getPermission(FORGE_AUTHOR, true).setExclusiveGroup(true);
+ section.getPermission(FORGE_COMMITTER, true).setExclusiveGroup(true);
+ section.getPermission(FORGE_SERVER, true).setExclusiveGroup(true);
+ }
+
+ if (1 <= old.max_value) {
+ add(section, FORGE_AUTHOR, old.exclusive, rule(group));
+ }
+
+ if (2 <= old.max_value) {
+ add(section, FORGE_COMMITTER, old.exclusive, rule(group));
+ } else if (2 <= inheritedMax(config, old)) {
+ add(section, FORGE_COMMITTER, old.exclusive, deny(group));
+ }
+
+ if (3 <= old.max_value) {
+ add(section, FORGE_SERVER, old.exclusive, rule(group));
+ } else if (3 <= inheritedMax(config, old)) {
+ add(section, FORGE_SERVER, old.exclusive, deny(group));
+ }
+
+ } else {
+ PermissionRule rule = rule(group);
+ rule.setRange(old.min_value, old.max_value);
+ if (old.min_value == 0 && old.max_value == 0) {
+ rule.setDeny(true);
+ }
+ add(section, LABEL + varNameOf(old.category), old.exclusive, rule);
+ }
+ }
+ }
+
+ private static Permission newChangePermission(ProjectConfig config,
+ String name) {
+ if (name.startsWith(AccessSection.REGEX_PREFIX)) {
+ name = AccessSection.REGEX_PREFIX
+ + "refs/for/"
+ + name.substring(AccessSection.REGEX_PREFIX.length());
+ } else {
+ name = "refs/for/" + name;
+ }
+ return config.getAccessSection(name, true).getPermission(PUSH, true);
+ }
+
+ private static Permission newMergePermission(ProjectConfig config,
+ String name) {
+ if (name.startsWith(AccessSection.REGEX_PREFIX)) {
+ name = AccessSection.REGEX_PREFIX
+ + "refs/for/"
+ + name.substring(AccessSection.REGEX_PREFIX.length());
+ } else {
+ name = "refs/for/" + name;
+ }
+ return config.getAccessSection(name, true).getPermission(PUSH_MERGE, true);
+ }
+
+ private static PermissionRule rule(GroupReference group) {
+ return new PermissionRule(group);
+ }
+
+ private static PermissionRule deny(GroupReference group) {
+ PermissionRule rule = rule(group);
+ rule.setDeny(true);
+ return rule;
+ }
+
+ private int inheritedMax(ProjectConfig config, OldRefRight old) {
+ int max = 0;
+
+ String ref = old.ref_pattern;
+ String category = old.category;
+ AccountGroup.UUID group = old.group.getUUID();
+
+ Project.NameKey project = config.getProject().getParent();
+ if (project == null) {
+ project = systemConfig.wildProjectName;
+ }
+ do {
+ List<OldRefRight> rights = rightsByProject.get(project);
+ if (rights != null) {
+ for (OldRefRight r : rights) {
+ if (r.ref_pattern.equals(ref) //
+ && r.group.getUUID().equals(group) //
+ && r.category.equals(category)) {
+ max = Math.max(max, r.max_value);
+ break;
+ }
+ }
+ }
+ project = parentsByProject.get(project);
+ } while (!project.equals(systemConfig.wildProjectName));
+
+ return max;
+ }
+
+ private String varNameOf(String id) {
+ ApprovalCategory category = categoryMap.get(new ApprovalCategory.Id(id));
+ if (category == null) {
+ category = new ApprovalCategory(new ApprovalCategory.Id(id), id);
+ }
+ return category.getLabelName();
+ }
+
+ private static void add(AccessSection section, String name,
+ boolean exclusive, PermissionRule rule) {
+ Permission p = section.getPermission(name, true);
+ if (exclusive) {
+ p.setExclusiveGroup(true);
+ }
+ p.add(rule);
+ }
+
+ private class OldRefRight {
+ final int min_value;
+ final int max_value;
+ final String ref_pattern;
+ final boolean exclusive;
+ final GroupReference group;
+ final String category;
+ final Project.NameKey project;
+
+ OldRefRight(ResultSet rs) throws SQLException {
+ min_value = rs.getInt("min_value");
+ max_value = rs.getInt("max_value");
+ project = new Project.NameKey(rs.getString("project_name"));
+
+ String r = rs.getString("ref_pattern");
+ exclusive = r.startsWith("-");
+ if (exclusive) {
+ r = r.substring(1);
+ }
+ ref_pattern = r;
+
+ category = rs.getString("category_id");
+ group = groupMap.get(new AccountGroup.Id(rs.getInt("group_id")));
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_54.java
similarity index 89%
rename from gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_54.java
index 0977ee9..77c6775 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_49.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_54.java
@@ -17,10 +17,9 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
-public class Schema_49 extends SchemaVersion {
-
+public class Schema_54 extends SchemaVersion {
@Inject
- Schema_49(Provider<Schema_48> prior) {
+ Schema_54(Provider<Schema_53> prior) {
super(prior);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_55.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_55.java
new file mode 100644
index 0000000..8d00b73
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_55.java
@@ -0,0 +1,61 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.util.Collections;
+
+public class Schema_55 extends SchemaVersion {
+ private final LocalDiskRepositoryManager mgr;
+
+ @Inject
+ Schema_55(Provider<Schema_54> prior, LocalDiskRepositoryManager mgr) {
+ super(prior);
+ this.mgr = mgr;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
+ SystemConfig sc = db.systemConfig().get(new SystemConfig.Key());
+ String oldName = sc.wildProjectName.get();
+ String newName = "All-Projects";
+ if ("-- All Projects --".equals(oldName)) {
+ ui.message("Renaming \"" + oldName + "\" to \"" + newName + "\"");
+
+ File base = mgr.getBasePath();
+ File oldDir = FileKey.resolve(new File(base, oldName), FS.DETECTED);
+ File newDir = new File(base, newName + Constants.DOT_GIT_EXT);
+ if (!oldDir.renameTo(newDir)) {
+ throw new OrmException("Cannot rename " + oldDir.getAbsolutePath()
+ + " to " + newDir.getAbsolutePath());
+ }
+
+ sc.wildProjectName = new Project.NameKey(newName);
+ db.systemConfig().update(Collections.singleton(sc));
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
new file mode 100644
index 0000000..bbb7109
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
@@ -0,0 +1,80 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class Schema_56 extends SchemaVersion {
+ private final LocalDiskRepositoryManager mgr;
+ private final Set<String> keysOne;
+ private final Set<String> keysTwo;
+
+ @Inject
+ Schema_56(Provider<Schema_55> prior, LocalDiskRepositoryManager mgr) {
+ super(prior);
+ this.mgr = mgr;
+
+ keysOne = new HashSet<String>();
+ keysTwo = new HashSet<String>();
+
+ keysOne.add(GitRepositoryManager.REF_CONFIG);
+ keysTwo.add(GitRepositoryManager.REF_CONFIG);
+ keysTwo.add(Constants.HEAD);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) {
+ for (Project.NameKey name : mgr.list()) {
+ Repository git;
+ try {
+ git = mgr.openRepository(name);
+ } catch (RepositoryNotFoundException e) {
+ ui.message("warning: Cannot open " + name.get());
+ continue;
+ }
+ try {
+ Map<String, Ref> all = git.getAllRefs();
+ if (all.keySet().equals(keysOne) || all.keySet().equals(keysTwo)) {
+ try {
+ RefUpdate update = git.updateRef(Constants.HEAD);
+ update.disableRefLog();
+ update.link(GitRepositoryManager.REF_CONFIG);
+ } catch (IOException err) {
+ ui.message("warning: " + name.get() + ": Cannot update HEAD to "
+ + GitRepositoryManager.REF_CONFIG + ": " + err.getMessage());
+ }
+ }
+ } finally {
+ git.close();
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..2ae1b7a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
@@ -0,0 +1,170 @@
+// 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.schema;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountGroupName;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.NoReplication;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.IOException;
+import java.util.Collections;
+
+public class Schema_57 extends SchemaVersion {
+ private final SitePaths site;
+ private final LocalDiskRepositoryManager mgr;
+ private final PersonIdent serverUser;
+
+ @Inject
+ Schema_57(Provider<Schema_56> prior, SitePaths site,
+ LocalDiskRepositoryManager mgr, @GerritPersonIdent PersonIdent serverUser) {
+ super(prior);
+ this.site = site;
+ this.mgr = mgr;
+ this.serverUser = serverUser;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
+ SystemConfig sc = db.systemConfig().get(new SystemConfig.Key());
+ Project.NameKey allProjects = sc.wildProjectName;
+
+ FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+ boolean cfgDirty = false;
+ try {
+ cfg.load();
+ } catch (ConfigInvalidException err) {
+ throw new OrmException("Cannot read " + site.gerrit_config, err);
+ } catch (IOException err) {
+ throw new OrmException("Cannot read " + site.gerrit_config, err);
+ }
+
+ if (!allProjects.get().equals(AllProjectsNameProvider.DEFAULT)) {
+ ui.message("Setting gerrit.allProjects = " + allProjects.get());
+ cfg.setString("gerrit", null, "allProjects", allProjects.get());
+ cfgDirty = true;
+ }
+
+ try {
+ Repository git = mgr.openRepository(allProjects);
+ try {
+ MetaDataUpdate md = new MetaDataUpdate(new NoReplication(), allProjects, git);
+ md.getCommitBuilder().setAuthor(serverUser);
+ md.getCommitBuilder().setCommitter(serverUser);
+
+ ProjectConfig config = ProjectConfig.read(md);
+ AccessSection cap = config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true);
+
+ // Move the Administrators group reference to All-Projects.
+ cap.getPermission(GlobalCapability.ADMINISTRATE_SERVER, true)
+ .add(new PermissionRule(config.resolve(db.accountGroups().get(sc.adminGroupId))));
+
+ // Move the repository.*.createGroup to Create Project.
+ String[] createGroupList = cfg.getStringList("repository", "*", "createGroup");
+ for (String name : createGroupList) {
+ AccountGroup.NameKey key = new AccountGroup.NameKey(name);
+ AccountGroupName groupName = db.accountGroupNames().get(key);
+ if (groupName == null) {
+ continue;
+ }
+
+ AccountGroup group = db.accountGroups().get(groupName.getId());
+ if (group == null) {
+ continue;
+ }
+
+ cap.getPermission(GlobalCapability.CREATE_PROJECT, true)
+ .add(new PermissionRule(config.resolve(group)));
+ }
+ if (createGroupList.length != 0) {
+ ui.message("Moved repository.*.createGroup to 'Create Project' capability");
+ cfg.unset("repository", "*", "createGroup");
+ cfgDirty = true;
+ }
+
+ AccountGroup batch = db.accountGroups().get(sc.batchUsersGroupId);
+ if (batch != null
+ && db.accountGroupMembers().byGroup(sc.batchUsersGroupId).toList().isEmpty()
+ && db.accountGroupIncludes().byGroup(sc.batchUsersGroupId).toList().isEmpty()) {
+ // If the batch user group is not used, delete it.
+ //
+ db.accountGroups().delete(Collections.singleton(batch));
+
+ AccountGroupName name = db.accountGroupNames().get(batch.getNameKey());
+ if (name != null) {
+ db.accountGroupNames().delete(Collections.singleton(name));
+ }
+ } else if (batch != null) {
+ cap.getPermission(GlobalCapability.PRIORITY, true)
+ .getRule(config.resolve(batch), true)
+ .setAction(Action.BATCH);
+ }
+
+ md.setMessage("Upgrade to Gerrit Code Review schema 57\n");
+ if (!config.commit(md)) {
+ throw new OrmException("Cannot update " + allProjects);
+ }
+ } finally {
+ git.close();
+ }
+ } catch (ConfigInvalidException err) {
+ throw new OrmException("Cannot read " + allProjects, err);
+ } catch (IOException err) {
+ throw new OrmException("Cannot update " + allProjects, err);
+ }
+
+ if (cfgDirty) {
+ try {
+ cfg.save();
+ } catch (IOException err) {
+ throw new OrmException("Cannot update " + site.gerrit_config, err);
+ }
+ }
+
+ // We cannot set the columns to NULL, so use 0 and a DELETED tag.
+ sc.adminGroupId = new AccountGroup.Id(0);
+ sc.adminGroupUUID = new AccountGroup.UUID("DELETED");
+ sc.anonymousGroupId = new AccountGroup.Id(0);
+ sc.registeredGroupId = new AccountGroup.Id(0);
+ sc.wildProjectName = new Project.NameKey("DELETED");
+ sc.ownerGroupId = new AccountGroup.Id(0);
+ sc.batchUsersGroupId = new AccountGroup.Id(0);
+ sc.batchUsersGroupUUID = new AccountGroup.UUID("DELETED");
+ sc.registerEmailPrivateKey = "DELETED";
+
+ db.systemConfig().update(Collections.singleton(sc));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java
index 67003932..ffed95a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java
@@ -15,11 +15,10 @@
package com.google.gerrit.server.workflow;
import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.PatchSetApproval;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.project.RefControl;
import java.util.HashMap;
import java.util.Map;
@@ -29,7 +28,6 @@
private static Map<String, CategoryFunction> all =
new HashMap<String, CategoryFunction>();
static {
- all.put(SubmitFunction.NAME, new SubmitFunction());
all.put(MaxWithBlock.NAME, new MaxWithBlock());
all.put(MaxNoBlock.NAME, new MaxNoBlock());
all.put(NoOpFunction.NAME, new NoOpFunction());
@@ -44,22 +42,11 @@
* is not known to Gerrit and thus cannot be executed.
*/
public static CategoryFunction forCategory(final ApprovalCategory category) {
- final CategoryFunction r = forName(category.getFunctionName());
+ final CategoryFunction r = all.get(category.getFunctionName());
return r != null ? r : new NoOpFunction();
}
/**
- * Locate a function by name.
- *
- * @param functionName the function's unique name.
- * @return the function implementation; null if the function is not known to
- * Gerrit and thus cannot be executed.
- */
- public static CategoryFunction forName(final String functionName) {
- return all.get(functionName);
- }
-
- /**
* Normalize ChangeApprovals and set the valid flag for this category.
* <p>
* Implementors should invoke:
@@ -92,13 +79,8 @@
public boolean isValid(final CurrentUser user, final ApprovalType at,
final FunctionState state) {
- RefControl rc = state.controlFor(user);
- for (final RefRight pr : rc.getApplicableRights(at.getCategory().getId())) {
- if (user.getEffectiveGroups().contains(pr.getAccountGroupId())
- && (pr.getMinValue() < 0 || pr.getMaxValue() > 0)) {
- return true;
- }
- }
- return false;
+ return !state.controlFor(user) //
+ .getRange(Permission.forLabel(at.getCategory().getLabelName())) //
+ .isEmpty();
}
}
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 36a52e2..2cb3e81 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
@@ -16,13 +16,13 @@
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
-import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ApprovalCategory.Id;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -139,28 +139,12 @@
* of them is used.
* <p>
*/
- private void applyRightFloor(final PatchSetApproval a) {
+ private void applyRightFloor(final ApprovalType at, final PatchSetApproval a) {
+ final ApprovalCategory category = at.getCategory();
+ final String permission = Permission.forLabel(category.getLabelName());
final IdentifiedUser user = userFactory.create(a.getAccountId());
- RefControl rc = controlFor(user);
-
- // Find the maximal range actually granted to the user.
- //
- short minAllowed = 0, maxAllowed = 0;
- for (final RefRight r : rc.getApplicableRights(a.getCategoryId())) {
- final AccountGroup.Id grp = r.getAccountGroupId();
- if (user.getEffectiveGroups().contains(grp)) {
- minAllowed = (short) Math.min(minAllowed, r.getMinValue());
- maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue());
- }
- }
-
- // Normalize the value into that range.
- //
- if (a.getValue() < minAllowed) {
- a.setValue(minAllowed);
- } else if (a.getValue() > maxAllowed) {
- a.setValue(maxAllowed);
- }
+ final PermissionRange range = controlFor(user).getRange(permission);
+ a.setValue((short) range.squash(a.getValue()));
}
RefControl controlFor(final CurrentUser user) {
@@ -172,7 +156,7 @@
/** Run <code>applyTypeFloor</code>, <code>applyRightFloor</code>. */
public void normalize(final ApprovalType at, final PatchSetApproval ca) {
applyTypeFloor(at, ca);
- applyRightFloor(ca);
+ applyRightFloor(at, ca);
}
private static Id id(final ApprovalType at) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java
deleted file mode 100644
index f0a00ff..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java
+++ /dev/null
@@ -1,68 +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.server.workflow;
-
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.reviewdb.ApprovalCategory;
-import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.project.RefControl;
-
-/**
- * Computes if the submit function can be used.
- * <p>
- * In order to be considered "approved" this function requires that all approval
- * categories with a position >= 0 (that is any whose
- * {@link ApprovalCategory#isAction()} method returns false) is valid and that
- * the change state be {@link Change.Status#NEW}.
- * <p>
- * This is mostly useful for actions, like {@link ApprovalCategory#SUBMIT}.
- */
-public class SubmitFunction extends CategoryFunction {
- public static String NAME = "Submit";
-
- @Override
- public void run(final ApprovalType at, final FunctionState state) {
- state.valid(at, valid(at, state));
- }
-
- @Override
- public boolean isValid(final CurrentUser user, final ApprovalType at,
- final FunctionState state) {
- if (valid(at, state)) {
- RefControl rc = state.controlFor(user);
- for (final RefRight pr : rc.getApplicableRights(at.getCategory().getId())) {
- if (user.getEffectiveGroups().contains(pr.getAccountGroupId())
- && pr.getMaxValue() > 0) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static boolean valid(final ApprovalType at, final FunctionState state) {
- if (state.getChange().getStatus() != Change.Status.NEW) {
- return false;
- }
- for (final ApprovalType t : state.getApprovalTypes()) {
- if (!state.isValid(t)) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_$load_commit_labels_1.java b/gerrit-server/src/main/java/gerrit/PRED_$load_commit_labels_1.java
new file mode 100644
index 0000000..eff444e
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_$load_commit_labels_1.java
@@ -0,0 +1,82 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package gerrit;
+
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gwtorm.client.OrmException;
+
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
+import com.googlecode.prolog_cafe.lang.JavaException;
+import com.googlecode.prolog_cafe.lang.ListTerm;
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+/** Exports list of {@code commit_label( label('Code-Review', 2), user(12345789) )}. */
+class PRED_$load_commit_labels_1 extends Predicate.P1 {
+ private static final long serialVersionUID = 1L;
+
+ private static final SymbolTerm sym_commit_label = SymbolTerm.intern("commit_label", 2);
+ private static final SymbolTerm sym_label = SymbolTerm.intern("label", 2);
+ private static final SymbolTerm sym_user = SymbolTerm.intern("user", 1);
+
+ PRED_$load_commit_labels_1(Term a1, Operation n) {
+ arg1 = a1;
+ cont = n;
+ }
+
+ @Override
+ public Operation exec(Prolog engine) throws PrologException {
+ engine.setB0();
+ Term a1 = arg1.dereference();
+
+ Term listHead = Prolog.Nil;
+ try {
+ PrologEnvironment env = (PrologEnvironment) engine.control;
+ ReviewDb db = StoredValues.REVIEW_DB.get(engine);
+ PatchSet.Id patchSetId = StoredValues.PATCH_SET_ID.get(engine);
+ ApprovalTypes types = env.getInjector().getInstance(ApprovalTypes.class);
+
+ for (PatchSetApproval a : db.patchSetApprovals().byPatchSet(patchSetId)) {
+ if (a.getValue() == 0) {
+ continue;
+ }
+
+ ApprovalType t = types.byId(a.getCategoryId());
+ if (t == null) {
+ continue;
+ }
+
+ StructureTerm labelTerm = new StructureTerm(
+ sym_label,
+ SymbolTerm.intern(t.getCategory().getLabelName()),
+ new IntegerTerm(a.getValue()));
+
+ StructureTerm userTerm = new StructureTerm(
+ sym_user,
+ new IntegerTerm(a.getAccountId().get()));
+
+ listHead = new ListTerm(
+ new StructureTerm(sym_commit_label, labelTerm, userTerm),
+ listHead);
+ }
+ } catch (OrmException err) {
+ throw new JavaException(this, 1, err);
+ }
+
+ if (!a1.unify(listHead, engine.trail)) {
+ return engine.fail();
+ }
+ return cont;
+ }
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_$user_label_range_4.java b/gerrit-server/src/main/java/gerrit/PRED_$user_label_range_4.java
new file mode 100644
index 0000000..2fdf5ab
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_$user_label_range_4.java
@@ -0,0 +1,94 @@
+// 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 gerrit;
+
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.project.ChangeControl;
+
+import com.googlecode.prolog_cafe.lang.IllegalTypeException;
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.PInstantiationException;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.Term;
+
+/**
+ * Resolves the valid range for a label on a CurrentUser.
+ *
+ * <pre>
+ * '$user_label_range'(+Label, +CurrentUser, -Min, -Max)
+ * </pre>
+ */
+class PRED_$user_label_range_4 extends Predicate.P4 {
+ private static final long serialVersionUID = 1L;
+
+ PRED_$user_label_range_4(Term a1, Term a2, Term a3, Term a4, Operation n) {
+ arg1 = a1;
+ arg2 = a2;
+ arg3 = a3;
+ arg4 = a4;
+ cont = n;
+ }
+
+ @Override
+ public Operation exec(Prolog engine) throws PrologException {
+ engine.setB0();
+ Term a1 = arg1.dereference();
+ Term a2 = arg2.dereference();
+ Term a3 = arg3.dereference();
+ Term a4 = arg4.dereference();
+
+ if (a1.isVariable()) {
+ throw new PInstantiationException(this, 1);
+ }
+ if (!a1.isSymbol()) {
+ throw new IllegalTypeException(this, 1, "atom", a1);
+ }
+ String label = a1.name();
+
+ if (a2.isVariable()) {
+ throw new PInstantiationException(this, 2);
+ }
+ if (!a2.isJavaObject() || !a2.convertible(CurrentUser.class)) {
+ throw new IllegalTypeException(this, 2, "CurrentUser)", a2);
+ }
+ CurrentUser user = (CurrentUser) ((JavaObjectTerm) a2).object();
+
+ ChangeControl ctl = StoredValues.CHANGE_CONTROL.get(engine).forUser(user);
+ PermissionRange range = ctl.getRange(Permission.LABEL + label);
+ if (range == null) {
+ return engine.fail();
+ }
+
+ IntegerTerm min = new IntegerTerm(range.getMin());
+ IntegerTerm max = new IntegerTerm(range.getMax());
+
+ if (!a3.unify(min, engine.trail)) {
+ return engine.fail();
+ }
+
+ if (!a4.unify(max, engine.trail)) {
+ return engine.fail();
+ }
+
+ return cont;
+ }
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
new file mode 100644
index 0000000..fe74f5d
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
@@ -0,0 +1,155 @@
+// 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 gerrit;
+
+import static com.googlecode.prolog_cafe.lang.SymbolTerm.intern;
+
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Provider;
+
+import com.googlecode.prolog_cafe.lang.HashtableOfTerm;
+import com.googlecode.prolog_cafe.lang.IllegalTypeException;
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
+import com.googlecode.prolog_cafe.lang.InternalException;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.PInstantiationException;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+/**
+ * Loads a CurrentUser object for a user identity.
+ * <p>
+ * Values are cached in the hash {@code current_user}, avoiding recreation
+ * during a single evaluation.
+ *
+ * <pre>
+ * current_user(user(+AccountId), -CurrentUser).
+ * </pre>
+ */
+class PRED_current_user_2 extends Predicate.P2 {
+ private static final long serialVersionUID = 1L;
+ private static final SymbolTerm user = intern("user", 1);
+ private static final SymbolTerm anonymous = intern("anonymous");
+ private static final SymbolTerm current_user = intern("current_user");
+
+ PRED_current_user_2(Term a1, Term a2, Operation n) {
+ arg1 = a1;
+ arg2 = a2;
+ cont = n;
+ }
+
+ @Override
+ public Operation exec(Prolog engine) throws PrologException {
+ engine.setB0();
+ Term a1 = arg1.dereference();
+ Term a2 = arg2.dereference();
+
+ if (a1.isVariable()) {
+ throw new PInstantiationException(this, 1);
+ }
+
+ HashtableOfTerm userHash = userHash(engine);
+ Term userTerm = userHash.get(a1);
+ if (userTerm != null && userTerm.isJavaObject()) {
+ if (!(((JavaObjectTerm) userTerm).object() instanceof CurrentUser)) {
+ userTerm = createUser(engine, a1, userHash);
+ }
+ } else {
+ userTerm = createUser(engine, a1, userHash);
+ }
+
+ if (!a2.unify(userTerm, engine.trail)) {
+ return engine.fail();
+ }
+
+ return cont;
+ }
+
+ public Term createUser(Prolog engine, Term key, HashtableOfTerm userHash) {
+ if (!key.isStructure()
+ || key.arity() != 1
+ || !((StructureTerm) key).functor().equals(user)) {
+ throw new IllegalTypeException(this, 1, "user(int)", key);
+ }
+
+ Term idTerm = key.arg(0);
+ CurrentUser user;
+ if (idTerm.isInteger()) {
+ Account.Id accountId = new Account.Id(((IntegerTerm) idTerm).intValue());
+
+ final ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
+ IdentifiedUser.GenericFactory userFactory = userFactory(engine);
+ if (db != null) {
+ user = userFactory.create(new Provider<ReviewDb>() {
+ public ReviewDb get() {
+ return db;
+ }
+ }, accountId);
+ } else {
+ user = userFactory.create(accountId);
+ }
+
+
+ } else if (idTerm.equals(anonymous)) {
+ user = anonymousUser(engine);
+
+ } else {
+ throw new IllegalTypeException(this, 1, "user(int)", key);
+ }
+
+ Term userTerm = new JavaObjectTerm(user);
+ userHash.put(key, userTerm);
+ return userTerm;
+ }
+
+ private static HashtableOfTerm userHash(Prolog engine) {
+ Term userHash = engine.getHashManager().get(current_user);
+ if (userHash == null) {
+ HashtableOfTerm users = new HashtableOfTerm();
+ engine.getHashManager().put(current_user, new JavaObjectTerm(userHash));
+ return users;
+ }
+
+ if (userHash.isJavaObject()) {
+ Object obj = ((JavaObjectTerm) userHash).object();
+ if (obj instanceof HashtableOfTerm) {
+ return (HashtableOfTerm) obj;
+ }
+ }
+
+ throw new InternalException(current_user + " is not HashtableOfTerm");
+ }
+
+ private static AnonymousUser anonymousUser(Prolog engine) {
+ PrologEnvironment env = (PrologEnvironment) engine.control;
+ return env.getInjector().getInstance(AnonymousUser.class);
+ }
+
+ private static IdentifiedUser.GenericFactory userFactory(Prolog engine) {
+ PrologEnvironment env = (PrologEnvironment) engine.control;
+ return env.getInjector().getInstance(IdentifiedUser.GenericFactory.class);
+ }
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_approval_types_1.java b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_approval_types_1.java
new file mode 100644
index 0000000..9399865
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_approval_types_1.java
@@ -0,0 +1,84 @@
+// 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 gerrit;
+
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.rules.PrologEnvironment;
+
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
+import com.googlecode.prolog_cafe.lang.ListTerm;
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+import java.util.List;
+
+/**
+ * Obtain a list of approval types from the server configuration.
+ * <p>
+ * Unifies to a Prolog list of: {@code approval_type(Label, Id, Fun, Min, Max)}
+ * where:
+ * <ul>
+ * <li>{@code Label} - the newer style label name</li>
+ * <li>{@code Id} - the legacy ApprovalCategory.Id from the database</li>
+ * <li>{@code Fun} - legacy function name</li>
+ * <li>{@code Min, Max} - the smallest and largest configured values.</li>
+ * </ul>
+ */
+class PRED_get_legacy_approval_types_1 extends Predicate.P1 {
+ private static final long serialVersionUID = 1L;
+
+ PRED_get_legacy_approval_types_1(Term a1, Operation n) {
+ arg1 = a1;
+ cont = n;
+ }
+
+ @Override
+ public Operation exec(Prolog engine) throws PrologException {
+ engine.setB0();
+ Term a1 = arg1.dereference();
+
+ PrologEnvironment env = (PrologEnvironment) engine.control;
+ ApprovalTypes types = env.getInjector().getInstance(ApprovalTypes.class);
+
+ List<ApprovalType> list = types.getApprovalTypes();
+ Term head = Prolog.Nil;
+ for (int idx = list.size() - 1; 0 <= idx; idx--) {
+ head = new ListTerm(export(list.get(idx)), head);
+ }
+
+ if (!a1.unify(head, engine.trail)) {
+ return engine.fail();
+ }
+ return cont;
+ }
+
+ static final SymbolTerm symApprovalType = SymbolTerm.intern(
+ "approval_type", 5);
+
+ static Term export(ApprovalType type) {
+ return new StructureTerm(symApprovalType,
+ SymbolTerm.intern(type.getCategory().getLabelName()),
+ SymbolTerm.intern(type.getCategory().getId().get()),
+ SymbolTerm.intern(type.getCategory().getFunctionName()),
+ new IntegerTerm(type.getMin().getValue()),
+ new IntegerTerm(type.getMax().getValue()));
+ }
+}
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
new file mode 100644
index 0000000..18015dc
--- /dev/null
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -0,0 +1,266 @@
+%% 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 gerrit.
+'$init' :- init.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% init:
+%%
+%% Initialize the module's private state. These typically take the form of global
+%% aliased hashes carrying "constant" data about the current change for any
+%% predicate that needs to obtain it.
+%%
+init :-
+ define_hash(commit_labels),
+ define_hash(current_user).
+
+define_hash(A) :- hash_exists(A), !, hash_clear(A).
+define_hash(A) :- atom(A), !, new_hash(_, [alias(A)]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% commit_label/2:
+%%
+%% During rule evaluation of a change, this predicate is defined to
+%% be a table of labels that pertain to the commit of interest.
+%%
+%% commit_label( label('Code-Review', 2), user(12345789) ).
+%% commit_label( label('Verified', -1), user(8181) ).
+%%
+:- public commit_label/2.
+%%
+commit_label(L, User) :- L = label(H, _),
+ atom(H),
+ !,
+ hash_get(commit_labels, H, Cached),
+ ( [] == Cached ->
+ get_commit_labels(_),
+ hash_get(commit_labels, H, Rs), !
+ ;
+ Rs = Cached
+ ),
+ scan_commit_labels(Rs, L, User)
+ .
+commit_label(Label, User) :-
+ get_commit_labels(Rs),
+ scan_commit_labels(Rs, Label, User).
+
+scan_commit_labels([R | Rs], L, U) :- R = commit_label(L, U).
+scan_commit_labels([_ | Rs], L, U) :- scan_commit_labels(Rs, L, U).
+scan_commit_labels([], _, _) :- fail.
+
+get_commit_labels(Rs) :-
+ hash_contains_key(commit_labels, '$all'),
+ !,
+ hash_get(commit_labels, '$all', Rs)
+ .
+get_commit_labels(Rs) :-
+ '$load_commit_labels'(Rs),
+ set_commit_labels(Rs).
+
+set_commit_labels(Rs) :-
+ define_hash(commit_labels),
+ hash_put(commit_labels, '$all', Rs),
+ index_commit_labels(Rs).
+
+index_commit_labels([]).
+index_commit_labels([R | Rs]) :-
+ R = commit_label(label(H, _), _),
+ atom(H),
+ !,
+ hash_get(commit_labels, H, Tmp),
+ hash_put(commit_labels, H, [R | Tmp]),
+ index_commit_labels(Rs)
+ .
+index_commit_labels([_ | Rs]) :-
+ index_commit_labels(Rs).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% user_label_range/4:
+%%
+%% Lookup the range allowed to be used.
+%%
+user_label_range(Label, Who, Min, Max) :-
+ Who = user(_), !,
+ atom(Label),
+ current_user(Who, User),
+ '$user_label_range'(Label, User, Min, Max).
+user_label_range(Label, test_user(Name), Min, Max) :-
+ clause(user:test_grant(Label, test_user(Name), range(Min, Max)), _)
+ .
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% not_same/2:
+%%
+:- public not_same/2.
+%%
+not_same(ok(A), ok(B)) :- !, A \= B.
+not_same(label(_, ok(A)), label(_, ok(B))) :- !, A \= B.
+not_same(_, _).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% can_submit/2:
+%%
+%% Executes the SubmitRule for each solution until one where all of the
+%% states has the format label(_, ok(_)) is found, then cut away any
+%% remaining choice points leaving this as the last solution.
+%%
+:- public can_submit/2.
+%%
+can_submit(SubmitRule, S) :-
+ call_submit_rule(SubmitRule, Tmp),
+ Tmp =.. [submit | Ls],
+ ( is_all_ok(Ls) -> S = ok(Tmp), ! ; S = not_ready(Tmp) ).
+
+call_submit_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
+call_submit_rule(X, Arg) :- !, F =.. [X, Arg], F.
+
+is_all_ok([]).
+is_all_ok([label(_, ok(__)) | Ls]) :- is_all_ok(Ls).
+is_all_ok(_) :- fail.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% locate_submit_rule/1:
+%%
+%% Finds a submit_rule depending on what rules are available.
+%% If none are available, use default_submit/1.
+%%
+:- public locate_submit_rule/1.
+%%
+
+locate_submit_rule(RuleName) :-
+ '$compiled_predicate'(user, submit_rule, 1),
+ !,
+ RuleName = user:submit_rule
+ .
+locate_submit_rule(RuleName) :-
+ clause(user:submit_rule(_), _),
+ !,
+ RuleName = user:submit_rule
+ .
+locate_submit_rule(RuleName) :-
+ RuleName = gerrit:default_submit.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% default_submit/1:
+%%
+:- public default_submit/1.
+%%
+default_submit(P) :-
+ get_legacy_approval_types(ApprovalTypes),
+ default_submit(ApprovalTypes, P).
+
+% Apply the old "all approval categories must be satisfied"
+% loop by scanning over all of the approval types to build
+% up the submit record.
+%
+default_submit(ApprovalTypes, P) :-
+ default_submit(ApprovalTypes, [], Tmp),
+ reverse(Tmp, Ls),
+ P =.. [ submit | Ls].
+
+default_submit([], Out, Out).
+default_submit([Type | Types], Tmp, Out) :-
+ approval_type(Label, Id, Fun, Min, Max) = Type,
+ legacy_submit_rule(Fun, Label, Id, Min, Max, Status),
+ R = label(Label, Status),
+ default_submit(Types, [R | Tmp], Out).
+
+
+%% legacy_submit_rule:
+%%
+%% Apply the old -2..+2 style logic.
+%%
+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) :- true.
+legacy_submit_rule('NoOp', Label, Id, Min, Max, T) :- true.
+legacy_submit_rule(Fun, Label, Id, Min, Max, T) :- T = impossible(unsupported(Fun)).
+
+
+%% max_with_block:
+%%
+%% - The minimum is never used.
+%% - At least one maximum is used.
+%%
+:- public max_with_block/4.
+%%
+max_with_block(Label, Min, Max, reject(Who)) :-
+ check_label_range_permission(Label, Min, ok(Who)),
+ !
+ .
+max_with_block(Label, Min, Max, ok(Who)) :-
+ \+ check_label_range_permission(Label, Min, ok(_)),
+ check_label_range_permission(Label, Max, ok(Who)),
+ !
+ .
+max_with_block(Label, Min, Max, need(Max)) :-
+ true
+ .
+%TODO Uncomment this clause when group suggesting is possible.
+%max_with_block(Label, Min, Max, need(Max, Group)) :-
+% \+ check_label_range_permission(Label, Max, ok(_)),
+% check_label_range_permission(Label, Max, ask(Group))
+% .
+%max_with_block(Label, Min, Max, impossible(no_access)) :-
+% \+ check_label_range_permission(Label, Max, ask(Group))
+% .
+
+
+%% max_no_block:
+%%
+%% - At least one maximum is used.
+%%
+max_no_block(Label, Max, ok(Who)) :-
+ check_label_range_permission(Label, Max, ok(Who)),
+ !
+ .
+max_no_block(Label, Max, need(Max)) :-
+ true
+ .
+%TODO Uncomment this clause when group suggesting is possible.
+%max_no_block(Label, Max, need(Max, Group)) :-
+% check_label_range_permission(Label, Max, ask(Group))
+% .
+%max_no_block(Label, Max, impossible(no_access)) :-
+% \+ check_label_range_permission(Label, Max, ask(Group))
+% .
+
+
+%% check_label_range_permission:
+%%
+check_label_range_permission(Label, ExpValue, ok(Who)) :-
+ commit_label(label(Label, ExpValue), Who),
+ user_label_range(Label, Who, Min, Max),
+ Min @=< ExpValue, ExpValue @=< Max
+ .
+%TODO Uncomment this clause when group suggesting is possible.
+%check_label_range_permission(Label, ExpValue, ask(Group)) :-
+% grant_range(Label, Group, Min, Max),
+% Min @=< ExpValue, ExpValue @=< Max
+% .
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
new file mode 100644
index 0000000..2b8dad5
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
@@ -0,0 +1,81 @@
+// 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.rules;
+
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.ApprovalCategoryValue;
+import com.google.inject.AbstractModule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class GerritCommonTest extends PrologTestCase {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final ApprovalTypes types = new ApprovalTypes(Arrays.asList(
+ codeReviewCategory(),
+ verifiedCategory()
+ ));
+
+ load("gerrit", "gerrit_common_test.pl", new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ApprovalTypes.class).toInstance(types);
+ }
+ });
+ }
+
+ private static ApprovalType codeReviewCategory() {
+ ApprovalCategory cat = category(0, "CRVW", "Code Review");
+ List<ApprovalCategoryValue> vals = newList();
+ vals.add(value(cat, 2, "Looks good to me, approved"));
+ vals.add(value(cat, 1, "Looks good to me, but someone else must approve"));
+ vals.add(value(cat, 0, "No score"));
+ vals.add(value(cat, -1, "I would prefer that you didn't submit this"));
+ vals.add(value(cat, -2, "Do not submit"));
+ return new ApprovalType(cat, vals);
+ }
+
+ private static ApprovalType verifiedCategory() {
+ ApprovalCategory cat = category(1, "VRIF", "Verified");
+ List<ApprovalCategoryValue> vals = newList();
+ vals.add(value(cat, 1, "Verified"));
+ vals.add(value(cat, 0, "No score"));
+ vals.add(value(cat, -1, "Fails"));
+ return new ApprovalType(cat, vals);
+ }
+
+ private static ApprovalCategory category(int pos, String id, String name) {
+ ApprovalCategory cat;
+ cat = new ApprovalCategory(new ApprovalCategory.Id(id), name);
+ cat.setPosition((short) pos);
+ return cat;
+ }
+
+ private static ArrayList<ApprovalCategoryValue> newList() {
+ return new ArrayList<ApprovalCategoryValue>();
+ }
+
+ private static ApprovalCategoryValue value(ApprovalCategory c, int v, String n) {
+ return new ApprovalCategoryValue(
+ new ApprovalCategoryValue.Id(c.getId(), (short) v),
+ n);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
new file mode 100644
index 0000000..cc9d40f
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
@@ -0,0 +1,181 @@
+// 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.rules;
+
+import com.google.inject.Guice;
+import com.google.inject.Module;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologClassLoader;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+import com.googlecode.prolog_cafe.lang.VariableTerm;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PushbackReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/** Base class for any tests written in Prolog. */
+public abstract class PrologTestCase extends TestCase {
+ private static final SymbolTerm test_1 = SymbolTerm.intern("test", 1);
+
+ private String pkg;
+ private boolean hasSetup;
+ private boolean hasTeardown;
+ private List<Term> tests;
+ private PrologMachineCopy machine;
+ private PrologEnvironment.Factory envFactory;
+
+ protected void load(String pkg, String prologResource, Module... modules)
+ throws CompileException, IOException {
+ ArrayList<Module> moduleList = new ArrayList<Module>();
+ moduleList.add(new PrologModule());
+ moduleList.addAll(Arrays.asList(modules));
+
+ envFactory = Guice.createInjector(moduleList)
+ .getInstance(PrologEnvironment.Factory.class);
+ PrologEnvironment env = envFactory.create(newMachine());
+ consult(env, getClass(), prologResource);
+
+ this.pkg = pkg;
+ hasSetup = has(env, "setup");
+ hasTeardown = has(env, "teardown");
+
+ StructureTerm head = new StructureTerm(":",
+ SymbolTerm.intern(pkg),
+ new StructureTerm(test_1, new VariableTerm()));
+
+ tests = new ArrayList<Term>();
+ for (Term[] pair : env.all(Prolog.BUILTIN, "clause", head, new VariableTerm())) {
+ tests.add(pair[0]);
+ }
+ assertTrue("has tests", tests.size() > 0);
+ machine = PrologMachineCopy.save(env);
+ }
+
+ private PrologMachineCopy newMachine() {
+ BufferingPrologControl ctl = new BufferingPrologControl();
+ ctl.setMaxDatabaseSize(16 * 1024);
+ ctl.setPrologClassLoader(new PrologClassLoader(getClass().getClassLoader()));
+ return PrologMachineCopy.save(ctl);
+ }
+
+ protected void consult(BufferingPrologControl env,
+ Class<?> clazz,
+ String prologResource) throws CompileException, IOException {
+ InputStream in = clazz.getResourceAsStream(prologResource);
+ if (in == null) {
+ throw new FileNotFoundException(prologResource);
+ }
+ try {
+ SymbolTerm pathTerm = SymbolTerm.create(prologResource);
+ JavaObjectTerm inTerm =
+ new JavaObjectTerm(new PushbackReader(new BufferedReader(
+ new InputStreamReader(in, "UTF-8")), Prolog.PUSHBACK_SIZE));
+ if (!env.execute(Prolog.BUILTIN, "consult_stream", pathTerm, inTerm)) {
+ throw new CompileException("Cannot consult " + prologResource);
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ private boolean has(BufferingPrologControl env, String name) {
+ StructureTerm head = SymbolTerm.create(pkg, name, 0);
+ return env.execute(Prolog.BUILTIN, "clause", head, new VariableTerm());
+ }
+
+ public void testRunPrologTestCases() {
+ int errors = 0;
+ long start = System.currentTimeMillis();
+
+ for (Term test : tests) {
+ PrologEnvironment env = envFactory.create(machine);
+ env.setEnabled(Prolog.Feature.IO, true);
+
+ System.out.format("Prolog %-60s ...", removePackage(test));
+ System.out.flush();
+
+ if (hasSetup) {
+ call(env, "setup");
+ }
+
+ List<Term> all = env.all(Prolog.BUILTIN, "call", test);
+
+ if (hasTeardown) {
+ call(env, "teardown");
+ }
+
+ System.out.println(all.size() == 1 ? "OK" : "FAIL");
+
+ if (all.size() > 0 && !test.equals(all.get(0))) {
+ for (Term t : all) {
+ Term head = ((StructureTerm) removePackage(t)).args()[0];
+ Term[] args = ((StructureTerm) head).args();
+ System.out.print(" Result: ");
+ for (int i = 0; i < args.length; i++) {
+ if (0 < i) {
+ System.out.print(", ");
+ }
+ System.out.print(args[i]);
+ }
+ System.out.println();
+ }
+ System.out.println();
+ }
+
+ if (all.size() != 1) {
+ errors++;
+ }
+ }
+
+ long end = System.currentTimeMillis();
+ System.out.println("-------------------------------");
+ System.out.format("Prolog tests: %d, Failures: %d, Time elapsed %.3f sec",
+ tests.size(), errors, (end - start) / 1000.0);
+ System.out.println();
+
+ assertEquals("No Errors", 0, errors);
+ }
+
+ private void call(BufferingPrologControl env, String name) {
+ StructureTerm head = SymbolTerm.create(pkg, name, 0);
+ if (!env.execute(Prolog.BUILTIN, "call", head)) {
+ fail("Cannot invoke " + pkg + ":" + name);
+ }
+ }
+
+ private Term removePackage(Term test) {
+ Term name = test;
+ if (name.isStructure() && ":".equals(((StructureTerm) name).name())) {
+ name = ((StructureTerm) name).args()[1];
+ }
+ return name;
+ }
+}
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
new file mode 100644
index 0000000..e8a235f
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -0,0 +1,191 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+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.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.Project;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
+ private final GroupReference developers = new GroupReference(
+ new AccountGroup.UUID("X"), "Developers");
+ private final GroupReference staff = new GroupReference(
+ new AccountGroup.UUID("Y"), "Staff");
+
+ private Repository db;
+ private TestRepository<Repository> util;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ db = createBareRepository();
+ util = new TestRepository<Repository>(db);
+ }
+
+ @Test
+ public void testReadConfig() throws Exception {
+ RevCommit rev = util.commit(util.tree( //
+ util.file("groups", util.blob(group(developers))), //
+ util.file("project.config", util.blob(""//
+ + "[access \"refs/heads/*\"]\n" //
+ + " exclusiveGroupPermissions = read submit create\n" //
+ + " submit = group Developers\n" //
+ + " push = group Developers\n" //
+ + " read = group Developers\n")) //
+ ));
+
+ ProjectConfig cfg = read(rev);
+ AccessSection section = cfg.getAccessSection("refs/heads/*");
+ assertNotNull("has refs/heads/*", section);
+ assertNull("no refs/*", cfg.getAccessSection("refs/*"));
+
+ Permission create = section.getPermission(Permission.CREATE);
+ Permission submit = section.getPermission(Permission.SUBMIT);
+ Permission read = section.getPermission(Permission.READ);
+ Permission push = section.getPermission(Permission.PUSH);
+
+ assertTrue(create.getExclusiveGroup());
+ assertTrue(submit.getExclusiveGroup());
+ assertTrue(read.getExclusiveGroup());
+ assertFalse(push.getExclusiveGroup());
+ }
+
+ @Test
+ public void testEditConfig() throws Exception {
+ RevCommit rev = util.commit(util.tree( //
+ util.file("groups", util.blob(group(developers))), //
+ util.file("project.config", util.blob(""//
+ + "[access \"refs/heads/*\"]\n" //
+ + " exclusiveGroupPermissions = read submit\n" //
+ + " submit = group Developers\n" //
+ + " upload = group Developers\n" //
+ + " read = group Developers\n")) //
+ ));
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ AccessSection section = cfg.getAccessSection("refs/heads/*");
+ Permission submit = section.getPermission(Permission.SUBMIT);
+ submit.add(new PermissionRule(cfg.resolve(staff)));
+ rev = commit(cfg);
+ assertEquals(""//
+ + "[access \"refs/heads/*\"]\n" //
+ + " exclusiveGroupPermissions = read submit\n" //
+ + " submit = group Developers\n" //
+ + "\tsubmit = group Staff\n" //
+ + " upload = group Developers\n" //
+ + " read = group Developers\n", text(rev, "project.config"));
+ }
+
+ @Test
+ public void testEditConfigMissingGroupTableEntry() throws Exception {
+ RevCommit rev = util.commit(util.tree( //
+ util.file("groups", util.blob(group(developers))), //
+ util.file("project.config", util.blob(""//
+ + "[access \"refs/heads/*\"]\n" //
+ + " exclusiveGroupPermissions = read submit\n" //
+ + " submit = group People Who Can Submit\n" //
+ + " upload = group Developers\n" //
+ + " read = group Developers\n")) //
+ ));
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ AccessSection section = cfg.getAccessSection("refs/heads/*");
+ Permission submit = section.getPermission(Permission.SUBMIT);
+ submit.add(new PermissionRule(cfg.resolve(staff)));
+ rev = commit(cfg);
+ assertEquals(""//
+ + "[access \"refs/heads/*\"]\n" //
+ + " exclusiveGroupPermissions = read submit\n" //
+ + " submit = group People Who Can Submit\n" //
+ + "\tsubmit = group Staff\n" //
+ + " upload = group Developers\n" //
+ + " read = group Developers\n", text(rev, "project.config"));
+ }
+
+ private ProjectConfig read(RevCommit rev) throws IOException,
+ ConfigInvalidException {
+ ProjectConfig cfg = new ProjectConfig(new Project.NameKey("test"));
+ cfg.load(db, rev);
+ return cfg;
+ }
+
+ private RevCommit commit(ProjectConfig cfg) throws IOException,
+ MissingObjectException, IncorrectObjectTypeException {
+ MetaDataUpdate md = new MetaDataUpdate(new NoReplication(), //
+ cfg.getProject().getNameKey(), //
+ db);
+ util.tick(5);
+ util.setAuthorAndCommitter(md.getCommitBuilder());
+ md.setMessage("Edit\n");
+ assertTrue("commit finished", cfg.commit(md));
+
+ Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
+ return util.getRevWalk().parseCommit(ref.getObjectId());
+ }
+
+ private void update(RevCommit rev) throws Exception {
+ RefUpdate u = db.updateRef(GitRepositoryManager.REF_CONFIG);
+ u.disableRefLog();
+ u.setNewObjectId(rev);
+ switch (u.forceUpdate()) {
+ case FAST_FORWARD:
+ case FORCED:
+ case NEW:
+ case NO_CHANGE:
+ break;
+ default:
+ fail("Cannot update ref for test: " + u.getResult());
+ }
+ }
+
+ private String text(RevCommit rev, String path) throws Exception {
+ RevObject blob = util.get(rev.getTree(), path);
+ byte[] data = db.open(blob).getCachedBytes(Integer.MAX_VALUE);
+ return RawParseUtils.decode(data);
+ }
+
+ private static String group(GroupReference g) {
+ return g.getUUID().get() + "\t" + g.getName() + "\n";
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index f819dac..6d56b4a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -278,7 +278,7 @@
account.setFullName(name);
account.setPreferredEmail(email);
final AccountState s =
- new AccountState(account, Collections.<AccountGroup.Id> emptySet(),
+ new AccountState(account, Collections.<AccountGroup.UUID> emptySet(),
Collections.<AccountExternalId> emptySet());
return s;
}
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 6b0f13a..90cc342 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
@@ -14,44 +14,50 @@
package com.google.gerrit.server.project;
-import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
-import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
-import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT;
+import static com.google.gerrit.common.data.Permission.OWNER;
+import static com.google.gerrit.common.data.Permission.PUSH;
+import static com.google.gerrit.common.data.Permission.READ;
+import static com.google.gerrit.common.data.Permission.SUBMIT;
+import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gerrit.reviewdb.RefRight.RefPattern;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.AccessPath;
-import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.assistedinject.FactoryProvider;
import junit.framework.TestCase;
-import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.lib.Config;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
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.Map;
import java.util.Set;
public class RefControlTest extends TestCase {
public void testOwnerProject() {
- grant(local, OWN, admin, "refs/*", 1);
+ grant(local, OWNER, admin, "refs/*");
ProjectControl uBlah = user(devs);
ProjectControl uAdmin = user(devs, admin);
@@ -61,8 +67,8 @@
}
public void testBranchDelegation1() {
- grant(local, OWN, admin, "refs/*", 1);
- grant(local, OWN, devs, "refs/heads/x/*", 1);
+ grant(local, OWNER, admin, "refs/*");
+ grant(local, OWNER, devs, "refs/heads/x/*");
ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner());
@@ -77,9 +83,10 @@
}
public void testBranchDelegation2() {
- grant(local, OWN, admin, "refs/*", 1);
- grant(local, OWN, devs, "refs/heads/x/*", 1);
- grant(local, OWN, fixers, "-refs/heads/x/y/*", 1);
+ grant(local, OWNER, admin, "refs/*");
+ grant(local, OWNER, devs, "refs/heads/x/*");
+ grant(local, OWNER, fixers, "refs/heads/x/y/*");
+ doNotInherit(local, OWNER, "refs/heads/x/y/*");
ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner());
@@ -104,11 +111,14 @@
}
public void testInheritRead_SingleBranchDeniesUpload() {
- grant(parent, READ, registered, "refs/*", 1, 2);
- grant(local, READ, registered, "-refs/heads/foobar", 1);
+ grant(parent, READ, registered, "refs/*");
+ grant(parent, PUSH, registered, "refs/for/refs/*");
+ grant(local, READ, registered, "refs/heads/foobar");
+ doNotInherit(local, READ, "refs/heads/foobar");
+ doNotInherit(local, PUSH, "refs/for/refs/heads/foobar");
ProjectControl u = user();
- assertTrue("can upload", u.canPushToAtLeastOneRef());
+ assertTrue("can upload", u.canPushToAtLeastOneRef() == Capable.OK);
assertTrue("can upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
@@ -118,11 +128,12 @@
}
public void testInheritRead_SingleBranchDoesNotOverrideInherited() {
- grant(parent, READ, registered, "refs/*", 1, 2);
- grant(local, READ, registered, "refs/heads/foobar", 1);
+ grant(parent, READ, registered, "refs/*");
+ grant(parent, PUSH, registered, "refs/for/refs/*");
+ grant(local, READ, registered, "refs/heads/foobar");
ProjectControl u = user();
- assertTrue("can upload", u.canPushToAtLeastOneRef());
+ assertTrue("can upload", u.canPushToAtLeastOneRef() == Capable.OK);
assertTrue("can upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
@@ -132,16 +143,16 @@
}
public void testInheritRead_OverrideWithDeny() {
- grant(parent, READ, registered, "refs/*", 1);
- grant(local, READ, registered, "refs/*", 0);
+ grant(parent, READ, registered, "refs/*");
+ grant(local, READ, registered, "refs/*").setDeny(true);
ProjectControl u = user();
assertFalse("can't read", u.isVisible());
}
public void testInheritRead_AppendWithDenyOfRef() {
- grant(parent, READ, registered, "refs/*", 1);
- grant(local, READ, registered, "refs/heads/*", 0);
+ grant(parent, READ, registered, "refs/*");
+ grant(local, READ, registered, "refs/heads/*").setDeny(true);
ProjectControl u = user();
assertTrue("can read", u.isVisible());
@@ -151,9 +162,9 @@
}
public void testInheritRead_OverridesAndDeniesOfRef() {
- grant(parent, READ, registered, "refs/*", 1);
- grant(local, READ, registered, "refs/*", 0);
- grant(local, READ, registered, "refs/heads/*", -1, 1);
+ grant(parent, READ, registered, "refs/*");
+ grant(local, READ, registered, "refs/*").setDeny(true);
+ grant(local, READ, registered, "refs/heads/*");
ProjectControl u = user();
assertTrue("can read", u.isVisible());
@@ -163,9 +174,9 @@
}
public void testInheritSubmit_OverridesAndDeniesOfRef() {
- grant(parent, SUBMIT, registered, "refs/*", 1);
- grant(local, SUBMIT, registered, "refs/*", 0);
- grant(local, SUBMIT, registered, "refs/heads/*", -1, 1);
+ grant(parent, SUBMIT, registered, "refs/*");
+ grant(local, SUBMIT, registered, "refs/*").setDeny(true);
+ grant(local, SUBMIT, registered, "refs/heads/*");
ProjectControl u = user();
assertFalse("can't submit", u.controlForRef("refs/foobar").canSubmit());
@@ -174,11 +185,12 @@
}
public void testCannotUploadToAnyRef() {
- grant(parent, READ, registered, "refs/*", 1);
- grant(local, READ, devs, "refs/heads/*", 1, 2);
+ grant(parent, READ, registered, "refs/*");
+ grant(local, READ, devs, "refs/heads/*");
+ grant(local, PUSH, devs, "refs/for/refs/heads/*");
ProjectControl u = user();
- assertFalse("cannot upload", u.canPushToAtLeastOneRef());
+ assertFalse("cannot upload", u.canPushToAtLeastOneRef() == Capable.OK);
assertFalse("cannot upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
}
@@ -186,33 +198,52 @@
// -----------------------------------------------------------------------
- private final Project.NameKey local = new Project.NameKey("test");
- private final Project.NameKey parent = new Project.NameKey("parent");
- private final AccountGroup.Id admin = new AccountGroup.Id(1);
- private final AccountGroup.Id anonymous = new AccountGroup.Id(2);
- private final AccountGroup.Id registered = new AccountGroup.Id(3);
- private final AccountGroup.Id owners = new AccountGroup.Id(4);
+ private final Map<Project.NameKey, ProjectState> all;
+ private final AllProjectsName allProjectsName = new AllProjectsName("parent");
+ private final ProjectCache projectCache;
- private final AccountGroup.Id devs = new AccountGroup.Id(5);
- private final AccountGroup.Id fixers = new AccountGroup.Id(6);
+ private ProjectConfig local;
+ private ProjectConfig parent;
+ private final AccountGroup.UUID admin = new AccountGroup.UUID("test.admin");
+ private final AccountGroup.UUID anonymous = AccountGroup.ANONYMOUS_USERS;
+ private final AccountGroup.UUID registered = AccountGroup.REGISTERED_USERS;
- private final SystemConfig systemConfig;
- private final AuthConfig authConfig;
- private final AnonymousUser anonymousUser;
+ private final AccountGroup.UUID devs = new AccountGroup.UUID("test.devs");
+ private final AccountGroup.UUID fixers = new AccountGroup.UUID("test.fixers");
+
+ private final CapabilityControl.Factory capabilityControlFactory;
public RefControlTest() {
- systemConfig = SystemConfig.create();
- systemConfig.adminGroupId = admin;
- systemConfig.anonymousGroupId = anonymous;
- systemConfig.registeredGroupId = registered;
- systemConfig.ownerGroupId = owners;
- systemConfig.batchUsersGroupId = anonymous;
- try {
- byte[] bin = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
- systemConfig.registerEmailPrivateKey = Base64.encodeBase64String(bin);
- } catch (UnsupportedEncodingException err) {
- throw new RuntimeException("Cannot encode key", err);
- }
+ all = new HashMap<Project.NameKey, ProjectState>();
+ projectCache = new ProjectCache() {
+ @Override
+ public ProjectState getAllProjects() {
+ return get(allProjectsName);
+ }
+
+ @Override
+ public ProjectState get(Project.NameKey projectName) {
+ return all.get(projectName);
+ }
+
+ @Override
+ public void evict(Project p) {
+ }
+
+ @Override
+ public Iterable<Project.NameKey> all() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Iterable<Project.NameKey> byName(String prefix) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void onCreateProject(Project.NameKey newProjectName) {
+ }
+ };
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
@@ -221,23 +252,27 @@
.annotatedWith(GerritServerConfig.class) //
.toInstance(new Config());
- bind(SystemConfig.class).toInstance(systemConfig);
- bind(AuthConfig.class);
- bind(AnonymousUser.class);
+ bind(CapabilityControl.Factory.class)
+ .toProvider(FactoryProvider.newFactory(
+ CapabilityControl.Factory.class,
+ CapabilityControl.class));
+
+ bind(ProjectCache.class).toInstance(projectCache);
}
});
- authConfig = injector.getInstance(AuthConfig.class);
- anonymousUser = injector.getInstance(AnonymousUser.class);
+ capabilityControlFactory = injector.getInstance(CapabilityControl.Factory.class);
}
- private List<RefRight> localRights;
- private List<RefRight> inheritedRights;
-
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
- localRights = new ArrayList<RefRight>();
- inheritedRights = new ArrayList<RefRight>();
+
+ parent = new ProjectConfig(new Project.NameKey("parent"));
+ parent.createInMemory();
+
+ local = new ProjectConfig(new Project.NameKey("local"));
+ local.createInMemory();
+ local.getProject().setParentName(parent.getProject().getName());
}
private static void assertOwner(String ref, ProjectControl u) {
@@ -248,64 +283,72 @@
assertFalse("NOT OWN " + ref, u.controlForRef(ref).isOwner());
}
- private void grant(Project.NameKey project, ApprovalCategory.Id categoryId,
- AccountGroup.Id group, String ref, int maxValue) {
- grant(project, categoryId, group, ref, maxValue, maxValue);
+ private PermissionRule grant(ProjectConfig project, String permissionName,
+ AccountGroup.UUID group, String ref) {
+ PermissionRule rule = newRule(project, group);
+ project.getAccessSection(ref, true) //
+ .getPermission(permissionName, true) //
+ .add(rule);
+ return rule;
}
- private void grant(Project.NameKey project, ApprovalCategory.Id categoryId, AccountGroup.Id group,
- String ref, int minValue, int maxValue) {
- RefRight right =
- new RefRight(new RefRight.Key(project, new RefPattern(ref),
- categoryId, group));
- right.setMinValue((short) minValue);
- right.setMaxValue((short) maxValue);
-
- if (project == parent) {
- inheritedRights.add(right);
- } else if (project == local) {
- localRights.add(right);
- } else {
- fail("Unknown project key: " + project);
- }
+ private void doNotInherit(ProjectConfig project, String permissionName,
+ String ref) {
+ project.getAccessSection(ref, true) //
+ .getPermission(permissionName, true) //
+ .setExclusiveGroup(true);
}
- private ProjectControl user(AccountGroup.Id... memberOf) {
+ private PermissionRule newRule(ProjectConfig project, AccountGroup.UUID groupUUID) {
+ GroupReference group = new GroupReference(groupUUID, groupUUID.get());
+ group = project.resolve(group);
+
+ return new PermissionRule(group);
+ }
+
+ private ProjectControl user(AccountGroup.UUID... memberOf) {
+ SchemaFactory<ReviewDb> schema = null;
+ GroupCache groupCache = null;
+ String canonicalWebUrl = "http://localhost";
+
RefControl.Factory refControlFactory = new RefControl.Factory() {
@Override
public RefControl create(final ProjectControl projectControl, final String ref) {
- return new RefControl(systemConfig, projectControl, ref);
+ return new RefControl(projectControl, ref);
}
};
- return new ProjectControl(systemConfig,
- Collections.<AccountGroup.Id> emptySet(),
- Collections.<AccountGroup.Id> emptySet(), refControlFactory,
- new MockUser(memberOf), newProjectState());
+ return new ProjectControl(Collections.<AccountGroup.UUID> emptySet(),
+ Collections.<AccountGroup.UUID> emptySet(), schema, groupCache,
+ canonicalWebUrl, refControlFactory, new MockUser(memberOf),
+ newProjectState());
}
private ProjectState newProjectState() {
- ProjectCache projectCache = null;
- Project.NameKey wildProject = new Project.NameKey("-- All Projects --");
+ PrologEnvironment.Factory envFactory = null;
+ GitRepositoryManager mgr = null;
ProjectControl.AssistedFactory projectControlFactory = null;
- ProjectState ps =
- new ProjectState(anonymousUser, projectCache, wildProject,
- projectControlFactory, new Project(parent), localRights);
- ps.setInheritedRights(inheritedRights);
- return ps;
+ RulesCache rulesCache = null;
+ all.put(local.getProject().getNameKey(), new ProjectState(
+ projectCache, allProjectsName, projectControlFactory,
+ envFactory, mgr, rulesCache, local));
+ all.put(parent.getProject().getNameKey(), new ProjectState(
+ projectCache, allProjectsName, projectControlFactory,
+ envFactory, mgr, rulesCache, parent));
+ return all.get(local.getProject().getNameKey());
}
private class MockUser extends CurrentUser {
- private final Set<AccountGroup.Id> groups;
+ private final Set<AccountGroup.UUID> groups;
- MockUser(AccountGroup.Id[] groupId) {
- super(AccessPath.UNKNOWN, RefControlTest.this.authConfig);
- groups = new HashSet<AccountGroup.Id>(Arrays.asList(groupId));
+ MockUser(AccountGroup.UUID[] groupId) {
+ super(RefControlTest.this.capabilityControlFactory, AccessPath.UNKNOWN);
+ groups = new HashSet<AccountGroup.UUID>(Arrays.asList(groupId));
groups.add(registered);
groups.add(anonymous);
}
@Override
- public Set<AccountGroup.Id> getEffectiveGroups() {
+ public Set<AccountGroup.UUID> getEffectiveGroups() {
return groups;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index ee9af13..50b87da 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -14,15 +14,9 @@
package com.google.gerrit.server.schema;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
-import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gerrit.server.workflow.NoOpFunction;
-import com.google.gerrit.server.workflow.SubmitFunction;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -73,11 +67,6 @@
//
db.create();
db.assertSchemaVersion();
- final SystemConfig config = db.getSystemConfig();
- assertNotNull(config);
- assertNotNull(config.adminGroupId);
- assertNotNull(config.anonymousGroupId);
- assertNotNull(config.registeredGroupId);
// By default sitePath is set to the current working directory.
//
@@ -85,88 +74,7 @@
if (sitePath.getName().equals(".")) {
sitePath = sitePath.getParentFile();
}
- assertEquals(sitePath.getAbsolutePath(), config.sitePath);
-
- // This is randomly generated and should be at least 20 bytes long.
- //
- assertNotNull(config.registerEmailPrivateKey);
- assertTrue(20 < config.registerEmailPrivateKey.length());
- }
-
- public void testSubsequentGetReads() throws OrmException {
- db.create();
- final SystemConfig exp = db.getSystemConfig();
- final SystemConfig act = db.getSystemConfig();
-
- assertNotSame(exp, act);
- assertEquals(exp.adminGroupId, act.adminGroupId);
- assertEquals(exp.anonymousGroupId, act.anonymousGroupId);
- assertEquals(exp.registeredGroupId, act.registeredGroupId);
- assertEquals(exp.sitePath, act.sitePath);
- assertEquals(exp.registerEmailPrivateKey, act.registerEmailPrivateKey);
- }
-
- public void testCreateSchema_Group_Administrators() throws OrmException {
- db.create();
- final SystemConfig config = db.getSystemConfig();
- final ReviewDb c = db.open();
- try {
- final AccountGroup admin = c.accountGroups().get(config.adminGroupId);
- assertNotNull(admin);
- assertEquals(config.adminGroupId, admin.getId());
- assertEquals("Administrators", admin.getName());
- assertSame(AccountGroup.Type.INTERNAL, admin.getType());
- } finally {
- c.close();
- }
- }
-
- public void testCreateSchema_Group_AnonymousUsers() throws OrmException {
- db.create();
- final SystemConfig config = db.getSystemConfig();
- final ReviewDb c = db.open();
- try {
- final AccountGroup anon = c.accountGroups().get(config.anonymousGroupId);
- assertNotNull(anon);
- assertEquals(config.anonymousGroupId, anon.getId());
- assertEquals("Anonymous Users", anon.getName());
- assertSame(AccountGroup.Type.SYSTEM, anon.getType());
- } finally {
- c.close();
- }
- }
-
- public void testCreateSchema_Group_RegisteredUsers() throws OrmException {
- db.create();
- final SystemConfig config = db.getSystemConfig();
- final ReviewDb c = db.open();
- try {
- final AccountGroup reg = c.accountGroups().get(config.registeredGroupId);
- assertNotNull(reg);
- assertEquals(config.registeredGroupId, reg.getId());
- assertEquals("Registered Users", reg.getName());
- assertSame(AccountGroup.Type.SYSTEM, reg.getType());
- } finally {
- c.close();
- }
- }
-
- public void testCreateSchema_WildCardProject() throws OrmException {
- final ReviewDb c = db.create().open();
- try {
- final SystemConfig cfg;
- final Project all;
-
- cfg = c.systemConfig().get(new SystemConfig.Key());
- all = c.projects().get(cfg.wildProjectName);
- assertNotNull(all);
- assertEquals("-- All Projects --", all.getName());
- assertFalse(all.isUseContributorAgreements());
- assertFalse(all.isUseSignedOffBy());
- assertFalse(all.isRequireChangeID());
- } finally {
- c.close();
- }
+ assertEquals(sitePath.getAbsolutePath(), db.getSystemConfig().sitePath);
}
public void testCreateSchema_ApprovalCategory_CodeReview()
@@ -182,7 +90,6 @@
assertEquals("R", cat.getAbbreviatedName());
assertEquals("MaxWithBlock", cat.getFunctionName());
assertTrue(cat.isCopyMinScore());
- assertFalse(cat.isAction());
assertTrue(0 <= cat.getPosition());
} finally {
c.close();
@@ -190,101 +97,6 @@
assertValueRange(codeReview, -2, -1, 0, 1, 2);
}
- public void testCreateSchema_ApprovalCategory_Read() throws OrmException {
- final ReviewDb c = db.create().open();
- try {
- final ApprovalCategory cat;
-
- cat = c.approvalCategories().get(ApprovalCategory.READ);
- assertNotNull(cat);
- assertEquals(ApprovalCategory.READ, cat.getId());
- assertEquals("Read Access", cat.getName());
- assertNull(cat.getAbbreviatedName());
- assertEquals(NoOpFunction.NAME, cat.getFunctionName());
- assertTrue(cat.isAction());
- } finally {
- c.close();
- }
- assertValueRange(ApprovalCategory.READ, -1, 1, 2, 3);
- }
-
- public void testCreateSchema_ApprovalCategory_Submit() throws OrmException {
- final ReviewDb c = db.create().open();
- try {
- final ApprovalCategory cat;
-
- cat = c.approvalCategories().get(ApprovalCategory.SUBMIT);
- assertNotNull(cat);
- assertEquals(ApprovalCategory.SUBMIT, cat.getId());
- assertEquals("Submit", cat.getName());
- assertNull(cat.getAbbreviatedName());
- assertEquals(SubmitFunction.NAME, cat.getFunctionName());
- assertTrue(cat.isAction());
- } finally {
- c.close();
- }
- assertValueRange(ApprovalCategory.SUBMIT, 1);
- }
-
- public void testCreateSchema_ApprovalCategory_PushTag() throws OrmException {
- final ReviewDb c = db.create().open();
- try {
- final ApprovalCategory cat;
-
- cat = c.approvalCategories().get(ApprovalCategory.PUSH_TAG);
- assertNotNull(cat);
- assertEquals(ApprovalCategory.PUSH_TAG, cat.getId());
- assertEquals("Push Tag", cat.getName());
- assertNull(cat.getAbbreviatedName());
- assertEquals(NoOpFunction.NAME, cat.getFunctionName());
- assertTrue(cat.isAction());
- } finally {
- c.close();
- }
- assertValueRange(ApprovalCategory.PUSH_TAG, //
- ApprovalCategory.PUSH_TAG_SIGNED, //
- ApprovalCategory.PUSH_TAG_ANNOTATED);
- }
-
- public void testCreateSchema_ApprovalCategory_PushHead() throws OrmException {
- final ReviewDb c = db.create().open();
- try {
- final ApprovalCategory cat;
-
- cat = c.approvalCategories().get(ApprovalCategory.PUSH_HEAD);
- assertNotNull(cat);
- assertEquals(ApprovalCategory.PUSH_HEAD, cat.getId());
- assertEquals("Push Branch", cat.getName());
- assertNull(cat.getAbbreviatedName());
- assertEquals(NoOpFunction.NAME, cat.getFunctionName());
- assertTrue(cat.isAction());
- } finally {
- c.close();
- }
- assertValueRange(ApprovalCategory.PUSH_HEAD, //
- ApprovalCategory.PUSH_HEAD_UPDATE, //
- ApprovalCategory.PUSH_HEAD_CREATE, //
- ApprovalCategory.PUSH_HEAD_REPLACE);
- }
-
- public void testCreateSchema_ApprovalCategory_Owner() throws OrmException {
- final ReviewDb c = db.create().open();
- try {
- final ApprovalCategory cat;
-
- cat = c.approvalCategories().get(ApprovalCategory.OWN);
- assertNotNull(cat);
- assertEquals(ApprovalCategory.OWN, cat.getId());
- assertEquals("Owner", cat.getName());
- assertNull(cat.getAbbreviatedName());
- assertEquals(NoOpFunction.NAME, cat.getFunctionName());
- assertTrue(cat.isAction());
- } finally {
- c.close();
- }
- assertValueRange(ApprovalCategory.OWN, 1);
- }
-
private void assertValueRange(ApprovalCategory.Id cat, int... range)
throws OrmException {
final HashSet<ApprovalCategoryValue.Id> act =
@@ -314,57 +126,4 @@
fail("Category " + cat + " has additional values: " + act);
}
}
-
- public void testCreateSchema_DefaultAccess_AnonymousUsers()
- throws OrmException {
- db.create();
- final SystemConfig config = db.getSystemConfig();
- assertDefaultRight("refs/*", config.anonymousGroupId,
- ApprovalCategory.READ, 1, 1);
- }
-
- public void testCreateSchema_DefaultAccess_RegisteredUsers()
- throws OrmException {
- db.create();
- final SystemConfig config = db.getSystemConfig();
- assertDefaultRight("refs/*", config.registeredGroupId,
- ApprovalCategory.READ, 1, 2);
- assertDefaultRight("refs/heads/*", config.registeredGroupId, codeReview,
- -1, 1);
- }
-
- public void testCreateSchema_DefaultAccess_Administrators()
- throws OrmException {
- db.create();
- final SystemConfig config = db.getSystemConfig();
- assertDefaultRight("refs/*", config.adminGroupId, ApprovalCategory.READ, 1,
- 1);
- }
-
- private void assertDefaultRight(final String pattern,
- final AccountGroup.Id group, final ApprovalCategory.Id category, int min,
- int max) throws OrmException {
- final ReviewDb c = db.open();
- try {
- final SystemConfig cfg;
- final Project all;
- final RefRight right;
-
- cfg = c.systemConfig().get(new SystemConfig.Key());
- all = c.projects().get(cfg.wildProjectName);
- right =
- c.refRights().get(
- new RefRight.Key(all.getNameKey(), new RefRight.RefPattern(
- pattern), category, group));
-
- assertNotNull(right);
- assertEquals(all.getNameKey(), right.getProjectNameKey());
- assertEquals(group, right.getAccountGroupId());
- assertEquals(category, right.getApprovalCategoryId());
- assertEquals(min, right.getMinValue());
- assertEquals(max, right.getMaxValue());
- } finally {
- c.close();
- }
- }
}
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 a009f24..dfdf1f43 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
@@ -16,7 +16,13 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
@@ -27,6 +33,9 @@
import junit.framework.TestCase;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;
@@ -58,6 +67,25 @@
bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toInstance(db);
bind(SitePaths.class).toInstance(paths);
install(new SchemaVersion.Module());
+
+ Config cfg = new Config();
+ cfg.setString("gerrit", null, "basePath", "git");
+ cfg.setString("user", null, "name", "Gerrit Code Review");
+ cfg.setString("user", null, "email", "gerrit@localhost");
+
+ bind(Config.class) //
+ .annotatedWith(GerritServerConfig.class) //
+ .toInstance(cfg);
+
+ bind(PersonIdent.class) //
+ .annotatedWith(GerritPersonIdent.class) //
+ .toProvider(GerritPersonIdentProvider.class);
+
+ bind(AllProjectsName.class)
+ .toInstance(new AllProjectsName("All-Projects"));
+
+ bind(GitRepositoryManager.class) //
+ .to(LocalDiskRepositoryManager.class);
}
}).getInstance(SchemaUpdater.class);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
index fe138c6..82dcbea 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
@@ -17,7 +17,13 @@
import com.google.gerrit.reviewdb.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
-import com.google.gerrit.server.config.SystemConfigProvider;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.schema.Current;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.schema.SchemaVersion;
@@ -25,13 +31,18 @@
import com.google.gwtorm.client.SchemaFactory;
import com.google.gwtorm.jdbc.Database;
import com.google.gwtorm.jdbc.SimpleDataSource;
+import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Key;
-import com.google.inject.Provider;
import junit.framework.TestCase;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+
import java.io.File;
+import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
@@ -84,8 +95,33 @@
database = new Database<ReviewDb>(dataSource, ReviewDb.class);
schemaVersion =
- Guice.createInjector(new SchemaVersion.Module()).getBinding(
- Key.get(SchemaVersion.class, Current.class)).getProvider().get();
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new SchemaVersion.Module());
+
+ bind(File.class) //
+ .annotatedWith(SitePath.class) //
+ .toInstance(new File("."));
+
+ Config cfg = new Config();
+ cfg.setString("gerrit", null, "basePath", "git");
+ cfg.setString("user", null, "name", "Gerrit Code Review");
+ cfg.setString("user", null, "email", "gerrit@localhost");
+
+ bind(Config.class) //
+ .annotatedWith(GerritServerConfig.class) //
+ .toInstance(cfg);
+
+ bind(PersonIdent.class) //
+ .annotatedWith(GerritPersonIdent.class) //
+ .toProvider(GerritPersonIdentProvider.class);
+
+ bind(GitRepositoryManager.class) //
+ .to(LocalDiskRepositoryManager.class);
+ }
+ }).getBinding(Key.get(SchemaVersion.class, Current.class))
+ .getProvider().get();
} catch (SQLException e) {
throw new OrmException(e);
}
@@ -106,7 +142,18 @@
created = true;
final ReviewDb c = open();
try {
- new SchemaCreator(new File("."), schemaVersion).create(c);
+ try {
+ new SchemaCreator(
+ new File("."),
+ schemaVersion,
+ null,
+ new AllProjectsName("Test-Projects"),
+ new PersonIdent("name", "email@site")).create(c);
+ } catch (IOException e) {
+ throw new OrmException("Cannot create in-memory database", e);
+ } catch (ConfigInvalidException e) {
+ throw new OrmException("Cannot create in-memory database", e);
+ }
} finally {
c.close();
}
@@ -128,12 +175,13 @@
}
}
- public SystemConfig getSystemConfig() {
- return new SystemConfigProvider(this, new Provider<SchemaVersion>() {
- public SchemaVersion get() {
- return schemaVersion;
- }
- }).get();
+ public SystemConfig getSystemConfig() throws OrmException {
+ final ReviewDb c = open();
+ try {
+ return c.systemConfig().get(new SystemConfig.Key());
+ } finally {
+ c.close();
+ }
}
public CurrentSchemaVersion getSchemaVersion() throws OrmException {
diff --git a/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl b/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl
new file mode 100644
index 0000000..6aae509
--- /dev/null
+++ b/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl
@@ -0,0 +1,114 @@
+%% 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 gerrit.
+
+
+%% not_same
+%%
+test(not_same_success) :-
+ not_same(ok(a), ok(b)),
+ not_same(label(e, ok(a)), label(e, ok(b))).
+
+
+%% get_legacy_approval_types
+%%
+test(get_legacy_approval_types) :-
+ get_legacy_approval_types(T),
+ T = [C, V],
+ C = approval_type('Code-Review', 'CRVW', 'MaxWithBlock', -2, 2),
+ V = approval_type('Verified', 'VRIF', 'MaxWithBlock', -1, 1).
+
+
+%% commit_label
+%%
+test(commit_label_all) :-
+ findall(commit_label(L, U), commit_label(L, U), Out),
+ all_commit_labels(Ls),
+ Ls = Out.
+
+test(commit_label_CodeReview) :-
+ L = label('Code-Review', _),
+ findall(p(L, U), commit_label(L, U), Out),
+ [ p(label('Code-Review', 2), test_user(bob)),
+ p(label('Code-Review', 2), test_user(alice)) ] == Out.
+
+
+%% max_with_block
+%%
+test(max_with_block_success_accept_max_score) :-
+ max_with_block('Code-Review', -2, 2, ok(test_user(alice))).
+
+test(max_with_block_success_reject_min_score) :-
+ max_with_block('You-Fail', -1, 1, reject(test_user(failer))).
+
+test(max_with_block_success_need_suggest) :-
+ max_with_block('Verified', -1, 1, need(1)).
+
+skip_test(max_with_block_success_impossible) :-
+ max_with_block('Code-Style', 0, 1, impossible(no_access)).
+
+
+%% default_submit
+%%
+test(default_submit_fails) :-
+ findall(P, default_submit(P), All),
+ All = [submit(C, V)],
+ C = label('Code-Review', ok(test_user(alice))),
+ V = label('Verified', need(1)).
+
+
+%% can_submit
+%%
+test(can_submit_ok) :-
+ set_commit_labels([
+ commit_label( label('Code-Review', 2), test_user(alice) ),
+ commit_label( label('Verified', 1), test_user(builder) )
+ ]),
+ can_submit(gerrit:default_submit, S),
+ S = ok(submit(C, V)),
+ C = label('Code-Review', ok(test_user(alice))),
+ V = label('Verified', ok(test_user(builder))).
+
+test(can_submit_not_ready) :-
+ can_submit(gerrit:default_submit, S),
+ S = not_ready(submit(C, V)),
+ C = label('Code-Review', ok(test_user(alice))),
+ V = label('Verified', need(1)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Supporting Data
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+setup :-
+ init,
+ all_commit_labels(Ls),
+ set_commit_labels(Ls).
+
+all_commit_labels(Ls) :-
+ Ls = [
+ commit_label( label('Code-Review', 2), test_user(alice) ),
+ commit_label( label('Code-Review', 2), test_user(bob) ),
+ commit_label( label('You-Fail', -1), test_user(failer) ),
+ commit_label( label('You-Fail', -1), test_user(alice) )
+ ].
+
+:- package user.
+test_grant('Code-Review', test_user(alice), range(-2, 2)).
+test_grant('Verified', test_user(builder), range(-1, 1)).
+test_grant('You-Fail', test_user(alice), range(-1, 1)).
+test_grant('You-Fail', test_user(failer), range(-1, 1)).
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 32bcf57..0ccf0ec 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-sshd</artifactId>
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 f7d7226..52a0277 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
@@ -43,6 +43,7 @@
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
@@ -57,7 +58,7 @@
private static final int PRIVATE_STATUS = 1 << 30;
static final int STATUS_CANCEL = PRIVATE_STATUS | 1;
static final int STATUS_NOT_FOUND = PRIVATE_STATUS | 2;
- static final int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3;
+ public static final int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3;
@Option(name = "--help", usage = "display this help text", aliases = {"-h"})
private boolean help;
@@ -239,7 +240,8 @@
protected synchronized void startThread(final CommandRunnable thunk) {
final TaskThunk tt = new TaskThunk(thunk);
- if (isAdminCommand()||(isAdminHighPriorityCommand() && userProvider.get().isAdministrator())) {
+ if (isAdminCommand() || (isAdminHighPriorityCommand()
+ && userProvider.get().getCapabilities().canAdministrateServer())) {
// Admin commands should not block the main work threads (there
// might be an interactive shell there), nor should they wait
// for the main work threads.
@@ -285,17 +287,13 @@
}
private int handleError(final Throwable e) {
- if (e.getClass() == IOException.class
- && "Pipe closed".equals(e.getMessage())) {
- // This is sshd telling us the client just dropped off while
- // we were waiting for a read or a write to complete. Either
- // way its not really a fatal error. Don't log it.
- //
- return 127;
- }
-
- if (e.getClass() == SshException.class
- && "Already closed".equals(e.getMessage())) {
+ if ((e.getClass() == IOException.class
+ && "Pipe closed".equals(e.getMessage()))
+ || //
+ (e.getClass() == SshException.class
+ && "Already closed".equals(e.getMessage()))
+ || //
+ e.getClass() == InterruptedIOException.class) {
// This is sshd telling us the client just dropped off while
// we were waiting for a read or a write to complete. Either
// way its not really a fatal error. Don't log it.
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java
index a66386f..5c6f80a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -33,12 +34,6 @@
@Override
public WorkQueue.Executor get() {
- WorkQueue.Executor executor;
- if (user.isBatchUser()) {
- executor = queues.getBatchQueue();
- } else {
- executor = queues.getInteractiveQueue();
- }
- return executor;
+ return queues.getQueue(user.getCapabilities().getQueueType());
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
index d5a3373..bafb9ee 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
@@ -64,13 +65,13 @@
}
@Override
- public WorkQueue.Executor getInteractiveQueue() {
- return interactiveExecutor;
+ public WorkQueue.Executor getQueue(QueueType type) {
+ switch (type) {
+ case INTERACTIVE:
+ return interactiveExecutor;
+ case BATCH:
+ default:
+ return batchExecutor;
+ }
}
-
- @Override
- public WorkQueue.Executor getBatchQueue() {
- return batchExecutor;
- }
-
}
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 a421f70..8daa7f4 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
@@ -71,7 +71,8 @@
final Command cmd = p.get();
- if (isAdminCommand(cmd) && !currentUser.get().isAdministrator()) {
+ if (isAdminCommand(cmd)
+ && !currentUser.get().getCapabilities().canAdministrateServer()) {
final String msg = "fatal: Not a Gerrit administrator";
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/QueueProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/QueueProvider.java
deleted file mode 100644
index 282472a..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/QueueProvider.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.google.gerrit.sshd;
-
-import com.google.gerrit.server.git.WorkQueue;
-
-public interface QueueProvider {
-
- public WorkQueue.Executor getInteractiveQueue();
-
- public WorkQueue.Executor getBatchQueue();
-
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 2052343..fe9d9eb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -120,6 +120,7 @@
private static final Logger log = LoggerFactory.getLogger(SshDaemon.class);
private final List<SocketAddress> listen;
+ private final List<String> advertisedAddress;
private final boolean keepAlive;
private final List<HostKey> hostKeys;
private volatile IoAcceptor acceptor;
@@ -132,6 +133,7 @@
setPort(IANA_SSH_PORT /* never used */);
listen = parseListen(cfg);
+ advertisedAddress = parseAdvertisedAddress(cfg);
reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true);
keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
@@ -262,9 +264,9 @@
buf.putRawPublicKey(pub);
final byte[] keyBin = buf.getCompactData();
- for (final InetSocketAddress addr : myAddresses()) {
+ for (final String addr : myAdvertisedAddresses()) {
try {
- r.add(new HostKey(SocketUtil.format(addr, IANA_SSH_PORT), keyBin));
+ r.add(new HostKey(addr, keyBin));
} catch (JSchException e) {
log.warn("Cannot format SSHD host key", e);
}
@@ -273,6 +275,19 @@
return Collections.unmodifiableList(r);
}
+ private List<String> myAdvertisedAddresses() {
+ if (advertisedAddress != null) {
+ return advertisedAddress;
+ } else {
+ List<InetSocketAddress> addrs = myAddresses();
+ List<String> strAddrs = new ArrayList<String>(addrs.size());
+ for (final InetSocketAddress addr : addrs) {
+ strAddrs.add(SocketUtil.format(addr, IANA_SSH_PORT));
+ }
+ return strAddrs;
+ }
+ }
+
private List<InetSocketAddress> myAddresses() {
ArrayList<InetSocketAddress> pub = new ArrayList<InetSocketAddress>();
ArrayList<InetSocketAddress> local = new ArrayList<InetSocketAddress>();
@@ -317,6 +332,14 @@
return r.toString();
}
+ private List<String> parseAdvertisedAddress(final Config cfg) {
+ final String[] want = cfg.getStringList("sshd", null, "advertisedaddress");
+ if (want.length == 0) {
+ return null;
+ }
+ return Arrays.asList(want);
+ }
+
private List<SocketAddress> parseListen(final Config cfg) {
final ArrayList<SocketAddress> bind = new ArrayList<SocketAddress>(2);
final String[] want = cfg.getStringList("sshd", null, "listenaddress");
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 5b8edf0..e6cb6be 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
@@ -28,10 +28,12 @@
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritRequestModule;
+import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.ssh.SshInfo;
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.PatchSetIdHandler;
import com.google.gerrit.sshd.args4j.ProjectControlHandler;
@@ -117,6 +119,7 @@
registerOptionHandler(Account.Id.class, AccountIdHandler.class);
registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class);
+ registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class);
registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
registerOptionHandler(SocketAddress.class, SocketAddressHandler.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupUUIDHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupUUIDHandler.java
new file mode 100644
index 0000000..90b4987
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupUUIDHandler.java
@@ -0,0 +1,57 @@
+// 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.sshd.args4j;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+public class AccountGroupUUIDHandler extends OptionHandler<AccountGroup.UUID> {
+ private final GroupCache groupCache;
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Inject
+ public AccountGroupUUIDHandler(final GroupCache groupCache,
+ @Assisted final CmdLineParser parser, @Assisted final OptionDef option,
+ @Assisted final Setter setter) {
+ super(parser, option, setter);
+ this.groupCache = groupCache;
+ }
+
+ @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));
+ if (group == null) {
+ throw new CmdLineException(owner, "Group \"" + n + "\" does not exist");
+ }
+ setter.addValue(group.getGroupUUID());
+ return 1;
+ }
+
+ @Override
+ public final String getDefaultMetaVariable() {
+ return "GROUP";
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java
index b8bf0fd..1f6454b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountIdHandler.java
@@ -15,11 +15,12 @@
package com.google.gerrit.sshd.args4j;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AuthType;
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.AuthResult;
+import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -34,16 +35,19 @@
public class AccountIdHandler extends OptionHandler<Account.Id> {
private final AccountResolver accountResolver;
private final AccountManager accountManager;
+ private final AuthType authType;
@SuppressWarnings({"unchecked", "rawtypes"})
@Inject
public AccountIdHandler(final AccountResolver accountResolver,
final AccountManager accountManager,
+ final AuthConfig authConfig,
@Assisted final CmdLineParser parser, @Assisted final OptionDef option,
@Assisted final Setter setter) {
super(parser, option, setter);
this.accountResolver = accountResolver;
this.accountManager = accountManager;
+ this.authType = authConfig.getAuthType();
}
@Override
@@ -56,7 +60,15 @@
if (a != null) {
accountId = a.getId();
} else {
- accountId = createAccountIfUserCanBeAuthenticated(token);
+ switch (authType) {
+ case HTTP_LDAP:
+ case CLIENT_SSL_CERT_LDAP:
+ case LDAP:
+ accountId = createAccountByLdap(token);
+ break;
+ default:
+ throw new CmdLineException(owner, "user \"" + token + "\" not found");
+ }
}
} catch (OrmException e) {
throw new CmdLineException(owner, "database is down");
@@ -65,15 +77,18 @@
return 1;
}
- private Account.Id createAccountIfUserCanBeAuthenticated(final String username)
+ private Account.Id createAccountByLdap(String user)
throws CmdLineException {
+ if (!user.matches(Account.USER_NAME_PATTERN)) {
+ throw new CmdLineException(owner, "user \"" + user + "\" not found");
+ }
+
try {
- final AuthRequest areq = AuthRequest.forUser(username);
- final AuthResult arsp = accountManager.authenticate(areq);
- return arsp.getAccountId();
+ AuthRequest req = AuthRequest.forUser(user);
+ req.setSkipAuthentication(true);
+ return accountManager.authenticate(req).getAccountId();
} catch (AccountException e) {
- throw new CmdLineException(owner, "Unable to authenticate user \""
- + username + "\"", e);
+ throw new CmdLineException(owner, "user \"" + user + "\" not found");
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java
index 48733c03b..1e0c3a6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ProjectControlHandler.java
@@ -46,14 +46,21 @@
final String token = params.getParameter(0);
String projectName = token;
+ while (projectName.endsWith("/")) {
+ projectName = projectName.substring(0, projectName.length() - 1);
+ }
+
if (projectName.endsWith(".git")) {
// Be nice and drop the trailing ".git" suffix, which we never keep
// in our database, but clients might mistakenly provide anyway.
//
projectName = projectName.substring(0, projectName.length() - 4);
+ while (projectName.endsWith("/")) {
+ projectName = projectName.substring(0, projectName.length() - 1);
+ }
}
- if (projectName.startsWith("/")) {
+ while (projectName.startsWith("/")) {
// Be nice and drop the leading "/" if supplied by an absolute path.
// We don't have a file system hierarchy, just a flat namespace in
// the database's Project entities. We never encode these with a
@@ -64,8 +71,8 @@
final ProjectControl control;
try {
- control =
- projectControlFactory.validateFor(new Project.NameKey(projectName));
+ Project.NameKey nameKey = new Project.NameKey(projectName);
+ control = projectControlFactory.validateFor(nameKey);
} catch (NoSuchProjectException e) {
throw new CmdLineException(owner, "'" + token + "': not a Gerrit project");
}
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 07c573d..95bc563 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,22 +15,24 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.config.AllProjectsName;
+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.server.project.ProjectState;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
-import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
+import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -44,14 +46,13 @@
private List<ProjectControl> children = new ArrayList<ProjectControl>();
@Inject
- private ReviewDb db;
-
- @Inject
private ProjectCache projectCache;
@Inject
- @WildProjectName
- private Project.NameKey wildProject;
+ private MetaDataUpdate.User metaDataUpdateFactory;
+
+ @Inject
+ private AllProjectsName allProjectsName;
@Override
public void start(final Environment env) {
@@ -64,12 +65,12 @@
});
}
- private void updateParents() throws OrmException, UnloggedFailure {
+ private void updateParents() throws Failure {
final StringBuilder err = new StringBuilder();
final Set<Project.NameKey> grandParents = new HashSet<Project.NameKey>();
Project.NameKey newParentKey;
- grandParents.add(wildProject);
+ grandParents.add(allProjectsName);
if (newParent != null) {
newParentKey = newParent.getProject().getNameKey();
@@ -96,7 +97,7 @@
final Project.NameKey key = pc.getProject().getNameKey();
final String name = pc.getProject().getName();
- if (wildProject.equals(key)) {
+ if (allProjectsName.equals(key)) {
// Don't allow the wild card project to have a parent.
//
err.append("error: Cannot set parent of '" + name + "'\n");
@@ -107,27 +108,32 @@
// Try to avoid creating a cycle in the parent pointers.
//
err.append("error: Cycle exists between '" + name + "' and '"
- + (newParentKey != null ? newParentKey.get() : wildProject.get())
+ + (newParentKey != null ? newParentKey.get() : allProjectsName.get())
+ "'\n");
continue;
}
- final Project child = db.projects().get(key);
- if (child == null) {
- // Race condition? Its in the cache, but not the database.
- //
- err.append("error: Project '" + name + "' not found\n");
- continue;
+ try {
+ MetaDataUpdate md = metaDataUpdateFactory.create(key);
+ try {
+ ProjectConfig config = ProjectConfig.read(md);
+ config.getProject().setParentName(newParentKey.get());
+ md.setMessage("Inherit access from " + newParentKey.get() + "\n");
+ if (!config.commit(md)) {
+ err.append("error: Could not update project " + name + "\n");
+ }
+ } finally {
+ md.close();
+ }
+ } catch (RepositoryNotFoundException notFound) {
+ err.append("error: Project " + name + " not found\n");
+ } catch (IOException e) {
+ throw new Failure(1, "Cannot update project " + name, e);
+ } catch (ConfigInvalidException e) {
+ throw new Failure(1, "Cannot update project " + name, e);
}
-
- child.setParent(newParentKey);
- db.projects().update(Collections.singleton(child));
}
- // Invalidate all projects in cache since inherited rights were changed.
- //
- projectCache.evictAll();
-
if (err.length() > 0) {
while (err.charAt(err.length() - 1) == '\n') {
err.setLength(err.length() - 1);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java
deleted file mode 100644
index c27ad0d..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java
+++ /dev/null
@@ -1,208 +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.sshd.commands;
-
-import com.google.gerrit.sshd.AdminCommand;
-
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Statistics;
-import net.sf.ehcache.config.CacheConfiguration;
-
-import org.apache.sshd.server.Environment;
-import org.eclipse.jgit.storage.file.WindowCacheStatAccessor;
-
-import java.io.PrintWriter;
-
-/** Show the current cache states. */
-@AdminCommand
-final class AdminShowCaches extends CacheCommand {
- private PrintWriter p;
-
- @Override
- public void start(final Environment env) {
- startThread(new CommandRunnable() {
- @Override
- public void run() throws Exception {
- parseCommandLine();
- display();
- }
- });
- }
-
- private void display() {
- p = toPrintWriter(out);
-
- p.print(String.format(//
- "%1s %-18s %-4s|%-20s| %-5s |%-14s|\n" //
- , "" //
- , "Name" //
- , "Max" //
- , "Object Count" //
- , "AvgGet" //
- , "Hit Ratio" //
- ));
- p.print(String.format(//
- "%1s %-18s %-4s|%6s %6s %6s| %-5s |%-4s %-4s %-4s|\n" //
- , "" //
- , "" //
- , "Age" //
- , "Disk" //
- , "Mem" //
- , "Cnt" //
- , "" //
- , "Disk" //
- , "Mem" //
- , "Agg" //
- ));
- p.println("------------------"
- + "-------+--------------------+----------+--------------+");
- 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();
-
- if (useDisk) {
- p.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 {
- p.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) //
- ));
- }
- }
- p.println();
-
- final Runtime r = Runtime.getRuntime();
- final long mMax = r.maxMemory();
- final long mFree = r.freeMemory();
- final long mTotal = r.totalMemory();
- final long mInuse = mTotal - mFree;
- final long jgitBytes = WindowCacheStatAccessor.getOpenBytes();
-
- p.println("JGit Buffer Cache:");
- fItemCount("open files", WindowCacheStatAccessor.getOpenFiles());
- fByteCount("loaded", jgitBytes);
- fPercent("mem%", jgitBytes, mTotal);
- p.println();
-
- p.println("JVM Heap:");
- fByteCount("max", mMax);
- fByteCount("inuse", mInuse);
- fPercent("mem%", mInuse, mTotal);
- p.println();
-
- p.flush();
- }
-
- private void fItemCount(final String name, final long value) {
- p.println(String.format(" %1$-12s: %2$15d", name, value));
- }
-
- private void fByteCount(final String name, double value) {
- String suffix = "bytes";
- if (value > 1024) {
- value /= 1024;
- suffix = "kb";
- }
- if (value > 1024) {
- value /= 1024;
- suffix = "mb";
- }
- if (value > 1024) {
- value /= 1024;
- suffix = "gb";
- }
- p.println(String.format(" %1$-12s: %2$6.2f %3$s", name, value, suffix));
- }
-
- private String count(long cnt) {
- if (cnt == 0) {
- return "";
- }
- return String.format("%6d", cnt);
- }
-
- private String duration(double ms) {
- if (Math.abs(ms) <= 0.05) {
- return "";
- }
- String suffix = "ms";
- if (ms >= 1000) {
- ms /= 1000;
- 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;
- }
-
- private String percent(final long value, final long total) {
- if (total <= 0) {
- return "";
- }
- final long pcent = (100 * value) / total;
- return String.format("%3d%%", (int) pcent);
- }
-
- private void fPercent(final String name, final long value, final long total) {
- final long pcent = 0 < total ? (100 * value) / total : 0;
- p.println(String.format(" %1$-12s: %2$3d%%", name, (int) pcent));
- }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
similarity index 94%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index 06e92ca..b46a2f2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -26,7 +26,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.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
@@ -46,8 +45,7 @@
import java.util.List;
/** Create a new user account. **/
-@AdminCommand
-final class AdminCreateAccount extends BaseCommand {
+final class CreateAccountCommand extends BaseCommand {
@Option(name = "--group", aliases = {"-g"}, metaVar = "GROUP", usage = "groups to add account to")
private List<AccountGroup.Id> groups = new ArrayList<AccountGroup.Id>();
@@ -83,6 +81,13 @@
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
+ if (!currentUser.getCapabilities().canCreateAccount()) {
+ String msg = String.format(
+ "fatal: %s does not have \"Create Account\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
parseCommandLine();
createAccount();
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
similarity index 79%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
index 8622741..05e6e59 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -15,19 +15,17 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
+import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.server.account.PerformCreateGroup;
-import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
-import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@@ -36,8 +34,7 @@
* <p>
* Optionally, puts an initial set of user in the newly created group.
*/
-@AdminCommand
-public class AdminCreateGroup extends BaseCommand {
+final class CreateGroupCommand extends BaseCommand {
@Option(name = "--owner", aliases = {"-o"}, metaVar = "GROUP", usage = "owning group, if not specified the group will be self-owning")
private AccountGroup.Id ownerGroupId;
@@ -68,24 +65,25 @@
private PerformCreateGroup.Factory performCreateGroupFactory;
@Override
- public void start(Environment env) throws IOException {
+ public void start(Environment env) {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
- createGroup();
+ try {
+ performCreateGroupFactory.create().createGroup(groupName,
+ groupDescription,
+ visibleToAll,
+ ownerGroupId,
+ initialMembers,
+ initialGroups);
+ } catch (PermissionDeniedException e) {
+ throw die(e);
+
+ } catch (NameAlreadyUsedException e) {
+ throw die(e);
+ }
}
});
}
-
- private void createGroup() throws OrmException, UnloggedFailure {
- final PerformCreateGroup performCreateGroup =
- performCreateGroupFactory.create();
- try {
- performCreateGroup.createGroup(groupName, groupDescription, visibleToAll,
- ownerGroupId, initialMembers, initialGroups);
- } catch (NameAlreadyUsedException e) {
- throw die(e);
- }
- }
-}
\ No newline at end of file
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java
index 0be02fd..7a0c1a9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProject.java
@@ -14,27 +14,30 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.common.CollectionsUtil;
+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.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.RefRight;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Project.SubmitType;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.ProjectCreatorGroups;
+import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.ProjectOwnerGroups;
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.git.ReplicationQueue;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.sshd.BaseCommand;
-import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -42,15 +45,13 @@
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.StoredConfig;
+import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -59,11 +60,17 @@
final class CreateProject extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(CreateProject.class);
- @Option(name = "--name", required = true, aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created")
- private String projectName;
+ @Option(name = "--name", aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created (deprecated option)")
+ void setProjectNameFromOption(String name) {
+ if (projectName != null) {
+ throw new IllegalArgumentException("NAME already supplied");
+ } else {
+ projectName = name;
+ }
+ }
@Option(name = "--owner", aliases = {"-o"}, usage = "owner(s) of project")
- private List<AccountGroup.Id> ownerIds;
+ private List<AccountGroup.UUID> ownerIds;
@Option(name = "--parent", aliases = {"-p"}, metaVar = "NAME", usage = "parent project")
private ProjectControl newParent;
@@ -71,7 +78,7 @@
@Option(name = "--permissions-only", usage = "create project for use only as parent")
private boolean permissionsOnly;
- @Option(name = "--description", aliases = {"-d"}, metaVar = "DESC", usage = "description of project")
+ @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"
@@ -97,19 +104,28 @@
@Option(name = "--empty-commit", usage = "to create initial empty commit")
private boolean createEmptyCommit;
- @Inject
- private ReviewDb db;
+ private String projectName;
+ @Argument(index = 0, metaVar="NAME", usage="name of project to be created")
+ void setProjectNameFromArgument(String name) {
+ if (projectName != null) {
+ throw new IllegalArgumentException("--name already supplied");
+ } else {
+ projectName = name;
+ }
+ }
@Inject
private GitRepositoryManager repoManager;
@Inject
- @ProjectCreatorGroups
- private Set<AccountGroup.Id> projectCreatorGroups;
+ private ProjectCache projectCache;
+
+ @Inject
+ private GroupCache groupCache;
@Inject
@ProjectOwnerGroups
- private Set<AccountGroup.Id> projectOwnerGroups;
+ private Set<AccountGroup.UUID> projectOwnerGroups;
@Inject
private IdentifiedUser currentUser;
@@ -121,6 +137,9 @@
@GerritPersonIdent
private PersonIdent serverIdent;
+ @Inject
+ MetaDataUpdate.User metaDataUpdateFactory;
+
private Project.NameKey nameKey;
@Override
@@ -128,49 +147,54 @@
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
- PrintWriter p = toPrintWriter(out);
+ if (!currentUser.getCapabilities().canCreateProject()) {
+ String msg = String.format(
+ "fatal: %s does not have \"Create Project\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
parseCommandLine();
+ validateParameters();
try {
- validateParameters();
nameKey = new Project.NameKey(projectName);
- if (!permissionsOnly) {
- final Repository repo = repoManager.createRepository(nameKey);
+ String head = permissionsOnly ? GitRepositoryManager.REF_CONFIG : branch;
+ final Repository repo = repoManager.createRepository(nameKey);
+ try {
+ rq.replicateNewProject(nameKey, head);
+
+ RefUpdate u = repo.updateRef(Constants.HEAD);
+ u.disableRefLog();
+ u.link(head);
+
+ createProjectConfig();
+
+ if (!permissionsOnly && createEmptyCommit) {
+ createEmptyCommit(repo, nameKey, branch);
+ }
+ } finally {
+ repo.close();
+ }
+ } catch (IllegalStateException err) {
+ try {
+ Repository repo = repoManager.openRepository(nameKey);
try {
- repo.create(true);
-
- StoredConfig config = repo.getConfig();
- config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
- null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
- config.save();
-
- RefUpdate u = repo.updateRef(Constants.HEAD);
- u.disableRefLog();
- u.link(branch);
-
- repoManager.setProjectDescription(nameKey, projectDescription);
-
- createProject();
-
- rq.replicateNewProject(nameKey, branch);
-
- if (createEmptyCommit) {
- createEmptyCommit(repo, nameKey, branch);
+ if (repo.getObjectDatabase().exists()) {
+ throw new UnloggedFailure(1, "fatal: project \"" + projectName + "\" exists");
}
} finally {
repo.close();
}
- } else {
- createProject();
+ } catch (RepositoryNotFoundException doesNotExist) {
+ throw new Failure(1, "fatal: Cannot create " + projectName, err);
}
- } catch (Exception e) {
- p.print("Error when trying to create project: " + e.getMessage()
- + "\n");
- p.flush();
+ } catch (RepositoryNotFoundException badName) {
+ throw new UnloggedFailure(1, "fatal: " + badName.getMessage());
+ } catch (Exception err) {
+ throw new Failure(1, "fatal: Cannot create " + projectName, err);
}
-
}
});
}
@@ -181,9 +205,9 @@
try {
CommitBuilder cb = new CommitBuilder();
cb.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {}));
+ cb.setAuthor(metaDataUpdateFactory.getUserPersonIdent());
cb.setCommitter(serverIdent);
- cb.setAuthor(cb.getCommitter());
- cb.setMessage("Initial empty repository");
+ cb.setMessage("Initial empty repository\n");
ObjectId id = oi.insert(cb);
oi.flush();
@@ -207,48 +231,60 @@
}
}
- private void createProject() throws OrmException {
- List<RefRight> access = new ArrayList<RefRight>();
- for (AccountGroup.Id ownerId : ownerIds) {
- final RefRight.Key prk =
- new RefRight.Key(nameKey, new RefRight.RefPattern(
- RefRight.ALL), ApprovalCategory.OWN, ownerId);
- final RefRight pr = new RefRight(prk);
- pr.setMaxValue((short) 1);
- pr.setMinValue((short) 1);
- access.add(pr);
- }
- db.refRights().insert(access);
+ private void createProjectConfig() throws IOException, ConfigInvalidException {
+ MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
+ try {
+ ProjectConfig config = ProjectConfig.read(md);
+ config.load(md);
- final Project newProject = new Project(nameKey);
- newProject.setDescription(projectDescription);
- newProject.setSubmitType(submitType);
- newProject.setUseContributorAgreements(contributorAgreements);
- newProject.setUseSignedOffBy(signedOffBy);
- newProject.setUseContentMerge(contentMerge);
- newProject.setRequireChangeID(requireChangeID);
- if (newParent != null) {
- newProject.setParent(newParent.getProject().getNameKey());
- }
+ Project newProject = config.getProject();
+ newProject.setDescription(projectDescription);
+ newProject.setSubmitType(submitType);
+ newProject.setUseContributorAgreements(contributorAgreements);
+ newProject.setUseSignedOffBy(signedOffBy);
+ newProject.setUseContentMerge(contentMerge);
+ newProject.setRequireChangeID(requireChangeID);
+ if (newParent != null) {
+ newProject.setParentName(newParent.getProject().getName());
+ }
- db.projects().insert(Collections.singleton(newProject));
+ if (!ownerIds.isEmpty()) {
+ AccessSection all = config.getAccessSection(AccessSection.ALL, true);
+ for (AccountGroup.UUID ownerId : ownerIds) {
+ AccountGroup accountGroup = groupCache.get(ownerId);
+ GroupReference group = config.resolve(accountGroup);
+ all.getPermission(Permission.OWNER, true).add(
+ new PermissionRule(group));
+ }
+ }
+
+ md.setMessage("Created project\n");
+ if (!config.commit(md)) {
+ throw new IOException("Cannot create " + projectName);
+ }
+ } finally {
+ md.close();
+ }
+ projectCache.onCreateProject(nameKey);
+ repoManager.setProjectDescription(nameKey, projectDescription);
+ rq.scheduleUpdate(nameKey, GitRepositoryManager.REF_CONFIG);
}
private void validateParameters() throws Failure {
+ if (projectName == null || projectName.isEmpty()) {
+ throw new Failure(1, "fatal: Argument NAME is required");
+ }
+
if (projectName.endsWith(Constants.DOT_GIT_EXT)) {
projectName = projectName.substring(0, //
projectName.length() - Constants.DOT_GIT_EXT.length());
}
- if (!CollectionsUtil.isAnyIncludedIn(currentUser.getEffectiveGroups(), projectCreatorGroups)) {
- throw new Failure(1, "fatal: Not permitted to create " + projectName);
- }
-
if (ownerIds != null && !ownerIds.isEmpty()) {
ownerIds =
- new ArrayList<AccountGroup.Id>(new HashSet<AccountGroup.Id>(ownerIds));
+ new ArrayList<AccountGroup.UUID>(new HashSet<AccountGroup.UUID>(ownerIds));
} else {
- ownerIds = new ArrayList<AccountGroup.Id>(projectOwnerGroups);
+ ownerIds = new ArrayList<AccountGroup.UUID>(projectOwnerGroups);
}
while (branch.startsWith("/")) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 3511c71..a369b86 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.sshd.CommandModule;
import com.google.gerrit.sshd.CommandName;
import com.google.gerrit.sshd.Commands;
@@ -34,11 +35,11 @@
// SlaveCommandModule.
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
- command(gerrit, "flush-caches").to(AdminFlushCaches.class);
+ command(gerrit, "flush-caches").to(FlushCaches.class);
command(gerrit, "ls-projects").to(ListProjects.class);
command(gerrit, "query").to(Query.class);
- command(gerrit, "show-caches").to(AdminShowCaches.class);
- command(gerrit, "show-connections").to(AdminShowConnections.class);
+ command(gerrit, "show-caches").to(ShowCaches.class);
+ command(gerrit, "show-connections").to(ShowConnections.class);
command(gerrit, "show-queue").to(ShowQueue.class);
command(gerrit, "stream-events").to(StreamEvents.class);
command(gerrit, "version").to(VersionCommand.class);
@@ -48,7 +49,7 @@
command(git, "upload-pack").to(Upload.class);
command("ps").to(ShowQueue.class);
- command("kill").to(AdminKill.class);
+ command("kill").to(KillCommand.class);
command("scp").to(ScpCommand.class);
// Honor the legacy hyphenated forms as aliases for the non-hyphenated forms
@@ -58,5 +59,12 @@
command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
command("suexec").to(SuExec.class);
+
+ install(new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(ShowCaches.StartupListener.class);
+ }
+ });
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminFlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
similarity index 77%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminFlushCaches.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index b3241ff..639cc42 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminFlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -14,7 +14,9 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.sshd.AdminCommand;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.inject.Inject;
import net.sf.ehcache.Ehcache;
@@ -27,8 +29,9 @@
import java.util.SortedSet;
/** Causes the caches to purge all entries and reload. */
-@AdminCommand
-final class AdminFlushCaches extends CacheCommand {
+final class FlushCaches extends CacheCommand {
+ private static final String WEB_SESSIONS = "web_sessions";
+
@Option(name = "--cache", usage = "flush named cache", metaVar = "NAME")
private List<String> caches = new ArrayList<String>();
@@ -38,6 +41,9 @@
@Option(name = "--list", usage = "list available caches")
private boolean list;
+ @Inject
+ IdentifiedUser currentUser;
+
private PrintWriter p;
@Override
@@ -45,6 +51,13 @@
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
+ if (!currentUser.getCapabilities().canFlushCaches()) {
+ String msg = String.format(
+ "fatal: %s does not have \"Flush Caches\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
parseCommandLine();
flush();
}
@@ -52,6 +65,14 @@
}
private void flush() throws Failure {
+ if (caches.contains(WEB_SESSIONS)
+ && !currentUser.getCapabilities().canAdministrateServer()) {
+ String msg = String.format(
+ "fatal: only site administrators can flush %s",
+ WEB_SESSIONS);
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
p = toPrintWriter(err);
if (list) {
if (all || caches.size() > 0) {
@@ -113,7 +134,7 @@
return true;
} else if (all) {
- if ("web_sessions".equals(cacheName)) {
+ if (WEB_SESSIONS.equals(cacheName)) {
return false;
}
return true;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminKill.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
similarity index 80%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminKill.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
index 5550964..69018af 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminKill.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -14,10 +14,10 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.server.IdentifiedUser;
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.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Inject;
@@ -29,8 +29,10 @@
import java.util.Set;
/** Kill a task in the work queue. */
-@AdminCommand
-final class AdminKill extends BaseCommand {
+final class KillCommand extends BaseCommand {
+ @Inject
+ private IdentifiedUser currentUser;
+
@Inject
private WorkQueue workQueue;
@@ -50,8 +52,15 @@
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
+ if (!currentUser.getCapabilities().canKillTask()) {
+ String msg = String.format(
+ "fatal: %s does not have \"Kill Task\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
parseCommandLine();
- AdminKill.this.commitMurder();
+ KillCommand.this.commitMurder();
}
});
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
index bb04f7a..1ee3cfb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
@@ -15,36 +15,64 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
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.BaseCommand;
-import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
final class ListProjects extends BaseCommand {
+ private static final Logger log = LoggerFactory.getLogger(ListProjects.class);
+
private static final String NODE_PREFIX = "|-- ";
private static final String LAST_NODE_PREFIX = "`-- ";
private static final String DEFAULT_TAB_SEPARATOR = "|";
private static final String NOT_VISIBLE_PROJECT = "(x)";
- @Inject
- private ReviewDb db;
+ static enum FilterType {
+ CODE {
+ @Override
+ boolean matches(Repository git) throws IOException {
+ return !PERMISSIONS.matches(git);
+ }
+ },
+ PERMISSIONS {
+ @Override
+ boolean matches(Repository git) throws IOException {
+ Ref head = git.getRef(Constants.HEAD);
+ return head != null
+ && head.isSymbolic()
+ && GitRepositoryManager.REF_CONFIG.equals(head.getLeaf().getName());
+ }
+ },
+ ALL {
+ @Override
+ boolean matches(Repository git) {
+ return true;
+ }
+ };
+
+ abstract boolean matches(Repository git) throws IOException;
+ }
@Inject
private IdentifiedUser currentUser;
@@ -56,16 +84,19 @@
private GitRepositoryManager repoManager;
@Inject
- @WildProjectName
- private Project.NameKey wildProject;
+ private AllProjectsName allProjectsName;
- @Option(name = "--show-branch", aliases = {"-b"}, usage = "displays the sha of each project in the specified branch")
- private String showBranch;
+ @Option(name = "--show-branch", aliases = {"-b"}, multiValued = true,
+ usage = "displays the sha of each project in the specified branch")
+ private List<String> showBranch;
@Option(name = "--tree", aliases = {"-t"}, usage = "displays project inheritance in a tree-like format\n" +
"this option does not work together with the show-branch option")
private boolean showTree;
+ @Option(name = "--type", usage = "type of project")
+ private FilterType type = FilterType.CODE;
+
private String currentTabSeparator = DEFAULT_TAB_SEPARATOR;
@Override
@@ -85,22 +116,10 @@
}
final PrintWriter stdout = toPrintWriter(out);
-
- TreeMap<String, TreeNode> treeMap = null;
-
- if (showTree) {
- treeMap = new TreeMap<String, TreeNode>();
- }
-
+ final TreeMap<String, TreeNode> treeMap = new TreeMap<String, TreeNode>();
try {
- for (final Project p : db.projects().all()) {
- if (p.getNameKey().equals(wildProject)) {
- // This project "doesn't exist". At least not as a repository.
- //
- continue;
- }
-
- final ProjectState e = projectCache.get(p.getNameKey());
+ for (final Project.NameKey projectName : projectCache.all()) {
+ final ProjectState e = projectCache.get(projectName);
if (e == null) {
// If we can't get it from the cache, pretend its not present.
//
@@ -108,77 +127,136 @@
}
final ProjectControl pctl = e.controlFor(currentUser);
+ final boolean isVisible = pctl.isVisible();
+ if (showTree) {
+ treeMap.put(projectName.get(), new TreeNode(pctl.getProject(), isVisible));
+ continue;
+ }
- if (!showTree) {
+ if (!isVisible) {
+ // Require the project itself to be visible to the user.
+ //
+ continue;
+ }
- if (!pctl.isVisible()) {
- // Require the project itself to be visible to the user.
- //
- continue;
- }
-
+ try {
if (showBranch != null) {
- final Ref ref = getBranchRef(p.getNameKey());
- if (ref == null || ref.getObjectId() == null
- || !pctl.controlForRef(ref.getLeaf().getName()).isVisible()) {
- // No branch, or the user can't see this branch, so skip it.
- //
- continue;
+ Repository git = repoManager.openRepository(projectName);
+ try {
+ if (!type.matches(git)) {
+ continue;
+ }
+
+ List<Ref> refs = getBranchRefs(projectName, pctl);
+ if (!hasValidRef(refs)) {
+ continue;
+ }
+
+ for (Ref ref : refs) {
+ if (ref == null) {
+ // Print stub (forty '-' symbols)
+ stdout.print("----------------------------------------");
+ } else {
+ stdout.print(ref.getObjectId().name());
+ }
+ stdout.print(' ');
+ }
+ } finally {
+ git.close();
}
- stdout.print(ref.getObjectId().name());
- stdout.print(' ');
+ } else if (type != FilterType.ALL) {
+ Repository git = repoManager.openRepository(projectName);
+ try {
+ if (!type.matches(git)) {
+ continue;
+ }
+ } finally {
+ git.close();
+ }
}
- stdout.print(p.getName() + "\n");
- } else {
- treeMap.put(p.getName(), new TreeNode(p, pctl.isVisible()));
+ } 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;
}
+
+ stdout.print(projectName.get() + "\n");
}
if (showTree && treeMap.size() > 0) {
- final List<TreeNode> sortedNodes = new ArrayList<TreeNode>();
-
- // Builds the inheritance tree using a list.
- //
- for (final TreeNode key : treeMap.values()) {
- final String parentName = key.getParentName();
- if (parentName != null) {
- final TreeNode node = treeMap.get((String)parentName);
- if (node != null) {
- node.addChild(key);
- } else {
- sortedNodes.add(key);
- }
- } else {
- sortedNodes.add(key);
- }
- }
-
- // Builds a fake root node, which contains the sorted projects.
- //
- final TreeNode fakeRoot = new TreeNode(null, sortedNodes, false);
- printElement(stdout, fakeRoot, -1, false, sortedNodes.get(sortedNodes.size() - 1));
- stdout.flush();
+ printProjectTree(stdout, treeMap);
}
- } catch (OrmException e) {
- throw new Failure(1, "fatal: database error", e);
} finally {
stdout.flush();
}
}
- private Ref getBranchRef(Project.NameKey projectName) {
- try {
- final Repository r = repoManager.openRepository(projectName);
+ private void printProjectTree(final PrintWriter stdout,
+ final TreeMap<String, TreeNode> treeMap) {
+ final List<TreeNode> sortedNodes = new ArrayList<TreeNode>();
+
+ // Builds the inheritance tree using a list.
+ //
+ for (TreeNode key : treeMap.values()) {
+ if (allProjectsName.equals(key.getProject().getNameKey())) {
+ sortedNodes.add(key);
+ continue;
+ }
+
+ String parentName = key.getParentName();
+ if (parentName == null) {
+ parentName = allProjectsName.get();
+ }
+
+ TreeNode node = treeMap.get(parentName);
+ if (node != null) {
+ node.addChild(key);
+ } else {
+ sortedNodes.add(key);
+ }
+ }
+
+ // Builds a fake root node, which contains the sorted projects.
+ //
+ final TreeNode fakeRoot = new TreeNode(null, sortedNodes, false);
+ printElement(stdout, fakeRoot, -1, false, sortedNodes.get(sortedNodes.size() - 1));
+ stdout.flush();
+ }
+
+ private List<Ref> getBranchRefs(Project.NameKey projectName,
+ ProjectControl projectControl) {
+ Ref[] result = new Ref[showBranch.size()];
+ try {
+ Repository git = repoManager.openRepository(projectName);
try {
- return r.getRef(showBranch);
+ for (int i = 0; i < showBranch.size(); i++) {
+ Ref ref = git.getRef(showBranch.get(i));
+ if (ref != null
+ && ref.getObjectId() != null
+ && projectControl.controlForRef(ref.getLeaf().getName()).isVisible()) {
+ result[i] = ref;
+ }
+ }
} finally {
- r.close();
+ git.close();
}
} catch (IOException ioe) {
- return null;
+ // Fall through and return what is available.
}
+ return Arrays.asList(result);
+ }
+
+ private static boolean hasValidRef(List<Ref> refs) {
+ for (int i = 0; i < refs.size(); i++) {
+ if (refs.get(i) != null) {
+ return true;
+ }
+ }
+ return false;
}
/** Class created to manipulate the nodes of the project inheritance tree **/
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 466d454..c2f7608 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
@@ -26,12 +26,13 @@
final CommandName gerrit = Commands.named("gerrit");
command(gerrit, "approve").to(ReviewCommand.class);
- command(gerrit, "create-account").to(AdminCreateAccount.class);
- command(gerrit, "create-group").to(AdminCreateGroup.class);
+ command(gerrit, "create-account").to(CreateAccountCommand.class);
+ command(gerrit, "create-group").to(CreateGroupCommand.class);
command(gerrit, "create-project").to(CreateProject.class);
command(gerrit, "gsql").to(AdminQueryShell.class);
+ command(gerrit, "modify-reviewers").to(ModifyReviewersCommand.class);
command(gerrit, "receive-pack").to(Receive.class);
- command(gerrit, "replicate").to(AdminReplicate.class);
+ command(gerrit, "replicate").to(Replicate.class);
command(gerrit, "set-project-parent").to(AdminSetParent.class);
command(gerrit, "review").to(ReviewCommand.class);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ModifyReviewersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ModifyReviewersCommand.java
new file mode 100644
index 0000000..b351b16
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ModifyReviewersCommand.java
@@ -0,0 +1,289 @@
+// 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.sshd.commands;
+
+import com.google.gerrit.common.data.ReviewerResult;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.RevId;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.patch.AddReviewer;
+import com.google.gerrit.server.patch.RemoveReviewer;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.inject.Inject;
+
+import org.apache.sshd.server.Environment;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ModifyReviewersCommand extends BaseCommand {
+ private static final Logger log =
+ LoggerFactory.getLogger(ModifyReviewersCommand.class);
+
+ @Option(name = "--project", aliases = "-p", usage = "project containing the change")
+ private ProjectControl projectControl;
+
+ @Option(name = "--add", aliases = {"-a"}, metaVar = "EMAIL", usage = "reviewer to add")
+ void optionAdd(Account.Id who) {
+ toAdd.add(who);
+ }
+
+ @Option(name = "--remove", aliases = {"-r"}, metaVar = "EMAIL", usage = "reviewer to remove")
+ void optionRemove(Account.Id who) {
+ toRemove.add(who);
+ }
+
+ @Argument(index = 0, required = true, multiValued = true, metaVar = "COMMIT", usage = "changes to modify")
+ void addChange(String token) {
+ try {
+ changes.addAll(parseChangeId(token));
+ } catch (UnloggedFailure e) {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ } catch (OrmException e) {
+ throw new IllegalArgumentException("database is down", e);
+ }
+ }
+
+ @Inject
+ private ReviewDb db;
+
+ @Inject
+ private AddReviewer.Factory addReviewerFactory;
+
+ @Inject
+ private RemoveReviewer.Factory removeReviewerFactory;
+
+ @Inject
+ private ChangeControl.Factory changeControlFactory;
+
+ private Set<Account.Id> toAdd = new HashSet<Account.Id>();
+ private Set<Account.Id> toRemove = new HashSet<Account.Id>();
+ private Set<Change.Id> changes = new HashSet<Change.Id>();
+
+ @Override
+ public final void start(final Environment env) {
+ startThread(new CommandRunnable() {
+ @Override
+ public void run() throws Failure {
+ parseCommandLine();
+
+ boolean ok = true;
+ for (Change.Id changeId : changes) {
+ try {
+ ok &= modifyOne(changeId);
+ } catch (Exception err) {
+ ok = false;
+ log.error("Error updating reviewers on change " + changeId, err);
+ writeError("fatal", "internal error while updating " + changeId);
+ }
+ }
+
+ if (!ok) {
+ throw error("fatal: one or more updates failed; review output above");
+ }
+ }
+ });
+ }
+
+ private boolean modifyOne(Change.Id changeId) throws Exception {
+ changeControlFactory.validateFor(changeId);
+
+ ReviewerResult result;
+ boolean ok = true;
+
+ // Remove reviewers
+ //
+ result = removeReviewerFactory.create(changeId, toRemove).call();
+ ok &= result.getErrors().isEmpty();
+ for (ReviewerResult.Error resultError : result.getErrors()) {
+ String message;
+ switch (resultError.getType()) {
+ case REMOVE_NOT_PERMITTED:
+ message = "not permitted to remove {0} from {1}";
+ break;
+ case COULD_NOT_REMOVE:
+ message = "could not remove {0} from {1}";
+ break;
+ default:
+ message = "could not remove {0}: {2}";
+ }
+ writeError("error", MessageFormat.format(message,
+ resultError.getName(), changeId, resultError.getType()));
+ }
+
+ // Add reviewers
+ //
+ result = addReviewerFactory.create(changeId, stringSet(toAdd)).call();
+ ok &= result.getErrors().isEmpty();
+ for (ReviewerResult.Error resultError : result.getErrors()) {
+ String message;
+ switch (resultError.getType()) {
+ case ACCOUNT_NOT_FOUND:
+ message = "account {0} not found";
+ break;
+ case ACCOUNT_INACTIVE:
+ message = "account {0} inactive";
+ break;
+ case CHANGE_NOT_VISIBLE:
+ message = "change {1} not visible to {0}";
+ break;
+ default:
+ message = "could not add {0}: {2}";
+ }
+ writeError("error", MessageFormat.format(message,
+ resultError.getName(), changeId, resultError.getType()));
+ }
+
+ return ok;
+ }
+
+ private static Set<String> stringSet(Set<Account.Id> ids) {
+ Set<String> res = new HashSet<String>();
+ for (Account.Id id : ids) {
+ res.add(Integer.toString(id.get()));
+ }
+ return res;
+ }
+
+ private Set<Change.Id> parseChangeId(String idstr)
+ throws UnloggedFailure, OrmException {
+ Set<Change.Id> matched = new HashSet<Change.Id>(4);
+ boolean isCommit = idstr.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$");
+
+ // By newer style changeKey?
+ //
+ boolean changeKeyParses = false;
+ if (idstr.matches("^I[0-9a-fA-F]*$")) {
+ Change.Key key;
+ try {
+ key = Change.Key.parse(idstr);
+ changeKeyParses = true;
+ } catch (IllegalArgumentException e) {
+ key = null;
+ changeKeyParses = false;
+ }
+
+ if (changeKeyParses) {
+ for (Change change : db.changes().byKeyRange(key, key.max())) {
+ matchChange(matched, change);
+ }
+ }
+ }
+
+ // By commit?
+ //
+ if (isCommit) {
+ RevId id = new RevId(idstr);
+ ResultSet<PatchSet> patches;
+ if (id.isComplete()) {
+ patches = db.patchSets().byRevision(id);
+ } else {
+ patches = db.patchSets().byRevisionRange(id, id.max());
+ }
+
+ for (PatchSet ps : patches) {
+ matchChange(matched, ps.getId().getParentKey());
+ }
+ }
+
+ // By older style changeId?
+ //
+ boolean changeIdParses = false;
+ if (idstr.matches("^[1-9][0-9]*$")) {
+ Change.Id id;
+ try {
+ id = Change.Id.parse(idstr);
+ changeIdParses = true;
+ } catch (IllegalArgumentException e) {
+ id = null;
+ changeIdParses = false;
+ }
+
+ if (changeIdParses) {
+ matchChange(matched, id);
+ }
+ }
+
+ if (!changeKeyParses && !isCommit && !changeIdParses) {
+ throw error("\"" + idstr + "\" is not a valid change");
+ }
+
+ switch (matched.size()) {
+ case 0:
+ throw error("\"" + idstr + "\" no such change");
+
+ case 1:
+ return matched;
+
+ default:
+ throw error("\"" + idstr + "\" matches multiple changes");
+ }
+ }
+
+ private void matchChange(Set<Change.Id> matched, Change.Id changeId) {
+ if (changeId != null && !matched.contains(changeId)) {
+ try {
+ matchChange(matched, db.changes().get(changeId));
+ } catch (OrmException e) {
+ log.warn("Error reading change " + changeId, e);
+ }
+ }
+ }
+
+ private void matchChange(Set<Change.Id> matched, Change change) {
+ try {
+ if (change != null
+ && inProject(change)
+ && changeControlFactory.controlFor(change).isVisible()) {
+ matched.add(change.getId());
+ }
+ } catch (NoSuchChangeException e) {
+ // Ignore this change.
+ }
+ }
+
+ private boolean inProject(Change change) {
+ if (projectControl != null) {
+ return projectControl.getProject().getNameKey().equals(change.getProject());
+ } else {
+ // No --project option, so they want every project.
+ return true;
+ }
+ }
+
+ private void writeError(String type, String msg) {
+ try {
+ err.write((type + ": " + msg + "\n").getBytes(ENC));
+ } catch (IOException e) {
+ }
+ }
+
+ private static UnloggedFailure error(String msg) {
+ return new UnloggedFailure(1, msg);
+ }
+}
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 e31925b..425d4f8 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
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.common.data.Capable;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.ReceiveCommits;
@@ -29,7 +30,6 @@
import org.kohsuke.args4j.Option;
import java.io.IOException;
-import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -71,8 +71,8 @@
final ReceiveCommits receive = factory.create(projectControl, repo);
- ReceiveCommits.Capable r = receive.canUpload();
- if (r != ReceiveCommits.Capable.OK) {
+ Capable r = receive.canUpload();
+ if (r != Capable.OK) {
throw new UnloggedFailure(1, "\nfatal: " + r.getMessage());
}
@@ -88,9 +88,6 @@
try {
receive.advertiseHistory();
rp.receive(in, out, err);
- } catch (InterruptedIOException err) {
- throw new Failure(128, "fatal: client IO read/write timeout", err);
-
} catch (UnpackException badStream) {
// 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/AdminReplicate.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
similarity index 84%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
index 8e2c74f..9dc8671 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
@@ -15,10 +15,10 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Inject;
@@ -31,8 +31,7 @@
import java.util.concurrent.TimeUnit;
/** Force a project to replicate, again. */
-@AdminCommand
-final class AdminReplicate extends BaseCommand {
+final class Replicate extends BaseCommand {
@Option(name = "--all", usage = "push all known projects")
private boolean all;
@@ -43,6 +42,9 @@
private List<String> projectNames = new ArrayList<String>(2);
@Inject
+ IdentifiedUser currentUser;
+
+ @Inject
private PushAllProjectsOp.Factory pushAllOpFactory;
@Inject
@@ -56,8 +58,15 @@
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
+ if (!currentUser.getCapabilities().canStartReplication()) {
+ String msg = String.format(
+ "fatal: %s does not have \"Start Replication\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
parseCommandLine();
- AdminReplicate.this.schedule();
+ Replicate.this.schedule();
}
});
}
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 47bae4f..0f63577 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
@@ -17,6 +17,7 @@
import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Branch;
@@ -32,8 +33,8 @@
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PublishComments;
-import com.google.gerrit.server.project.CanSubmitResult;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.workflow.FunctionState;
@@ -202,9 +203,8 @@
});
}
- private void approveOne(final PatchSet.Id patchSetId)
- throws NoSuchChangeException, UnloggedFailure, OrmException,
- EmailException {
+ private void approveOne(final PatchSet.Id patchSetId) throws
+ NoSuchChangeException, OrmException, EmailException, Failure {
final Change.Id changeId = patchSetId.getParentKey();
ChangeControl changeControl = changeControlFactory.validateFor(changeId);
@@ -222,37 +222,89 @@
}
}
- publishCommentsFactory.create(patchSetId, changeComment, aps).call();
+ try {
+ publishCommentsFactory.create(patchSetId, changeComment, aps).call();
- if (abandonChange) {
- if (changeControl.canAbandon()) {
- ChangeUtil.abandon(patchSetId, currentUser, changeComment, db,
- abandonedSenderFactory, hooks);
- } else {
- throw error("Not permitted to abandon change");
+ if (abandonChange) {
+ if (changeControl.canAbandon()) {
+ ChangeUtil.abandon(patchSetId, currentUser, changeComment, db,
+ abandonedSenderFactory, hooks);
+ } else {
+ throw error("Not permitted to abandon change");
+ }
}
- }
- if (restoreChange) {
- if (changeControl.canRestore()) {
- ChangeUtil.restore(patchSetId, currentUser, changeComment, db,
- abandonedSenderFactory, hooks);
- } else {
- throw error("Not permitted to restore change");
+ if (restoreChange) {
+ if (changeControl.canRestore()) {
+ ChangeUtil.restore(patchSetId, currentUser, changeComment, db,
+ abandonedSenderFactory, hooks);
+ } else {
+ throw error("Not permitted to restore change");
+ }
+ if (submitChange) {
+ changeControl = changeControlFactory.validateFor(changeId);
+ }
}
- if (submitChange) {
- changeControl = changeControlFactory.validateFor(changeId);
- }
+ } catch (InvalidChangeOperationException e) {
+ throw error(e.getMessage());
}
if (submitChange) {
- CanSubmitResult result =
- changeControl.canSubmit(patchSetId, db, approvalTypes,
- functionStateFactory);
- if (result == CanSubmitResult.OK) {
- toSubmit.add(patchSetId);
- } else {
- throw error(result.getMessage());
+ List<SubmitRecord> result = changeControl.canSubmit(db, patchSetId);
+ if (result.isEmpty()) {
+ throw new Failure(1, "ChangeControl.canSubmit returned empty list");
+ }
+ switch (result.get(0).status) {
+ case OK:
+ if (changeControl.getRefControl().canSubmit()) {
+ toSubmit.add(patchSetId);
+ } else {
+ throw error("change " + changeId + ": you do not have submit permission");
+ }
+ break;
+
+ case NOT_READY: {
+ StringBuilder msg = new StringBuilder();
+ for (SubmitRecord.Label lbl : result.get(0).labels) {
+ switch (lbl.status) {
+ case OK:
+ break;
+
+ case REJECT:
+ if (msg.length() > 0) msg.append("\n");
+ msg.append("change " + changeId + ": blocked by " + lbl.label);
+ break;
+
+ case NEED:
+ if (msg.length() > 0) msg.append("\n");
+ msg.append("change " + changeId + ": needs " + lbl.label);
+ break;
+
+ case IMPOSSIBLE:
+ if (msg.length() > 0) msg.append("\n");
+ msg.append("change " + changeId + ": needs " + lbl.label
+ + " (check project access)");
+ break;
+
+ default:
+ throw new Failure(1, "Unsupported label status " + lbl.status);
+ }
+ }
+ throw error(msg.toString());
+ }
+
+ case CLOSED:
+ throw error("change " + changeId + " is closed");
+
+ case RULE_ERROR:
+ if (result.get(0).errorMessage != null) {
+ throw error("change " + changeId + ": " + result.get(0).errorMessage);
+ } else {
+ throw error("change " + changeId + ": internal rule error");
+ }
+
+ default:
+ throw new Failure(1, "Unsupported status " + result.get(0).status);
}
}
}
@@ -331,7 +383,7 @@
functionStateFactory.create(changeControl.getChange(), patchSetId,
Collections.<PatchSetApproval> emptyList());
psa.setValue(v);
- fs.normalize(approvalTypes.getApprovalType(psa.getCategoryId()), psa);
+ fs.normalize(approvalTypes.byId(psa.getCategoryId()), psa);
if (v != psa.getValue()) {
throw error(ao.name() + "=" + ao.value() + " not permitted");
}
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
new file mode 100644
index 0000000..4de10d6
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -0,0 +1,376 @@
+// 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.sshd.commands;
+
+import com.google.gerrit.common.Version;
+import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.server.IdentifiedUser;
+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.BaseCommand;
+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 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+
+/** Show the current cache states. */
+final class ShowCaches extends CacheCommand {
+ private static volatile long serverStarted;
+
+ static class StartupListener implements LifecycleListener {
+ @Override
+ public void start() {
+ serverStarted = System.currentTimeMillis();
+ }
+
+ @Override
+ public void stop() {
+ }
+ }
+
+ @Option(name = "--gc", usage = "perform Java GC before printing memory stats")
+ private boolean gc;
+
+ @Option(name = "--show-jvm", usage = "show details about the JVM")
+ private boolean showJVM;
+
+ @Inject
+ private IdentifiedUser currentUser;
+
+ @Inject
+ private WorkQueue workQueue;
+
+ @Inject
+ private SshDaemon daemon;
+
+ @Inject
+ @SitePath
+ private File sitePath;
+
+ private PrintWriter p;
+
+ @Override
+ public void start(final Environment env) {
+ startThread(new CommandRunnable() {
+ @Override
+ public void run() throws Exception {
+ if (!currentUser.getCapabilities().canViewCaches()) {
+ String msg = String.format(
+ "fatal: %s does not have \"View Caches\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
+ parseCommandLine();
+ display();
+ }
+ });
+ }
+
+ private void display() {
+ p = toPrintWriter(out);
+
+ Date now = new Date();
+ p.format(
+ "%-25s %-20s now %16s\n",
+ "Gerrit Code Review",
+ Version.getVersion() != null ? Version.getVersion() : "",
+ new SimpleDateFormat("HH:mm:ss zzz").format(now));
+ p.format(
+ "%-25s %-20s uptime %16s\n",
+ "", "",
+ uptime(now.getTime() - serverStarted));
+ p.print('\n');
+
+ p.print(String.format(//
+ "%1s %-18s %-4s|%-20s| %-5s |%-14s|\n" //
+ , "" //
+ , "Name" //
+ , "Max" //
+ , "Object Count" //
+ , "AvgGet" //
+ , "Hit Ratio" //
+ ));
+ p.print(String.format(//
+ "%1s %-18s %-4s|%6s %6s %6s| %-5s |%-4s %-4s %-4s|\n" //
+ , "" //
+ , "" //
+ , "Age" //
+ , "Disk" //
+ , "Mem" //
+ , "Cnt" //
+ , "" //
+ , "Disk" //
+ , "Mem" //
+ , "Agg" //
+ ));
+ p.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();
+
+ if (useDisk) {
+ p.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 {
+ p.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) //
+ ));
+ }
+ }
+ p.print('\n');
+
+ if (gc) {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ sshSummary();
+ taskSummary();
+ memSummary();
+
+ if (showJVM) {
+ jvmSummary();
+ }
+
+ p.flush();
+ }
+
+ private void memSummary() {
+ final Runtime r = Runtime.getRuntime();
+ final long mMax = r.maxMemory();
+ final long mFree = r.freeMemory();
+ final long mTotal = r.totalMemory();
+ final long mInuse = mTotal - mFree;
+
+ final int jgitOpen = WindowCacheStatAccessor.getOpenFiles();
+ final long jgitBytes = WindowCacheStatAccessor.getOpenBytes();
+
+ p.format("Mem: %s total = %s used + %s free + %s buffers\n",
+ bytes(mTotal),
+ bytes(mInuse - jgitBytes),
+ bytes(mFree),
+ bytes(jgitBytes));
+ p.format(" %s max\n", bytes(mMax));
+ p.format(" %8d open files, %8d cpus available, %8d threads\n",
+ jgitOpen,
+ r.availableProcessors(),
+ ManagementFactory.getThreadMXBean().getThreadCount());
+ p.print('\n');
+ }
+
+ private void taskSummary() {
+ Collection<Task<?>> pending = workQueue.getTasks();
+ int tasksTotal = pending.size();
+ int tasksRunning = 0, tasksReady = 0, tasksSleeping = 0;
+ for (Task<?> task : pending) {
+ switch (task.getState()) {
+ case RUNNING: tasksRunning++; break;
+ case READY: tasksReady++; break;
+ case SLEEPING: tasksSleeping++; break;
+ }
+ }
+ p.format(
+ "Tasks: %4d total = %4d running + %4d ready + %4d sleeping\n",
+ tasksTotal,
+ tasksRunning,
+ tasksReady,
+ tasksSleeping);
+ }
+
+ private void sshSummary() {
+ IoAcceptor acceptor = daemon.getIoAcceptor();
+ if (acceptor == null) {
+ return;
+ }
+
+ long now = System.currentTimeMillis();
+ Collection<IoSession> list = acceptor.getManagedSessions().values();
+ long oldest = now;
+ for (IoSession s : list) {
+ oldest = Math.min(oldest, s.getCreationTime());
+ }
+
+ p.format(
+ "SSH: %4d users, oldest session started %s ago\n",
+ list.size(),
+ uptime(now - oldest));
+ }
+
+ private void jvmSummary() {
+ OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
+ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
+ p.format("JVM: %s %s %s\n",
+ runtimeBean.getVmVendor(),
+ runtimeBean.getVmName(),
+ runtimeBean.getVmVersion());
+ p.format(" on %s %s %s\n", "",
+ osBean.getName(),
+ osBean.getVersion(),
+ osBean.getArch());
+ try {
+ p.format(" running as %s on %s\n",
+ System.getProperty("user.name"),
+ InetAddress.getLocalHost().getHostName());
+ } catch (UnknownHostException e) {
+ }
+ p.format(" cwd %s\n", path(new File(".").getAbsoluteFile().getParentFile()));
+ p.format(" site %s\n", path(sitePath));
+ }
+
+ private String path(File file) {
+ try {
+ return file.getCanonicalPath();
+ } catch (IOException err) {
+ return file.getAbsolutePath();
+ }
+ }
+
+ private String uptime(long uptimeMillis) {
+ if (uptimeMillis < 1000) {
+ return String.format("%3d ms", uptimeMillis);
+ }
+
+ long uptime = uptimeMillis / 1000L;
+
+ long min = uptime / 60;
+ if (min < 60) {
+ return String.format("%2d min %2d sec", min, uptime - min * 60);
+ }
+
+ long hr = uptime / 3600;
+ if (hr < 24) {
+ min = (uptime - hr * 3600) / 60;
+ return String.format("%2d hrs %2d min", hr, min);
+ }
+
+ long days = uptime / (24 * 3600);
+ hr = (uptime - (days * 24 * 3600)) / 3600;
+ return String.format("%4d days %2d hrs", days, hr);
+ }
+
+ private String bytes(double value) {
+ value /= 1024;
+ String suffix = "k";
+
+ if (value > 1024) {
+ value /= 1024;
+ suffix = "m";
+ }
+ if (value > 1024) {
+ value /= 1024;
+ suffix = "g";
+ }
+ return String.format("%1$6.2f%2$s", value, suffix);
+ }
+
+ private String count(long cnt) {
+ if (cnt == 0) {
+ return "";
+ }
+ return String.format("%6d", cnt);
+ }
+
+ private String duration(double ms) {
+ if (Math.abs(ms) <= 0.05) {
+ return "";
+ }
+ String suffix = "ms";
+ if (ms >= 1000) {
+ ms /= 1000;
+ 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;
+ }
+
+ private String percent(final long value, final long total) {
+ if (total <= 0) {
+ return "";
+ }
+ final long pcent = (100 * value) / total;
+ return String.format("%3d%%", (int) pcent);
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
similarity index 92%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index b6c6119..a72ce90 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -17,7 +17,6 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.util.IdGenerator;
-import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.SshDaemon;
import com.google.gerrit.sshd.SshSession;
@@ -41,14 +40,16 @@
import java.util.List;
/** Show the current SSH connections. */
-@AdminCommand
-final class AdminShowConnections extends BaseCommand {
+final class ShowConnections extends BaseCommand {
@Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
private boolean numeric;
private PrintWriter p;
@Inject
+ IdentifiedUser currentUser;
+
+ @Inject
private SshDaemon daemon;
@Override
@@ -56,8 +57,15 @@
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
+ if (!currentUser.getCapabilities().canViewConnections()) {
+ String msg = String.format(
+ "fatal: %s does not have \"View Connections\" capability.",
+ currentUser.getUserName());
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ }
+
parseCommandLine();
- AdminShowConnections.this.display();
+ ShowConnections.this.display();
}
});
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index a196a3e..80a6c11 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -15,7 +15,7 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.git.WorkQueue.Task;
@@ -50,7 +50,7 @@
private ProjectCache projectCache;
@Inject
- private CurrentUser userProvider;
+ private IdentifiedUser currentUser;
private PrintWriter p;
private int columns = 80;
@@ -110,7 +110,7 @@
int numberOfPendingTasks = 0;
final long now = System.currentTimeMillis();
- final boolean isAdministrator = userProvider.isAdministrator();
+ final boolean viewAll = currentUser.getCapabilities().canViewQueue();
for (final Task<?> task : pending) {
final long delay = task.getDelay(TimeUnit.MILLISECONDS);
@@ -137,7 +137,7 @@
Project.NameKey projectName = null;
String remoteName = null;
- if (!isAdministrator) {
+ if (!viewAll) {
if (task instanceof ProjectTask<?>) {
projectName = ((ProjectTask<?>)task).getProjectNameKey();
remoteName = ((ProjectTask<?>)task).getRemoteName();
@@ -149,7 +149,7 @@
e = projectCache.get(projectName);
}
- regularUserCanSee = e != null && e.controlFor(userProvider).isVisible();
+ regularUserCanSee = e != null && e.controlFor(currentUser).isVisible();
if (regularUserCanSee) {
numberOfPendingTasks++;
@@ -157,7 +157,7 @@
}
// Shows information about tasks depending on the user rights
- if (isAdministrator || (!hasCustomizedPrint && regularUserCanSee)) {
+ if (viewAll || (!hasCustomizedPrint && regularUserCanSee)) {
p.print(String.format("%8s %-12s %-8s %s\n", //
id(task.getTaskId()), start, "", format(task)));
} else if (regularUserCanSee) {
@@ -174,7 +174,7 @@
p.print("----------------------------------------------"
+ "--------------------------------\n");
- if (isAdministrator) {
+ if (viewAll) {
numberOfPendingTasks = pending.size();
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
index 951568a..b072abf 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -25,7 +25,6 @@
import org.eclipse.jgit.transport.UploadPack;
import java.io.IOException;
-import java.io.InterruptedIOException;
/** Publishes Git repositories over SSH using the Git upload-pack protocol. */
final class Upload extends AbstractGitCommand {
@@ -51,10 +50,6 @@
}
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
- try {
- up.upload(in, out, err);
- } catch (InterruptedIOException err) {
- throw new Failure(128, "fatal: client IO read/write timeout", err);
- }
+ up.upload(in, out, err);
}
}
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index 6db5241..cfa302e 100644
--- a/gerrit-util-cli/pom.xml
+++ b/gerrit-util-cli/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index 39d3ce0..312a215 100644
--- a/gerrit-util-ssl/pom.xml
+++ b/gerrit-util-ssl/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 2d85b74..110f966 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gerrit-war</artifactId>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java
new file mode 100644
index 0000000..3bbcc98
--- /dev/null
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java
@@ -0,0 +1,61 @@
+// 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;
+
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.config.SitePath;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.File;
+import java.util.List;
+
+/** Provides {@link java.io.File} annotated with {@link SitePath}. */
+class SitePathFromSystemConfigProvider implements Provider<File> {
+ private final File path;
+
+ @Inject
+ SitePathFromSystemConfigProvider(SchemaFactory<ReviewDb> schemaFactory)
+ throws OrmException {
+ path = read(schemaFactory);
+ }
+
+ @Override
+ public File get() {
+ return path;
+ }
+
+ private static File read(SchemaFactory<ReviewDb> schemaFactory)
+ throws OrmException {
+ ReviewDb db = schemaFactory.open();
+ try {
+ List<SystemConfig> all = db.systemConfig().all().toList();
+ switch (all.size()) {
+ case 1:
+ return new File(all.get(0).sitePath);
+ case 0:
+ throw new OrmException("system_config table is empty");
+ default:
+ throw new OrmException("system_config must have exactly 1 row;"
+ + " found " + all.size() + " rows instead");
+ }
+ } finally {
+ db.close();
+ }
+ }
+}
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 19c16ca..ac05f0f 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
@@ -25,9 +25,10 @@
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.config.SitePath;
-import com.google.gerrit.server.config.SitePathFromSystemConfigProvider;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DatabaseModule;
+import com.google.gerrit.server.schema.SchemaModule;
+import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.inject.AbstractModule;
@@ -114,6 +115,7 @@
manager = new LifecycleManager();
manager.add(dbInjector);
+ manager.add(cfgInjector);
manager.add(sysInjector);
manager.add(sshInjector);
manager.add(webInjector);
@@ -166,6 +168,8 @@
});
modules.add(new GerritServerConfigModule());
}
+ modules.add(new SchemaModule());
+ modules.add(SchemaVersionCheck.module());
modules.add(new AuthConfigModule());
return dbInjector.createChildInjector(modules);
}
diff --git a/pom.xml b/pom.xml
index c02d42c..693e046 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-parent</artifactId>
<packaging>pom</packaging>
- <version>2.1-SNAPSHOT</version>
+ <version>2.2-SNAPSHOT</version>
<name>Gerrit Code Review - Parent</name>
<url>http://code.google.com/p/gerrit/</url>
@@ -48,9 +48,9 @@
<properties>
<jgitVersion>0.12.1.53-g5ec4977</jgitVersion>
<gwtormVersion>1.1.5</gwtormVersion>
- <gwtjsonrpcVersion>1.2.3</gwtjsonrpcVersion>
- <gwtexpuiVersion>1.2.2</gwtexpuiVersion>
- <gwtVersion>2.1.1</gwtVersion>
+ <gwtjsonrpcVersion>1.2.5</gwtjsonrpcVersion>
+ <gwtexpuiVersion>1.2.4</gwtexpuiVersion>
+ <gwtVersion>2.3.0</gwtVersion>
<slf4jVersion>1.6.1</slf4jVersion>
<guiceVersion>2.0</guiceVersion>
<jettyVersion>7.2.1.v20101111</jettyVersion>
@@ -324,6 +324,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
</plugin>
@@ -355,13 +361,13 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
- <version>2.1.0-1</version>
+ <version>2.3.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
- <version>1.6</version>
+ <version>1.5</version>
</plugin>
</plugins>
</pluginManagement>
@@ -376,19 +382,6 @@
<encoding>UTF-8</encoding>
</configuration>
</plugin>
-
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <version>2.1.2</version>
- <executions>
- <execution>
- <goals>
- <goal>jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
</plugins>
</build>
@@ -486,7 +479,7 @@
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
- <version>0.1.44</version>
+ <version>0.1.44-1</version>
</dependency>
<dependency>
@@ -508,6 +501,20 @@
</dependency>
<dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>1.0.0.GA</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>1.0.0.GA</version>
+ <classifier>sources</classifier>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>com.google.code.guice</groupId>
<artifactId>guice</artifactId>
<version>${guiceVersion}</version>
@@ -533,12 +540,6 @@
</dependency>
<dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <version>1.6</version>
- </dependency>
-
- <dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>2.2</version>
@@ -751,6 +752,12 @@
<artifactId>automaton</artifactId>
<version>1.11.2</version>
</dependency>
+
+ <dependency>
+ <groupId>com.googlecode.prolog-cafe</groupId>
+ <artifactId>PrologCafe</artifactId>
+ <version>1.3-SNAPSHOT</version>
+ </dependency>
</dependencies>
</dependencyManagement>