Merge "Add adminUrl to replication for repository creation"
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 72e3f86..f167cc5 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -29,7 +29,6 @@
DOC_HTML := $(patsubst %.txt,%.html,$(wildcard *.txt))
LOCAL_ROOT := .published
-SCRIPTSDIR := $(shell pwd)/javascript
COMMIT := $(shell git describe HEAD | sed s/^v//)
PUB_DIR := $(PUB_ROOT)/$(VERSION)
PRIOR = PRIOR
@@ -71,13 +70,13 @@
$(SVN) commit -m "Updated $(VERSION) documentation to v$(COMMIT)"
@-rm -rf $(LOCAL_ROOT)
-$(DOC_HTML): %.html : %.txt $(SCRIPTSDIR)/toc.js
+$(DOC_HTML): %.html : %.txt
@echo "FORMAT $@"
@rm -f $@+ $@
- @$(ASCIIDOC) --unsafe \
- -a toc \
+ @$(ASCIIDOC) -a toc \
-a 'revision=$(REVISION)' \
- -a 'scriptsdir=$(SCRIPTSDIR)' \
- -b xhtml11 -f asciidoc.conf \
- $(ASCIIDOC_EXTRA) -o $@+ $<
+ -b xhtml11 \
+ -f asciidoc.conf \
+ $(ASCIIDOC_EXTRA) \
+ -o $@+ $<
@mv $@+ $@
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 7c9fcbf..2862287 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -131,14 +131,13 @@
groups in an approval category. For example, a user is a member of
`Foo Leads`, and the following ACLs are granted on a project:
-[grid="all"]
-`---------------`---------------`-------------`-------
-Group Reference Name Category Range
-------------------------------------------------------
-Anonymous Users refs/heads/* Code Review -1..+1
-Registered Users refs/heads/* Code Review -1..+2
-Foo Leads refs/heads/* Code Review -2..0
-------------------------------------------------------
+[options="header"]
+|=================================================
+|Group |Reference Name |Category|Range
+|Anonymous Users |refs/heads/*|Code Review|-1..+1
+|Registered Users|refs/heads/*|Code Review|-1..+2
+|Foo Leads |refs/heads/*|Code Review|-2..0
+|=================================================
Then the effective range permitted to be used by the user is
`-2..+2`, as the user is a member of all three groups (see above
@@ -173,14 +172,13 @@
the `refs/heads/qa` branch, and the following ACLs are granted
on the project:
-[grid="all"]
-`---------------`---------------`-------------`-------
-Group Reference Name Category Range
-------------------------------------------------------
-Registered Users refs/heads/* Code Review -1..+1
-Foo Leads refs/heads/* Code Review -2..+2
-QA Leads refs/heads/qa Code Review -2..+2
-------------------------------------------------------
+[options="header"]
+|=====================================================
+|Group |Reference Name|Category |Range
+|Registered Users |refs/heads/* |Code Review| -1..+1
+|Foo Leads |refs/heads/* |Code Review| -2..+2
+|QA Leads |refs/heads/qa |Code Review| -2..+2
+|=====================================================
Then the effective range permitted to be used by the user is
`-2..+2`, as the user's membership of `Foo Leads` effectively grant
@@ -197,14 +195,13 @@
review a change destined for branch `refs/heads/qa` in a project,
and the following ACLs are granted:
-[grid="all"]
-`---------------`----------------`--------------`-------
-Group Reference Name Category Range
---------------------------------------------------------
-Registered Users refs/heads/* Code Review -1..+1
-Foo Leads refs/heads/* Code Review -2..+2
-QA Leads -refs/heads/qa Code Review -2..+2
---------------------------------------------------------
+[options="header"]
+|=====================================================
+|Group |Reference Name|Category |Range
+|Registered Users|refs/heads/* |Code Review| -1..+1
+|Foo Leads |refs/heads/* |Code Review| -2..+2
+|QA Leads |-refs/heads/qa|Code Review| -2..+2
+|=====================================================
Then this user will not have `Code Review` rights on that change,
since there is an exclusive access right in place for the
@@ -216,15 +213,14 @@
`Foo Leads`, in `refs/heads/qa` then the following access rights
would be needed:
-[grid="all"]
-`---------------`----------------`--------------`-------
-Group Reference Name Category Range
---------------------------------------------------------
-Registered Users refs/heads/* Code Review -1..+1
-Foo Leads refs/heads/* Code Review -2..+2
-QA Leads -refs/heads/qa Code Review -2..+2
-Foo Leads refs/heads/qa Code Review -2..+2
---------------------------------------------------------
+[options="header"]
+|=====================================================
+|Group |Reference Name|Category |Range
+|Registered Users|refs/heads/* |Code Review| -1..+1
+|Foo Leads |refs/heads/* |Code Review| -2..+2
+|QA Leads |-refs/heads/qa|Code Review| -2..+2
+|Foo Leads |refs/heads/qa |Code Review| -2..+2
+|=====================================================
OpenID Authentication
@@ -328,9 +324,9 @@
Upload Access
~~~~~~~~~~~~~
-The `Read Access +2` permits the user to upload a commit to the
-project's `refs/for/BRANCH` namespace, creating a new change for
-code review.
+The `Read Access +2` permits the user to upload a non-merge commit
+to the project's `refs/for/BRANCH` namespace, creating a new change
+for code review.
Rather than place this permission in its own category, its chained
into the Read Access category as a higher level of access. A user
@@ -344,6 +340,15 @@
Projects \--` ACL. For more private installations, its common to
simply grant `Read Access +1..+2` to all users of a project.
+[[category_READ_3]]
+Upload Merge Access
+~~~~~~~~~~~~~~~~~~~
+The `Read Access +3` permits the user to upload merge commits, but is
+otherwise identical to `Read Access +2`. Some projects wish to
+restrict merges to being created by Gerrit. By granting,
+`Read Access +1..+2`, the only merges that enter the system will be
+those created by Gerrit, or those pushed directly.
+
[[category_pTAG]]
Push Tag
~~~~~~~~
diff --git a/Documentation/cmd-cherry-pick.txt b/Documentation/cmd-cherry-pick.txt
index 5068672..0831d9d 100644
--- a/Documentation/cmd-cherry-pick.txt
+++ b/Documentation/cmd-cherry-pick.txt
@@ -38,9 +38,9 @@
To obtain the 'gerrit-cherry-pick' script use scp, curl or wget to
copy it to your local system:
- $ scp -p -P 29418 gerrit.example.com:bin/gerrit-cherry-pick ~/bin/
+ $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
- $ curl http://gerrit.example.com/tools/bin/gerrit-cherry-pick
+ $ curl http://review.example.com/tools/bin/gerrit-cherry-pick
GERRIT
------
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index f698d5c..71c0d1f 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -8,18 +8,18 @@
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' \
+ --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]
DESCRIPTION
-----------
diff --git a/Documentation/cmd-flush-caches.txt b/Documentation/cmd-flush-caches.txt
index 795af04..573cd78 100644
--- a/Documentation/cmd-flush-caches.txt
+++ b/Documentation/cmd-flush-caches.txt
@@ -8,8 +8,8 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit flush-caches' \
-[\--all | \--list | \--cache <NAME> ...]
+ 'ssh' -p <port> <host> 'gerrit flush-caches' \
+ [\--all | \--list | \--cache <NAME> ...]
DESCRIPTION
-----------
diff --git a/Documentation/cmd-gsql.txt b/Documentation/cmd-gsql.txt
index 43ba682..7fc8a9b 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
-----------
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index c773984..b21f5c0 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -56,7 +56,7 @@
To obtain the 'commit-msg' script use scp, wget or curl to copy it
to your local system:
- $ scp -p -P 29418 review.example.com:hooks/commit-msg .git/hooks/
+ $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
$ curl http://review.example.com/tools/hooks/commit-msg
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 79557d3..8a6cb6d3 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -9,8 +9,8 @@
To download a client command or hook, use scp or an http client:
- $ scp -p -P 29418 review.example.com:bin/gerrit-cherry-pick ~/bin/
- $ scp -p -P 29418 review.example.com:hooks/commit-msg .git/hooks/
+ $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
+ $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
$ curl http://review.example.com/tools/bin/gerrit-cherry-pick
$ curl http://review.example.com/tools/hooks/commit-msg
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 9047202..c8996eb 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
-----------
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index d84a2eb..5645f51 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -8,8 +8,8 @@
SYNOPSIS
--------
[verse]
-'ssh' -p <port> <host> 'gerrit approve' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
-'ssh' -p <port> <host> 'gerrit review' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
+'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}...
DESCRIPTION
-----------
@@ -54,9 +54,18 @@
differs per site, check the output of \--help, or contact
your site administrator for further details.
+\--abandon::
+ Abandon the specified patch set(s).
+ (option is mutually exclusive with --submit and --restore)
+
+\--restore::
+ Restore the specified abandonned patch set(s).
+ (option is mutually exclusive with --abandon)
+
\--submit::
-s::
Submit the specified patch set(s) for merging.
+ (option is mutually exclusive with --abandon)
ACCESS
------
@@ -74,6 +83,13 @@
$ 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"'
+=====
+
Mark the unmerged commits both "Verified +1" and "Code Review +2" and
submit them for merging:
====
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 864092e..7a116c4 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -182,6 +182,19 @@
+
If not set, no "Register" link is displayed.
+[[auth.cookiePath]]auth.cookiePath::
++
+Sets "path" attribute of the authentication cookie.
++
+If not set, HTTP request's path is used.
+
+[[auth.cookieSecure]]auth.cookieSecure::
++
+Sets "secure" flag of the authentication cookie. If true, cookies
+will be transmitted only over HTTPS protocol.
++
+By default, false.
+
[[auth.emailFormat]]auth.emailFormat::
+
Optional format string to construct user email addresses out of
@@ -262,6 +275,7 @@
+
Default is `90 days` for most caches, except:
+
+* `"adv_bases"`: default is `10 minutes`
* `"ldap_groups"`: default is `1 hour`
* `"web_sessions"`: default is `12 hours`
@@ -272,6 +286,7 @@
+
Default is 1024 for most caches, except:
+
+* `"adv_bases"`: default is `4096`
* `"diff"`: default is `128`
[[cache.name.diskLimit]]cache.<name>.diskLimit::
@@ -319,6 +334,14 @@
from the `account_external_ids` database table. If updates are
made to this table, this cache should be flushed.
+cache `"adv_bases"`::
++
+Used only for push over smart HTTP when branch level access controls
+are enabled. The cache entry contains all commits that are avaliable
+for the client to use as potential delta bases. Push over smart HTTP
+requires two HTTP requests, and this cache tries to carry state from
+the first request into the second to ensure it can complete.
+
cache `"diff"`::
+
Each item caches the differences between two commits, at both the
@@ -397,14 +420,43 @@
[[cache_options]]Cache Options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-cache.diff.intraline::
+cache.diff_intraline.maxIdleWorkers::
++
+Number of idle worker threads to maintain for the intraline difference
+computations. There is no upper bound on how many concurrent requests
+can occur at once, if additional threads are started to handle a peak
+load, only this many will remaining idle afterwards.
++
+Default is 1.5x number of available CPUs.
+
+cache.diff_intraline.timeout::
++
+Maximum number of milliseconds to wait for intraline difference data
+before giving up and disabling it for a particular file pair. This is
+a work around for an infinite loop bug in the intraline difference
+implementation. If computation takes longer than the timeout the
+worker thread is terminated and no intraline difference is displayed.
++
+Values should use common unit suffixes to express their setting:
++
+* ms, milliseconds
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+
++
+If a unit suffix is not specified, `milliseconds` is assumed.
++
+Default is 5 seconds.
+
+cache.diff_intraline.enabled::
+
Boolean to enable or disable the computation of intraline differences
-when populating a diff cache entry. Changing this setting in the
-server configuration requires flushing the "diff" cache after a
-restart, otherwise older cache entries stored on disk may not reflect
-the current server setting. This flag is provided primarily as a
-backdoor to disable the intraline difference feature if necessary.
+when populating a diff cache entry. This flag is provided primarily
+as a backdoor to disable the intraline difference feature if
+necessary. To maintain backwards compatability with prior versions,
+this setting will fallback to `cache.diff.intraline` if not set in the
+configuration.
+
Default is true, enabled.
@@ -1533,6 +1585,25 @@
+
By default, unset, permitting delivery to any email address.
+[[sendemail.importance]]sendemail.importance::
++
+If present, emails sent from Gerrit will have the given level
+of importance. Valid values include 'high' and 'low', which
+email clients will render in different ways.
++
+By default, unset, so no Importance header is generated.
+
+[[sendemail.expiryDays]]sendemail.expiryDays::
++
+If present, emails sent from Gerrit will expire after the given
+number of days. This will add the Expiry-Date header and
+email clients may expire or expunge mails whose Expiry-Date
+header is in the past. This should be a positive non-zero
+number indicating how many days in the future the mails
+should expire.
++
+By default, unset, so no Expiry-Date header is generated.
+
[[sshd]] Section sshd
~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/config-replication.txt b/Documentation/config-replication.txt
index 7bc74f8..81fda98 100644
--- a/Documentation/config-replication.txt
+++ b/Documentation/config-replication.txt
@@ -86,7 +86,9 @@
+
Within each URL value the magic placeholder `$\{name}` is replaced
with the Gerrit project name. This is a Gerrit specific extension
-to the otherwise standard Git URL syntax.
+to the otherwise standard Git URL syntax and it must be included
+in each URL so that Gerrit can figure out where each project needs
+to be replicated.
+
See link:http://www.kernel.org/pub/software/scm/git/docs/git-push.html#URLS[GIT URLS]
for details on Git URL syntax.
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index 0353b09..861fbe0d 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -450,18 +450,17 @@
amounts to parameters such as the following:
.Design Parameters
-[grid="all"]
-`-----------------'----------------
-Parameter Estimated Maximum
------------------------------------
-Projects 500
-Contributors 2,000
-Changes/Day 400
-Revisions/Change 2.0
-Files/Change 4
-Comments/File 2
-Reviewers/Change 1.0
------------------------------------
+[options="header"]
+|====================================
+|Parameter | Estimated Maximum
+|Projects | 500
+|Contributors | 2,000
+|Changes/Day | 400
+|Revisions/Change | 2.0
+|Files/Change | 4
+|Comments/File | 2
+|Reviewers/Change | 1.0
+|====================================
CPU Usage
~~~~~~~~~
diff --git a/Documentation/error-branch-not-found.txt b/Documentation/error-branch-not-found.txt
new file mode 100644
index 0000000..b19bf92
--- /dev/null
+++ b/Documentation/error-branch-not-found.txt
@@ -0,0 +1,31 @@
+branch ... not found
+====================
+
+With this error message Gerrit rejects to push a commit for code
+review if the specified target branch does not exist.
+
+To push a change for code review the commit has to be pushed to the
+project's magical `refs/for/'branch'` ref (for details have a look at
+link:user-upload.html#push_create[Create Changes]).
+If you specify a non existing branch in the `refs/for/'branch'` ref
+the push is failing with the error message 'branch ... not found'.
+
+To fix this problem verify
+* that the branch name in the push specification is typed correctly
+ (case sensitive) and
+* that the branch really exists for this project (in the Gerrit WebUI
+ go to 'Admin' -> 'Projects' and browse your project, then click on
+ 'Branches' to see all existing branches).
+
+If it was your intention to create a new branch you can either
+* bypass code review on push as explained link:user-upload.html#bypass_review[here] or
+* create the new branch in the Gerrit WebUI before pushing (go to
+ 'Admin' -> 'Projects' and browse your project, in the 'Branches'
+ tab you can then create a new branch).
+Please note that you need the access right '+2 Create Branch' in the
+link:access-control.html#category_pHD['Push Branch'] category to create new branches.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-change-closed.txt b/Documentation/error-change-closed.txt
new file mode 100644
index 0000000..7170a65
--- /dev/null
+++ b/Documentation/error-change-closed.txt
@@ -0,0 +1,30 @@
+change ... closed
+=================
+
+With this error message Gerrit rejects to push a commit to a change
+that is already closed.
+
+This error occurs if you are trying to push a commit that contains
+the Change-Id of a closed change in its commit message. A change can
+be closed either because it was already submitted and merged or
+because it was abandoned.
+
+If the change for which you wanted to upload a new patch set was
+already submitted and merged you may want to push your commit as a
+new change. To do this you have to remove the Change-Id from the
+commit message as explained link:error-push-fails-due-to-commit-message.html[here] and ideally generate a new Change-Id
+using the link:cmd-hook-commit-msg.html[commit hook] or EGit. Before pushing again it is also
+recommendable to do a link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] to base your commit on the submitted
+change. Pushing again should now create a new change in Gerrit.
+
+If the change for which you wanted to upload a new patch set was
+abandoned and your new changes overcome the reasons for abandoning
+this change you may want to restore the change in the Gerrit WebUI
+(browse the abandoned change in the Gerrit WebUI and click on the
+'Restore Change' button). Afterwards the push should succeed and a
+new patch set for this change will be created.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-change-does-not-belong-to-project.txt b/Documentation/error-change-does-not-belong-to-project.txt
new file mode 100644
index 0000000..29957e1
--- /dev/null
+++ b/Documentation/error-change-does-not-belong-to-project.txt
@@ -0,0 +1,16 @@
+change ... does not belong to project ...
+=========================================
+
+With this error message Gerrit rejects to push a commit to a change
+that belongs to another project.
+
+This error message means that the user explicitly pushed a commit to
+a change that belongs to another project by specifying it as target
+ref. This way of adding a new patch set to a change is deprecated as
+explained link:user-upload.html#manual_replacement_mapping[here]. It is recommended to only rely on Change-ID's for
+link:user-upload.html#push_replace[replacing changes].
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-change-not-found.txt b/Documentation/error-change-not-found.txt
new file mode 100644
index 0000000..c9ac0d8
--- /dev/null
+++ b/Documentation/error-change-not-found.txt
@@ -0,0 +1,15 @@
+change ... not found
+====================
+
+With this error message Gerrit rejects to push a commit to a change
+that cannot be found.
+
+This error message means that the user explicitly pushed a commit to
+a non-existing change by specifying it as target ref. This way of
+adding a new patch set to a change is deprecated as explained link:user-upload.html#manual_replacement_mapping[here].
+It is recommended to only rely on Change-ID's for link:user-upload.html#push_replace[replacing changes].
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-change-upload-blocked.txt b/Documentation/error-change-upload-blocked.txt
new file mode 100644
index 0000000..6bad02e
--- /dev/null
+++ b/Documentation/error-change-upload-blocked.txt
@@ -0,0 +1,41 @@
+One or more refs/for/ names blocks change upload
+================================================
+
+With this error message Gerrit rejects to push a commit for code
+review if the remote git repository has a branch under the
+'refs/for/' namespace.
+
+Gerrit uses the 'refs/for/' namespace for magical refs that represent
+the review queues for branches in the git repository hosted by
+Gerrit. If, for a project, a real branch is created under the
+'refs/for' namespace this conflicts with the namespace reserved for
+the Gerrit review queues and Gerrit can't accept further pushes for
+code review.
+
+To solve this problem all real branches that exist under the
+'refs/for/' namespace have to be deleted or renamed in the remote git
+repository.
+
+To see which branches exist under the 'refs/for/' namespace a Gerrit
+administrator can run the following command:
+
+----
+ $ git for-each-ref refs/for
+----
+
+If all these branches should be deleted it can be done with the
+following command:
+
+----
+ $ for n in $(git for-each-ref --format='%(refname)' refs/for);
+ do git update-ref -d $n; done
+----
+
+Branches under the 'refs/for/' namespace can be created by users that
+bypass Gerrit and push directly to the git repository itself (not
+using the Gerrit server's SSH port).
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-has-duplicates.txt b/Documentation/error-has-duplicates.txt
new file mode 100644
index 0000000..e9e42f4
--- /dev/null
+++ b/Documentation/error-has-duplicates.txt
@@ -0,0 +1,24 @@
+... has duplicates
+==================
+
+With this error message Gerrit rejects to push a commit if its commit
+message contains a Change-ID for which multiple changes can be found
+in the project.
+
+This error means that there is an inconsistency in Gerrit since for
+one project there are multiple changes that have the same Change-ID.
+Every change is expected to have an unique Change-ID.
+
+Since this error should never occur in practice, you should inform
+your Gerrit administrator if you hit this problem and/or
+link:http://code.google.com/p/gerrit/issues/list[open a Gerrit issue].
+
+In any case to not be blocked with your work, you can simply create a
+new Change-ID for your commit and then push it as new change to
+Gerrit. How to exchange the Change-ID in the commit message of your
+commit is explained link:error-push-fails-due-to-commit-message.html[here].
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-invalid-changeid-line.txt b/Documentation/error-invalid-changeid-line.txt
new file mode 100644
index 0000000..2f57542
--- /dev/null
+++ b/Documentation/error-invalid-changeid-line.txt
@@ -0,0 +1,31 @@
+invalid Change-Id line format in commit message
+===============================================
+
+With this error message Gerrit rejects to push a commit if its commit
+message contains an invalid Change-Id line.
+
+You can see the commit messages for existing commits in the history
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+
+If it was the intention to rework a change and to push a new patch
+set, find the change in the Gerrit WebUI, copy its Change-Id line and
+use it to correct the invalid Change-Id line in the commit message of
+the commit for which the push is failing. How to do this is explained
+link:error-push-fails-due-to-commit-message.html#commit_hook[here].
+
+If it was the intention to create a new change in Gerrit simply
+remove the invalid Change-Id line from the commit message of the
+commit for which the push is failing. How to do this is explained
+link:error-push-fails-due-to-commit-message.html#commit_hook[here]. In case you have configured the link:cmd-hook-commit-msg.html[commit hook] a new valid
+Change-Id will be automatically generated and inserted.
+
+
+SEE ALSO
+--------
+
+* link:user-changeid.html[Change-Id Lines]
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-messages.txt b/Documentation/error-messages.txt
new file mode 100644
index 0000000..a0d74b7
--- /dev/null
+++ b/Documentation/error-messages.txt
@@ -0,0 +1,45 @@
+Gerrit Code Review - Error Messages
+===================================
+
+This page provides access to detailed explanations of Gerrit error
+messages. For each error message it is explained why the error is
+occurring and what can be done to solve it.
+
+
+Error Messages
+--------------
+
+* link:error-branch-not-found.html[branch ... not found]
+* link:error-change-closed.html[change ... closed]
+* link:error-change-does-not-belong-to-project.html[change ... does not belong to project ...]
+* link:error-change-not-found.html[change ... not found]
+* link:error-has-duplicates.html[... has duplicates]
+* link:error-invalid-changeid.html[invalid Change-Id line format in commit message]
+* link:error-missing-changeid.html[missing Change-Id in commit message]
+* link:error-multiple-changeid-lines.html[multiple Change-Id lines in commit message]
+* link:error-no-changes-made.html[no changes made]
+* link:error-no-common-ancestry.html[no common ancestry]
+* link:error-no-new-changes.html[no new changes]
+* link:error-not-a-gerrit-administrator[Not a Gerrit administrator]
+* link:error-not-a-gerrit-project.html[not a Gerrit project]
+* link:error-not-permitted-to-create.html[Not permitted to create ...]
+* link:error-not-signed-off-by.html[not Signed-off-by author/committer/uploader]
+* link:error-not-valid-ref.html[not valid ref]
+* link:error-change-upload-blocked.html[One or more refs/for/ names blocks change upload]
+* link:error-permission-denied.html[Permission denied (publickey)]
+* link:error-prohibited-by-gerrit.html[prohibited by Gerrit]
+* link:error-squash-commits-first.html[squash commits first]
+* link:error-upload-denied.html[Upload denied for project '...']
+* link:error-you-are-not-author.html[you are not author ...]
+* link:error-you-are-not-committer.html[you are not committer ...]
+
+
+General Hints
+-------------
+
+* link:error-push-fails-due-to-commit-message.html[push fails due to commit message]
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/error-missing-changeid.txt b/Documentation/error-missing-changeid.txt
new file mode 100644
index 0000000..5af5f7a
--- /dev/null
+++ b/Documentation/error-missing-changeid.txt
@@ -0,0 +1,56 @@
+missing Change-Id in commit message
+===================================
+
+With this error message Gerrit rejects to push a commit to a project
+which is configured to always require a Change-Id in the commit
+message if the commit message of the pushed commit does not contain
+a Change-Id.
+
+This error may happen for two reasons:
+1. missing Change-Id in the commit message
+2. Change-Id is contained in the commit message but not in the last
+ paragraph
+
+You can see the commit messages for existing commits in the history
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+
+To avoid this error you should use the link:cmd-hook-commit-msg.html[commit hook] or EGit to
+automatically create and insert a unique Change-Id into the commit
+message on every commit.
+
+
+Missing Change-Id in the commit message
+---------------------------------------
+
+If the commit message of a commit that you want to push does not
+contain a Change-Id you have to update its commit message and insert
+a Change-Id.
+
+If you want to upload a new change to Gerrit make sure that you have
+configured your environment so that a unique Change-Id is
+automatically created and inserted on every commit as explained
+above. Now you can rewrite the commits for which the Change-Ids are
+missing and the Change-Ids will be automatically created and inserted
+into the commit messages. This is explained link:error-push-fails-due-to-commit-message.html#commit_hook[here].
+
+If you want to update an existing change in Gerrit by uploading a new
+patch set you should copy its Change-Id from the Gerrit WebUI and
+insert it into the commit message. How to update the commit message
+is explained link:error-push-fails-due-to-commit-message.html[here].
+
+
+Change-Id is contained in the commit message but not in the last paragraph
+--------------------------------------------------------------------------
+
+To be picked up by Gerrit, a Change-Id must be in the last paragraph
+of a commit message, for details, see link:user-changeid.html[Change-Id Lines].
+
+If the Change-Id is contained in the commit message but not in its
+last paragraph you have to update the commit message and move the
+Change-ID into the last paragraph. How to update the commit message
+is explained link:error-push-fails-due-to-commit-message.html[here].
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-multiple-changeid-lines.txt b/Documentation/error-multiple-changeid-lines.txt
new file mode 100644
index 0000000..e604974
--- /dev/null
+++ b/Documentation/error-multiple-changeid-lines.txt
@@ -0,0 +1,31 @@
+multiple Change-Id lines in commit message
+==========================================
+
+With this error message Gerrit rejects to push a commit if the commit
+message of the pushed commit contains several Change-Id lines.
+
+You can see the commit messages for existing commits in the history
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+
+If it was the intention to rework a change and to push a new patch
+set, find the change in the Gerrit WebUI, copy its Change-Id line and
+use the copied Change-Id line instead of the existing Change-Id lines
+in the commit message of the commit for which the push is failing.
+How to do this is explained link:error-push-fails-due-to-commit-message.html#commit_hook[here].
+
+If it was the intention to create a new change in Gerrit simply
+remove all Change-Id lines from the commit message of the
+commit for which the push is failing. How to do this is explained
+link:error-push-fails-due-to-commit-message.html#commit_hook[here]. In case you have configured the link:cmd-hook-commit-msg.html[commit hook] a new Change-Id
+will be automatically generated and inserted.
+
+
+SEE ALSO
+--------
+
+* link:user-changeid.html[Change-Id Lines]
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-no-changes-made.txt b/Documentation/error-no-changes-made.txt
new file mode 100644
index 0000000..aabfea9
--- /dev/null
+++ b/Documentation/error-no-changes-made.txt
@@ -0,0 +1,19 @@
+no changes made
+===============
+
+With this error message Gerrit rejects to push a commit as a new
+patch set for a change, if the pushed commit is identical with the
+current patch set of this change.
+
+A pushed commit is considered to be identical with the current patch
+set if
+- the files in the commit,
+- the commit message,
+- the author of the commit and
+- the parents of the commit
+are all identical.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-no-common-ancestry.txt b/Documentation/error-no-common-ancestry.txt
new file mode 100644
index 0000000..615da71
--- /dev/null
+++ b/Documentation/error-no-common-ancestry.txt
@@ -0,0 +1,20 @@
+no common ancestry
+==================
+
+With this error message Gerrit rejects to push a commit for code
+review if the pushed commit and the commit at the tip of the target
+branch do not have a common ancestry.
+
+This means that your local development history and the development
+history of the branch to which the push is done are completely
+independent (they have completely independent commit graphs).
+
+This error usually occurs if you do a change in one project and then
+you accidentally push the commit to another project for code review.
+To fix the problem you should check your push specification and
+verify that you are pushing the commit to the correct project.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-no-new-changes.txt b/Documentation/error-no-new-changes.txt
new file mode 100644
index 0000000..4000f72
--- /dev/null
+++ b/Documentation/error-no-new-changes.txt
@@ -0,0 +1,49 @@
+no new changes
+==============
+
+With this error message Gerrit rejects to push a commit if the pushed
+commit was already successfully pushed to Gerrit. In this case there
+is no new change and consequently there is nothing to do for Gerrit.
+
+If your push is failing with this error message, you normally
+don't have to do anything since the commit was already successfully
+pushed. Still this error message may sometimes come as a surprise if
+you expected a new commit to be pushed. In this case you should
+verify that:
+1. your changes were successfully committed locally (otherwise there
+ is no new commit which can be pushed)
+2. you are pushing the correct commit (e.g. if you are pushing HEAD
+ make sure you have locally checked out the correct branch)
+
+If you are sure you are pushing the correct commit and you are still
+getting the "no new changes" error unexpectedly you can take the
+commit ID and search for the corresponding change in Gerrit. To do
+this simply paste the commit ID in the Gerrit WebUI into the search
+field. Details about how to search in Gerrit are explained link:user-search.html[here].
+
+Please note that each commit can really be pushed only once. This
+means:
+1. you cannot push a commit again even if the change for which the
+ commit was pushed before was abandoned (but you may restore the
+ abandoned change)
+2. you cannot reset a change to an old patch set by pushing the old
+ commit for this change again
+3. if a commit was pushed to one branch you cannot push this commit
+ to another branch
+4. if a commit was pushed directly to a branch (without going through
+ code review) you cannot push this commit once again for code
+ review (please note that in this case searching by the commit ID
+ in the Gerrit WebUI will not find any change)
+
+If you need to re-push a commit you may rewrite this commit by
+link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[amending] it or doing an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase]. By rewriting the
+commit you actually create a new commit (with a new commit ID) which
+can then be pushed to Gerrit. If the old commit contains a Change-Id
+in the commit message you also need to replace it with a new
+Change-Id (case 1. and 3. above), otherwise the push will fail with
+another error message.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-not-a-gerrit-administrator.txt b/Documentation/error-not-a-gerrit-administrator.txt
new file mode 100644
index 0000000..0468d83
--- /dev/null
+++ b/Documentation/error-not-a-gerrit-administrator.txt
@@ -0,0 +1,14 @@
+Not a Gerrit administrator
+==========================
+
+With this error message Gerrit rejects to execute a SSH command that
+requires administrator privileges if the user is not a Gerrit
+administrator.
+
+The Gerrit link:cmd-index.html#admin_commands[administrator commands] can only be executed by users who
+are member of the Gerrit link:access-control.html#administrators[Administrators] group.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-not-a-gerrit-project.txt b/Documentation/error-not-a-gerrit-project.txt
new file mode 100644
index 0000000..1b529a7
--- /dev/null
+++ b/Documentation/error-not-a-gerrit-project.txt
@@ -0,0 +1,32 @@
+not a Gerrit project
+====================
+
+With this error message Gerrit rejects to push a commit if the git
+repository to which the push is done does not exist as a project in
+the Gerrit server or if the pushing user has no read access for this
+project.
+
+The name of the project in Gerrit has the same name as the path of
+its git repository (excluding the '.git' extension).
+
+If you are facing this problem, do the following:
+1. Verify that the project name specified as git repository in the
+ push command is typed correctly (case sensitive).
+2. Verify that you are pushing to the correct Gerrit server.
+3. Go in the Gerrit WebUI to 'Admin' -> 'Projects' and check that the
+ project is listed. If the project is not listed the project either
+ does not exist or you don't have read access ('+1 Read Access' in
+ the link:access-control.html#category_READ['Read Access'] category) for it. This means if you certain that
+ the project name is right you should contact the Gerrit
+ Administrator or project owner to request access to the project.
+
+This error message might be misleading if the project actually exists
+but the push is failing because the pushing user has no read access
+for the project. The reason that Gerrit in this case denies the
+existence of the project is to prevent users from probing the Gerrit
+server to see if a particular project exists.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-not-permitted-to-create.txt b/Documentation/error-not-permitted-to-create.txt
new file mode 100644
index 0000000..9c07fd1
--- /dev/null
+++ b/Documentation/error-not-permitted-to-create.txt
@@ -0,0 +1,16 @@
+Not permitted to create ...
+===========================
+
+With this error message Gerrit rejects to create a new project in
+Gerrit if the user has no privileges for project creation.
+
+In Gerrit it is possible to link:config-gerrit.html#repository[configure which groups are allowed to create projects].
+
+If you are getting this error and you need to create projects in
+Gerrit you have to contact a Gerrit administrator and request
+permissions for project creation.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-not-signed-off-by.txt b/Documentation/error-not-signed-off-by.txt
new file mode 100644
index 0000000..726caa4
--- /dev/null
+++ b/Documentation/error-not-signed-off-by.txt
@@ -0,0 +1,30 @@
+not Signed-off-by author/committer/uploader
+===========================================
+
+Projects in Gerrit can be configured to require a link:user-signedoffby.html#Signed-off-by[Signed-off-by] in
+the commit message to enforce that every change is signed by the
+author, committer or uploader. If for a project a Signed-off-by is
+required and the commit message does not contain it, Gerrit rejects
+to push the commit with this error message.
+
+This policy can be bypassed by having the access right '+2 Forge
+Committer or Tagger Identity' in the link:access-control.html#category_FORG['Forge Identity'] category.
+
+This error may happen for different reasons if you do not have the
+access right to forge the committer identity:
+1. missing Signed-off-by in the commit message
+2. Signed-off-by is contained in the commit message but it's neither
+ from the author, committer nor uploader
+3. Signed-off-by from the author, committer or uploader is contained
+ in the commit message but not in the last paragraph
+
+To be able to push your commits you have to update the commit
+messages as explained link:error-push-fails-due-to-commit-message.html[here] so that they contain a Signed-off-by from
+the author, committer or uploader in the last paragraph. However it
+is important that you only add a Signed-off-by if you understand the
+semantics of the link:user-signedoffby.html#Signed-off-by[Signed-off-by] and the commit applies to the rules
+that are connected with this footer.
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-not-valid-ref.txt b/Documentation/error-not-valid-ref.txt
new file mode 100644
index 0000000..128e796
--- /dev/null
+++ b/Documentation/error-not-valid-ref.txt
@@ -0,0 +1,46 @@
+not valid ref
+=============
+
+With this error message Gerrit rejects to push a commit if the target
+ref in the push specification has an incorrect format (for example:
+'/refs/for/master', 'refs/for//master').
+
+To solve the problem you have to correct the target ref in the push
+specification. Depending on whether you want to push your commit with
+or without code review the ref format is different:
+
+
+ref format for pushing a commit for code review:
+------------------------------------------------
+
+If it was the intention to push a commit for code review the target
+ref in the push specification must be the project's magical ref
+`refs/for/'branch'` (where 'branch' must be replaced with the name
+of an existing branch to which you want to push your commit). Further
+details about how to push a commit for code review are explained at
+link:user-upload.html#push_create[Create Changes]).
+
+Example for pushing a commit for code review to the 'master' branch:
+----
+$ git push ssh://JohnDoe@host:29418/myProject HEAD:refs/for/master
+----
+
+
+ref format for directly pushing a commit (without code review):
+---------------------------------------------------------------
+
+If it was the intention to bypass code review and to push directly to
+a branch the target ref in the push specification must be the name of
+the branch to which you want to push. Further details about how to
+bypass code review are explained at link:user-upload.html#bypass_review[Bypass Review].
+
+Example for pushing a commit directly to the 'master' branch (without
+code review):
+----
+$ git push ssh://JohnDoe@host:29418/myProject HEAD:master
+----
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-permission-denied.txt b/Documentation/error-permission-denied.txt
new file mode 100644
index 0000000..75c4528
--- /dev/null
+++ b/Documentation/error-permission-denied.txt
@@ -0,0 +1,61 @@
+Permission denied (publickey)
+=============================
+
+With this error message a SSH command to Gerrit is rejected if the
+SSH authentication is not successful.
+
+The link:http://en.wikipedia.org/wiki/Secure_Shell[SSH] protocol uses link:http://en.wikipedia.org/wiki/Public-key_cryptography[Public-key Cryptography] for authentication.
+This means for a successful SSH authentication you need your private
+SSH key and the corresponding public SSH key must be known to Gerrit.
+
+If you are facing this problem, do the following:
+1. Verify that you are using the correct username for the SSH command
+ and that it is typed correctly (case sensitive). You can look up
+ your username in the Gerrit WebUI under 'Settings' -> 'Profile'.
+2. Verify that you have uploaded your public SSH key for your Gerrit
+ account. To do this go in the Gerrit WebUI to 'Settings' ->
+ 'SSH Public Keys' and check that your public SSH key is there. If
+ your public SSH key is not there you have to upload it.
+3. Verify that you are using the correct private SSH key. To find out
+ which private SSH key is used test the SSH authentication as
+ described below. From the trace you should see which private SSH
+ key is used.
+
+
+Test SSH authentication
+-----------------------
+
+To test the SSH authentication you can run the following SSH command.
+This command will print out a detailed trace which is helpful to
+analyze problems with the SSH authentication:
+
+----
+ $ ssh -vv -p 29418 john.doe@git.example.com
+----
+
+If the SSH authentication is successful you should find the following
+lines in the output:
+
+----
+ ...
+
+ debug1: Authentication succeeded (publickey).
+
+ ...
+
+ **** Welcome to Gerrit Code Review ****
+
+ Hi John Doe, you have successfully connected over SSH.
+
+ Unfortunately, interactive shells are disabled.
+ To clone a hosted Git repository, use:
+
+ git clone ssh://john.doe@git.example.com:29418/REPOSITORY_NAME.git
+
+ ...
+----
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-prohibited-by-gerrit.txt b/Documentation/error-prohibited-by-gerrit.txt
new file mode 100644
index 0000000..3f8e4d8
--- /dev/null
+++ b/Documentation/error-prohibited-by-gerrit.txt
@@ -0,0 +1,31 @@
+prohibited by Gerrit
+====================
+
+This is a general error message that is returned by Gerrit if a push
+is not allowed, e.g. because the pushing user has no sufficient
+privileges.
+
+In particular this error occurs:
+1. if you push a commit for code review to a branch for which you
+ don't have upload permissions (access right '+2 Upload permission'
+ in the link:access-control.html#category_READ['Read Access'] category)
+2. if you bypass code review without sufficient privileges in the
+ link:access-control.html#category_pHD['Push Branch'] category
+3. if you push a signed or annotated tag without sufficient
+ privileges in the link:access-control.html#category_pTAG['Push Tag'] category
+4. if you push a lightweight tag without the access right '+2 Create
+ Branch' for the reference name 'refs/tags/*' in the link:access-control.html#category_pHD['Push Branch']
+ category
+
+For new users it happens often that they accidentally try to bypass
+code review. The push then fails with the error message 'prohibited
+by Gerrit' because the project didn't allow to bypass code review.
+Bypassing the code review is done by pushing directly to refs/heads/*
+(e.g. refs/heads/master) instead of pushing to refs/for/* (e.g.
+refs/for/master). Details about how to push commits for code review
+are explained link:user-upload.html#push_create[here].
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-push-fails-due-to-commit-message.txt b/Documentation/error-push-fails-due-to-commit-message.txt
new file mode 100644
index 0000000..01e0a8e
--- /dev/null
+++ b/Documentation/error-push-fails-due-to-commit-message.txt
@@ -0,0 +1,41 @@
+Push fails due to commit message
+================================
+
+If Gerrit rejects pushing a commit it is often the case that there is
+an issue with the commit message of the pushed commit. In this case
+often the problem can be resolved by fixing the commit message.
+
+If the commit message of the last commit needs to be fixed you can
+simply amend the last commit (please find a detailed description in
+the link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[Git documentation]):
+
+----
+ $ git commit --amend
+----
+
+If you need to fix the commit messages of several commits or of any
+commit other than the last one you have to do an interactive git
+rebase for the affected commits. While doing the interactive rebase
+you can e.g. choose 'reword' for those commits for which you want to
+fix the commit messages. For a detailed description of git rebase
+please check the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
+
+Please use interactive git rebase with care as it rewrites existing
+commits. Generally you should never rewrite commits that have already
+been submitted in Gerrit.
+
+[[commit_hooks]]
+Sometimes commit hooks are used to automatically insert/update
+information in the commit message. If such information is missing in
+the commit message of existing commits (e.g. because the commit hook
+was only configured later) rewriting the commits will (re)execute the
+commit hook and so update the commit messages. If you do an
+interactive rebase to achieve this make sure that the affected
+commits are really rewritten, e.g. by choosing 'reword' for all these
+commits and then confirming all the commit messages. Just picking a
+commit may not rewrite it.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-squash-commits-first.txt b/Documentation/error-squash-commits-first.txt
new file mode 100644
index 0000000..138ad98
--- /dev/null
+++ b/Documentation/error-squash-commits-first.txt
@@ -0,0 +1,108 @@
+squash commits first
+====================
+
+With this error message Gerrit rejects to push a commit if it
+contains the same Change-ID as a predecessor commit.
+
+The reason for rejecting such a commit is that it would introduce, for
+the corresponding change in Gerrit, a dependency upon itself. Gerrit
+prevents such dependencies between patch sets within the same change
+to keep the review process simple. Otherwise reviewers would not only
+have to review the latest patch set but also all the patch sets the
+latest one is depending on.
+
+This error is quite common, it appears when a user tries to address
+review comments and creates a new commit instead of amending the
+existing commit. Another possibility for this error, although less
+likely, is that the user tried to create a patch series with multiple
+changes to be reviewed and accidentally included the same Change-ID
+into the different commit messages.
+
+
+Example
+-------
+
+Here an example about how the push is failing. Please note that the
+two commits 'one commit' and 'another commit' both have the same
+Change-ID (of course in real life it can happen that there are more
+than two commits that have the same Change-ID).
+
+----
+ $ git log
+ commit 13d381265ffff88088e1af88d0e2c2c1143743cd
+ Author: John Doe <john.doe@example.com>
+ Date: Thu Dec 16 10:15:48 2010 +0100
+
+ another commit
+
+ Change-Id: I93478acac09965af91f03c82e55346214811ac79
+
+ commit ca45e125145b12fe9681864b123bc9daea501bf7
+ Author: John Doe <john.doe@example.com>
+ Date: Thu Dec 16 10:12:54 2010 +0100
+
+ one commit
+
+ Change-Id: I93478acac09965af91f03c82e55346214811ac79
+
+ $ git push ssh://JohnDoe@host:29418/myProject HEAD:refs/for/master
+ Counting objects: 8, done.
+ Delta compression using up to 2 threads.
+ Compressing objects: 100% (2/2), done.
+ Writing objects: 100% (6/6), 558 bytes, done.
+ Total 6 (delta 0), reused 0 (delta 0)
+ To ssh://JohnDoe@host:29418/myProject
+ ! [remote rejected] HEAD -> refs/for/master (squash commits first)
+ error: failed to push some refs to 'ssh://JohnDoe@host:29418/myProject'
+----
+
+If it was the intention to rework on a change and to push a new patch
+set the problem can be fixed by squashing the commits that contain the
+same Change-ID. The squashed commit can then be pushed to Gerrit.
+To squash the commits use git rebase to do an interactive rebase. For
+the example above where the last two commits have the same Change-ID
+this means an interactive rebase for the last two commits should be
+done. For further details about the git rebase command please check
+the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation for rebase].
+
+----
+ $ git rebase -i HEAD~2
+
+ pick ca45e12 one commit
+ squash 13d3812 another commit
+
+ [detached HEAD ab37207] squashed commit
+ 1 files changed, 3 insertions(+), 0 deletions(-)
+ Successfully rebased and updated refs/heads/master.
+
+ $ git log
+ commit ab37207d33647685801dba36cb4fd51f3eb73507
+ Author: John Doe <john.doe@example.com>
+ Date: Thu Dec 16 10:12:54 2010 +0100
+
+ squashed commit
+
+ Change-Id: I93478acac09965af91f03c82e55346214811ac79
+
+ $ git push ssh://JohnDoe@host:29418/myProject HEAD:refs/for/master
+ Counting objects: 5, done.
+ Writing objects: 100% (3/3), 307 bytes, done.
+ Total 3 (delta 0), reused 0 (delta 0)
+ To ssh://JohnDoe@host:29418/myProject
+ * [new branch] HEAD -> refs/for/master
+----
+
+If it was the intention to create a patch series with multiple
+changes to be reviewed each commit message should contain the
+Change-ID of the corresponding change in Gerrit, if a change in
+Gerrit does not exist yet, the Change-ID should be generated (either
+by using a link:cmd-hook-commit-msg.html[commit hook] or by using EGit) or the Change-ID could be
+removed (not recommended since then amending this commit to create
+subsequent patch sets is more error prone). To change the Change-ID
+of an existing commit do an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] and fix the
+affected commit messages.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-upload-denied.txt b/Documentation/error-upload-denied.txt
new file mode 100644
index 0000000..0d8fb99
--- /dev/null
+++ b/Documentation/error-upload-denied.txt
@@ -0,0 +1,18 @@
+Upload denied for project '...'
+===============================
+
+With this error message Gerrit rejects to push a commit if the
+pushing user has no upload permissions for the project to which the
+push was done.
+
+There are two possibilities how to continue in this situation:
+1. contact one of the project owners and request upload permissions
+ for the project (access right '+2 Upload permission' in the
+ link:access-control.html#category_READ['Read Access'] category)
+2. export your commit as a patch using the link:http://www.kernel.org/pub/software/scm/git/docs/git-format-patch.html[git format-patch] command
+ and provide the patch file to one of the project owners
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-you-are-not-author.txt b/Documentation/error-you-are-not-author.txt
new file mode 100644
index 0000000..47a7652
--- /dev/null
+++ b/Documentation/error-you-are-not-author.txt
@@ -0,0 +1,145 @@
+you are not author ...
+======================
+
+Gerrit verifies for every pushed commit that the e-mail address of
+the author matches one of the registered e-mail addresses of the
+pushing user. If this is not the case pushing the commit fails with
+the error message "you are not author ...". This policy can be
+bypassed by having the access right '+1 Forge Author Identity' in the
+link:access-control.html#category_FORG['Forge Identity'] category.
+
+This error may happen for two reasons:
+
+. incorrect configuration of the e-mail address on client or server side
+. missing privileges to push commits of other authors
+
+
+Incorrect configuration of the e-mail address on client or server side
+----------------------------------------------------------------------
+
+If pushing to Gerrit fails with the error message "you are not
+author ..." and you are the author of the commit for which the push
+fails, then either you have not successfully registered this e-mail
+address for your Gerrit account or the author information of the
+pushed commit is incorrect.
+
+Configuration of e-mail address in Gerrit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Check in Gerrit under 'Settings -> Identities' which e-mail addresses
+you've configured for your Gerrit account, if no e-mail address is
+registered go to 'Settings -> Contact Information' and register a new
+e-mail address there. Make sure you confirm your e-mail address by
+clicking on the link in the e-mail verification mail sent by Gerrit.
+If you don't receive the e-mail verification mail it might be that it
+was caught by your spam filter.
+
+Incorrect author information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For every commit Git maintains the author. If not explicitly
+specified Git computes the author on commit out of the Git
+configuration parameters 'user.name' and 'user.email'.
+
+----
+ $ git config -l
+ ...
+ user.name=John Doe
+ user.email=john.doe@example.com
+ ...
+----
+
+A commit done with the above Git configuration would have
+"John Doe <john.doe@example.com>" as author.
+
+You can see the author information for existing commits in the
+history.
+
+----
+ $ git log
+ commit cbe31bdba7d14963eb42f7e1e0eef1fe58698c05
+ Author: John Doe <john.doe@example.com>
+ Date: Mon Dec 20 15:36:33 2010 +0100
+
+ my commit
+
+----
+
+Check in Git that the author information of the commit that should
+be pushed is correct. The author should have the same e-mail address
+that you've configured for your Gerrit account. If the author
+information is incorrect set the Git configuration parameters
+'user.name' and 'user.email' to the correct values (you might want to
+set this globally by including the option '--global'):
+
+----
+ $ git config user.name "John Doe"
+ $
+ $ git config user.email john.doe@example.com
+ $
+----
+
+Now you should update the author for those commits where the author
+information is wrong. If only the last commit is affected you can do
+this by amending the last commit and explicitly setting the author:
+
+----
+ $ git commit --amend --author "John Doe <john.doe@example.com>"
+----
+
+If you need to update the author information for several commits it
+gets more complicated. In this case you have to do an interactive
+git rebase for the affected commits. While doing the interactive
+rebase you have to choose 'edit' for those commits for which the
+author should be rewritten. When the rebase stops at such a commit
+you have to amend the commit with explicitly setting the author
+before continuing the rebase.
+
+Here is an exmaple that shows how the interactive rebase is used to
+update the author for the last 3 commits:
+
+----
+ $ git rebase -i HEAD~3
+
+ edit 51f0d47 one commit
+ edit 7299690 another commit
+ edit 304ad96 one more commit
+
+ Stopped at 51f0d47... one commit
+ You can amend the commit now, with
+
+ git commit --amend
+
+ Once you are satisfied with your changes, run
+
+ git rebase --continue
+
+ $ git commit --amend --author "John Doe <john.doe@example.com>"
+ [detached HEAD baea1e4] one commit
+ Author: John Doe <john.doe@example.com>
+ 1 files changed, 4 insertions(+), 1 deletions(-)
+
+ $ git rebase --continue
+
+ ...
+----
+
+For further details about git rebase please check the
+link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
+
+
+Missing privileges to push commits of other users
+-------------------------------------------------
+
+If pushing to Gerrit fails with the error message "you are not
+author ..." and somebody else is author of the commit for which the
+push fails, then you have no permissions to forge the author
+identity. In this case you may contact the project owner to request
+the access right '+1 Forge Author Identity' in the 'Forge Identity'
+category or ask the maintainer to commit this change on the author's
+behalf.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/error-you-are-not-committer.txt b/Documentation/error-you-are-not-committer.txt
new file mode 100644
index 0000000..0c51fff
--- /dev/null
+++ b/Documentation/error-you-are-not-committer.txt
@@ -0,0 +1,109 @@
+you are not committer ...
+=========================
+
+Gerrit verifies for every pushed commit that the e-mail address of
+the committer matches one of the registered e-mail addresses of the
+pushing user. If this is not the case pushing the commit fails with
+the error message "you are not committer ...". This policy can be
+bypassed by having the access right '+2 Forge Committer or Tagger
+Identity' in the link:access-control.html#category_FORG['Forge Identity'] category.
+
+This error may happen for two reasons:
+1. incorrect configuration of the e-mail address on client or server
+ side
+2. missing privileges to push commits that were committed by other
+ users
+
+
+Incorrect configuration of the e-mail address on client or server side
+----------------------------------------------------------------------
+
+If pushing to Gerrit fails with the error message "you are not
+committer ..." and you committed the change for which the push fails,
+then either you have not successfully registered this e-mail address
+for your Gerrit account or the committer information of the pushed
+commit is incorrect.
+
+Configuration of e-mail address in Gerrit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Check in Gerrit under 'Settings -> Identities' which e-mail addresses
+you've configured for your Gerrit account, if no e-mail address is
+registered go to 'Settings -> Contact Information' and register a new
+e-mail address there. Make sure you confirm your e-mail address by
+clicking on the link in the e-mail verification mail sent by Gerrit.
+
+Incorrect committer information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For every commit Git maintains the user who did the commit, the so
+called committer. Git computes the committer out of the Git
+configuration parameters 'user.name' and 'user.email'.
+
+----
+ $ git config -l
+ ...
+ user.name=John Doe
+ user.email=john.doe@example.com
+ ...
+----
+
+A commit done with the above Git configuration would have
+"John Doe <john.doe@example.com>" as committer.
+
+To see the committer information for existing commits do
+"git log --format=full":
+
+----
+ $ git log --format=full
+ commit cbe31bdba7d14963eb42f7e1e0eef1fe58698c05
+ Author: John Doe <john.doe@example.com>
+ Commit: John Doe <john.doe@example.com>
+
+ my commit
+
+----
+
+Check in Git that the committer information of the commit that should
+be pushed is correct. As explained above you can do this by
+'git log --format=full'. The committer should have the same e-mail
+address that you've configured for your Gerrit account. If the
+committer information is incorrect set the Git configuration
+parameters 'user.name' and 'user.email' to the correct values (you
+might want to set this globally by including the option '--global'):
+
+----
+ $ git config user.name "John Doe"
+ $
+ $ git config user.email john.doe@example.com
+ $
+----
+
+Now you should rewrite the commits for which the committer
+information is wrong. If only the last commit is affected you can do
+this by doing a 'commit --amend'. If you need to update the committer
+information for several commits it gets more complicated. In this
+case you have to do an interactive git rebase for the affected
+commits. While doing the interactive rebase you have to ensure that
+the commits are rewritten (e.g. by choosing 'reword' for all these
+commits and then confirming all the commit messages). Just picking
+all the changes will not work as in this case the committer is not
+rewritten. For further details about git rebase please check the
+link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
+
+
+Missing privileges to push commits that were committed by other users
+---------------------------------------------------------------------
+
+If pushing to Gerrit fails with the error message "you are not
+committer ..." and somebody else committed the change for which the
+push fails, then you have no permissions to forge the committer
+identity. In this case you may contact the project owner to request
+the access right '+2 Forge Committer or Tagger Identity' in the
+'Forge Identity' category or ask the maintainer to commit this change
+on the author's behalf.
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
diff --git a/Documentation/index.txt b/Documentation/index.txt
index e419455..0ffd0ca 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -12,6 +12,7 @@
* link:user-changeid.html[Change-Id Lines]
* link:user-signedoffby.html[Signed-off-by Lines]
* link:access-control.html[Access Controls]
+* link:error-messages.html[Error Messages]
Installation
------------
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 394bebd..d926ada 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -125,7 +125,7 @@
if existing Git repositories were not imported during 'init'.
-[[rc.d]]
+[[rc_d]]
Start/Stop Daemon
-----------------
diff --git a/Documentation/javascript/toc.js b/Documentation/javascript/toc.js
deleted file mode 100644
index 36023f8..0000000
--- a/Documentation/javascript/toc.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/* Author: Mihai Bazon, September 2002
- * http://students.infoiasi.ro/~mishoo
- *
- * Table Of Content generator
- * Version: 0.4.sp
- *
- * Feel free to use this script under the terms of the GNU General Public
- * License, as long as you do not remove or alter this notice.
- */
-
- /* modified by Troy D. Hanson, September 2006. License: GPL */
- /* modified by Stuart Rackham, October 2006. License: GPL */
- /* modified by Shawn Pearce, August 2009. License: GPL */
-
-function getText(el) {
- var text = "";
- for (var i = el.firstChild; i != null; i = i.nextSibling) {
- if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
- text += i.data;
- else if (i.firstChild != null)
- text += getText(i);
- }
- return text;
-}
-
-function TocEntry(el, text, toclevel) {
- this.element = el;
- this.text = text;
- this.toclevel = toclevel;
- this.assigned = false;
-
- if (el.id != '') {
- this.id = el.id;
-
- } else {
- var a = el.firstChild;
- if ((a.tagName == "a" || a.tagName == "A") && a.id != "") {
- this.id = a.id;
- } else {
- this.id = '';
- }
- }
-}
-
-function tocEntries(el, toclevels) {
- var result = new Array;
- var re = new RegExp('[hH]([2-'+(toclevels+1)+'])');
- // Function that scans the DOM tree for header elements (the DOM2
- // nodeIterator API would be a better technique but not supported by all
- // browsers).
- var iterate = function (el) {
- for (var i = el.firstChild; i != null; i = i.nextSibling) {
- if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
- var mo = re.exec(i.tagName)
- if (mo)
- result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
- iterate(i);
- }
- }
- }
- iterate(el);
- return result;
-}
-
-// This function does the work. toclevels = 1..4.
-function generateToc(toclevels) {
- var simple_re = new RegExp('^[a-zA-Z._ -]{1,}$');
- var entries = tocEntries(document.getElementsByTagName("body")[0], toclevels);
- var usedIds = new Array();
-
- for (var i = 0; i < entries.length; ++i) {
- var entry = entries[i];
- if (entry.id != "")
- usedIds[entry.id] = entry;
- }
-
- for (var i = 0; i < entries.length; ++i) {
- var entry = entries[i];
- if (entry.id != "" || !simple_re.exec(entry.text))
- continue;
-
- var n = entry.text.replace(/ /g, '_').toLowerCase();
- var e = usedIds[n];
- if (e) {
- if (e.assigned)
- e.id = '';
- continue;
- }
-
- entry.assigned = true;
- entry.id = n;
- entry.element.id = entry.id;
- usedIds[n] = entry;
- }
-
- for (var i = 0; i < entries.length; ++i) {
- var entry = entries[i];
- if (entry.id == '') {
- entry.id = "toc" + i;
- entry.element.id = entry.id;
- }
- }
-
- var toc = document.getElementById("toc");
- for (var i = 0; i < entries.length; ++i) {
- var entry = entries[i];
- var a = document.createElement("a");
- a.href = "#" + entry.id;
- a.appendChild(document.createTextNode(entry.text));
- var div = document.createElement("div");
- div.appendChild(a);
- div.className = "toclevel" + entry.toclevel;
- toc.appendChild(div);
- }
-}
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 25194a9..c95b1b1 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -8,50 +8,49 @@
Included Components
-------------------
-[grid="all"]
-`---------------------------`------------------------------
-Included Package License
------------------------------------------------------------
-Gerrit Code Review <<apache2,Apache License 2.0>>
-gwtexpui <<apache2,Apache License 2.0>>
-gwtjsonrpc <<apache2,Apache License 2.0>>
-gwtorm <<apache2,Apache License 2.0>>
-Google Gson <<apache2,Apache License 2.0>>
-Google Web Toolkit <<apache2,Apache License 2.0>>
-Guice <<apache2,Apache License 2.0>>
-Apache Commons Codec <<apache2,Apache License 2.0>>
-Apache Commons DBCP <<apache2,Apache License 2.0>>
-Apache Commons Http Client <<apache2,Apache License 2.0>>
-Apache Commons Lang <<apache2,Apache License 2.0>>
-Apache Commons Logging <<apache2,Apache License 2.0>>
-Apache Commons Net <<apache2,Apache License 2.0>>
-Apache Commons Pool <<apache2,Apache License 2.0>>
-Apache Log4J <<apache2,Apache License 2.0>>
-Apache MINA <<apache2,Apache License 2.0>>
-Apache Tomact Servlet API <<apache2,Apache License 2.0>>
-Apache SSHD <<apache2,Apache License 2.0>>, see also <<sshd,NOTICE>>
-Apache Velocity <<apache2,Apache License 2.0>>
-Apache Xerces <<apache2,Apache License 2.0>>
-OpenID4Java <<apache2,Apache License 2.0>>
-Neko HTML <<apache2,Apache License 2.0>>
-Ehcache <<apache2,Apache License 2.0>>
-mime-util <<apache2,Apache License 2.0>>
-Jetty <<apache2,Apache License 2.0>>, or link:http://www.eclipse.org/legal/epl-v10.html[EPL]
-Google Code Prettify <<apache2,Apache License 2.0>>
-JGit <<jgit,New-Style BSD>>
-JSch <<sshd,New-Style BSD>>
-PostgreSQL JDBC Driver <<postgresql,New-Style BSD>>
-H2 Database <<h2,EPL or modified MPL>>
-ObjectWeb ASM <<asm,New-Style BSD>>
-ANTLR <<antlr,New-Style BSD>>
-args4j <<args4j,MIT License>>
-SLF4J <<slf4j,MIT License>>
-Clippy <<clippy,MIT License>>
-juniversalchardet <<mpl1_1,MPL 1.1>>
-AOP Alliance Public Domain
-JSR 305 <<jsr305,New-Style BSD>>
-dk.brics.automaton <<automaton,New-Style BSD>>
------------------------------------------------------------
+[options="header"]
+|======================================================================
+|Included Package | License
+|Gerrit Code Review | <<apache2,Apache License 2.0>>
+|gwtexpui | <<apache2,Apache License 2.0>>
+|gwtjsonrpc | <<apache2,Apache License 2.0>>
+|gwtorm | <<apache2,Apache License 2.0>>
+|Google Gson | <<apache2,Apache License 2.0>>
+|Google Web Toolkit | <<apache2,Apache License 2.0>>
+|Guice | <<apache2,Apache License 2.0>>
+|Apache Commons Codec | <<apache2,Apache License 2.0>>
+|Apache Commons DBCP | <<apache2,Apache License 2.0>>
+|Apache Commons Http Client | <<apache2,Apache License 2.0>>
+|Apache Commons Lang | <<apache2,Apache License 2.0>>
+|Apache Commons Logging | <<apache2,Apache License 2.0>>
+|Apache Commons Net | <<apache2,Apache License 2.0>>
+|Apache Commons Pool | <<apache2,Apache License 2.0>>
+|Apache Log4J | <<apache2,Apache License 2.0>>
+|Apache MINA | <<apache2,Apache License 2.0>>
+|Apache Tomact Servlet API | <<apache2,Apache License 2.0>>
+|Apache SSHD | <<apache2,Apache License 2.0>>, see also <<sshd,NOTICE>>
+|Apache Velocity | <<apache2,Apache License 2.0>>
+|Apache Xerces | <<apache2,Apache License 2.0>>
+|OpenID4Java | <<apache2,Apache License 2.0>>
+|Neko HTML | <<apache2,Apache License 2.0>>
+|Ehcache | <<apache2,Apache License 2.0>>
+|mime-util | <<apache2,Apache License 2.0>>
+|Jetty | <<apache2,Apache License 2.0>>, or link:http://www.eclipse.org/legal/epl-v10.html[EPL]
+|Google Code Prettify | <<apache2,Apache License 2.0>>
+|JGit | <<jgit,New-Style BSD>>
+|JSch | <<sshd,New-Style BSD>>
+|PostgreSQL JDBC Driver | <<postgresql,New-Style BSD>>
+|H2 Database | <<h2,EPL or modified MPL>>
+|ObjectWeb ASM | <<asm,New-Style BSD>>
+|ANTLR | <<antlr,New-Style BSD>>
+|args4j | <<args4j,MIT License>>
+|SLF4J | <<slf4j,MIT License>>
+|Clippy | <<clippy,MIT License>>
+|juniversalchardet | <<mpl1_1,MPL 1.1>>
+|AOP Alliance | Public Domain
+|JSR 305 | <<jsr305,New-Style BSD>>
+|dk.brics.automaton | <<automaton,New-Style BSD>>
+|======================================================================
Cryptography Notice
-------------------
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index a54f378..1fa627c 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -48,7 +48,7 @@
create and insert a unique Change-Id line during `git commit`.
To install the hook, copy it from Gerrit's daemon:
- $ scp -p -P 29418 review.example.com:hooks/commit-msg .git/hooks/
+ $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
$ curl http://review.example.com/tools/hooks/commit-msg
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 5b2f81e..5f0f004 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -10,18 +10,17 @@
matches the search, the change will be presented instead of a list.
-[grid="all"]
-`---------------------------`------------------------------
-Description Default Query
------------------------------------------------------------
-All > Open status:open '(or is:open)'
-All > Merged status:merged
-All > Abandoned status:abandoned
-My > Dafts has:draft
-My > Watched Changes status:open is:watched
-My > Starred Changes is:starred
-Open changes in Foo status:open project:Foo
------------------------------------------------------------
+[options="header"]
+|=================================================
+|Description | Default Query
+|All > Open | status:open '(or is:open)'
+|All > Merged | status:merged
+|All > Abandoned | status:abandoned
+|My > Dafts | has:draft
+|My > Watched Changes | status:open is:watched
+|My > Starred Changes | is:starred
+|Open changes in Foo | status:open project:Foo
+|=================================================
Basic Change Search
-------------------
@@ -29,16 +28,15 @@
Similar to many popular search engines on the web, just enter some
text and let Gerrit figure out the meaning:
-[grid="all"]
-`---------------------------------`------------------------------
-Description Examples
------------------------------------------------------------------
-Legacy numerical id 15183
-Full or abbreviated Change-Id Ic0ff33
-Full or abbreviated commit SHA-1 d81b32ef
-Email address user@example.com
-Approval requirement CodeReview>=+2, Verified=1
------------------------------------------------------------------
+[options="header"]
+|=============================================================
+|Description | Examples
+|Legacy numerical id | 15183
+|Full or abbreviated Change-Id | Ic0ff33
+|Full or abbreviated commit SHA-1 | d81b32ef
+|Email address | user@example.com
+|Approval requirement | CodeReview>=+2, Verified=1
+|=============================================================
Search Operators
@@ -123,7 +121,7 @@
If 'REF' starts with `^` it matches reference names by regular
expression patterns.
-[[tr]][[bug]]
+[[tr,bug]]
tr:'ID', bug:'ID'::
+
Search for changes whose commit message contains 'ID' and matched
@@ -146,13 +144,13 @@
Changes that matches 'MESSAGE' arbitrary string in body commit messages.
[[file]]
-file:\^'REGEX'::
+file:^'REGEX'::
+
Matches any change where REGEX matches a file that was affected
by the change. The regular expression pattern must start with
-`\^`. For example, to match all XML files use `file:^.*\.xml$`.
+`^`. For example, to match all XML files use `file:^.*\.xml$`.
+
-The `\^` required at the beginning of the regular expression not only
+The `^` required at the beginning of the regular expression not only
denotes a regular expression, but it also has the usual meaning of
anchoring the match to the start of the string. To match all Java
files, use `file:^.*\.java`.
@@ -351,7 +349,7 @@
starredby:'USER'::
+
-Matches changes that have been started by 'USER'.
+Matches changes that have been starred by 'USER'.
watchedby:'USER'::
+
@@ -370,14 +368,14 @@
preferences. Including it in a web query may lead to unpredictable
results with regards to pagination.
-resume\_sortkey:'KEY'::
+resume_sortkey:'KEY'::
+
Positions the low level scan routine to start from 'KEY' and
continue through changes from this point. This is most often used
for paginating result sets. Including this in a web query may lead
to unpredictable results.
-sortkey\_after:'KEY', sortkey\_before:'KEY'::
+sortkey_after:'KEY', sortkey_before:'KEY'::
+
Restart the low level scan routine from 'KEY'. This is automatically
set by the pagination system as the user navigates through results
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 10fddd3..4a137e4 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -191,6 +191,7 @@
For more about Change-Ids, see link:user-changeid.html[Change-Id Lines].
+[[manual_replacement_mapping]]
Manual Replacment Mapping
^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.1.txt b/ReleaseNotes/ReleaseNotes-2.1.6.1.txt
new file mode 100644
index 0000000..d531a6c
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.1.txt
@@ -0,0 +1,63 @@
+Release notes for Gerrit 2.1.6.1
+================================
+
+Gerrit 2.1.6.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.6.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.6.1.war]
+
+Schema Change
+-------------
+
+If upgrading from 2.1.6, there are no schema changes. Replace the
+WAR and restart the daemon.
+
+If upgrading from 2.1.5 or earlier, there are schema changes.
+To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+New Features
+------------
+* Display the originator of each access rule
++
+The project access panel now shows which project each rule inherits
+from. This can be informative when the inheritance chain is more
+than 1 project deep (for example C inherits from B, which inherits
+from A, which inherits from \-- All Projects \--).
+
+* Improved user->gerrit push speed
++
+Pushing changes for review (or directly to a branch) should be
+quicker now, especially if the project contains many changes.
+
+* Allow Owner permission to inherit
++
+The project Owner permission can now be inherited from any parent
+project, provided that the parent project is not the root level
+\-- All Projects \--.
+
+Bug Fixes
+---------
+* Fix disabled intraline difference checkbox
++
+Intraline difference couldn't be enabled once it was disabled by
+a user in their user preferences. Fixed.
+
+* Fix push over HTTP
++
+Users couldn't push to Gerrit over http://, due to a bug in the
+way user authentication was handled for the request. Fixed.
+
+* issue 751 Update displayed owner group after group rename
++
+The group owner field didn't update when a group was self-owned,
+and the self-owned group was renamed. This left the owner name
+at the old name, leaving the user to wonder if the group owner was
+also reassigned by another user. Fixed.
+
+* init: Fix string out of bounds when importing projects
++
+Project importing died when the top level directory contained a
+".git" directory (usually by accident by the site administrator).
+Fixed.
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.txt b/ReleaseNotes/ReleaseNotes-2.1.6.txt
new file mode 100644
index 0000000..198a064
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.txt
@@ -0,0 +1,322 @@
+Release notes for Gerrit 2.1.6
+==============================
+
+Gerrit 2.1.6 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.6.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.6.war]
+
+Schema Change
+-------------
+
+*WARNING* This release contains multiple schema changes. To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+
+New Features
+------------
+
+Web UI
+~~~~~~
+* issue 312 Abandoned changes can now be restored.
+* issue 698 Make date and time fields customizable
+* issue 556 Preference to display patch sets in reverse order
+* issue 584 Allow deleted and/or uncommented files to be skipped
+
+* Use HistogramDiff for content differences
++
+HistogramDiff is an adaptation of Bram Cohen's Patience Difference
+algorithm, and was recently included in the upstream JGit project.
+Patience Difference tends to produce more readable differences for
+source code files, and JGit's HistogramDiff implementation tends to
+run several times faster than the prior Myers O(ND) algorithm.
+
+* Automatic merge file content during submit
++
+Project owners can now enable file-level content merge during submit,
+allowing Gerrit to automatically resolve many path conflict cases.
+This is built upon experimental merge code inherited from JGit,
+and is therefore still experimental in Gerrit.
+
+Change Query
+~~~~~~~~~~~~
+* issue 688 Match branch, topic, project, ref by regular expressions
++
+Similar to other features in Gerrit Code Review, starting any of these
+expressions with \^ will now treat the argument as a regular
+expression instead of an exact string match.
+
+* Search changes by commit messages with `message:` operator.
+
+* issue 729 query: Add a \--all-approvals option to queries
++
+The new flag includes approval information for all patch sets in the
+resulting query output.
+
+Notifications
+~~~~~~~~~~~~
+* Customize email notification templates
++
+Email notifications are now driven by the Velocity template engine,
+and may be modified by the site administrator by editing a template
+file under `'$site_path'/etc/mail`.
+
+* issue 311 Clarify email texts/subject
++
+The default email notification formatting was changed to make the
+subject lines and message bodies more consistent, and easier to
+understand.
+
+* issue 204 Add project list popup under Settings > Watched Projects
++
+The project list panel makes it easier for users to browse all
+projects they have at least READ +1 access to, and add them to their
+watched project set so notifications can be configured.
+
+* stream-event support for all ref-update events
++
+Whenever a ref is updated via either a direct push to a branch or a
+Gerrit change submission, Gerrit will now send a new "ref-updated"
+event to the event stream.
+
+User Management
+~~~~~~~~~~~~~~~
+* SSO via client SSL certificates
++
+A new auth.type of CLIENT_SSL_CERT_LDAP supports authenticating users
+using client SSL certificates. This feature requires using the
+embedded Jetty web server with SSL enabled, and an LDAP directory to
+lookup individual account information.
+
+* issue 503 Inactive acounts may be disabled.
++
+Administrators can manually update the accounts table, setting
+inactive = `Y` to mark user accounts inactive. Inactive accounts
+cannot sign-in, cannot be added as a reviewer, and cannot be added
+to a group.
+
+* Improve the no-interactive-shell error message over SSH
++
+Instead of giving a short 'no shell available' error, Gerrit Code
+Review now prints a banner letting the user know they have
+authenticated successfully, interactive shells are disabled, and how
+to clone a hosted project:
++
+----
+$ ssh -p 29418 review.example.com
+
+ **** Welcome to Gerrit Code Review ****
+
+ Hi A. U. Thor, you have successfully connected over SSH.
+
+ Unfortunately, interactive shells are disabled.
+ To clone a hosted Git repository, use:
+
+ git clone ssh://author@review.example.com:29418/REPOSITORY_NAME.git
+
+Connection to review.example.com closed.
+----
+
+* Configure SSHD maxAuthTries, loginGraceTime, maxConnectionsPerUser
++
+The internal SSH daemon now supports additional configuration
+settings to reduce the risk of abuse.
+
+Administration
+~~~~~~~~~~~~~~
+* issue 558 Allow Access rights to be edited by clicking on them.
+
+* New 'Project Owner' system group to define default rights
++
+The new system group 'Project Owners' can be used in access
+rights to mean any user that is a member of any group that
+has the 'Owner' access category granted within that project.
+This system group is primarily useful in higher level projects
+such as '\-- All Projects \--' to define standard access rights
+for all project owners.
+
+* issue 557 Allow rejection of changes without Change-Id line.
++
+Project owners can set a flag to require all commits to include
+the Gerrit specific 'Change-Id: I...' line during initial upload,
+reducing the risk of confusion when amends need to occur to
+incorporate reviewer feedback.
+
+* issue 613 create-project: Add --permissions-only option
++
+The new flag skips creating the associated Git repository, making the
+new project suitable for use as a parent to inherit permissions from.
+
+* create-project: Optionally create empty initial commit
++
+The `repo` tool used by Android doesn't like to clone an empty Git
+repository, making it difficult to setup a review for the initial file
+contents. create-project can now optionally create an empty initial
+commit, permitting repo to sync the empty project.
+
+* Block off commands on a server for certain user groups.
++
+The upload.allowGroup and receive.allowGroup settings in gerrit.config
+can be used to restrict which users can perform git clone/fetch or git
+push on this server. This can be useful if clone/fetch should be
+limited to only site administrators, while normal users are supposed
+to use to less expensive mirror servers.
+
+* issue 685 Define gerrit.replicateOnStartup to control replication
++
+The automatic replicate every project action that occurs during server
+startup can now be disabled by setting replicateOnStartup = false.
+This is primarily useful for sites with extremely large numbers of
+projects and replication targets, but runs the risk of having a target
+be out of date relative to the master server.
+
+* New non-blocking function category "NoBlock"
++
+Site defined approval categories may now use the function "NoBlock"
+to permit scoring without blocking submission. This is mostly
+useful for automated tools to provide optional feedback on a change.
+
+* Ability to reject commits from entering repository
++
+The Git-note style branch `refs/meta/reject-commits` can be created
+by the project owner or site administrator to define a list of
+commits that must not be pushed into the repository. This can be
+useful after performing a project-wide filter-branch operation to
+prevent the older (pre-filter-branch) history from being reintroduced
+into the repository.
+
+Bug Fixes
+---------
+
+Web UI
+~~~~~~
+* issue 498 Enable Keyboard navigation after change submit
+* issue 691 Make ']' on last file go up to change
+* issue 741 Make ENTER work for 'Create Group'
+* issue 622 Denote a symbolic link in side-by-side viewer
+* issue 612 Display empty branch list when project has no repository
+* issue 672 Fix deleting exclusive branch level rights
+* issue 645 Display 'No difference' between unchanged patchsets
+* Display groups as links to group information
+* Remove ctrl-d keybinding to discard comment, honor browser default
+* Do not auto enable save buttons, wait for changes to be made
+* Disable 'Create Group' button if group name not entered
+* Show commit message in PatchScreen if old patch sets are compared
+* Fixed a number of focus and shortcut bugs in Firefox, Chrome
+
+* issue 487 Work around buggy MyersDiff by killing threads
++
+MyersDiff sometimes locked up in an infinite loop when computing
+the intraline difference information for a file. These threads
+are now killed after an administrator specified timeout
+(cache.diff_intraline.timeout, default is 5 seconds). If the
+timeout is reached the file content is displayed without intraline
+differences. This offers reduced functionality to the end-user, but
+prevents the "path of death" which usually took down a Gerrit server.
+
+* Hide access rights not visible to user
++
+Users were able to view access rights for branches they didn't
+actually have READ +1 permission on. This may have leaked
+information about branches and/or groups to users that shouldn't
+know about code names contained within either string. Users that
+are not project owners may now only view access rights for branches
+they have at least READ +1 permission on.
+
+Change Query
+~~~~~~~~~~~~
+* issue 689 Fix age:4days to parse correctly
+* Make branch: operator slightly less ambiguous
+
+Push Support
+~~~~~~~~~~~~
+* issue 695 Permit changing only the author of a commit
++
+Correcting only the author of a change failed to upload the new patch
+set onto the existing change, as neither the message nor the files
+were modified. Fixed.
+
+* issue 576 Allow Push Branch +3 to force replace a tag
++
+Previously it was not possible to replace a tag object, even if
+`git push \--force` was used. Fixed.
+
+* issue 690 Refuse to run receive-pack if refs/for/branch exists
++
+If a server repository was corrupted by an administrator manually
+creating a reference within the magical refs/for/ namespace, Gerrit
+became confused when changes were uploaded for review. If this case
+occurs push now aborts very early, with a clear error message
+indicating the problem. To recover an administrator must clear the
+refs/for/ namespace manually.
+
+* Allow receive-pack without Read +2 but with Push Head +1
++
+Users who had direct branch push permission but lacked the ability to
+create changes for review were unable to push to a project. Fixed.
+This (finally) makes Gerrit a replacement for Gitosis or Gitolite.
+
+Replication
+~~~~~~~~~~~
+* issue 683 Don't assume authGroup = "Registered Users" in replication
++
+Previously a misconfigured authGroup in replication.config may have
+caused the server to assume "Registered Users" instead of the group(s)
+admin actually wanted. This may have caused the replication to see
+(or not see) the correct set of projects.
+
+* issue 482 Upon replication fail, automatically retry later
++
+If replication fails (for example due to temporary network
+connectivity problems), other pending replication events to the
+same server are deferred and retried later until successful.
+
+* Replicate all refs received from push
++
+Replication now replicates all references, not just those that
+appear under `refs/heads`, `refs/tags`, or `refs/changes`. This
+fix may be relevant if the server supports user-private sandboxes
+such as `refs/dev/'$\{username\}'/*`.
+
+* issue 658 Allow refspec shortcuts (push = master) for replication
+
+User Management
+~~~~~~~~~~~~~~~
+* Ensure proper escaping of LDAP group names
++
+Some special characters may appear in LDAP group names, these must be
+escape when looking up the group information from JNDI, otherwise the
+lookup fails. Fixed by applying the necessary escape sequences.
+
+* Let login fail if user name cannot be set
++
+If the user name for a new account is supposed to import from LDAP
+but cannot because it is already in use by another user on this
+server, the new account won't be created.
+
+Administration
+~~~~~~~~~~~~~~
+* gerrit.sh: actually verify running processes
++
+Previously `gerrit.sh check` claimed a server was running if the
+pid file was present, even if the process itself was dead. It now
+checks `ps` for the process before claiming it is running.
+
+* Don't allow exclusive branch rights to block Owner inheritance
++
+Exclusive branch level rights prevented the a higher level branch
+owner from managing the branch rights, unless they had an additional
+access right for the exclusive rights. Now Owner inheritance cannot
+be blocked, ensuring that the higher level owner can manage their
+entire namespace.
+
+* Allow overriding permissions from parent project
++
+Permissions in the parent project could not be overridden in the
+child project. Permissions can now be overidden if the category,
+group name and reference name all match.
+
+Version
+-------
+ef16a1816f293d00c33de9f90470021e2468a709
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 985c0dd..a40d19d 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,6 +4,8 @@
[[2_1]]
Version 2.1.x
-------------
+* link:ReleaseNotes-2.1.6.html[2.1.6],
+ link:ReleaseNotes-2.1.6.1.html[2.1.6.1]
* link:ReleaseNotes-2.1.5.html[2.1.5]
* link:ReleaseNotes-2.1.4.html[2.1.4]
* link:ReleaseNotes-2.1.3.html[2.1.3]
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index af03191..e878b80 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -73,7 +73,7 @@
<id>generate-version</id>
<phase>generate-resources</phase>
<configuration>
- <tasks>
+ <target>
<property name="dst" location="${project.build.outputDirectory}" />
<property name="pkg" location="${dst}/com/google/gerrit/common" />
<mkdir dir="${pkg}" />
@@ -82,7 +82,7 @@
<arg value="HEAD"/>
</exec>
<echo file="${pkg}/Version">${v}</echo>
- </tasks>
+ </target>
</configuration>
<goals>
<goal>run</goal>
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 b83bc7d..f4fa721 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
@@ -47,7 +47,7 @@
@SignInRequired
void renameGroup(AccountGroup.Id groupId, String newName,
- AsyncCallback<VoidResult> callback);
+ AsyncCallback<GroupDetail> callback);
@SignInRequired
void changeGroupType(AccountGroup.Id groupId, AccountGroup.Type newType,
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 e24405f..24cdee4 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
@@ -55,6 +55,7 @@
protected List<Patch> history;
protected boolean hugeFile;
protected boolean intralineDifference;
+ protected boolean intralineFailure;
public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
final String nn, final FileMode om, final FileMode nm,
@@ -62,7 +63,7 @@
final SparseFileContent ca, final SparseFileContent cb,
final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
final CommentDetail cd, final List<Patch> hist, final boolean hf,
- final boolean id) {
+ final boolean id, final boolean idf) {
changeId = ck;
changeType = ct;
oldName = on;
@@ -80,6 +81,7 @@
history = hist;
hugeFile = hf;
intralineDifference = id;
+ intralineFailure = idf;
}
protected PatchScript() {
@@ -149,6 +151,10 @@
return intralineDifference;
}
+ public boolean hasIntralineFailure() {
+ return intralineFailure;
+ }
+
public SparseFileContent getA() {
return a;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/NoSuchGroupException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java
similarity index 85%
rename from gerrit-server/src/main/java/com/google/gerrit/server/account/NoSuchGroupException.java
rename to gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java
index 0e44422..f7115b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/NoSuchGroupException.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/NoSuchGroupException.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.account;
+package com.google.gerrit.common.errors;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -20,12 +20,14 @@
public class NoSuchGroupException extends Exception {
private static final long serialVersionUID = 1L;
+ public static final String MESSAGE = "Group Not Found: ";
+
public NoSuchGroupException(final AccountGroup.Id key) {
this(key, null);
}
public NoSuchGroupException(final AccountGroup.Id key, final Throwable why) {
- super(key.toString(), why);
+ super(MESSAGE + key.toString(), why);
}
public NoSuchGroupException(final AccountGroup.NameKey k) {
@@ -33,6 +35,6 @@
}
public NoSuchGroupException(final AccountGroup.NameKey k, final Throwable why) {
- super(k.toString(), why);
+ super(MESSAGE + k.toString(), why);
}
}
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index 9a27d12..178f8f4 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -175,12 +175,12 @@
<goal>run</goal>
</goals>
<configuration>
- <tasks>
+ <target>
<property name="dst" location="${project.build.outputDirectory}" />
<property name="pkg" location="${dst}/com/google/gerrit/client/account"/>
<mkdir dir="${pkg}"/>
<echo file="${pkg}/keyapplet_jar">gerrit-keyapplet-${keyappletVersion}.cache.jar</echo>
- </tasks>
+ </target>
</configuration>
</execution>
</executions>
@@ -216,7 +216,7 @@
<goal>run</goal>
</goals>
<configuration>
- <tasks>
+ <target>
<property name="dst" location="${project.build.directory}/${project.build.finalName}"/>
<property name="app" location="${dst}/gerrit"/>
@@ -233,7 +233,7 @@
<outputmapper type="glob" from="*" to="${app}/*.gz"/>
</redirector>
</apply>
- </tasks>
+ </target>
</configuration>
</execution>
</executions>
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 d5ccdf9..b421e48 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
@@ -30,6 +30,7 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.AuthType;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
@@ -206,6 +207,7 @@
switch (myConfig.getAuthType()) {
case HTTP:
case HTTP_LDAP:
+ case CLIENT_SSL_CERT_LDAP:
Location.assign(Location.getPath() + "login/" + token);
break;
@@ -476,7 +478,9 @@
if (signedIn) {
whoAmI();
addLink(menuRight, C.menuSettings(), PageLinks.SETTINGS);
- menuRight.add(anchor(C.menuSignOut(), "logout"));
+ if (cfg.getAuthType() != AuthType.CLIENT_SSL_CERT_LDAP) {
+ menuRight.add(anchor(C.menuSignOut(), "logout"));
+ }
} else {
switch (cfg.getAuthType()) {
case HTTP:
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 b32a32d..fbec6aa 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
@@ -46,6 +46,8 @@
String nameAlreadyUsedBody();
String noSuchAccountTitle();
+ String noSuchGroupTitle();
+
String inactiveAccountBody();
String menuAll();
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 3363015..617ab6b 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
@@ -29,6 +29,8 @@
nameAlreadyUsedBody = The name is already in use.
noSuchAccountTitle = Code Review - Unknown User
+noSuchGroupTitle = Code Review - Unknown Group
+
inactiveAccountBody = This user is currently inactive.
menuAll = All
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
index bfc663e..aefa3a5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
@@ -22,4 +22,6 @@
String poweredBy(String version);
String noSuchAccountMessage(String who);
+
+ String noSuchGroupMessage(String who);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
index 159ddfc..a91df3c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
@@ -4,3 +4,5 @@
| <a href="http://code.google.com/p/gerrit/issues/list" target="_blank">Report Bug</a>
noSuchAccountMessage = {0} is not a registered user.
+
+noSuchGroupMessage = Group {0} does not exist or is not visible to you.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
index be808fa..b3bd0cb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
@@ -134,9 +134,9 @@
table.setWidget(row, 1, new CheckBox());
table.setWidget(row, 2, fp);
- addNotifyButton(AccountProjectWatch.Type.NEW_CHANGES, info, row, 3);
- addNotifyButton(AccountProjectWatch.Type.COMMENTS, info, row, 4);
- addNotifyButton(AccountProjectWatch.Type.SUBMITS, info, row, 5);
+ addNotifyButton(AccountProjectWatch.NotifyType.NEW_CHANGES, info, row, 3);
+ addNotifyButton(AccountProjectWatch.NotifyType.ALL_COMMENTS, info, row, 4);
+ addNotifyButton(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES, info, row, 5);
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
@@ -148,7 +148,7 @@
setRowItem(row, info);
}
- protected void addNotifyButton(final AccountProjectWatch.Type type,
+ protected void addNotifyButton(final AccountProjectWatch.NotifyType type,
final AccountProjectWatchInfo info, final int row, final int col) {
final CheckBox cbox = new CheckBox();
@@ -157,13 +157,16 @@
public void onClick(final ClickEvent event) {
final boolean oldVal = info.getWatch().isNotify(type);
info.getWatch().setNotify(type, cbox.getValue());
+ cbox.setEnabled(false);
Util.ACCOUNT_SVC.updateProjectWatch(info.getWatch(),
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
+ cbox.setEnabled(true);
}
@Override
public void onFailure(final Throwable caught) {
+ cbox.setEnabled(true);
info.getWatch().setNotify(type, oldVal);
cbox.setValue(oldVal);
super.onFailure(caught);
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
index 762cc47..ce4f094 100644
--- 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
@@ -21,25 +21,21 @@
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.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
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.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
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.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
@@ -148,7 +144,7 @@
.getActionTypes()) {
final ApprovalCategory c = at.getCategory();
if (Gerrit.getConfig().getWildProject().equals(projectKey)
- && ApprovalCategory.OWN.equals(c.getId())) {
+ && !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
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 389f22b..fd06cdb 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
@@ -124,10 +124,10 @@
public void onClick(final ClickEvent event) {
final String newName = groupNameTxt.getText().trim();
Util.GROUP_SVC.renameGroup(groupId, newName,
- new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
+ new GerritCallback<GroupDetail>() {
+ public void onSuccess(final GroupDetail groupDetail) {
saveName.setEnabled(false);
- setPageTitle(Util.M.group(newName));
+ display(groupDetail);
}
});
}
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 6f27ce5e..bb82678 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
@@ -64,6 +64,7 @@
String columnProjectName();
String columnGroupDescription();
String columnProjectDescription();
+ String columnRightOrigin();
String columnApprovalCategory();
String columnRightRange();
String columnRefName();
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 0874c37..1b31d7b 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
@@ -46,6 +46,7 @@
columnGroupDescription = Description
columnProjectDescription = Description
columnApprovalCategory = Category
+columnRightOrigin = Origin
columnRightRange = Permitted Range
columnRefName = Reference Name
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 5b863c7..547e81fa 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
@@ -155,10 +155,11 @@
RightsTable() {
table.setWidth("");
- table.setText(0, 2, Util.C.columnApprovalCategory());
- table.setText(0, 3, Util.C.columnGroupName());
- table.setText(0, 4, Util.C.columnRefName());
- table.setText(0, 5, Util.C.columnRightRange());
+ 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());
@@ -166,6 +167,7 @@
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
@@ -224,18 +226,26 @@
canDelete = true;
}
- table.setText(row, 2, ar != null ? ar.getCategory().getName()
+ 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, 3, new Hyperlink(group.getName(), Dispatcher
+ table.setWidget(row, 4, new Hyperlink(group.getName(), Dispatcher
.toAccountGroup(group.getId())));
} else {
- table.setText(row, 3, Util.M.deletedGroup(right.getAccountGroupId()
+ table.setText(row, 4, Util.M.deletedGroup(right.getAccountGroupId()
.get()));
}
- table.setText(row, 4, right.getRefPatternForDisplay());
+ table.setText(row, 5, right.getRefPatternForDisplay());
{
final SafeHtmlBuilder m = new SafeHtmlBuilder();
@@ -248,7 +258,7 @@
m.br();
}
formatValue(m, right.getMaxValue(), max);
- SafeHtml.set(table, row, 5, m);
+ SafeHtml.set(table, row, 6, m);
}
final FlexCellFormatter fmt = table.getFlexCellFormatter();
@@ -257,7 +267,8 @@
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, 5, Gerrit.RESOURCES.css()
+ fmt.addStyleName(row, 6, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 6, Gerrit.RESOURCES.css()
.projectAdminApprovalCategoryRangeLine());
setRowItem(row, right);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
index 34e300a..bc63fc0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AbandonChangeDialog.java
@@ -75,6 +75,7 @@
@Override
public void onClick(final ClickEvent event) {
sendButton.setEnabled(false);
+ cancelButton.setEnabled(false);
Util.MANAGE_SVC.abandonChange(psid, message.getText().trim(),
new GerritCallback<ChangeDetail>() {
public void onSuccess(ChangeDetail result) {
@@ -88,6 +89,7 @@
@Override
public void onFailure(Throwable caught) {
sendButton.setEnabled(true);
+ cancelButton.setEnabled(true);
super.onFailure(caught);
}
});
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 23dd635..a20b02e 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
@@ -232,7 +232,7 @@
table.setWidget(row, col++, link(ad.getAccount()));
if (ad.canRemove()) {
- PushButton remove = new PushButton( //
+ final PushButton remove = new PushButton( //
new Image(Util.R.removeReviewerNormal()), //
new Image(Util.R.removeReviewerPressed()));
remove.setTitle(Util.M.removeReviewer( //
@@ -241,7 +241,7 @@
remove.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
- doRemove(ad);
+ doRemove(ad, remove);
}
});
table.setWidget(row, col, remove);
@@ -294,7 +294,8 @@
col++;
}
- private void doRemove(final ApprovalDetail ad) {
+ private void doRemove(final ApprovalDetail ad, final PushButton remove) {
+ remove.setEnabled(false);
PatchUtil.DETAIL_SVC.removeReviewer(changeId, ad.getAccount(),
new GerritCallback<ReviewerResult>() {
@Override
@@ -306,6 +307,12 @@
new ErrorDialog(result.getErrors().get(0).toString()).center();
}
}
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ remove.setEnabled(true);
+ super.onFailure(caught);
+ }
});
}
}
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 248e57d..2c62565 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
@@ -400,6 +400,7 @@
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
+ b.setEnabled(false);
new AbandonChangeDialog(patchSet.getId(),
new AsyncCallback<ChangeDetail>() {
public void onSuccess(ChangeDetail result) {
@@ -420,6 +421,7 @@
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
+ b.setEnabled(false);
new RestoreChangeDialog(patchSet.getId(),
new AsyncCallback<ChangeDetail>() {
public void onSuccess(ChangeDetail result) {
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 8a8996a..f9e55be 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
@@ -120,6 +120,19 @@
buttonRow.add(cancel);
}
+ private void enableForm(final boolean enabled) {
+ for (final ValueRadioButton approvalButton : approvalButtons) {
+ approvalButton.setEnabled(enabled);
+ }
+ message.setEnabled(enabled);
+ for (final CommentEditorPanel commentEditor : commentEditors) {
+ commentEditor.enableButtons(enabled);
+ }
+ send.setEnabled(enabled);
+ submit.setEnabled(enabled);
+ cancel.setEnabled(enabled);
+ }
+
@Override
protected void onLoad() {
super.onLoad();
@@ -326,6 +339,7 @@
}
}
+ enableForm(false);
PatchUtil.DETAIL_SVC.publishComments(patchSetId, message.getText().trim(),
new HashSet<ApprovalCategoryValue.Id>(values.values()),
new GerritCallback<VoidResult>() {
@@ -337,6 +351,12 @@
goChange();
}
}
+
+ @Override
+ public void onFailure(Throwable caught) {
+ super.onFailure(caught);
+ enableForm(true);
+ }
});
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java
index e05c42a..69d7c1f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RestoreChangeDialog.java
@@ -75,6 +75,7 @@
@Override
public void onClick(final ClickEvent event) {
sendButton.setEnabled(false);
+ cancelButton.setEnabled(false);
Util.MANAGE_SVC.restoreChange(psid, message.getText().trim(),
new GerritCallback<ChangeDetail>() {
@Override
@@ -89,6 +90,7 @@
@Override
public void onFailure(Throwable caught) {
sendButton.setEnabled(true);
+ cancelButton.setEnabled(true);
super.onFailure(caught);
}
});
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index 93e8deb..9b85131 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -31,6 +31,8 @@
String patchHistoryTitle();
String disabledOnLargeFiles();
+ String intralineFailure();
+ String illegalNumberOfColumns();
String upToChange();
String linePrev();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 90def1d..a22fc1e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -13,6 +13,8 @@
patchHeaderNew = New Version
patchHistoryTitle = Patch History
disabledOnLargeFiles = Disabled on very large source files.
+intralineFailure = Intraline difference not available due to server error.
+illegalNumberOfColumns = The number of columns cannot be zero or negative
upToChange = Up to change
linePrev = Previous line
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 812a74e..cbe037b 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
@@ -15,6 +15,7 @@
package com.google.gerrit.client.patches;
import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.changes.CommitMessageBlock;
@@ -140,6 +141,7 @@
/** Keys that cause an action on this screen */
private KeyCommandSet keysNavigation;
private HandlerRegistration regNavigation;
+ private boolean intralineFailure;
/**
* How this patch should be displayed in the patch screen.
@@ -461,6 +463,17 @@
settingsPanel.getReviewedCheckBox().setValue(true);
setReviewedByCurrentUser(true /* reviewed */);
}
+
+ intralineFailure = isFirst && script.hasIntralineFailure();
+ }
+
+ @Override
+ public void onShowView() {
+ super.onShowView();
+ if (intralineFailure) {
+ intralineFailure = false;
+ new ErrorDialog(PatchUtil.C.intralineFailure()).show();
+ }
}
private void showPatch(final boolean showPatch) {
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 3550229..5c777ff 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
@@ -14,6 +14,7 @@
package com.google.gerrit.client.patches;
+import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.Util;
import com.google.gerrit.client.rpc.GerritCallback;
@@ -216,6 +217,11 @@
}
private void update() {
+ if (colWidth.getIntValue() <= 0) {
+ new ErrorDialog(PatchUtil.C.illegalNumberOfColumns()).center();
+ return;
+ }
+
AccountDiffPreference dp = new AccountDiffPreference(getValue());
dp.setIgnoreWhitespace(getIgnoreWhitespace());
dp.setContext(getContext());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index f29742a..1871bb7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -21,6 +21,7 @@
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -51,6 +52,13 @@
} else if (isNameAlreadyUsed(caught)) {
new ErrorDialog(Gerrit.C.nameAlreadyUsedBody()).center();
+ } else if (isNoSuchGroup(caught)) {
+ final String msg = caught.getMessage();
+ final String group = msg.substring(NoSuchGroupException.MESSAGE.length());
+ final ErrorDialog d = new ErrorDialog(Gerrit.M.noSuchGroupMessage(group));
+ d.setText(Gerrit.C.noSuchGroupTitle());
+ d.center();
+
} else if (caught instanceof ServerUnavailableException) {
new ErrorDialog(RpcConstants.C.errorServerUnavailable()).center();
@@ -89,4 +97,9 @@
return caught instanceof RemoteJsonException
&& caught.getMessage().equals(NameAlreadyUsedException.MESSAGE);
}
+
+ private static boolean isNoSuchGroup(final Throwable caught) {
+ return caught instanceof RemoteJsonException
+ && caught.getMessage().startsWith(NoSuchGroupException.MESSAGE);
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
index 06c17e5..249c70c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
@@ -195,7 +195,7 @@
handlerManager.fireEvent(event);
}
- protected void enableButtons(final boolean on) {
+ public void enableButtons(final boolean on) {
for (Widget w : getButtonPanel()) {
if (w instanceof Button) {
((Button) w).setEnabled(on);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
new file mode 100644
index 0000000..95a4f14
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.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.httpd;
+
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Project;
+
+class AdvertisedObjectsCacheKey {
+ private final Account.Id account;
+ private final Project.NameKey project;
+
+ AdvertisedObjectsCacheKey(Account.Id account, Project.NameKey project) {
+ this.account = account;
+ this.project = project;
+ }
+
+ @Override
+ public int hashCode() {
+ return account.hashCode();
+ }
+
+ public boolean equals(Object other) {
+ if (other instanceof AdvertisedObjectsCacheKey) {
+ AdvertisedObjectsCacheKey o = (AdvertisedObjectsCacheKey) other;
+ return account.equals(o.account) && project.equals(o.project);
+ }
+ return false;
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index f67f12f..929d034 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -126,6 +126,7 @@
|| nonce == null //
|| uri == null //
|| response == null //
+ || !"auth".equals(qop) //
|| !REALM_NAME.equals(realm)) {
context.log("Invalid header: " + AUTHORIZATION + ": " + hdr);
rsp.sendError(SC_FORBIDDEN);
@@ -146,14 +147,8 @@
final String A1 = username + ":" + realm + ":" + passwd;
final String A2 = method + ":" + uri;
-
- final String expect;
- if ("auth".equals(qop)) {
- expect = KD(H(A1), //
- nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2));
- } else {
- expect = KD(H(A1), nonce + ":" + H(A2));
- }
+ final String expect =
+ KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2));
if (expect.equals(response)) {
try {
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 09e2cce..5eea773 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
@@ -20,6 +20,8 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReceiveCommits;
@@ -31,27 +33,39 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.http.server.resolver.AsIsFileService;
-import org.eclipse.jgit.http.server.resolver.ReceivePackFactory;
-import org.eclipse.jgit.http.server.resolver.RepositoryResolver;
-import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException;
-import org.eclipse.jgit.http.server.resolver.UploadPackFactory;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.RepositoryResolver;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -63,6 +77,8 @@
LoggerFactory.getLogger(ProjectServlet.class);
private static final String ATT_CONTROL = ProjectControl.class.getName();
+ private static final String ATT_RC = ReceiveCommits.class.getName();
+ private static final String ID_CACHE = "adv_bases";
static class Module extends AbstractModule {
@Override
@@ -70,10 +86,21 @@
bind(Resolver.class);
bind(Upload.class);
bind(Receive.class);
+ bind(ReceiveFilter.class);
+ install(new CacheModule() {
+ @Override
+ protected void configure() {
+ TypeLiteral<Cache<AdvertisedObjectsCacheKey, Set<ObjectId>>> cache =
+ new TypeLiteral<Cache<AdvertisedObjectsCacheKey, Set<ObjectId>>>() {};
+ core(cache, ID_CACHE)
+ .memoryLimit(4096)
+ .maxAge(10, TimeUnit.MINUTES);
+ }
+ });
}
}
- static ProjectControl getProjectControl(HttpServletRequest req)
+ static ProjectControl getProjectControl(ServletRequest req)
throws ServiceNotEnabledException {
ProjectControl pc = (ProjectControl) req.getAttribute(ATT_CONTROL);
if (pc == null) {
@@ -88,6 +115,7 @@
@Inject
ProjectServlet(final Resolver resolver, final Upload upload,
final Receive receive,
+ final ReceiveFilter receiveFilter,
@CanonicalWebUrl @Nullable Provider<String> urlProvider) {
this.urlProvider = urlProvider;
@@ -95,6 +123,7 @@
setAsIsFileService(AsIsFileService.DISABLED);
setUploadPackFactory(upload);
setReceivePackFactory(receive);
+ addReceivePackFilter(receiveFilter);
}
@Override
@@ -124,7 +153,7 @@
});
}
- static class Resolver implements RepositoryResolver {
+ static class Resolver implements RepositoryResolver<HttpServletRequest> {
private final GitRepositoryManager manager;
private final ProjectControl.Factory projectControlFactory;
@@ -171,11 +200,11 @@
}
req.setAttribute(ATT_CONTROL, pc);
- return manager.openRepository(pc.getProject().getName());
+ return manager.openRepository(pc.getProject().getNameKey());
}
}
- static class Upload implements UploadPackFactory {
+ static class Upload implements UploadPackFactory<HttpServletRequest> {
private final Provider<ReviewDb> db;
private final PackConfig packConfig;
@@ -198,13 +227,13 @@
UploadPack up = new UploadPack(repo);
up.setPackConfig(packConfig);
if (!pc.allRefsAreVisible()) {
- up.setRefFilter(new VisibleRefFilter(repo, pc, db.get()));
+ up.setRefFilter(new VisibleRefFilter(repo, pc, db.get(), true));
}
return up;
}
}
- static class Receive implements ReceivePackFactory {
+ static class Receive implements ReceivePackFactory<HttpServletRequest> {
private final ReceiveCommits.Factory factory;
@Inject
@@ -235,6 +264,7 @@
}
rc.getReceivePack().setRefLogIdent(user.newRefLogIdent());
+ req.setAttribute(ATT_RC, rc);
return rc.getReceivePack();
} else {
@@ -242,4 +272,76 @@
}
}
}
+
+ static class ReceiveFilter implements Filter {
+ private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
+
+ @Inject
+ ReceiveFilter(
+ @Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ boolean isGet =
+ "GET".equalsIgnoreCase(((HttpServletRequest) request).getMethod());
+
+ ReceiveCommits rc = (ReceiveCommits) request.getAttribute(ATT_RC);
+ ReceivePack rp = rc.getReceivePack();
+ ProjectControl pc;
+ try {
+ pc = getProjectControl(request);
+ } catch (ServiceNotEnabledException e) {
+ // This shouldn't occur, the parent should have stopped processing.
+ throw new ServletException(e);
+ }
+
+ Project.NameKey projectName = pc.getProject().getNameKey();
+
+ if (!rp.isCheckReferencedObjectsAreReachable()) {
+ if (isGet) {
+ rc.advertiseHistory();
+ }
+ chain.doFilter(request, response);
+ return;
+ }
+
+ if (!(pc.getCurrentUser() instanceof IdentifiedUser)) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ AdvertisedObjectsCacheKey cacheKey = new AdvertisedObjectsCacheKey(
+ ((IdentifiedUser) pc.getCurrentUser()).getAccountId(),
+ projectName);
+
+ if (isGet) {
+ rc.advertiseHistory();
+ cache.remove(cacheKey);
+ } else {
+ Set<ObjectId> ids = cache.get(cacheKey);
+ if (ids != null) {
+ rp.getAdvertisedObjects().addAll(ids);
+ cache.remove(cacheKey);
+ }
+ }
+
+ chain.doFilter(request, response);
+
+ if (isGet) {
+ cache.put(cacheKey, Collections.unmodifiableSet(
+ new HashSet<ObjectId>(rp.getAdvertisedObjects())));
+ }
+ }
+
+ @Override
+ public void init(FilterConfig arg0) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+ }
}
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 90ccdc5..c20f8a1 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
@@ -28,6 +28,7 @@
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
+import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
@@ -62,6 +63,7 @@
private final HttpServletRequest request;
private final HttpServletResponse response;
private final WebSessionManager manager;
+ private final AuthConfig authConfig;
private final AnonymousUser anonymous;
private final IdentifiedUser.RequestFactory identified;
private AccessPath accessPath = AccessPath.WEB_UI;
@@ -73,11 +75,12 @@
@Inject
WebSession(final HttpServletRequest request,
final HttpServletResponse response, final WebSessionManager manager,
- final AnonymousUser anonymous,
+ final AuthConfig authConfig, final AnonymousUser anonymous,
final IdentifiedUser.RequestFactory identified) {
this.request = request;
this.response = response;
this.manager = manager;
+ this.authConfig = authConfig;
this.anonymous = anonymous;
this.identified = identified;
@@ -182,13 +185,17 @@
}
if (outCookie == null) {
- String path = request.getContextPath();
- if (path.equals("")) {
- path = "/";
+ String path = authConfig.getCookiePath();
+ if (path == null || path.isEmpty()) {
+ path = request.getContextPath();
+ if (path.isEmpty()) {
+ path = "/";
+ }
}
outCookie = new Cookie(ACCOUNT_COOKIE, token);
outCookie.setPath(path);
outCookie.setMaxAge(ageSeconds);
+ outCookie.setSecure(authConfig.getCookieSecure());
response.addCookie(outCookie);
} else {
outCookie.setValue(token);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java
new file mode 100644
index 0000000..6125405
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java
@@ -0,0 +1,78 @@
+//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.auth.container;
+
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet bound to {@code /login/*} to redirect after client SSL certificate
+ * login.
+ * <p>
+ * When using client SSL certificate one should normally never see the sign in
+ * dialog. However, this will happen if users session gets invalidated in some
+ * way. Like in other authentication types, we need to force page to fully
+ * reload in order to initialize a new session and create a valid xsrfKey.
+ */
+@Singleton
+public class HttpsClientSslCertLoginServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ private final Provider<WebSession> webSession;
+ private final Provider<String> urlProvider;
+
+ @Inject
+ public HttpsClientSslCertLoginServlet(final Provider<WebSession> webSession,
+ @CanonicalWebUrl @Nullable final Provider<String> urlProvider) {
+ this.webSession = webSession;
+ this.urlProvider = urlProvider;
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ final StringBuilder rdr = new StringBuilder();
+ rdr.append(urlProvider.get());
+ rdr.append('#');
+ rdr.append(getToken(req));
+
+ rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
+ rsp.setHeader("Pragma", "no-cache");
+ rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
+ rsp.sendRedirect(rdr.toString());
+ }
+
+ private String getToken(final HttpServletRequest req) {
+ String token = req.getPathInfo();
+ if (token != null && token.startsWith("/")) {
+ token = token.substring(1);
+ }
+ if (token == null || token.isEmpty()) {
+ token = PageLinks.MINE;
+ }
+ return token;
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java
index f0976f3..7d32ac8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertModule.java
@@ -21,5 +21,6 @@
@Override
protected void configureServlets() {
filter("/").through(HttpsClientSslCertAuthFilter.class);
+ serve("/login/*").with(HttpsClientSslCertLoginServlet.class);
}
}
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 8991ea9..2d7ed09 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
@@ -323,9 +323,9 @@
name = name.substring(0, name.length() - 4);
}
+ final Project.NameKey nameKey = new Project.NameKey(name);
final ProjectControl project;
try {
- final Project.NameKey nameKey = new Project.NameKey(name);
project = projectControl.validateFor(nameKey);
if (!project.allRefsAreVisible()) {
// Pretend the project doesn't exist
@@ -338,7 +338,7 @@
final Repository repo;
try {
- repo = repoManager.openRepository(name);
+ repo = repoManager.openRepository(nameKey);
} catch (RepositoryNotFoundException e) {
getServletContext().log("Cannot open repository", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
index 832ae99..b4c3519 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -157,7 +157,7 @@
final Repository repo;
try {
- repo = repoManager.openRepository(project.getNameKey().get());
+ repo = repoManager.openRepository(project.getNameKey());
} catch (RepositoryNotFoundException e) {
getServletContext().log("Cannot open repository", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
index 120de30..6bb905f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
@@ -17,11 +17,11 @@
import com.google.gerrit.common.errors.CorruptEntityException;
import com.google.gerrit.common.errors.InvalidQueryException;
import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwt.user.client.rpc.AsyncCallback;
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 74e2966..3ce22e7 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
@@ -18,72 +18,34 @@
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupMember;
-import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.AccountGroupName;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gwtorm.client.OrmDuplicateKeyException;
+import com.google.gerrit.server.account.PerformCreateGroup;
+import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.Collections;
-
class CreateGroup extends Handler<AccountGroup.Id> {
interface Factory {
- CreateGroup create(String newName);
+ CreateGroup create(String groupName);
}
- private final ReviewDb db;
+ private final PerformCreateGroup.Factory performCreateGroupFactory;
private final IdentifiedUser user;
- private final AccountCache accountCache;
-
- private final String name;
+ private final String groupName;
@Inject
- CreateGroup(final ReviewDb db, final IdentifiedUser user,
- final AccountCache accountCache,
-
- @Assisted final String newName) {
- this.db = db;
+ CreateGroup(final PerformCreateGroup.Factory performCreateGroupFactory,
+ final IdentifiedUser user, @Assisted final String groupName) {
+ this.performCreateGroupFactory = performCreateGroupFactory;
this.user = user;
- this.accountCache = accountCache;
-
- this.name = newName;
+ this.groupName = groupName;
}
@Override
public AccountGroup.Id call() throws OrmException, NameAlreadyUsedException {
- final AccountGroup.NameKey key = new AccountGroup.NameKey(name);
-
- if (db.accountGroupNames().get(key) != null) {
- throw new NameAlreadyUsedException();
- }
-
- final AccountGroup.Id id = new AccountGroup.Id(db.nextAccountGroupId());
+ final PerformCreateGroup performCreateGroup = performCreateGroupFactory.create();
final Account.Id me = user.getAccountId();
-
- final AccountGroup group = new AccountGroup(key, id);
- db.accountGroups().insert(Collections.singleton(group));
-
- try {
- final AccountGroupName n = new AccountGroupName(group);
- db.accountGroupNames().insert(Collections.singleton(n));
- } catch (OrmDuplicateKeyException dupeErr) {
- db.accountGroups().delete(Collections.singleton(group));
- throw new NameAlreadyUsedException();
- }
-
- AccountGroupMember member = new AccountGroupMember(//
- new AccountGroupMember.Key(me, id));
-
- db.accountGroupMembersAudit().insert(
- Collections.singleton(new AccountGroupMemberAudit(member, me)));
- db.accountGroupMembers().insert(Collections.singleton(member));
-
- accountCache.evict(me);
- return id;
+ return performCreateGroup.createGroup(groupName, null, null, me);
}
}
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 870d77c..b8e4090 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
@@ -20,6 +20,7 @@
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -31,7 +32,6 @@
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.account.Realm;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -163,7 +163,7 @@
}
public void renameGroup(final AccountGroup.Id groupId, final String newName,
- final AsyncCallback<VoidResult> callback) {
+ final AsyncCallback<GroupDetail> callback) {
renameGroupFactory.create(groupId, newName).to(callback);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java
index 1b05660..7f93cd5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc.account;
import com.google.gerrit.common.data.GroupDetail;
+import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -23,7 +24,6 @@
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
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 63b5bce..d62f0c0 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
@@ -14,15 +14,15 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
+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.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.account.NoSuchGroupException;
-import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -30,7 +30,7 @@
import java.util.Collections;
-class RenameGroup extends Handler<VoidResult> {
+class RenameGroup extends Handler<GroupDetail> {
interface Factory {
RenameGroup create(AccountGroup.Id id, String newName);
}
@@ -38,6 +38,7 @@
private final ReviewDb db;
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
+ private final GroupDetailFactory.Factory groupDetailFactory;
private final AccountGroup.Id groupId;
private final String newName;
@@ -45,18 +46,18 @@
@Inject
RenameGroup(final ReviewDb db, final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
-
+ final GroupDetailFactory.Factory groupDetailFactory,
@Assisted final AccountGroup.Id groupId, @Assisted final String newName) {
this.db = db;
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
-
+ this.groupDetailFactory = groupDetailFactory;
this.groupId = groupId;
this.newName = newName;
}
@Override
- public VoidResult call() throws OrmException, NameAlreadyUsedException,
+ public GroupDetail call() throws OrmException, NameAlreadyUsedException,
NoSuchGroupException {
final GroupControl ctl = groupControlFactory.validateFor(groupId);
final AccountGroup group = db.accountGroups().get(groupId);
@@ -75,7 +76,7 @@
//
AccountGroupName other = db.accountGroupNames().get(key);
if (other != null && other.getId().equals(groupId)) {
- return VoidResult.INSTANCE;
+ return groupDetailFactory.create(groupId).call();
}
// Otherwise, someone else has this identity.
@@ -94,6 +95,6 @@
groupCache.evict(group);
groupCache.evictAfterRename(old);
- return VoidResult.INSTANCE;
+ 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 4a4d9d1..d2f59f5 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
@@ -19,9 +19,7 @@
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
-import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -30,14 +28,10 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.Collections;
-import java.util.List;
-
import javax.annotation.Nullable;
class AbandonChange extends Handler<ChangeDetail> {
@@ -78,60 +72,15 @@
@Override
public ChangeDetail call() throws NoSuchChangeException, OrmException,
EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
+
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
if (!control.canAbandon()) {
throw new NoSuchChangeException(changeId);
}
- Change change = control.getChange();
- final PatchSet patch = db.patchSets().get(patchSetId);
- if (patch == null) {
- throw new NoSuchChangeException(changeId);
- }
- final ChangeMessage cmsg =
- new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
- .messageUUID(db)), currentUser.getAccountId());
- final StringBuilder msgBuf =
- new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
- if (message != null && message.length() > 0) {
- msgBuf.append("\n\n");
- msgBuf.append(message);
- }
- cmsg.setMessage(msgBuf.toString());
-
- change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isOpen()
- && change.currentPatchSetId().equals(patchSetId)) {
- change.setStatus(Change.Status.ABANDONED);
- ChangeUtil.updated(change);
- return change;
- } else {
- return null;
- }
- }
- });
-
- if (change != null) {
- db.changeMessages().insert(Collections.singleton(cmsg));
-
- final List<PatchSetApproval> approvals =
- db.patchSetApprovals().byChange(changeId).toList();
- for (PatchSetApproval a : approvals) {
- a.cache(change);
- }
- db.patchSetApprovals().update(approvals);
-
- // Email the reviewers
- final AbandonedSender cm = abandonedSenderFactory.create(change);
- cm.setFrom(currentUser.getAccountId());
- cm.setChangeMessage(cmsg);
- cm.send();
- }
-
- hooks.doChangeAbandonedHook(change, currentUser.getAccount(), message);
+ ChangeUtil.abandon(patchSetId, currentUser, message, db,
+ abandonedSenderFactory, hooks);
return changeDetailFactory.create(changeId).call();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
index 7d6f764..398ca7e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
@@ -77,7 +77,7 @@
final PatchSet patch =
db.patchSets().get(control.getChange().currentPatchSetId());
final Repository repo =
- repoManager.openRepository(control.getProject().getName());
+ repoManager.openRepository(control.getProject().getNameKey());
try {
final RevWalk rw = new RevWalk(repo);
try {
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 9639f1a..4a67260 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
@@ -73,59 +73,15 @@
@Override
public ChangeDetail call() throws NoSuchChangeException, OrmException,
EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
+
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
if (!control.canRestore()) {
throw new NoSuchChangeException(changeId);
}
- final PatchSet patch = db.patchSets().get(patchSetId);
- if (patch == null) {
- throw new NoSuchChangeException(changeId);
- }
- final ChangeMessage cmsg =
- new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
- .messageUUID(db)), currentUser.getAccountId());
- final StringBuilder msgBuf =
- new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
- if (message != null && message.length() > 0) {
- msgBuf.append("\n\n");
- msgBuf.append(message);
- }
- cmsg.setMessage(msgBuf.toString());
-
- Change change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus() == Change.Status.ABANDONED
- && change.currentPatchSetId().equals(patchSetId)) {
- change.setStatus(Change.Status.NEW);
- ChangeUtil.updated(change);
- return change;
- } else {
- return null;
- }
- }
- });
-
- if (change != null) {
- db.changeMessages().insert(Collections.singleton(cmsg));
-
- final List<PatchSetApproval> approvals =
- db.patchSetApprovals().byChange(changeId).toList();
- for (PatchSetApproval a : approvals) {
- a.cache(change);
- }
- db.patchSetApprovals().update(approvals);
-
- // Email the reviewers
- final AbandonedSender cm = abandonedSenderFactory.create(change);
- cm.setFrom(currentUser.getAccountId());
- cm.setChangeMessage(cmsg);
- cm.send();
- }
-
- hooks.doChangeRestoreHook(change, currentUser.getAccount(), message);
+ ChangeUtil.restore(patchSetId, currentUser, message, db,
+ abandonedSenderFactory, hooks);
return changeDetailFactory.create(changeId).call();
}
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 4ab9072..721656c 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
@@ -81,7 +81,7 @@
CanSubmitResult err =
changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
if (err == CanSubmitResult.OK) {
- ChangeUtil.submit(opFactory, patchSetId, user, db, merger);
+ ChangeUtil.submit(patchSetId, user, db, opFactory, merger);
return changeDetailFactory.create(changeId).call();
} else {
throw new IllegalStateException(err.getMessage());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
index 30d7716..302135e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
@@ -20,12 +20,14 @@
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.AccountDiffPreference;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
-import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.patch.IntraLineDiff;
+import com.google.gerrit.server.patch.IntraLineDiffKey;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.Text;
@@ -65,6 +67,7 @@
};
private Repository db;
+ private Project.NameKey projectKey;
private ObjectReader reader;
private Change change;
private AccountDiffPreference diffPrefs;
@@ -88,8 +91,9 @@
patchListCache = plc;
}
- void setRepository(final Repository r) {
- db = r;
+ void setRepository(Repository r, Project.NameKey projectKey) {
+ this.db = r;
+ this.projectKey = projectKey;
}
void setChange(final Change c) {
@@ -127,7 +131,8 @@
private PatchScript build(final PatchListEntry content,
final CommentDetail comments, final List<Patch> history)
throws IOException {
- boolean intralineDifference = diffPrefs.isIntralineDifference();
+ boolean intralineDifferenceIsPossible = true;
+ boolean intralineFailure = false;
a.path = oldName(content);
b.path = newName(content);
@@ -137,16 +142,31 @@
edits = new ArrayList<Edit>(content.getEdits());
- if (intralineDifference) {
- if (isModify(content)) {
- IntraLineDiff d = patchListCache.get(a.id, a.src, b.id, b.src, edits);
- if (d != null) {
- edits = new ArrayList<Edit>(d.getEdits());
- } else {
- intralineDifference = false;
+ if (!isModify(content)) {
+ intralineDifferenceIsPossible = false;
+ } else if (diffPrefs.isIntralineDifference()) {
+ IntraLineDiff d =
+ patchListCache.getIntraLineDiff(new IntraLineDiffKey(a.id, a.src,
+ b.id, b.src, edits, projectKey, bId, b.path));
+ if (d != null) {
+ switch (d.getStatus()) {
+ case EDIT_LIST:
+ edits = new ArrayList<Edit>(d.getEdits());
+ break;
+
+ case DISABLED:
+ intralineDifferenceIsPossible = false;
+ break;
+
+ case ERROR:
+ case TIMEOUT:
+ intralineDifferenceIsPossible = false;
+ intralineFailure = true;
+ break;
}
} else {
- intralineDifference = false;
+ intralineDifferenceIsPossible = false;
+ intralineFailure = true;
}
}
@@ -187,10 +207,11 @@
packContent(diffPrefs.getIgnoreWhitespace() != Whitespace.IGNORE_NONE);
}
- return new PatchScript(change.getKey(), content.getChangeType(), content
- .getOldName(), content.getNewName(), a.fileMode, b.fileMode, content
- .getHeaderLines(), diffPrefs, a.dst, b.dst, edits, a.displayMethod,
- b.displayMethod, comments, history, hugeFile, intralineDifference);
+ return new PatchScript(change.getKey(), content.getChangeType(),
+ content.getOldName(), content.getNewName(), a.fileMode, b.fileMode,
+ content.getHeaderLines(), diffPrefs, a.dst, b.dst, edits,
+ a.displayMethod, b.displayMethod, comments, history, hugeFile,
+ intralineDifferenceIsPossible, intralineFailure);
}
private static boolean isModify(PatchListEntry content) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index 377cf49..9ad8e3a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -137,7 +137,7 @@
final Repository git;
try {
- git = repoManager.openRepository(projectKey.get());
+ git = repoManager.openRepository(projectKey);
} catch (RepositoryNotFoundException e) {
log.error("Repository " + projectKey + " not found", e);
throw new NoSuchChangeException(changeId, e);
@@ -173,7 +173,7 @@
private PatchScriptBuilder newBuilder(final PatchList list, Repository git) {
final AccountDiffPreference dp = new AccountDiffPreference(diffPrefs);
final PatchScriptBuilder b = builderFactory.get();
- b.setRepository(git);
+ b.setRepository(git, projectKey);
b.setChange(change);
b.setDiffPrefs(dp);
b.setTrees(list.isAgainstParent(), list.getOldId(), list.getNewId());
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
index 90e038d..342a674 100644
--- 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
@@ -57,7 +57,6 @@
@Override
public ReviewerResult call() throws Exception {
ReviewerResult result = new ReviewerResult();
- List<Account.Id> accounts = new ArrayList<Account.Id>();
ChangeControl ctl = changeControlFactory.validateFor(changeId);
boolean permitted = true;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
index d4698c2..7cef016 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ReceiveCommits;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@@ -90,7 +91,8 @@
@Override
public ListBranchesResult call() throws NoSuchProjectException,
- InvalidNameException, InvalidRevisionException, IOException {
+ InvalidNameException, InvalidRevisionException, IOException,
+ BranchCreationNotAllowedException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
@@ -104,10 +106,13 @@
if (!Repository.isValidRefName(refname)) {
throw new InvalidNameException();
}
+ if (refname.startsWith(ReceiveCommits.NEW_CHANGE)) {
+ throw new BranchCreationNotAllowedException(ReceiveCommits.NEW_CHANGE);
+ }
final Branch.NameKey name = new Branch.NameKey(projectName, refname);
final RefControl refControl = projectControl.controlForRef(name);
- final Repository repo = repoManager.openRepository(projectName.get());
+ final Repository repo = repoManager.openRepository(projectName);
try {
final ObjectId revid = parseStartingRevision(repo);
final RevWalk rw = verifyConnected(repo, revid);
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
index 448fc9d..358b542 100644
--- 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
@@ -18,6 +18,7 @@
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;
@@ -25,7 +26,6 @@
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
import com.google.gerrit.server.project.ProjectCache;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/BranchCreationNotAllowedException.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/BranchCreationNotAllowedException.java
new file mode 100644
index 0000000..98cdc32
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/BranchCreationNotAllowedException.java
@@ -0,0 +1,26 @@
+// 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;
+
+public class BranchCreationNotAllowedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final String MESSAGE = "Branch creation is not allowed under: ";
+
+ public BranchCreationNotAllowedException(final String refnamePrefix) {
+ super(MESSAGE + refnamePrefix);
+ }
+}
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 8831e90..88c85b8f 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
@@ -73,7 +73,7 @@
projectCache.evict(proj);
if (!projectControl.getProjectState().isSpecialWildProject()) {
- repoManager.setProjectDescription(projectName.get(), update.getDescription());
+ repoManager.setProjectDescription(projectName, update.getDescription());
}
return projectDetailFactory.create(projectName).call();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
index fffc126..d3eae24 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
@@ -88,7 +88,7 @@
}
final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
- final Repository r = repoManager.openRepository(projectName.get());
+ final Repository r = repoManager.openRepository(projectName);
try {
for (final Branch.NameKey branchKey : toRemove) {
final String refname = branchKey.get();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
index 7b22de0..6e12662 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
@@ -70,7 +70,7 @@
final Repository db;
try {
- db = repoManager.openRepository(projectName.get());
+ db = repoManager.openRepository(projectName);
} catch (RepositoryNotFoundException noGitRepository) {
return new ListBranchesResult(branches, false, true);
}
@@ -106,7 +106,7 @@
target = target.substring(Constants.R_HEADS.length());
}
- Branch b = createBranch(Constants.HEAD);
+ Branch b = createBranch(ref.getName());
b.setRevision(new RevId(target));
b.setCanDelete(targetRefControl.canDelete());
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
index e05f811..bfc2de1 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
@@ -23,6 +23,11 @@
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.reviewdb.Branch;
@@ -42,6 +47,8 @@
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
+import org.junit.Before;
+import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
@@ -64,7 +71,8 @@
private List<RefControl> refMocks;
@Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() throws Exception {
super.setUp();
idA = ObjectId.fromString("df84c2f4f7ce7e0b25cdeac84b8870bcff319885");
@@ -108,6 +116,7 @@
}
}
+ @Test
public void testProjectNotVisible() throws Exception {
final NoSuchProjectException err = new NoSuchProjectException(name);
validate().andThrow(err);
@@ -128,7 +137,7 @@
validate().andReturn(pc);
- expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
+ expect(grm.openRepository(eq(name))).andReturn(mockDb);
expect(mockDb.getAllRefs()).andDelegateTo(realDb);
if (getHead) {
expect(mockDb.getRef(HEAD)).andDelegateTo(realDb);
@@ -170,6 +179,7 @@
}
}
+ @Test
public void testEmptyProject() throws Exception {
ListBranchesResult r = permitted(true);
@@ -189,6 +199,7 @@
assertEquals("master", b.getRevision().get());
}
+ @Test
public void testMasterBranch() throws Exception {
set("master", idA);
@@ -222,6 +233,7 @@
assertEquals(idA.name(), b.getRevision().get());
}
+ @Test
public void testBranchNotHead() throws Exception {
set("foo", idA);
@@ -255,6 +267,7 @@
assertEquals(idA.name(), b.getRevision().get());
}
+ @Test
public void testSortByName() throws Exception {
Map<String, Ref> u = new LinkedHashMap<String, Ref>();
u.put("foo", new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "foo", idA));
@@ -263,7 +276,7 @@
+ "master", null)));
validate().andReturn(pc);
- expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
+ expect(grm.openRepository(eq(name))).andReturn(mockDb);
expect(mockDb.getAllRefs()).andReturn(u);
for (Ref ref : u.values()) {
assumeVisible(ref, true);
@@ -283,6 +296,7 @@
assertEquals("foo", r.getBranches().get(2).getShortName());
}
+ @Test
public void testHeadNotVisible() throws Exception {
ObjectIdRef.Unpeeled bar =
new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA);
@@ -291,7 +305,7 @@
u.put(HEAD, new SymbolicRef(HEAD, bar));
validate().andReturn(pc);
- expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
+ expect(grm.openRepository(eq(name))).andReturn(mockDb);
expect(mockDb.getAllRefs()).andReturn(u);
assumeVisible(bar, false);
assumeVisible(bar, false);
@@ -306,6 +320,7 @@
assertTrue(r.getBranches().isEmpty());
}
+ @Test
public void testHeadVisibleButBranchHidden() throws Exception {
ObjectIdRef.Unpeeled bar =
new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA);
@@ -318,7 +333,7 @@
u.put(foo.getName(), foo);
validate().andReturn(pc);
- expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
+ expect(grm.openRepository(eq(name))).andReturn(mockDb);
expect(mockDb.getAllRefs()).andReturn(u);
assumeVisible(bar, true);
assumeVisible(bar, true);
diff --git a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
index 4db7de6..7d7bc49 100644
--- a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
+++ b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
@@ -103,10 +103,12 @@
if (types.contains("CRAM-MD5")) {
return authCram(smtpUser, smtpPass, "MD5");
}
+ if (types.contains("LOGIN")) {
+ return authLogin(smtpUser, smtpPass);
+ }
if (types.contains("PLAIN")) {
return authPlain(smtpUser, smtpPass);
}
-
throw new IOException("Unsupported AUTH: " + authTypes);
}
@@ -135,6 +137,21 @@
return SMTPReply.isPositiveCompletion(sendCommand(cmd));
}
+ private boolean authLogin(String smtpUser, String smtpPass) throws UnsupportedEncodingException,
+ IOException {
+ if (sendCommand("AUTH", "LOGIN") != 334) {
+ return false;
+ }
+
+ String cmd = encodeBase64(smtpUser.getBytes(UTF_8));
+ if(sendCommand(cmd) != 334) {
+ return false;
+ }
+
+ cmd = encodeBase64(smtpPass.getBytes(UTF_8));
+ return SMTPReply.isPositiveCompletion(sendCommand(cmd));
+ }
+
private static final char[] hexchar =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
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 3e10336..8c6879d 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
@@ -237,8 +237,8 @@
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<Module>();
- modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class));
modules.add(sshInjector.getInstance(WebModule.class));
+ modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class));
return sysInjector.createChildInjector(modules);
}
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 16c72d4..beeed24 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
@@ -116,7 +116,7 @@
final Project.NameKey project = change.getDest().getParentKey();
final Repository git;
try {
- git = gitManager.openRepository(project.get());
+ git = gitManager.openRepository(project);
} catch (RepositoryNotFoundException e) {
return;
}
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 6adab03..eb26b09 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
@@ -269,8 +269,11 @@
echo "** INFO: Using $JAVA"
fi
-if test -z "$JAVA" -a -n "$JAVA_HOME" ; then
- test -x $JAVA_HOME/bin/java -a ! -d $JAVA_HOME/bin/java && JAVA=$JAVA_HOME/bin/java
+if test -z "$JAVA" \
+ -a -n "$JAVA_HOME" \
+ -a -x "$JAVA_HOME/bin/java" \
+ -a ! -d "$JAVA_HOME/bin/java" ; then
+ JAVA="$JAVA_HOME/bin/java"
fi
if test -z "$JAVA" ; then
@@ -354,10 +357,11 @@
# If possible, use Perl to mask the name of the process so its
# something specific to us rather than the generic 'java' name.
#
+ export JAVA
RUN_EXEC=/usr/bin/perl
RUN_Arg1=-e
- RUN_Arg2='$x=shift @ARGV;exec $x @ARGV;die $!'
- RUN_Arg3="-- $JAVA GerritCodeReview"
+ RUN_Arg2='$x=$ENV{JAVA};exec $x @ARGV;die $!'
+ RUN_Arg3='-- GerritCodeReview'
else
RUN_EXEC=$JAVA
RUN_Arg1=
@@ -425,6 +429,17 @@
fi
fi
+ if test $UID = 0; then
+ PID=`cat "$GERRIT_PID"`
+ if test -f "/proc/${PID}/oom_score_adj" ; then
+ echo -1000 > "/proc/${PID}/oom_score_adj"
+ else
+ if test -f "/proc/${PID}/oom_adj" ; then
+ echo -16 > "/proc/${PID}/oom_adj"
+ fi
+ fi
+ fi
+
TIMEOUT=90 # seconds
sleep 1
while running "$GERRIT_PID" && test $TIMEOUT -gt 0 ; do
@@ -522,8 +537,10 @@
echo
if test -f "$GERRIT_PID" ; then
- echo "Gerrit running pid="`cat "$GERRIT_PID"`
- exit 0
+ if running "$GERRIT_PID" ; then
+ echo "Gerrit running pid="`cat "$GERRIT_PID"`
+ exit 0
+ fi
fi
exit 1
;;
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
index 00185581..493a440 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
@@ -19,6 +19,10 @@
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
@@ -27,6 +31,7 @@
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
+import org.junit.Test;
import java.io.File;
import java.io.FileWriter;
@@ -35,6 +40,8 @@
public class UpgradeFrom2_0_xTest extends InitTestCase {
+
+ @Test
public void testUpgrade() throws IOException, ConfigInvalidException {
final File p = newSitePath();
final SitePaths site = new SitePaths(p);
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
index 43b7b17..be2cb20 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
@@ -69,9 +69,13 @@
/** Regular expression that {@link #userName} must match. */
public static final String USER_NAME_PATTERN = "^" + //
+ "(" + //
USER_NAME_PATTERN_FIRST + //
USER_NAME_PATTERN_REST + "*" + //
USER_NAME_PATTERN_LAST + //
+ "|" + //
+ USER_NAME_PATTERN_FIRST + //
+ ")" + //
"$";
/** Key local to Gerrit to identify a user. */
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
index 6713d8f..c18ae82 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
@@ -21,8 +21,8 @@
/** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch {
- public enum Type {
- NEW_CHANGES, SUBMITS, COMMENTS
+ public enum NotifyType {
+ NEW_CHANGES, ALL_COMMENTS, SUBMITTED_CHANGES
}
public static final String FILTER_ALL = "*";
@@ -124,46 +124,32 @@
return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
}
- public boolean isNotifyNewChanges() {
- return notifyNewChanges;
- }
+ public boolean isNotify(final NotifyType type) {
+ switch (type) {
+ case NEW_CHANGES:
+ return notifyNewChanges;
- public void setNotifyNewChanges(final boolean a) {
- notifyNewChanges = a;
- }
+ case ALL_COMMENTS:
+ return notifyAllComments;
- public boolean isNotifyAllComments() {
- return notifyAllComments;
- }
-
- public void setNotifyAllComments(final boolean a) {
- notifyAllComments = a;
- }
-
- public boolean isNotifySubmittedChanges() {
- return notifySubmittedChanges;
- }
-
- public void setNotifySubmittedChanges(final boolean a) {
- notifySubmittedChanges = a;
- }
-
- public boolean isNotify(final Type type) {
- switch(type) {
- case NEW_CHANGES: return notifySubmittedChanges;
- case SUBMITS: return notifyNewChanges;
- case COMMENTS: return notifyAllComments;
+ case SUBMITTED_CHANGES:
+ return notifySubmittedChanges;
}
return false;
}
- public void setNotify(final Type type, final boolean v) {
- switch(type) {
- case NEW_CHANGES: notifySubmittedChanges = v;
+ public void setNotify(final NotifyType type, final boolean v) {
+ switch (type) {
+ case NEW_CHANGES:
+ notifyNewChanges = v;
break;
- case SUBMITS: notifyNewChanges = v;
+
+ case ALL_COMMENTS:
+ notifyAllComments = v;
break;
- case COMMENTS: notifyAllComments = v;
+
+ case SUBMITTED_CHANGES:
+ notifySubmittedChanges = v;
break;
}
}
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 a8946b5..29ea97e 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
@@ -74,12 +74,13 @@
id = newValue;
}
- /** True if the right can inherit from the magical "-- All Projects --". */
- public boolean canInheritFromWildProject() {
+ /** True if the right can be assigned on the wild project. */
+ public boolean canBeOnWildProject() {
if (OWN.equals(this)) {
return false;
+ } else {
+ return true;
}
- return true;
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java
index 7df7619..d59e492 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/TrackingId.java
@@ -144,7 +144,7 @@
public boolean equals(final Object obj) {
if (obj instanceof TrackingId) {
final TrackingId tr = (TrackingId) obj;
- return tr.key.equals(tr.key);
+ return key.equals(tr.key);
}
return false;
}
diff --git a/gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g b/gerrit-server/src/main/antlr3/com/google/gerrit/server/query/Query.g
similarity index 100%
rename from gerrit-server/src/main/antlr/com/google/gerrit/server/query/Query.g
rename to gerrit-server/src/main/antlr3/com/google/gerrit/server/query/Query.g
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 c7c51b6..1a4a863 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
@@ -191,7 +191,7 @@
*/
private Repository openRepository(final Project.NameKey name) {
try {
- return repoManager.openRepository(name.get());
+ return repoManager.openRepository(name);
} catch (RepositoryNotFoundException err) {
log.warn("Cannot open repository " + name.get(), err);
return null;
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 36fc2ff..11ab9e0 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
@@ -16,19 +16,28 @@
import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT;
+import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.TrackingId;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.TrackingFooter;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.mail.AbandonedSender;
+import com.google.gerrit.server.mail.EmailException;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmConcurrencyException;
import com.google.gwtorm.client.OrmException;
+
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.util.Base64;
import org.eclipse.jgit.util.NB;
@@ -136,14 +145,17 @@
db.trackingIds().delete(toDelete);
}
- public static void submit(MergeOp.Factory opFactory, PatchSet.Id patchSetId,
- IdentifiedUser user, ReviewDb db, MergeQueue merger) throws OrmException {
+ public static void submit(final PatchSet.Id patchSetId,
+ final IdentifiedUser user, final ReviewDb db,
+ final MergeOp.Factory opFactory, final MergeQueue merger)
+ throws OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final PatchSetApproval approval = createSubmitApproval(patchSetId, user, db);
db.patchSetApprovals().upsert(Collections.singleton(approval));
- final Change change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
+ final Change updatedChange = db.changes().atomicUpdate(changeId,
+ new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus() == Change.Status.NEW) {
@@ -154,13 +166,14 @@
}
});
- if (change.getStatus() == Change.Status.SUBMITTED) {
- merger.merge(opFactory, change.getDest());
+ if (updatedChange.getStatus() == Change.Status.SUBMITTED) {
+ merger.merge(opFactory, updatedChange.getDest());
}
}
- public static PatchSetApproval createSubmitApproval(PatchSet.Id patchSetId, IdentifiedUser user, ReviewDb db)
- throws OrmException {
+ public static PatchSetApproval createSubmitApproval(
+ final PatchSet.Id patchSetId, final IdentifiedUser user, final ReviewDb db
+ ) throws OrmException {
final List<PatchSetApproval> allApprovals =
new ArrayList<PatchSetApproval>(db.patchSetApprovals().byPatchSet(
patchSetId).toList());
@@ -178,6 +191,120 @@
return new PatchSetApproval(akey, (short) 1);
}
+ public static void abandon(final PatchSet.Id patchSetId,
+ final IdentifiedUser user, final String message, final ReviewDb db,
+ final AbandonedSender.Factory abandonedSenderFactory,
+ final ChangeHookRunner hooks) throws NoSuchChangeException,
+ EmailException, OrmException {
+ final Change.Id changeId = patchSetId.getParentKey();
+ final PatchSet patch = db.patchSets().get(patchSetId);
+ if (patch == null) {
+ throw new NoSuchChangeException(changeId);
+ }
+
+ final ChangeMessage cmsg =
+ new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
+ .messageUUID(db)), user.getAccountId());
+ final StringBuilder msgBuf =
+ new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
+ if (message != null && message.length() > 0) {
+ msgBuf.append("\n\n");
+ msgBuf.append(message);
+ }
+ cmsg.setMessage(msgBuf.toString());
+
+ final Change updatedChange = db.changes().atomicUpdate(changeId,
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus().isOpen()
+ && change.currentPatchSetId().equals(patchSetId)) {
+ change.setStatus(Change.Status.ABANDONED);
+ ChangeUtil.updated(change);
+ return change;
+ } else {
+ return null;
+ }
+ }
+ });
+
+ if (updatedChange != null) {
+ db.changeMessages().insert(Collections.singleton(cmsg));
+
+ 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();
+ }
+
+ hooks.doChangeAbandonedHook(updatedChange, user.getAccount(), message);
+ }
+
+ public static void restore(final PatchSet.Id patchSetId,
+ final IdentifiedUser user, final String message, final ReviewDb db,
+ final AbandonedSender.Factory abandonedSenderFactory,
+ final ChangeHookRunner hooks) throws NoSuchChangeException,
+ EmailException, OrmException {
+ final Change.Id changeId = patchSetId.getParentKey();
+ final PatchSet patch = db.patchSets().get(patchSetId);
+ if (patch == null) {
+ throw new NoSuchChangeException(changeId);
+ }
+
+ final ChangeMessage cmsg =
+ new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
+ .messageUUID(db)), user.getAccountId());
+ final StringBuilder msgBuf =
+ new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
+ if (message != null && message.length() > 0) {
+ msgBuf.append("\n\n");
+ msgBuf.append(message);
+ }
+ cmsg.setMessage(msgBuf.toString());
+
+ final Change updatedChange = db.changes().atomicUpdate(changeId,
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus() == Change.Status.ABANDONED
+ && change.currentPatchSetId().equals(patchSetId)) {
+ change.setStatus(Change.Status.NEW);
+ ChangeUtil.updated(change);
+ return change;
+ } else {
+ return null;
+ }
+ }
+ });
+
+ if (updatedChange != null) {
+ db.changeMessages().insert(Collections.singleton(cmsg));
+
+ 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();
+ }
+
+ hooks.doChangeRestoreHook(updatedChange, user.getAccount(), message);
+ }
+
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/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
index 40360b9..fb98e26 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.server.CurrentUser;
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
new file mode 100644
index 0000000..9e7c264
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
@@ -0,0 +1,118 @@
+// 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.errors.NameAlreadyUsedException;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountGroupMember;
+import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.AccountGroupName;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gwtorm.client.OrmDuplicateKeyException;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PerformCreateGroup {
+
+ public interface Factory {
+ PerformCreateGroup create();
+ }
+
+ private final ReviewDb db;
+ private final AccountCache accountCache;
+ private final IdentifiedUser currentUser;
+
+ @Inject
+ PerformCreateGroup(final ReviewDb db, final AccountCache accountCache,
+ final IdentifiedUser currentUser) {
+ this.db = db;
+ this.accountCache = accountCache;
+ this.currentUser = currentUser;
+ }
+
+ /**
+ * Creates a new group.
+ *
+ * @param groupName the name for the new group
+ * @param groupDescription the description of the new group, <code>null</code>
+ * if no description
+ * @param ownerGroupId the group that should own the new group, if
+ * <code>null</code> the new group will own itself
+ * @param initialMembers initial members to be added to the new group
+ * @return the id of the new group
+ * @throws OrmException is thrown in case of any data store read or write
+ * error
+ * @throws NameAlreadyUsedException is thrown in case a group with the given
+ * name already exists
+ */
+ public AccountGroup.Id createGroup(final String groupName,
+ final String groupDescription, final AccountGroup.Id ownerGroupId,
+ final Account.Id... initialMembers) throws OrmException,
+ NameAlreadyUsedException {
+ final AccountGroup.Id groupId =
+ new AccountGroup.Id(db.nextAccountGroupId());
+ final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
+ final AccountGroup group = new AccountGroup(nameKey, groupId);
+ if (ownerGroupId != null) {
+ group.setOwnerGroupId(ownerGroupId);
+ }
+ if (groupDescription != null) {
+ group.setDescription(groupDescription);
+ }
+ final AccountGroupName gn = new AccountGroupName(group);
+ // first insert the group name to validate that the group name hasn't
+ // already been used to create another group
+ try {
+ db.accountGroupNames().insert(Collections.singleton(gn));
+ } catch (OrmDuplicateKeyException e) {
+ throw new NameAlreadyUsedException();
+ }
+ db.accountGroups().insert(Collections.singleton(group));
+
+ addMembers(groupId, initialMembers);
+
+ return groupId;
+ }
+
+ private void addMembers(final AccountGroup.Id groupId,
+ final Account.Id... members) throws OrmException {
+ final List<AccountGroupMember> memberships =
+ new ArrayList<AccountGroupMember>();
+ final List<AccountGroupMemberAudit> membershipsAudit =
+ new ArrayList<AccountGroupMemberAudit>();
+ for (Account.Id accountId : members) {
+ final AccountGroupMember membership =
+ new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId));
+ memberships.add(membership);
+
+ final AccountGroupMemberAudit audit =
+ new AccountGroupMemberAudit(membership, currentUser.getAccountId());
+ membershipsAudit.add(audit);
+ }
+ db.accountGroupMembers().insert(memberships);
+ db.accountGroupMembersAudit().insert(membershipsAudit);
+
+ for (Account.Id accountId : members) {
+ accountCache.evict(accountId);
+ }
+ }
+
+}
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 6396431..16a6a7c 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
@@ -41,6 +41,8 @@
private final String logoutUrl;
private final List<OpenIdProviderPattern> trustedOpenIDs;
private final List<OpenIdProviderPattern> allowedOpenIDs;
+ private final String cookiePath;
+ private final boolean cookieSecure;
private final SignedToken emailReg;
private final AccountGroup.Id administratorGroup;
@@ -58,6 +60,8 @@
logoutUrl = cfg.getString("auth", null, "logouturl");
trustedOpenIDs = toPatterns(cfg, "trustedOpenID");
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);
@@ -106,6 +110,14 @@
return logoutUrl;
}
+ public String getCookiePath() {
+ return cookiePath;
+ }
+
+ public boolean getCookieSecure() {
+ return cookieSecure;
+ }
+
public SignedToken getEmailRegistrationToken() {
return emailReg;
}
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 5a75995..15e2665 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
@@ -21,6 +21,7 @@
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.ReceiveCommits;
import com.google.gerrit.server.mail.AbandonedSender;
@@ -69,5 +70,6 @@
factory(MergedSender.Factory.class);
factory(MergeFailSender.Factory.class);
factory(RegisterNewEmailSender.Factory.class);
+ factory(PerformCreateGroup.Factory.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
index 33661ab..3f2ff0d 100644
--- 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
@@ -79,6 +79,13 @@
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")) {
@@ -98,7 +105,7 @@
final Project.NameKey nameKey = new Project.NameKey(name);
final Project p = new Project(nameKey);
- p.setDescription(repositoryManager.getProjectDescription(name));
+ p.setDescription(repositoryManager.getProjectDescription(nameKey));
p.setSubmitType(SubmitType.MERGE_IF_NECESSARY);
p.setUseContributorAgreements(false);
p.setUseSignedOffBy(false);
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 369602a..701716d 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
@@ -14,6 +14,7 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.reviewdb.Project;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -30,6 +31,9 @@
* environment.
*/
public interface GitRepositoryManager {
+ /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
+ public static final String REF_REJECT_COMMITS = "refs/meta/reject-commits";
+
/**
* Get (or open) a repository by name.
*
@@ -39,7 +43,7 @@
* @throws RepositoryNotFoundException the name does not denote an existing
* repository, or the name cannot be read as a repository.
*/
- public abstract Repository openRepository(String name)
+ public abstract Repository openRepository(Project.NameKey name)
throws RepositoryNotFoundException;
/**
@@ -51,7 +55,7 @@
* @throws RepositoryNotFoundException the name does not denote an existing
* repository, or the name cannot be read as a repository.
*/
- public abstract Repository createRepository(String name)
+ public abstract Repository createRepository(Project.NameKey name)
throws RepositoryNotFoundException;
/**
@@ -66,7 +70,7 @@
* @throws IOException the description file exists, but is not readable by
* this process.
*/
- public abstract String getProjectDescription(final String name)
+ public abstract String getProjectDescription(Project.NameKey name)
throws RepositoryNotFoundException, IOException;
/**
@@ -78,6 +82,6 @@
* @param name the repository name, relative to the base directory.
* @param description new description text for the repository.
*/
- public abstract void setProjectDescription(final String name,
+ public abstract void setProjectDescription(Project.NameKey name,
final String description);
}
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 c644ca8..7e98348 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
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
@@ -84,14 +85,18 @@
return basePath;
}
- public Repository openRepository(String name)
+ private File gitDirOf(Project.NameKey name) {
+ return new File(getBasePath(), name.get());
+ }
+
+ public Repository openRepository(Project.NameKey name)
throws RepositoryNotFoundException {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
try {
- final FileKey loc = FileKey.lenient(new File(basePath, name), FS.DETECTED);
+ final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
return RepositoryCache.open(loc);
} catch (IOException e1) {
final RepositoryNotFoundException e2;
@@ -101,14 +106,14 @@
}
}
- public Repository createRepository(String name)
+ public Repository createRepository(final Project.NameKey name)
throws RepositoryNotFoundException {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
try {
- File dir = FileKey.resolve(new File(basePath, name), FS.DETECTED);
+ File dir = FileKey.resolve(gitDirOf(name), FS.DETECTED);
FileKey loc;
if (dir != null) {
// Already exists on disk, use the repository we found.
@@ -118,10 +123,11 @@
// It doesn't exist under any of the standard permutations
// of the repository name, so prefer the standard bare name.
//
- if (!name.endsWith(".git")) {
- name = name + ".git";
+ String n = name.get();
+ if (!n.endsWith(Constants.DOT_GIT_EXT)) {
+ n = n + Constants.DOT_GIT_EXT;
}
- loc = FileKey.exact(new File(basePath, name), FS.DETECTED);
+ loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
}
return RepositoryCache.open(loc, false);
} catch (IOException e1) {
@@ -132,7 +138,7 @@
}
}
- public String getProjectDescription(final String name)
+ public String getProjectDescription(final Project.NameKey name)
throws RepositoryNotFoundException, IOException {
final Repository e = openRepository(name);
try {
@@ -160,7 +166,8 @@
}
}
- public void setProjectDescription(final String name, final String description) {
+ public void setProjectDescription(final Project.NameKey name,
+ final String description) {
// Update git's description file, in case gitweb is being used
//
try {
@@ -193,7 +200,9 @@
}
}
- private boolean isUnreasonableName(final String name) {
+ private boolean isUnreasonableName(final Project.NameKey nameKey) {
+ final String name = nameKey.get();
+
if (name.length() == 0) return true; // no empty paths
if (name.indexOf('\\') >= 0) return true; // no windows/dos stlye paths
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 2efb1f4..e402728 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
@@ -240,11 +240,11 @@
}
private void openRepository() throws MergeException {
- final String name = destBranch.getParentKey().get();
+ final Project.NameKey name = destBranch.getParentKey();
try {
db = repoManager.openRepository(name);
} catch (RepositoryNotFoundException notGit) {
- final String m = "Repository \"" + name + "\" unknown.";
+ final String m = "Repository \"" + name.get() + "\" unknown.";
throw new MergeException(m, notGit);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
index 1c100df..049e0e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
@@ -158,7 +158,7 @@
// since the canceled flag would be set locking the queue.
if (!canceled) {
try {
- db = repoManager.openRepository(projectName.get());
+ db = repoManager.openRepository(projectName);
runImpl();
} catch (RepositoryNotFoundException e) {
log.error("Cannot replicate " + projectName + "; " + e.getMessage());
@@ -296,7 +296,7 @@
return Collections.emptyList();
}
try {
- local = new VisibleRefFilter(db, pc, meta).filter(local);
+ local = new VisibleRefFilter(db, pc, meta, true).filter(local);
} finally {
meta.close();
}
@@ -313,7 +313,7 @@
if (dst == null || !src.getObjectId().equals(dst.getObjectId())) {
// Doesn't exist yet, or isn't the same value, request to push.
//
- send(cmds, spec);
+ send(cmds, spec, src);
}
}
}
@@ -335,8 +335,9 @@
if (spec != null) {
// If the ref still exists locally, send it, otherwise delete it.
//
- if (local.containsKey(src)) {
- send(cmds, spec);
+ Ref srcRef = local.get(src);
+ if (srcRef != null) {
+ send(cmds, spec, srcRef);
} else {
delete(cmds, spec);
}
@@ -375,9 +376,8 @@
return null;
}
- private void send(final List<RemoteRefUpdate> cmds, final RefSpec spec)
- throws IOException {
- final String src = spec.getSource();
+ private void send(final List<RemoteRefUpdate> cmds, final RefSpec spec,
+ final Ref src) throws IOException {
final String dst = spec.getDestination();
final boolean force = spec.isForceUpdate();
cmds.add(new RemoteRefUpdate(db, src, dst, force, null, null));
@@ -387,7 +387,7 @@
throws IOException {
final String dst = spec.getDestination();
final boolean force = spec.isForceUpdate();
- cmds.add(new RemoteRefUpdate(db, null, dst, force, null, null));
+ cmds.add(new RemoteRefUpdate(db, (Ref) null, dst, force, null, null));
}
@Override
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 3fe75ed..9db9e44 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
@@ -30,22 +30,21 @@
import com.google.inject.Singleton;
import com.google.inject.assistedinject.FactoryProvider;
-import com.jcraft.jsch.Channel;
-import com.jcraft.jsch.ChannelExec;
-import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.SshConfigSessionFactory;
+import org.eclipse.jgit.transport.RemoteSession;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.QuotedString;
+import org.eclipse.jgit.util.io.StreamCopyThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -69,7 +68,7 @@
// Install our own factory which always runs in batch mode, as we
// have no UI available for interactive prompting.
//
- SshSessionFactory.setInstance(new SshConfigSessionFactory() {
+ SshSessionFactory.setInstance(new JschConfigSessionFactory() {
@Override
protected void configure(OpenSshConfig.Host hc, Session session) {
// Default configuration is batch mode.
@@ -254,7 +253,7 @@
private void replicateProject(final URIish replicateURI, final String head) {
SshSessionFactory sshFactory = SshSessionFactory.getInstance();
- Session sshSession;
+ RemoteSession sshSession;
String projectPath = QuotedString.BOURNE.quote(replicateURI.getPath());
if (!usingSSH(replicateURI)) {
@@ -270,30 +269,22 @@
+ QuotedString.BOURNE.quote(head);
try {
- sshSession =
- sshFactory.getSession(replicateURI.getUser(), replicateURI.getPass(),
- replicateURI.getHost(), replicateURI.getPort(), FS.DETECTED);
- sshSession.connect();
-
- Channel channel = sshSession.openChannel("exec");
- ((ChannelExec) channel).setCommand(cmd);
-
- channel.setInputStream(null);
-
- ((ChannelExec) channel).setErrStream(errStream);
-
- channel.connect();
-
- while (!channel.isClosed()) {
- try {
- final int delay = 50;
- Thread.sleep(delay);
- } catch (InterruptedException e) {
- }
+ sshSession = sshFactory.getSession(replicateURI, null, FS.DETECTED, 0);
+ Process proc = sshSession.exec(cmd, 0);
+ proc.getOutputStream().close();
+ StreamCopyThread out = new StreamCopyThread(proc.getInputStream(), errStream);
+ StreamCopyThread err = new StreamCopyThread(proc.getErrorStream(), errStream);
+ out.start();
+ err.start();
+ try {
+ proc.waitFor();
+ out.halt();
+ err.halt();
+ } catch (InterruptedException interrupted) {
+ // Don't wait, drop out immediately.
}
- channel.disconnect();
sshSession.disconnect();
- } catch (JSchException e) {
+ } catch (IOException e) {
log.error("Communication error when trying to replicate to: "
+ replicateURI.toString() + "\n" + "Error reported: "
+ e.getMessage() + "\n" + "Error in communication: "
@@ -315,7 +306,7 @@
}
@Override
- public void write(final int b) throws IOException {
+ public synchronized void write(final int b) throws IOException {
if (b == '\r') {
return;
}
@@ -462,7 +453,8 @@
// It locks access to pending variable.
synchronized (pending) {
- PushOp pendingPushOp = pending.get(pushOp.getURI());
+ URIish uri = pushOp.getURI();
+ PushOp pendingPushOp = pending.get(uri);
if (pendingPushOp != null) {
// There is one PushOp instance already pending to same URI.
@@ -495,7 +487,7 @@
// pending list and it will not execute its run implementation.
pendingPushOp.cancel();
- pending.remove(pendingPushOp);
+ pending.remove(uri);
pushOp.addRefs(pendingPushOp.getRefs());
}
@@ -507,7 +499,7 @@
pushOp.setToRetry();
- pending.put(pushOp.getURI(), pushOp);
+ pending.put(uri, pushOp);
pool.schedule(pushOp, retryDelay, TimeUnit.MINUTES);
}
}
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 8b6f397..cf11c9b 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
@@ -65,6 +65,7 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -101,7 +102,7 @@
private static final Logger log =
LoggerFactory.getLogger(ReceiveCommits.class);
- private static final String NEW_CHANGE = "refs/for/";
+ public static final String NEW_CHANGE = "refs/for/";
private static final Pattern NEW_PATCHSET =
Pattern.compile("^refs/changes/(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
@@ -148,6 +149,7 @@
private final Project project;
private final Repository repo;
private final ReceivePack rp;
+ private final NoteMap rejectCommits;
private ReceiveCommand newChange;
private Branch.NameKey destBranch;
@@ -159,6 +161,7 @@
private final Map<RevCommit, ReplaceRequest> replaceByCommit =
new HashMap<RevCommit, ReplaceRequest>();
+ private Collection<ObjectId> existingObjects;
private Map<ObjectId, Ref> refsById;
private String destTopicName;
@@ -177,7 +180,7 @@
final TrackingFooters trackingFooters,
@Assisted final ProjectControl projectControl,
- @Assisted final Repository repo) {
+ @Assisted final Repository repo) throws IOException {
this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
this.db = db;
this.approvalTypes = approvalTypes;
@@ -196,6 +199,7 @@
this.project = projectControl.getProject();
this.repo = repo;
this.rp = new ReceivePack(repo);
+ this.rejectCommits = loadRejectCommitsMap();
rp.setAllowCreates(true);
rp.setAllowDeletes(true);
@@ -204,8 +208,9 @@
if (!projectControl.allRefsAreVisible()) {
rp.setCheckReferencedObjectsAreReachable(true);
- rp.setRefFilter(new VisibleRefFilter(repo, projectControl, db));
+ rp.setRefFilter(new VisibleRefFilter(repo, projectControl, db, false));
}
+ rp.setRefFilter(new ReceiveCommitsRefFilter(rp.getRefFilter()));
rp.setPreReceiveHook(this);
rp.setPostReceiveHook(this);
@@ -226,6 +231,80 @@
return rp;
}
+ /** Scan part of history and include it in the advertisement. */
+ public void advertiseHistory() {
+ Set<ObjectId> toInclude = new HashSet<ObjectId>();
+
+ // Advertise some recent open changes, in case a commit is based one.
+ try {
+ Set<PatchSet.Id> toGet = new HashSet<PatchSet.Id>();
+ for (Change change : db.changes()
+ .byProjectOpenNext(project.getNameKey(), "z", 32)) {
+ PatchSet.Id id = change.currentPatchSetId();
+ if (id != null) {
+ toGet.add(id);
+ }
+ }
+ for (PatchSet ps : db.patchSets().get(toGet)) {
+ if (ps.getRevision() != null && ps.getRevision().get() != null) {
+ toInclude.add(ObjectId.fromString(ps.getRevision().get()));
+ }
+ }
+ } catch (OrmException err) {
+ log.error("Cannot list open changes of " + project.getNameKey(), err);
+ }
+
+ // Size of an additional ".have" line.
+ final int haveLineLen = 4 + Constants.OBJECT_ID_STRING_LENGTH + 1 + 5 + 1;
+
+ // Maximum number of bytes to "waste" in the advertisement with
+ // a peek at this repository's current reachable history.
+ final int maxExtraSize = 8192;
+
+ // Number of recent commits to advertise immediately, hoping to
+ // show a client a nearby merge base.
+ final int base = 64;
+
+ // Number of commits to skip once base has already been shown.
+ final int step = 16;
+
+ // Total number of commits to extract from the history.
+ final int max = maxExtraSize / haveLineLen;
+
+ // Scan history until the advertisement is full.
+ Set<ObjectId> alreadySending = rp.getAdvertisedObjects();
+ RevWalk rw = rp.getRevWalk();
+ for (ObjectId haveId : alreadySending) {
+ try {
+ rw.markStart(rw.parseCommit(haveId));
+ } catch (IOException badCommit) {
+ continue;
+ }
+ }
+
+ int stepCnt = 0;
+ RevCommit c;
+ try {
+ while ((c = rw.next()) != null && toInclude.size() < max) {
+ if (alreadySending.contains(c)) {
+ } else if (toInclude.contains(c)) {
+ } else if (c.getParentCount() > 1) {
+ } else if (toInclude.size() < base) {
+ toInclude.add(c);
+ } else {
+ stepCnt = ++stepCnt % step;
+ if (stepCnt == 0) {
+ toInclude.add(c);
+ }
+ }
+ }
+ } catch (IOException err) {
+ log.error("Error trying to advertise history on " + project.getNameKey(), err);
+ }
+ rw.reset();
+ rp.getAdvertisedObjects().addAll(toInclude);
+ }
+
/** Determine if the user can upload commits. */
public Capable canUpload() {
if (!projectControl.canPushToAtLeastOneRef()) {
@@ -297,8 +376,8 @@
}
}
- if (isHead(c) || isTag(c)) {
- // We only schedule heads and tags for replication.
+ if (!c.getRefName().startsWith(NEW_CHANGE)) {
+ // We only schedule direct refs updates for replication.
// Change refs are scheduled when they are created.
//
replication.scheduleUpdate(project.getNameKey(), c.getRefName());
@@ -718,6 +797,28 @@
}
}
+ /**
+ * Loads a list of commits to reject from {@code refs/meta/reject-commits}.
+ *
+ * @return NoteMap of commits to be rejected, null if there are none.
+ * @throws IOException the map cannot be loaded.
+ */
+ private NoteMap loadRejectCommitsMap() throws IOException {
+ try {
+ Ref ref = repo.getRef(GitRepositoryManager.REF_REJECT_COMMITS);
+ if (ref == null) {
+ return NoteMap.newEmptyMap();
+ }
+
+ RevWalk rw = rp.getRevWalk();
+ RevCommit map = rw.parseCommit(ref.getObjectId());
+ return NoteMap.read(rw.getObjectReader(), map);
+ } catch (IOException badMap) {
+ throw new IOException("Cannot load "
+ + GitRepositoryManager.REF_REJECT_COMMITS, badMap);
+ }
+ }
+
private void parseReplaceCommand(final ReceiveCommand cmd,
final Change.Id changeId) {
if (cmd.getType() != ReceiveCommand.Type.CREATE) {
@@ -747,32 +848,33 @@
return;
}
if (!project.getNameKey().equals(changeEnt.getProject())) {
- reject(cmd, "change " + changeId + " not found");
+ reject(cmd, "change " + changeId + " does not belong to project " + project.getName());
return;
}
requestReplace(cmd, changeEnt, newCommit);
}
- private void requestReplace(final ReceiveCommand cmd, final Change change,
+ private boolean requestReplace(final ReceiveCommand cmd, final Change change,
final RevCommit newCommit) {
if (change.getStatus().isClosed()) {
reject(cmd, "change " + change.getId() + " closed");
- return;
+ return false;
}
final ReplaceRequest req =
new ReplaceRequest(change.getId(), newCommit, cmd);
if (replaceByChange.containsKey(req.ontoChange)) {
reject(cmd, "duplicate request");
- return;
+ return false;
}
if (replaceByCommit.containsKey(req.newCommit)) {
reject(cmd, "duplicate request");
- return;
+ return false;
}
replaceByChange.put(req.ontoChange, req);
replaceByCommit.put(req.newCommit, req);
+ return true;
}
private void createNewChanges() {
@@ -783,14 +885,15 @@
walk.sort(RevSort.REVERSE, true);
try {
walk.markStart(walk.parseCommit(newChange.getNewId()));
- for (final Ref r : rp.getAdvertisedRefs().values()) {
+ for (ObjectId id : existingObjects()) {
try {
- walk.markUninteresting(walk.parseCommit(r.getObjectId()));
+ walk.markUninteresting(walk.parseCommit(id));
} catch (IOException e) {
continue;
}
}
+ final Set<Change.Key> newChangeIds = new HashSet<Change.Key>();
for (;;) {
final RevCommit c = walk.next();
if (c == null) {
@@ -811,6 +914,12 @@
if (!idList.isEmpty()) {
final String idStr = idList.get(idList.size() - 1).trim();
final Change.Key key = new Change.Key(idStr);
+
+ if (newChangeIds.contains(key)) {
+ reject(newChange, "squash commits first");
+ return;
+ }
+
final List<Change> changes =
db.changes().byProjectKey(project.getNameKey(), key).toList();
if (changes.size() > 1) {
@@ -827,8 +936,15 @@
if (changes.size() == 1) {
// Schedule as a replacement to this one matching change.
//
- requestReplace(newChange, changes.get(0), c);
- continue;
+ if (requestReplace(newChange, changes.get(0), c)) {
+ continue;
+ } else {
+ return;
+ }
+ }
+
+ if (changes.size() == 0) {
+ newChangeIds.add(key);
}
}
@@ -1046,6 +1162,7 @@
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
if (ps.getRevision() == null) {
+ log.warn("Patch set " + ps.getId() + " has no revision");
reject(request.cmd, "change state corrupt");
return null;
}
@@ -1387,9 +1504,9 @@
walk.sort(RevSort.NONE);
try {
walk.markStart(walk.parseCommit(cmd.getNewId()));
- for (final Ref r : rp.getAdvertisedRefs().values()) {
+ for (ObjectId id : existingObjects()) {
try {
- walk.markUninteresting(walk.parseCommit(r.getObjectId()));
+ walk.markUninteresting(walk.parseCommit(id));
} catch (IOException e) {
continue;
}
@@ -1407,12 +1524,29 @@
}
}
+ private Collection<ObjectId> existingObjects() {
+ if (existingObjects == null) {
+ Map<String, Ref> refs = repo.getAllRefs();
+ existingObjects = new ArrayList<ObjectId>(refs.size());
+ for (Ref r : refs.values()) {
+ existingObjects.add(r.getObjectId());
+ }
+ }
+ return existingObjects;
+ }
+
private boolean validCommit(final RefControl ctl, final ReceiveCommand cmd,
final RevCommit c) throws MissingObjectException, IOException {
rp.getRevWalk().parseBody(c);
final PersonIdent committer = c.getCommitterIdent();
final PersonIdent author = c.getAuthorIdent();
+ // Require permission to upload merges.
+ if (c.getParentCount() > 1 && !ctl.canUploadMerges()) {
+ reject(cmd, "you are not allowed to upload merges");
+ return false;
+ }
+
// Don't allow the user to amend a merge created by Gerrit Code Review.
// This seems to happen all too often, due to users not paying any
// attention to what they are doing.
@@ -1462,18 +1596,16 @@
}
}
- if (project.isRequireChangeID()) {
- final List<String> idList = c.getFooterLines(CHANGE_ID);
- if (idList.isEmpty()) {
- reject(cmd, "missing Change-Id in commit message ");
+ final List<String> idList = c.getFooterLines(CHANGE_ID);
+ if (idList.isEmpty()) {
+ if (project.isRequireChangeID()) {
+ reject(cmd, "missing Change-Id in commit message");
return false;
}
-
- if (idList.size() > 1) {
- reject(cmd, "multiple Change-Id lines in commit message ");
- return false;
- }
-
+ } else if (idList.size() > 1) {
+ reject(cmd, "multiple Change-Id lines in commit message");
+ return false;
+ } else {
final String v = idList.get(idList.size() - 1).trim();
if (!v.matches("^I[0-9a-f]{8,}.*$")) {
reject(cmd, "invalid Change-Id line format in commit message ");
@@ -1481,6 +1613,12 @@
}
}
+ // Check for banned commits to prevent them from entering the tree again.
+ if (rejectCommits.contains(c)) {
+ reject(newChange, "contains banned commit " + c.getName());
+ return false;
+ }
+
return true;
}
@@ -1593,10 +1731,10 @@
sendMergedEmail(result);
}
- private Map<ObjectId, Ref> changeRefsById() {
+ private Map<ObjectId, Ref> changeRefsById() throws IOException {
if (refsById == null) {
refsById = new HashMap<ObjectId, Ref>();
- for (final Ref r : repo.getAllRefs().values()) {
+ for (Ref r : repo.getRefDatabase().getRefs("refs/changes/").values()) {
if (PatchSet.isRef(r.getName())) {
refsById.put(r.getObjectId(), r);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsRefFilter.java
new file mode 100644
index 0000000..730305c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsRefFilter.java
@@ -0,0 +1,41 @@
+// 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.lib.Ref;
+import org.eclipse.jgit.transport.RefFilter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Exposes only the non refs/changes/ reference names. */
+class ReceiveCommitsRefFilter implements RefFilter {
+ private final RefFilter base;
+
+ public ReceiveCommitsRefFilter(RefFilter base) {
+ this.base = base != null ? base : RefFilter.DEFAULT;
+ }
+
+ @Override
+ public Map<String, Ref> filter(Map<String, Ref> refs) {
+ HashMap<String, Ref> r = new HashMap<String, Ref>();
+ for (Map.Entry<String, Ref> e : refs.entrySet()) {
+ if (!e.getKey().startsWith("refs/changes/")) {
+ r.put(e.getKey(), e.getValue());
+ }
+ }
+ return base.filter(r);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index ffc3fd8..8e4d8ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -52,12 +52,15 @@
private final Repository db;
private final ProjectControl projectCtl;
private final ReviewDb reviewDb;
+ private final boolean showChanges;
public VisibleRefFilter(final Repository db,
- final ProjectControl projectControl, final ReviewDb reviewDb) {
+ final ProjectControl projectControl, final ReviewDb reviewDb,
+ final boolean showChanges) {
this.db = db;
this.projectCtl = projectControl;
this.reviewDb = reviewDb;
+ this.showChanges = showChanges;
}
@Override
@@ -99,6 +102,10 @@
}
private Set<Change.Id> visibleChanges() {
+ if (!showChanges) {
+ return Collections.emptySet();
+ }
+
final Project project = projectCtl.getProject();
try {
final Set<Change.Id> visibleChanges = new HashSet<Change.Id>();
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 71712af..3725a3c 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
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.StarredChange;
+import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -47,12 +48,11 @@
/** Sends an email to one or more interested parties. */
public abstract class ChangeEmail extends OutgoingEmail {
protected final Change change;
- protected String projectName;
protected PatchSet patchSet;
protected PatchSetInfo patchSetInfo;
protected ChangeMessage changeMessage;
- private ProjectState projectState;
+ protected ProjectState projectState;
protected ChangeData changeData;
protected ChangeEmail(EmailArguments ea, final Change c, final String mc) {
@@ -104,11 +104,8 @@
protected void init() throws EmailException {
if (args.projectCache != null) {
projectState = args.projectCache.get(change.getProject());
- projectName =
- projectState != null ? projectState.getProject().getName() : null;
} else {
projectState = null;
- projectName = null;
}
if (patchSet == null) {
@@ -299,7 +296,7 @@
// BCC anyone else who has interest in this project's changes
//
for (final AccountProjectWatch w : getWatches()) {
- if (w.isNotifyAllComments()) {
+ if (w.isNotify(NotifyType.ALL_COMMENTS)) {
add(RecipientType.BCC, w.getAccountId());
}
}
@@ -398,7 +395,8 @@
velocityContext.put("coverLetter", getCoverLetter());
velocityContext.put("branch", change.getDest());
velocityContext.put("fromName", getNameFor(fromId));
- velocityContext.put("projectName", projectName);
+ velocityContext.put("projectName", //
+ projectState != null ? projectState.getProject().getName() : null);
velocityContext.put("patchSet", patchSet);
velocityContext.put("patchSetInfo", patchSetInfo);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 0425121..062d14b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -136,7 +136,7 @@
private Repository getRepository() {
try {
- return args.server.openRepository(projectName);
+ return args.server.openRepository(projectState.getProject().getNameKey());
} catch (RepositoryNotFoundException e) {
return null;
}
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 18bfe976..c14ff1b 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
@@ -19,6 +19,7 @@
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -61,7 +62,7 @@
// BCC anyone who has interest in this project's changes
//
for (final AccountProjectWatch w : getWatches()) {
- if (w.isNotifyNewChanges()) {
+ if (w.isNotify(NotifyType.NEW_CHANGES)) {
if (owners.contains(w.getAccountId())) {
add(RecipientType.TO, w.getAccountId());
} else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index 40f4790..b2a1c44 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -149,7 +150,7 @@
// BCC anyone else who has interest in this project's changes
//
for (final AccountProjectWatch w : getWatches()) {
- if (w.isNotifySubmittedChanges()) {
+ if (w.isNotify(NotifyType.SUBMITTED_CHANGES)) {
add(RecipientType.BCC, w.getAccountId());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
index d67d3b3..3c5e64a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
@@ -28,8 +28,10 @@
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
+import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -51,6 +53,8 @@
private Encryption smtpEncryption;
private boolean sslVerify;
private Set<String> allowrcpt;
+ private String importance;
+ private int expiryDays;
@Inject
SmtpEmailSender(@GerritServerConfig final Config cfg) {
@@ -88,6 +92,8 @@
rcpt.add(addr);
}
allowrcpt = Collections.unmodifiableSet(rcpt);
+ importance = cfg.getString("sendemail", null, "importance");
+ expiryDays = cfg.getInt("sendemail", null, "expiryDays", 0);
}
@Override
@@ -132,6 +138,15 @@
setMissingHeader(hdrs, "Content-Transfer-Encoding", "8bit");
setMissingHeader(hdrs, "Content-Disposition", "inline");
setMissingHeader(hdrs, "User-Agent", "Gerrit/" + Version.getVersion());
+ if(importance != null) {
+ setMissingHeader(hdrs, "Importance", importance);
+ }
+ if(expiryDays > 0) {
+ Date expiry = new Date(System.currentTimeMillis() +
+ expiryDays * 24 * 60 * 60 * 1000 );
+ setMissingHeader(hdrs, "Expiry-Date",
+ new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(expiry));
+ }
try {
final SMTPClient client = open();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java
index f8f339e..3805f8f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java
@@ -14,9 +14,13 @@
package com.google.gerrit.server.patch;
+import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum;
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
+import com.google.gerrit.reviewdb.CodedEnum;
+
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.ReplaceEdit;
@@ -33,10 +37,36 @@
public class IntraLineDiff implements Serializable {
static final long serialVersionUID = IntraLineDiffKey.serialVersionUID;
- private List<Edit> edits;
+ public static enum Status implements CodedEnum {
+ EDIT_LIST('e'), DISABLED('D'), TIMEOUT('T'), ERROR('E');
+
+ private final char code;
+
+ Status(char code) {
+ this.code = code;
+ }
+
+ @Override
+ public char getCode() {
+ return code;
+ }
+ }
+
+ private transient Status status;
+ private transient List<Edit> edits;
+
+ IntraLineDiff(Status status) {
+ this.status = status;
+ this.edits = Collections.emptyList();
+ }
IntraLineDiff(List<Edit> edits) {
- this.edits = edits;
+ this.status = Status.EDIT_LIST;
+ this.edits = Collections.unmodifiableList(edits);
+ }
+
+ public Status getStatus() {
+ return status;
}
public List<Edit> getEdits() {
@@ -44,6 +74,7 @@
}
private void writeObject(final ObjectOutputStream out) throws IOException {
+ writeEnum(out, status);
writeVarInt32(out, edits.size());
for (Edit e : edits) {
writeEdit(out, e);
@@ -61,6 +92,7 @@
}
private void readObject(final ObjectInputStream in) throws IOException {
+ status = readEnum(in, Status.values());
int editCount = readVarInt32(in);
Edit[] editArray = new Edit[editCount];
for (int i = 0; i < editCount; i++) {
@@ -69,11 +101,13 @@
int innerCount = readVarInt32(in);
if (0 < innerCount) {
Edit[] inner = new Edit[innerCount];
- for (int j = 0; j < innerCount; j++)
+ for (int j = 0; j < innerCount; j++) {
inner[j] = readEdit(in);
+ }
editArray[i] = new ReplaceEdit(editArray[i], toList(inner));
}
}
+ edits = toList(editArray);
}
private static void writeEdit(OutputStream out, Edit e) throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
index 899dda5..a8d62fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
@@ -17,6 +17,8 @@
import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
+import com.google.gerrit.reviewdb.Project;
+
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.lib.ObjectId;
@@ -27,7 +29,7 @@
import java.util.List;
public class IntraLineDiffKey implements Serializable {
- static final long serialVersionUID = 1L;
+ static final long serialVersionUID = 3L;
private transient ObjectId aId;
private transient ObjectId bId;
@@ -38,14 +40,22 @@
private transient Text bText;
private transient List<Edit> edits;
- IntraLineDiffKey(ObjectId aId, Text aText, ObjectId bId, Text bText,
- List<Edit> edits) {
+ private transient Project.NameKey projectKey;
+ private transient ObjectId commit;
+ private transient String path;
+
+ public IntraLineDiffKey(ObjectId aId, Text aText, ObjectId bId, Text bText,
+ List<Edit> edits, Project.NameKey projectKey, ObjectId commit, String path) {
this.aId = aId;
this.bId = bId;
this.aText = aText;
this.bText = bText;
this.edits = edits;
+
+ this.projectKey = projectKey;
+ this.commit = commit;
+ this.path = path;
}
Text getTextA() {
@@ -60,6 +70,26 @@
return edits;
}
+ ObjectId getBlobA() {
+ return aId;
+ }
+
+ ObjectId getBlobB() {
+ return bId;
+ }
+
+ Project.NameKey getProject() {
+ return projectKey;
+ }
+
+ ObjectId getCommit() {
+ return commit;
+ }
+
+ String getPath() {
+ return path;
+ }
+
@Override
public int hashCode() {
int h = 0;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
new file mode 100644
index 0000000..0ac1af2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -0,0 +1,472 @@
+// 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.server.cache.EntryCreator;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.MyersDiff;
+import org.eclipse.jgit.diff.ReplaceEdit;
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
+
+class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
+ private static final Logger log = LoggerFactory
+ .getLogger(IntraLineLoader.class);
+
+ private static final Pattern BLANK_LINE_RE = Pattern
+ .compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
+
+ private static final Pattern CONTROL_BLOCK_START_RE = Pattern
+ .compile("[{:][ \\t]*$");
+
+ private final BlockingQueue<Worker> workerPool;
+ private final long timeoutMillis;
+
+ @Inject
+ IntraLineLoader(final @GerritServerConfig Config cfg) {
+ final int workers =
+ cfg.getInt("cache", PatchListCacheImpl.INTRA_NAME, "maxIdleWorkers",
+ Runtime.getRuntime().availableProcessors() * 3 / 2);
+ workerPool = new ArrayBlockingQueue<Worker>(workers, true /* fair */);
+
+ timeoutMillis =
+ ConfigUtil.getTimeUnit(cfg, "cache", PatchListCacheImpl.INTRA_NAME,
+ "timeout", TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS),
+ TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
+ Worker w = workerPool.poll();
+ if (w == null) {
+ w = new Worker();
+ }
+
+ Worker.Result r = w.computeWithTimeout(key, timeoutMillis);
+
+ if (r == Worker.Result.TIMEOUT) {
+ // Don't keep this thread. We have to murder it unsafely, which
+ // means its unable to be reused in the future. Return back a
+ // null result, indicating the cache cannot load this key.
+ //
+ return new IntraLineDiff(IntraLineDiff.Status.TIMEOUT);
+ }
+
+ if (!workerPool.offer(w)) {
+ // If the idle worker pool is full, terminate this thread.
+ //
+ w.end();
+ }
+
+ if (r.error != null) {
+ // If there was an error computing the result, carry it
+ // up to the caller so the cache knows this key is invalid.
+ //
+ throw r.error;
+ }
+
+ return r.diff;
+ }
+
+ private static class Worker {
+ private static final AtomicInteger count = new AtomicInteger(1);
+
+ private final ArrayBlockingQueue<Input> input;
+ private final ArrayBlockingQueue<Result> result;
+ private final Thread thread;
+
+ Worker() {
+ input = new ArrayBlockingQueue<Input>(1);
+ result = new ArrayBlockingQueue<Result>(1);
+
+ thread = new Thread(new Runnable() {
+ public void run() {
+ workerLoop();
+ }
+ });
+ thread.setName("IntraLineDiff-" + count.getAndIncrement());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ Result computeWithTimeout(IntraLineDiffKey key, long timeoutMillis)
+ throws Exception {
+ if (!input.offer(new Input(key))) {
+ log.error("Cannot enqueue task to thread " + thread.getName());
+ return null;
+ }
+
+ Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+ if (r != null) {
+ return r;
+ } else {
+ log.warn(timeoutMillis + " ms timeout reached for IntraLineDiff"
+ + " in project " + key.getProject().get() //
+ + " on commit " + key.getCommit().name() //
+ + " for path " + key.getPath() //
+ + " comparing " + key.getBlobA().name() //
+ + ".." + key.getBlobB().name() //
+ + ". Killing " + thread.getName());
+ try {
+ thread.stop();
+ } catch (Throwable error) {
+ // Ignore any reason the thread won't stop.
+ log.error("Cannot stop runaway thread " + thread.getName(), error);
+ }
+ return Result.TIMEOUT;
+ }
+ }
+
+ void end() {
+ if (!input.offer(Input.END_THREAD)) {
+ log.error("Cannot gracefully stop thread " + thread.getName());
+ }
+ }
+
+ private void workerLoop() {
+ try {
+ for (;;) {
+ Input in;
+ try {
+ in = input.take();
+ } catch (InterruptedException e) {
+ log.error("Unexpected interrupt on " + thread.getName());
+ continue;
+ }
+
+ if (in == Input.END_THREAD) {
+ return;
+ }
+
+ Result r;
+ try {
+ r = new Result(IntraLineLoader.compute(in.key));
+ } catch (Exception error) {
+ r = new Result(error);
+ }
+
+ if (!result.offer(r)) {
+ log.error("Cannot return result from " + thread.getName());
+ }
+ }
+ } catch (ThreadDeath iHaveBeenShot) {
+ // Handle thread death by gracefully returning to the caller,
+ // allowing the thread to be destroyed.
+ }
+ }
+
+ private static class Input {
+ static final Input END_THREAD = new Input(null);
+
+ final IntraLineDiffKey key;
+
+ Input(IntraLineDiffKey key) {
+ this.key = key;
+ }
+ }
+
+ static class Result {
+ static final Result TIMEOUT = new Result((IntraLineDiff) null);
+
+ final IntraLineDiff diff;
+ final Exception error;
+
+ Result(IntraLineDiff diff) {
+ this.diff = diff;
+ this.error = null;
+ }
+
+ Result(Exception error) {
+ this.diff = null;
+ this.error = error;
+ }
+ }
+ }
+
+ private static IntraLineDiff compute(IntraLineDiffKey key) throws Exception {
+ List<Edit> edits = new ArrayList<Edit>(key.getEdits());
+ Text aContent = key.getTextA();
+ Text bContent = key.getTextB();
+ combineLineEdits(edits, aContent, bContent);
+
+ for (int i = 0; i < edits.size(); i++) {
+ Edit e = edits.get(i);
+
+ if (e.getType() == Edit.Type.REPLACE) {
+ CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
+ CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
+ CharTextComparator cmp = new CharTextComparator();
+
+ List<Edit> wordEdits = MyersDiff.INSTANCE.diff(cmp, a, b);
+
+ // Combine edits that are really close together. If they are
+ // just a few characters apart we tend to get better results
+ // by joining them together and taking the whole span.
+ //
+ for (int j = 0; j < wordEdits.size() - 1;) {
+ Edit c = wordEdits.get(j);
+ Edit n = wordEdits.get(j + 1);
+
+ if (n.getBeginA() - c.getEndA() <= 5
+ || n.getBeginB() - c.getEndB() <= 5) {
+ int ab = c.getBeginA();
+ int ae = n.getEndA();
+
+ int bb = c.getBeginB();
+ int be = n.getEndB();
+
+ if (canCoalesce(a, c.getEndA(), n.getBeginA())
+ && canCoalesce(b, c.getEndB(), n.getBeginB())) {
+ wordEdits.set(j, new Edit(ab, ae, bb, be));
+ wordEdits.remove(j + 1);
+ continue;
+ }
+ }
+
+ j++;
+ }
+
+ // Apply some simple rules to fix up some of the edits. Our
+ // logic above, along with our per-character difference tends
+ // to produce some crazy stuff.
+ //
+ for (int j = 0; j < wordEdits.size(); j++) {
+ Edit c = wordEdits.get(j);
+ int ab = c.getBeginA();
+ int ae = c.getEndA();
+
+ int bb = c.getBeginB();
+ int be = c.getEndB();
+
+ // Sometimes the diff generator produces an INSERT or DELETE
+ // right up against a REPLACE, but we only find this after
+ // we've also played some shifting games on the prior edit.
+ // If that happened to us, coalesce them together so we can
+ // correct this mess for the user. If we don't we wind up
+ // with silly stuff like "es" -> "es = Addresses".
+ //
+ if (1 < j) {
+ Edit p = wordEdits.get(j - 1);
+ if (p.getEndA() == ab || p.getEndB() == bb) {
+ if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
+ ab = p.getBeginA();
+ }
+ if (p.getEndB() == bb && p.getBeginB() < p.getEndB()) {
+ bb = p.getBeginB();
+ }
+ wordEdits.remove(--j);
+ }
+ }
+
+ // We sometimes collapsed an edit together in a strange way,
+ // such that the edges of each text is identical. Fix by
+ // by dropping out that incorrectly replaced region.
+ //
+ while (ab < ae && bb < be && cmp.equals(a, ab, b, bb)) {
+ ab++;
+ bb++;
+ }
+ while (ab < ae && bb < be && cmp.equals(a, ae - 1, b, be - 1)) {
+ ae--;
+ be--;
+ }
+
+ // The leading part of an edit and its trailing part in the same
+ // text might be identical. Slide down that edit and use the tail
+ // rather than the leading bit. If however the edit is only on a
+ // whitespace block try to shift it to the left margin, assuming
+ // that it is an indentation change.
+ //
+ boolean aShift = true;
+ if (ab < ae && isOnlyWhitespace(a, ab, ae)) {
+ int lf = findLF(wordEdits, j, a, ab);
+ if (lf < ab && a.charAt(lf) == '\n') {
+ int nb = lf + 1;
+ int p = 0;
+ while (p < ae - ab) {
+ if (cmp.equals(a, ab + p, a, ab + p))
+ p++;
+ else
+ break;
+ }
+ if (p == ae - ab) {
+ ab = nb;
+ ae = nb + p;
+ aShift = false;
+ }
+ }
+ }
+ if (aShift) {
+ while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
+ && cmp.equals(a, ab - 1, a, ae - 1)) {
+ ab--;
+ ae--;
+ }
+ if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
+ while (ab < ae && ae < a.size() && cmp.equals(a, ab, a, ae)) {
+ ab++;
+ ae++;
+ if (a.charAt(ae - 1) == '\n') {
+ break;
+ }
+ }
+ }
+ }
+
+ boolean bShift = true;
+ if (bb < be && isOnlyWhitespace(b, bb, be)) {
+ int lf = findLF(wordEdits, j, b, bb);
+ if (lf < bb && b.charAt(lf) == '\n') {
+ int nb = lf + 1;
+ int p = 0;
+ while (p < be - bb) {
+ if (cmp.equals(b, bb + p, b, bb + p))
+ p++;
+ else
+ break;
+ }
+ if (p == be - bb) {
+ bb = nb;
+ be = nb + p;
+ bShift = false;
+ }
+ }
+ }
+ if (bShift) {
+ while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
+ && cmp.equals(b, bb - 1, b, be - 1)) {
+ bb--;
+ be--;
+ }
+ if (!b.isLineStart(bb) || !b.contains(bb, be, '\n')) {
+ while (bb < be && be < b.size() && cmp.equals(b, bb, b, be)) {
+ bb++;
+ be++;
+ if (b.charAt(be - 1) == '\n') {
+ break;
+ }
+ }
+ }
+ }
+
+ // If most of a line was modified except the LF was common, make
+ // the LF part of the modification region. This is easier to read.
+ //
+ if (ab < ae //
+ && (ab == 0 || a.charAt(ab - 1) == '\n') //
+ && ae < a.size() && a.charAt(ae) == '\n') {
+ ae++;
+ }
+ if (bb < be //
+ && (bb == 0 || b.charAt(bb - 1) == '\n') //
+ && be < b.size() && b.charAt(be) == '\n') {
+ be++;
+ }
+
+ wordEdits.set(j, new Edit(ab, ae, bb, be));
+ }
+
+ edits.set(i, new ReplaceEdit(e, wordEdits));
+ }
+ }
+
+ return new IntraLineDiff(edits);
+ }
+
+ private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
+ for (int j = 0; j < edits.size() - 1;) {
+ Edit c = edits.get(j);
+ Edit n = edits.get(j + 1);
+
+ // Combine edits that are really close together. Right now our rule
+ // is, coalesce two line edits which are only one line apart if that
+ // common context line is either a "pointless line", or is identical
+ // on both sides and starts a new block of code. These are mostly
+ // block reindents to add or remove control flow operators.
+ //
+ final int ad = n.getBeginA() - c.getEndA();
+ final int bd = n.getBeginB() - c.getEndB();
+ if ((1 <= ad && isBlankLineGap(a, c.getEndA(), n.getBeginA()))
+ || (1 <= bd && isBlankLineGap(b, c.getEndB(), n.getBeginB()))
+ || (ad == 1 && bd == 1 && isControlBlockStart(a, c.getEndA()))) {
+ int ab = c.getBeginA();
+ int ae = n.getEndA();
+
+ int bb = c.getBeginB();
+ int be = n.getEndB();
+
+ edits.set(j, new Edit(ab, ae, bb, be));
+ edits.remove(j + 1);
+ continue;
+ }
+
+ j++;
+ }
+ }
+
+ private static boolean isBlankLineGap(Text a, int b, int e) {
+ for (; b < e; b++) {
+ if (!BLANK_LINE_RE.matcher(a.getString(b)).matches()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isControlBlockStart(Text a, int idx) {
+ return CONTROL_BLOCK_START_RE.matcher(a.getString(idx)).find();
+ }
+
+ private static boolean canCoalesce(CharText a, int b, int e) {
+ while (b < e) {
+ if (a.charAt(b++) == '\n') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int findLF(List<Edit> edits, int j, CharText t, int b) {
+ int lf = b;
+ int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
+ while (limit < lf && t.charAt(lf) != '\n') {
+ lf--;
+ }
+ return lf;
+ }
+
+ private static boolean isOnlyWhitespace(CharText t, final int b, final int e) {
+ for (int c = b; c < e; c++) {
+ if (!Character.isWhitespace(t.charAt(c))) {
+ return false;
+ }
+ }
+ return b < e;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
index c679f4a..a7cf10a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -16,12 +16,6 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
-import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
-
-import org.eclipse.jgit.diff.Edit;
-import org.eclipse.jgit.lib.ObjectId;
-
-import java.util.List;
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
@@ -29,8 +23,5 @@
public PatchList get(Change change, PatchSet patchSet);
- public PatchList get(Change change, PatchSet patchSet, Whitespace whitespace);
-
- public IntraLineDiff get(ObjectId aId, Text aText, ObjectId bId, Text bText,
- List<Edit> edits);
+ public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 087ed51..e1a0a40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -12,114 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
-// Some portions (e.g. outputDiff) below are:
-//
-// Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
-// Copyright (C) 2009, Johannes E. Schindelin
-// Copyright (C) 2009, Johannes Schindelin <johannes.schindelin@gmx.de>
-// and other copyright owners as documented in the project's IP log.
-//
-// This program and the accompanying materials are made available
-// under the terms of the Eclipse Distribution License v1.0 which
-// accompanies this distribution, is reproduced below, and is
-// available at http://www.eclipse.org/org/documents/edl-v10.php
-//
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or
-// without modification, are permitted provided that the following
-// conditions are met:
-//
-// - Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//
-// - Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following
-// disclaimer in the documentation and/or other materials provided
-// with the distribution.
-//
-// - Neither the name of the Eclipse Foundation, Inc. nor the
-// names of its contributors may be used to endorse or promote
-// products derived from this software without specific prior
-// written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
-// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
-// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-//
package com.google.gerrit.server.patch;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
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.cache.EvictionPolicy;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.Edit;
-import org.eclipse.jgit.diff.EditList;
-import org.eclipse.jgit.diff.HistogramDiff;
-import org.eclipse.jgit.diff.MyersDiff;
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.diff.RawTextComparator;
-import org.eclipse.jgit.diff.ReplaceEdit;
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.ObjectReader;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.patch.FileHeader;
-import org.eclipse.jgit.patch.FileHeader.PatchType;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.eclipse.jgit.util.io.DisabledOutputStream;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
/** Provides a cached list of {@link PatchListEntry}. */
@Singleton
public class PatchListCacheImpl implements PatchListCache {
private static final String FILE_NAME = "diff";
- private static final String INTRA_NAME = "diff_intraline";
-
- private static final Pattern BLANK_LINE_RE =
- Pattern.compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
- private static final Pattern CONTROL_BLOCK_START_RE =
- Pattern.compile("[{:][ \\t]*$");
+ static final String INTRA_NAME = "diff_intraline";
public static Module module() {
return new CacheModule() {
@@ -159,7 +77,9 @@
this.fileCache = fileCache;
this.intraCache = intraCache;
- this.computeIntraline = cfg.getBoolean("cache", "diff", "intraline", true);
+ this.computeIntraline =
+ cfg.getBoolean("cache", INTRA_NAME, "enabled",
+ cfg.getBoolean("cache", "diff", "intraline", true));
}
public PatchList get(final PatchListKey key) {
@@ -167,478 +87,23 @@
}
public PatchList get(final Change change, final PatchSet patchSet) {
- return get(change, patchSet, Whitespace.IGNORE_NONE);
- }
-
- public PatchList get(final Change change, final PatchSet patchSet,
- final Whitespace whitespace) {
final Project.NameKey projectKey = change.getProject();
final ObjectId a = null;
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
- return get(new PatchListKey(projectKey, a, b, whitespace));
+ final Whitespace ws = Whitespace.IGNORE_NONE;
+ return get(new PatchListKey(projectKey, a, b, ws));
}
@Override
- public IntraLineDiff get(ObjectId aId, Text aText, ObjectId bId, Text bText,
- List<Edit> edits) {
+ public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key) {
if (computeIntraline) {
- IntraLineDiffKey key =
- new IntraLineDiffKey(aId, aText, bId, bText, edits);
- return intraCache.get(key);
+ IntraLineDiff d = intraCache.get(key);
+ if (d == null) {
+ d = new IntraLineDiff(IntraLineDiff.Status.ERROR);
+ }
+ return d;
} else {
- return null;
- }
- }
-
- private static RawTextComparator comparatorFor(Whitespace ws) {
- switch (ws) {
- case IGNORE_ALL_SPACE:
- return RawTextComparator.WS_IGNORE_ALL;
-
- case IGNORE_SPACE_AT_EOL:
- return RawTextComparator.WS_IGNORE_TRAILING;
-
- case IGNORE_SPACE_CHANGE:
- return RawTextComparator.WS_IGNORE_CHANGE;
-
- case IGNORE_NONE:
- default:
- return RawTextComparator.DEFAULT;
- }
- }
-
- static class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
- private final GitRepositoryManager repoManager;
-
- @Inject
- PatchListLoader(GitRepositoryManager mgr) {
- repoManager = mgr;
- }
-
- @Override
- public PatchList createEntry(final PatchListKey key) throws Exception {
- final Repository repo = repoManager.openRepository(key.projectKey.get());
- try {
- return readPatchList(key, repo);
- } finally {
- repo.close();
- }
- }
-
- private PatchList readPatchList(final PatchListKey key,
- final Repository repo) throws IOException {
- // TODO(jeffschu) correctly handle merge commits
-
- final RawTextComparator cmp = comparatorFor(key.getWhitespace());
- final ObjectReader reader = repo.newObjectReader();
- try {
- final RevWalk rw = new RevWalk(reader);
- final RevCommit b = rw.parseCommit(key.getNewId());
- final RevObject a = aFor(key, repo, rw, b);
-
- if (a == null) {
- // This is a merge commit, compared to its ancestor.
- //
- final PatchListEntry[] entries = new PatchListEntry[1];
- entries[0] = newCommitMessage(cmp, repo, reader, null, b);
- return new PatchList(a, b, true, entries);
- }
-
- final boolean againstParent =
- b.getParentCount() > 0 && b.getParent(0) == a;
-
- RevCommit aCommit;
- RevTree aTree;
- if (a instanceof RevCommit) {
- aCommit = (RevCommit) a;
- aTree = aCommit.getTree();
- } else if (a instanceof RevTree) {
- aCommit = null;
- aTree = (RevTree) a;
- } else {
- throw new IOException("Unexpected type: " + a.getClass());
- }
-
- RevTree bTree = b.getTree();
-
- final TreeWalk walk = new TreeWalk(reader);
- walk.reset();
- walk.setRecursive(true);
- walk.addTree(aTree);
- walk.addTree(bTree);
- walk.setFilter(TreeFilter.ANY_DIFF);
-
- DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
- df.setRepository(repo);
- df.setDiffComparator(cmp);
- df.setDetectRenames(true);
- List<DiffEntry> diffEntries = df.scan(aTree, bTree);
-
- final int cnt = diffEntries.size();
- final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
- entries[0] = newCommitMessage(cmp, repo, reader, //
- againstParent ? null : aCommit, b);
- for (int i = 0; i < cnt; i++) {
- FileHeader fh = df.toFileHeader(diffEntries.get(i));
- entries[1 + i] = newEntry(aTree, fh);
- }
- return new PatchList(a, b, againstParent, entries);
- } finally {
- reader.release();
- }
- }
-
- private PatchListEntry newCommitMessage(final RawTextComparator cmp,
- final Repository db, final ObjectReader reader,
- final RevCommit aCommit, final RevCommit bCommit) throws IOException {
- StringBuilder hdr = new StringBuilder();
-
- hdr.append("diff --git");
- if (aCommit != null) {
- hdr.append(" a/" + Patch.COMMIT_MSG);
- } else {
- hdr.append(" " + FileHeader.DEV_NULL);
- }
- hdr.append(" b/" + Patch.COMMIT_MSG);
- hdr.append("\n");
-
- if (aCommit != null) {
- hdr.append("--- a/" + Patch.COMMIT_MSG + "\n");
- } else {
- hdr.append("--- " + FileHeader.DEV_NULL + "\n");
- }
- hdr.append("+++ b/" + Patch.COMMIT_MSG + "\n");
-
- Text aText =
- aCommit != null ? Text.forCommit(db, reader, aCommit) : Text.EMPTY;
- Text bText = Text.forCommit(db, reader, bCommit);
-
- byte[] rawHdr = hdr.toString().getBytes("UTF-8");
- RawText aRawText = new RawText(aText.getContent());
- RawText bRawText = new RawText(bText.getContent());
- EditList edits = new HistogramDiff().diff(cmp, aRawText, bRawText);
- FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
- return new PatchListEntry(fh, edits);
- }
-
- private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader) {
- final FileMode oldMode = fileHeader.getOldMode();
- final FileMode newMode = fileHeader.getNewMode();
-
- if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
-
- if (aTree == null // want combined diff
- || fileHeader.getPatchType() != PatchType.UNIFIED
- || fileHeader.getHunks().isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
-
- List<Edit> edits = fileHeader.toEditList();
- if (edits.isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- } else {
- return new PatchListEntry(fileHeader, edits);
- }
- }
-
- private static RevObject aFor(final PatchListKey key,
- final Repository repo, final RevWalk rw, final RevCommit b)
- throws IOException {
- if (key.getOldId() != null) {
- return rw.parseAny(key.getOldId());
- }
-
- switch (b.getParentCount()) {
- case 0:
- return rw.parseAny(emptyTree(repo));
- case 1: {
- RevCommit r = b.getParent(0);
- rw.parseBody(r);
- return r;
- }
- default:
- // merge commit, return null to force combined diff behavior
- return null;
- }
- }
-
- private static ObjectId emptyTree(final Repository repo) throws IOException {
- ObjectInserter oi = repo.newObjectInserter();
- try {
- ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
- oi.flush();
- return id;
- } finally {
- oi.release();
- }
- }
- }
-
- static class IntraLineLoader extends
- EntryCreator<IntraLineDiffKey, IntraLineDiff> {
- @Override
- public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
- List<Edit> edits = new ArrayList<Edit>(key.getEdits());
- Text aContent = key.getTextA();
- Text bContent = key.getTextB();
- combineLineEdits(edits, aContent, bContent);
-
- for (int i = 0; i < edits.size(); i++) {
- Edit e = edits.get(i);
-
- if (e.getType() == Edit.Type.REPLACE) {
- CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
- CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
- CharTextComparator cmp = new CharTextComparator();
-
- List<Edit> wordEdits = MyersDiff.INSTANCE.diff(cmp, a, b);
-
- // Combine edits that are really close together. If they are
- // just a few characters apart we tend to get better results
- // by joining them together and taking the whole span.
- //
- for (int j = 0; j < wordEdits.size() - 1;) {
- Edit c = wordEdits.get(j);
- Edit n = wordEdits.get(j + 1);
-
- if (n.getBeginA() - c.getEndA() <= 5
- || n.getBeginB() - c.getEndB() <= 5) {
- int ab = c.getBeginA();
- int ae = n.getEndA();
-
- int bb = c.getBeginB();
- int be = n.getEndB();
-
- if (canCoalesce(a, c.getEndA(), n.getBeginA())
- && canCoalesce(b, c.getEndB(), n.getBeginB())) {
- wordEdits.set(j, new Edit(ab, ae, bb, be));
- wordEdits.remove(j + 1);
- continue;
- }
- }
-
- j++;
- }
-
- // Apply some simple rules to fix up some of the edits. Our
- // logic above, along with our per-character difference tends
- // to produce some crazy stuff.
- //
- for (int j = 0; j < wordEdits.size(); j++) {
- Edit c = wordEdits.get(j);
- int ab = c.getBeginA();
- int ae = c.getEndA();
-
- int bb = c.getBeginB();
- int be = c.getEndB();
-
- // Sometimes the diff generator produces an INSERT or DELETE
- // right up against a REPLACE, but we only find this after
- // we've also played some shifting games on the prior edit.
- // If that happened to us, coalesce them together so we can
- // correct this mess for the user. If we don't we wind up
- // with silly stuff like "es" -> "es = Addresses".
- //
- if (1 < j) {
- Edit p = wordEdits.get(j - 1);
- if (p.getEndA() == ab || p.getEndB() == bb) {
- if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
- ab = p.getBeginA();
- }
- if (p.getEndB() == bb && p.getBeginB() < p.getEndB()) {
- bb = p.getBeginB();
- }
- wordEdits.remove(--j);
- }
- }
-
- // We sometimes collapsed an edit together in a strange way,
- // such that the edges of each text is identical. Fix by
- // by dropping out that incorrectly replaced region.
- //
- while (ab < ae && bb < be && cmp.equals(a, ab, b, bb)) {
- ab++;
- bb++;
- }
- while (ab < ae && bb < be && cmp.equals(a, ae - 1, b, be - 1)) {
- ae--;
- be--;
- }
-
- // The leading part of an edit and its trailing part in the same
- // text might be identical. Slide down that edit and use the tail
- // rather than the leading bit. If however the edit is only on a
- // whitespace block try to shift it to the left margin, assuming
- // that it is an indentation change.
- //
- boolean aShift = true;
- if (ab < ae && isOnlyWhitespace(a, ab, ae)) {
- int lf = findLF(wordEdits, j, a, ab);
- if (lf < ab && a.charAt(lf) == '\n') {
- int nb = lf + 1;
- int p = 0;
- while (p < ae - ab) {
- if (cmp.equals(a, ab + p, a, ab + p))
- p++;
- else
- break;
- }
- if (p == ae - ab) {
- ab = nb;
- ae = nb + p;
- aShift = false;
- }
- }
- }
- if (aShift) {
- while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
- && cmp.equals(a, ab - 1, a, ae - 1)) {
- ab--;
- ae--;
- }
- if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
- while (ab < ae && ae < a.size() && cmp.equals(a, ab, a, ae)) {
- ab++;
- ae++;
- if (a.charAt(ae - 1) == '\n') {
- break;
- }
- }
- }
- }
-
- boolean bShift = true;
- if (bb < be && isOnlyWhitespace(b, bb, be)) {
- int lf = findLF(wordEdits, j, b, bb);
- if (lf < bb && b.charAt(lf) == '\n') {
- int nb = lf + 1;
- int p = 0;
- while (p < be - bb) {
- if (cmp.equals(b, bb + p, b, bb + p))
- p++;
- else
- break;
- }
- if (p == be - bb) {
- bb = nb;
- be = nb + p;
- bShift = false;
- }
- }
- }
- if (bShift) {
- while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
- && cmp.equals(b, bb - 1, b, be - 1)) {
- bb--;
- be--;
- }
- if (!b.isLineStart(bb) || !b.contains(bb, be, '\n')) {
- while (bb < be && be < b.size() && cmp.equals(b, bb, b, be)) {
- bb++;
- be++;
- if (b.charAt(be - 1) == '\n') {
- break;
- }
- }
- }
- }
-
- // If most of a line was modified except the LF was common, make
- // the LF part of the modification region. This is easier to read.
- //
- if (ab < ae //
- && (ab == 0 || a.charAt(ab - 1) == '\n') //
- && ae < a.size() && a.charAt(ae) == '\n') {
- ae++;
- }
- if (bb < be //
- && (bb == 0 || b.charAt(bb - 1) == '\n') //
- && be < b.size() && b.charAt(be) == '\n') {
- be++;
- }
-
- wordEdits.set(j, new Edit(ab, ae, bb, be));
- }
-
- edits.set(i, new ReplaceEdit(e, wordEdits));
- }
- }
-
- return new IntraLineDiff(edits);
- }
-
- private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
- for (int j = 0; j < edits.size() - 1;) {
- Edit c = edits.get(j);
- Edit n = edits.get(j + 1);
-
- // Combine edits that are really close together. Right now our rule
- // is, coalesce two line edits which are only one line apart if that
- // common context line is either a "pointless line", or is identical
- // on both sides and starts a new block of code. These are mostly
- // block reindents to add or remove control flow operators.
- //
- final int ad = n.getBeginA() - c.getEndA();
- final int bd = n.getBeginB() - c.getEndB();
- if ((1 <= ad && isBlankLineGap(a, c.getEndA(), n.getBeginA()))
- || (1 <= bd && isBlankLineGap(b, c.getEndB(), n.getBeginB()))
- || (ad == 1 && bd == 1 && isControlBlockStart(a, c.getEndA()))) {
- int ab = c.getBeginA();
- int ae = n.getEndA();
-
- int bb = c.getBeginB();
- int be = n.getEndB();
-
- edits.set(j, new Edit(ab, ae, bb, be));
- edits.remove(j + 1);
- continue;
- }
-
- j++;
- }
- }
-
- private static boolean isBlankLineGap(Text a, int b, int e) {
- for (; b < e; b++) {
- if (!BLANK_LINE_RE.matcher(a.getString(b)).matches()) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isControlBlockStart(Text a, int idx) {
- final String l = a.getString(idx);
- return CONTROL_BLOCK_START_RE.matcher(l).find();
- }
-
- private static boolean canCoalesce(CharText a, int b, int e) {
- while (b < e) {
- if (a.charAt(b++) == '\n') {
- return false;
- }
- }
- return true;
- }
-
- private static int findLF(List<Edit> edits, int j, CharText t, int b) {
- int lf = b;
- int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
- while (limit < lf && t.charAt(lf) != '\n') {
- lf--;
- }
- return lf;
- }
-
- private static boolean isOnlyWhitespace(CharText t, final int b, final int e) {
- for (int c = b; c < e; c++) {
- if (!Character.isWhitespace(t.charAt(c))) {
- return false;
- }
- }
- return b < e;
+ return new IntraLineDiff(IntraLineDiff.Status.DISABLED);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
new file mode 100644
index 0000000..44e2e89
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -0,0 +1,235 @@
+// 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.reviewdb.Patch;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.EditList;
+import org.eclipse.jgit.diff.HistogramDiff;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.diff.RawTextComparator;
+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.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.patch.FileHeader;
+import org.eclipse.jgit.patch.FileHeader.PatchType;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ PatchListLoader(GitRepositoryManager mgr) {
+ repoManager = mgr;
+ }
+
+ @Override
+ public PatchList createEntry(final PatchListKey key) throws Exception {
+ final Repository repo = repoManager.openRepository(key.projectKey);
+ try {
+ return readPatchList(key, repo);
+ } finally {
+ repo.close();
+ }
+ }
+
+ private static RawTextComparator comparatorFor(Whitespace ws) {
+ switch (ws) {
+ case IGNORE_ALL_SPACE:
+ return RawTextComparator.WS_IGNORE_ALL;
+
+ case IGNORE_SPACE_AT_EOL:
+ return RawTextComparator.WS_IGNORE_TRAILING;
+
+ case IGNORE_SPACE_CHANGE:
+ return RawTextComparator.WS_IGNORE_CHANGE;
+
+ case IGNORE_NONE:
+ default:
+ return RawTextComparator.DEFAULT;
+ }
+ }
+
+ private PatchList readPatchList(final PatchListKey key,
+ final Repository repo) throws IOException {
+ // TODO(jeffschu) correctly handle merge commits
+
+ final RawTextComparator cmp = comparatorFor(key.getWhitespace());
+ final ObjectReader reader = repo.newObjectReader();
+ try {
+ final RevWalk rw = new RevWalk(reader);
+ final RevCommit b = rw.parseCommit(key.getNewId());
+ final RevObject a = aFor(key, repo, rw, b);
+
+ if (a == null) {
+ // This is a merge commit, compared to its ancestor.
+ //
+ final PatchListEntry[] entries = new PatchListEntry[1];
+ entries[0] = newCommitMessage(cmp, repo, reader, null, b);
+ return new PatchList(a, b, true, entries);
+ }
+
+ final boolean againstParent =
+ b.getParentCount() > 0 && b.getParent(0) == a;
+
+ RevCommit aCommit;
+ RevTree aTree;
+ if (a instanceof RevCommit) {
+ aCommit = (RevCommit) a;
+ aTree = aCommit.getTree();
+ } else if (a instanceof RevTree) {
+ aCommit = null;
+ aTree = (RevTree) a;
+ } else {
+ throw new IOException("Unexpected type: " + a.getClass());
+ }
+
+ RevTree bTree = b.getTree();
+
+ final TreeWalk walk = new TreeWalk(reader);
+ walk.reset();
+ walk.setRecursive(true);
+ walk.addTree(aTree);
+ walk.addTree(bTree);
+ walk.setFilter(TreeFilter.ANY_DIFF);
+
+ DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
+ df.setRepository(repo);
+ df.setDiffComparator(cmp);
+ df.setDetectRenames(true);
+ List<DiffEntry> diffEntries = df.scan(aTree, bTree);
+
+ final int cnt = diffEntries.size();
+ final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
+ entries[0] = newCommitMessage(cmp, repo, reader, //
+ againstParent ? null : aCommit, b);
+ for (int i = 0; i < cnt; i++) {
+ FileHeader fh = df.toFileHeader(diffEntries.get(i));
+ entries[1 + i] = newEntry(aTree, fh);
+ }
+ return new PatchList(a, b, againstParent, entries);
+ } finally {
+ reader.release();
+ }
+ }
+
+ private PatchListEntry newCommitMessage(final RawTextComparator cmp,
+ final Repository db, final ObjectReader reader,
+ final RevCommit aCommit, final RevCommit bCommit) throws IOException {
+ StringBuilder hdr = new StringBuilder();
+
+ hdr.append("diff --git");
+ if (aCommit != null) {
+ hdr.append(" a/" + Patch.COMMIT_MSG);
+ } else {
+ hdr.append(" " + FileHeader.DEV_NULL);
+ }
+ hdr.append(" b/" + Patch.COMMIT_MSG);
+ hdr.append("\n");
+
+ if (aCommit != null) {
+ hdr.append("--- a/" + Patch.COMMIT_MSG + "\n");
+ } else {
+ hdr.append("--- " + FileHeader.DEV_NULL + "\n");
+ }
+ hdr.append("+++ b/" + Patch.COMMIT_MSG + "\n");
+
+ Text aText =
+ aCommit != null ? Text.forCommit(db, reader, aCommit) : Text.EMPTY;
+ Text bText = Text.forCommit(db, reader, bCommit);
+
+ byte[] rawHdr = hdr.toString().getBytes("UTF-8");
+ RawText aRawText = new RawText(aText.getContent());
+ RawText bRawText = new RawText(bText.getContent());
+ EditList edits = new HistogramDiff().diff(cmp, aRawText, bRawText);
+ FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
+ return new PatchListEntry(fh, edits);
+ }
+
+ private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader) {
+ final FileMode oldMode = fileHeader.getOldMode();
+ final FileMode newMode = fileHeader.getNewMode();
+
+ if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
+
+ if (aTree == null // want combined diff
+ || fileHeader.getPatchType() != PatchType.UNIFIED
+ || fileHeader.getHunks().isEmpty()) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
+
+ List<Edit> edits = fileHeader.toEditList();
+ if (edits.isEmpty()) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ } else {
+ return new PatchListEntry(fileHeader, edits);
+ }
+ }
+
+ private static RevObject aFor(final PatchListKey key,
+ final Repository repo, final RevWalk rw, final RevCommit b)
+ throws IOException {
+ if (key.getOldId() != null) {
+ return rw.parseAny(key.getOldId());
+ }
+
+ switch (b.getParentCount()) {
+ case 0:
+ return rw.parseAny(emptyTree(repo));
+ case 1: {
+ RevCommit r = b.getParent(0);
+ rw.parseBody(r);
+ return r;
+ }
+ default:
+ // merge commit, return null to force combined diff behavior
+ return null;
+ }
+ }
+
+ private static ObjectId emptyTree(final Repository repo) throws IOException {
+ ObjectInserter oi = repo.newObjectInserter();
+ try {
+ ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
+ oi.flush();
+ return id;
+ } finally {
+ oi.release();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 840e76f..1c2c97d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -76,8 +76,7 @@
final PatchSet patchSet = db.patchSets().get(patchSetId);
final Change change = db.changes().get(patchSet.getId().getParentKey());
final Project.NameKey projectKey = change.getProject();
- final String projectName = projectKey.get();
- repo = repoManager.openRepository(projectName);
+ repo = repoManager.openRepository(projectKey);
final RevWalk rw = new RevWalk(repo);
try {
final RevCommit src =
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 567ecfe..68e80ee 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
@@ -56,12 +56,22 @@
@WildProjectName final Project.NameKey wildProject,
final ProjectControl.AssistedFactory projectControlFactory,
@Assisted final Project project,
- @Assisted final Collection<RefRight> rights) {
+ @Assisted Collection<RefRight> rights) {
this.anonymousUser = anonymousUser;
this.projectCache = projectCache;
this.wildProject = wildProject;
this.projectControlFactory = projectControlFactory;
+ 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();
+ }
+ }
+ rights = Collections.unmodifiableCollection(rights);
+ }
+
this.project = project;
this.localRights = rights;
@@ -152,6 +162,8 @@
@Override
public boolean equals(Object o) {
+ if (o == null)
+ return false;
Grant grant = (Grant) o;
return group.equals(grant.group) && pattern.equals(grant.pattern);
}
@@ -174,9 +186,7 @@
*/
public Collection<RefRight> getAllRights(ApprovalCategory.Id action, boolean dropOverridden) {
Collection<RefRight> rights = new LinkedList<RefRight>(getLocalRights(action));
- if (action.canInheritFromWildProject()) {
- rights.addAll(filter(getInheritedRights(), action));
- }
+ rights.addAll(filter(getInheritedRights(), action));
if (dropOverridden) {
Set<Grant> grants = new HashSet<Grant>();
Iterator<RefRight> iter = rights.iterator();
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 7a27835..4c9d0a4 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
@@ -147,6 +147,11 @@
return canPerform(READ, (short) 2);
}
+ /** @return true if this user can submit merge patch sets to this ref */
+ public boolean canUploadMerges() {
+ return canPerform(READ, (short) 3);
+ }
+
/** @return true if this user can submit patch sets to this ref */
public boolean canSubmit() {
return canPerform(ApprovalCategory.SUBMIT, (short) 1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
index 88ea4d3..f79fbac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
@@ -94,6 +94,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
return getClass() == other.getClass()
&& getChildren().equals(((Predicate<?>) other).getChildren());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
index b1806b4..e3750fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
@@ -39,6 +39,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
if (getClass() == other.getClass()) {
final IntPredicate<?> p = (IntPredicate<?>) other;
return getOperator().equals(p.getOperator())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
index 9e651a3..f039e19 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
@@ -74,6 +74,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
return getClass() == other.getClass()
&& getChildren().equals(((Predicate<?>) other).getChildren());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
index 4c6e203..899fc3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
@@ -50,6 +50,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
if (getClass() == other.getClass()) {
final OperatorPredicate<?> p = (OperatorPredicate<?>) other;
return getOperator().equals(p.getOperator())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
index 08f50f4..a198745 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
@@ -94,6 +94,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
return getClass() == other.getClass()
&& getChildren().equals(((Predicate<?>) other).getChildren());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java
index 0f6f957..5de116f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/VariablePredicate.java
@@ -81,6 +81,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
if (getClass() == other.getClass()) {
final VariablePredicate<?> v = (VariablePredicate<?>) other;
return getName().equals(v.getName())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java
index 61c8ea9..48f3898 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/WildPatternPredicate.java
@@ -45,6 +45,8 @@
@Override
public boolean equals(final Object other) {
+ if (other == null)
+ return false;
if (getClass() == other.getClass()) {
final WildPatternPredicate<?> p = (WildPatternPredicate<?>) other;
return getOperator().equals(p.getOperator());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 0490127..20b777d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -92,7 +92,7 @@
}
try {
- final Repository repo = repoManager.openRepository(projectName.get());
+ final Repository repo = repoManager.openRepository(projectName);
try {
final RevWalk rw = new RevWalk(repo);
try {
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 9a266ab..a24471a 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
@@ -250,6 +250,7 @@
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"));
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 b7328b5..1fb5e02 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_47.class;
+ private static final Class<? extends SchemaVersion> C = Schema_48.class;
public static class Module extends AbstractModule {
@Override
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
index 8730b4e..e7b104c 100644
--- 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
@@ -42,21 +42,26 @@
// update system_config
final Connection connection = ((JdbcSchema) db).getConnection();
- final Statement 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);
+ 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));
+ // 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_48.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_48.java
new file mode 100644
index 0000000..4e8b94d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_48.java
@@ -0,0 +1,54 @@
+// 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/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 81b1762..cf5d264 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
@@ -248,7 +248,7 @@
assertFalse("NOT OWN " + ref, u.controlForRef(ref).isOwner());
}
- private void grant(Project.NameKey project, ApprovalCategory.Id categoryId,
+ private void grant(Project.NameKey project, ApprovalCategory.Id categoryId,
AccountGroup.Id group, String ref, int maxValue) {
grant(project, categoryId, group, ref, maxValue, maxValue);
}
@@ -284,7 +284,7 @@
private ProjectState newProjectState() {
ProjectCache projectCache = null;
- Project.NameKey wildProject = null;
+ Project.NameKey wildProject = new Project.NameKey("-- All Projects --");
ProjectControl.AssistedFactory projectControlFactory = null;
ProjectState ps =
new ProjectState(anonymousUser, projectCache, wildProject,
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 e05ce2e..ee9af13 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
@@ -205,7 +205,7 @@
} finally {
c.close();
}
- assertValueRange(ApprovalCategory.READ, -1, 1, 2);
+ assertValueRange(ApprovalCategory.READ, -1, 1, 2, 3);
}
public void testCreateSchema_ApprovalCategory_Submit() throws OrmException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
index 0d1b6f9..0d84a93 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
@@ -14,9 +14,13 @@
package com.google.gerrit.server.tools.hooks;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
import com.google.gerrit.server.util.HostPlatform;
-import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -26,6 +30,10 @@
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
import java.io.File;
import java.io.IOException;
@@ -36,23 +44,19 @@
private final String SOB1 = "Signed-off-by: J Author <ja@example.com>\n";
private final String SOB2 = "Signed-off-by: J Committer <jc@example.com>\n";
- @Override
- public void runBare() throws Throwable {
- try {
- super.runBare();
- } catch (SkipTestOnThisPlatform e) {
- System.err.println(" - Skipping " + getName() + " on this system");
- }
- }
+ @Rule public TestName test = new TestName();
- private static class SkipTestOnThisPlatform extends RuntimeException {
- }
-
- @Override
- protected void setUp() throws Exception {
+ private void skipIfWin32Platform() {
if (HostPlatform.isWin32()) {
- throw new SkipTestOnThisPlatform();
+ System.err.println(" - Skipping " + test.getMethodName() + " on this system");
+ assumeTrue(false);
}
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ skipIfWin32Platform();
super.setUp();
final Date when = author.getWhen();
@@ -65,6 +69,7 @@
committer = new PersonIdent(committer, when, tz);
}
+ @Test
public void testEmptyMessages() throws Exception {
// Empty input must yield empty output so commit will abort.
// Note we must consider different commit templates formats.
@@ -85,6 +90,7 @@
+ "new file mode 100644\nindex 0000000..c78b7f0\n");
}
+ @Test
public void testChangeIdAlreadySet() throws Exception {
// If a Change-Id is already present in the footer, the hook must
// not modify the message but instead must leave the identity alone.
@@ -106,6 +112,7 @@
"Change-Id: I4f4e2e1e8568ddc1509baecb8c1270a1fb4b6da7\n");
}
+ @Test
public void testTimeAltersId() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -125,6 +132,7 @@
call("a\n"));
}
+ @Test
public void testFirstParentAltersId() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -138,6 +146,7 @@
call("a\n"));
}
+ @Test
public void testDirCacheAltersId() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -154,6 +163,7 @@
call("a\n"));
}
+ @Test
public void testSingleLineMessages() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -179,6 +189,7 @@
call("Fix-A-Widget: this thing\n"));
}
+ @Test
public void testMultiLineMessagesWithoutFooter() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -204,6 +215,7 @@
call("a\n" + "\n" + "b\nc\nd\ne\n" + "\n" + "f\ng\nh\n"));
}
+ @Test
public void testSingleLineMessagesWithSignedOffBy() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -219,6 +231,7 @@
call("a\n" + "\n" + SOB1 + SOB2));
}
+ @Test
public void testMultiLineMessagesWithSignedOffBy() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -267,6 +280,7 @@
SOB2));
}
+ @Test
public void testNoteInMiddle() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -280,6 +294,7 @@
"does not fix it.\n"));
}
+ @Test
public void testKernelStyleFooter() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -296,6 +311,7 @@
SOB2));
}
+ @Test
public void testChangeIdAfterBugOrIssue() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -318,6 +334,7 @@
SOB1));
}
+ @Test
public void testCommitDashV() throws Exception {
assertEquals("a\n" + //
"\n" + //
@@ -335,6 +352,7 @@
"index 0000000..c78b7f0\n"));
}
+ @Test
public void testWithEndingURL() throws Exception {
assertEquals("a\n" + //
"\n" + //
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
index 1f077a7..d9b237b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
@@ -50,8 +50,11 @@
package com.google.gerrit.server.tools.hooks;
+import static org.junit.Assert.fail;
+
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
import java.io.File;
import java.net.URISyntaxException;
@@ -61,7 +64,8 @@
protected Repository repository;
@Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() throws Exception {
super.setUp();
repository = createWorkRepository();
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java
index eca28d8..a72b50e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java
@@ -93,11 +93,10 @@
private void service() throws IOException, Failure {
project = projectControl.getProjectState().getProject();
- final String name = project.getName();
try {
- repo = repoManager.openRepository(name);
+ repo = repoManager.openRepository(project.getNameKey());
} catch (RepositoryNotFoundException e) {
- throw new Failure(1, "fatal: '" + name + "': not a git archive", e);
+ throw new Failure(1, "fatal: '" + project.getName() + "': not a git archive", e);
}
try {
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 9b155db..f7d7226 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
@@ -347,6 +347,10 @@
return new UnloggedFailure(1, "fatal: " + msg);
}
+ protected UnloggedFailure die(Throwable why) {
+ return new UnloggedFailure(1, "fatal: " + why.getMessage(), why);
+ }
+
private final class TaskThunk implements CancelableRunnable, ProjectRunnable {
private final CommandRunnable thunk;
private final Context context;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java
index d99d848..eca552a 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/AdminCreateGroup.java
@@ -14,16 +14,13 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupMember;
-import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.AccountGroupName;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.PerformCreateGroup;
+import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
-import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -32,10 +29,7 @@
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;
/**
@@ -54,12 +48,6 @@
@Argument(index = 0, required = true, metaVar = "GROUP", usage = "name of group to be created")
private String groupName;
- @Inject
- private IdentifiedUser currentUser;
-
- @Inject
- private ReviewDb db;
-
private final Set<Account.Id> initialMembers = new HashSet<Account.Id>();
@Option(name = "--member", aliases = {"-m"}, metaVar = "USERNAME", usage = "initial set of users to become members of the group")
@@ -67,6 +55,9 @@
initialMembers.add(id);
}
+ @Inject
+ private PerformCreateGroup.Factory performCreateGroupFactory;
+
@Override
public void start(Environment env) throws IOException {
startThread(new CommandRunnable() {
@@ -79,37 +70,12 @@
}
private void createGroup() throws OrmException, UnloggedFailure {
- AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
- AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
- AccountGroup group = new AccountGroup(nameKey, groupId);
- if (ownerGroupId != null) {
- group.setOwnerGroupId(ownerGroupId);
- }
- if (groupDescription != null) {
- group.setDescription(groupDescription);
- }
- AccountGroupName gn = new AccountGroupName(group);
- // first insert the group name to validate that the group name hasn't already been
- // used to create another group
+ final PerformCreateGroup performCreateGroup =
+ performCreateGroupFactory.create();
try {
- db.accountGroupNames().insert(Collections.singleton(gn));
- } catch (OrmDuplicateKeyException e) {
- throw die("group '" + groupName + "' already exists");
+ performCreateGroup.createGroup(groupName, groupDescription, ownerGroupId, initialMembers.toArray(new Account.Id[initialMembers.size()]));
+ } catch (NameAlreadyUsedException e) {
+ throw die(e);
}
- db.accountGroups().insert(Collections.singleton(group));
-
- List<AccountGroupMember> memberships = new ArrayList<AccountGroupMember>();
- List<AccountGroupMemberAudit> membershipsAudit = new ArrayList<AccountGroupMemberAudit>();
- for (Account.Id accountId : initialMembers) {
- AccountGroupMember membership =
- new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId));
- memberships.add(membership);
-
- AccountGroupMemberAudit audit =
- new AccountGroupMemberAudit(membership, currentUser.getAccountId());
- membershipsAudit.add(audit);
- }
- db.accountGroupMembers().insert(memberships);
- db.accountGroupMembersAudit().insert(membershipsAudit);
}
}
\ 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 5269e43..0be02fd 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
@@ -34,6 +34,7 @@
import org.apache.sshd.server.Environment;
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;
@@ -41,6 +42,7 @@
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.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -119,6 +121,8 @@
@GerritPersonIdent
private PersonIdent serverIdent;
+ private Project.NameKey nameKey;
+
@Override
public void start(final Environment env) {
startThread(new CommandRunnable() {
@@ -130,31 +134,37 @@
try {
validateParameters();
+ nameKey = new Project.NameKey(projectName);
if (!permissionsOnly) {
- final Repository repo = repoManager.createRepository(projectName);
+ final Repository repo = repoManager.createRepository(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(projectName, projectDescription);
+ repoManager.setProjectDescription(nameKey, projectDescription);
- final Project.NameKey project = new Project.NameKey(projectName);
- rq.replicateNewProject(project, branch);
+ createProject();
+
+ rq.replicateNewProject(nameKey, branch);
if (createEmptyCommit) {
- createEmptyCommit(repo, project, branch);
+ createEmptyCommit(repo, nameKey, branch);
}
} finally {
repo.close();
}
+ } else {
+ createProject();
}
-
- createProject();
} catch (Exception e) {
p.print("Error when trying to create project: " + e.getMessage()
+ "\n");
@@ -198,12 +208,10 @@
}
private void createProject() throws OrmException {
- final Project.NameKey newProjectNameKey = new Project.NameKey(projectName);
-
List<RefRight> access = new ArrayList<RefRight>();
for (AccountGroup.Id ownerId : ownerIds) {
final RefRight.Key prk =
- new RefRight.Key(newProjectNameKey, new RefRight.RefPattern(
+ new RefRight.Key(nameKey, new RefRight.RefPattern(
RefRight.ALL), ApprovalCategory.OWN, ownerId);
final RefRight pr = new RefRight(prk);
pr.setMaxValue((short) 1);
@@ -212,7 +220,7 @@
}
db.refRights().insert(access);
- final Project newProject = new Project(newProjectNameKey);
+ final Project newProject = new Project(nameKey);
newProject.setDescription(projectDescription);
newProject.setSubmitType(submitType);
newProject.setUseContributorAgreements(contributorAgreements);
@@ -227,9 +235,9 @@
}
private void validateParameters() throws Failure {
- if (projectName.endsWith(".git")) {
- projectName =
- projectName.substring(0, projectName.length() - ".git".length());
+ if (projectName.endsWith(Constants.DOT_GIT_EXT)) {
+ projectName = projectName.substring(0, //
+ projectName.length() - Constants.DOT_GIT_EXT.length());
}
if (!CollectionsUtil.isAnyIncludedIn(currentUser.getEffectiveGroups(), projectCreatorGroups)) {
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 7618432..bb04f7a 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
@@ -170,7 +170,7 @@
private Ref getBranchRef(Project.NameKey projectName) {
try {
- final Repository r = repoManager.openRepository(projectName.get());
+ final Repository r = repoManager.openRepository(projectName);
try {
return r.getRef(showBranch);
} finally {
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 1996ce4..e31925b 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
@@ -86,6 +86,7 @@
rp.setRefLogIdent(currentUser.newRefLogIdent());
rp.setTimeout(config.getTimeout());
try {
+ receive.advertiseHistory();
rp.receive(in, out, err);
} catch (InterruptedIOException err) {
throw new Failure(128, "fatal: client IO read/write timeout", err);
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 a0b9d25..5a4e39a 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
@@ -14,10 +14,13 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.common.ChangeHookRunner;
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.gerrit.reviewdb.Branch;
+import com.google.gerrit.reviewdb.Branch.NameKey;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
@@ -26,7 +29,10 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.MergeOp;
+import com.google.gerrit.server.git.MergeOp.Factory;
import com.google.gerrit.server.git.MergeQueue;
+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;
@@ -51,6 +57,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
public class ReviewCommand extends BaseCommand {
private static final Logger log =
@@ -84,6 +91,12 @@
@Option(name = "--message", aliases = "-m", usage = "cover message to publish on change", metaVar = "MESSAGE")
private String changeComment;
+ @Option(name = "--abandon", usage = "abandon the patch set")
+ private boolean abandonChange;
+
+ @Option(name = "--restore", usage = "restore an abandoned the patch set")
+ private boolean restoreChange;
+
@Option(name = "--submit", aliases = "-s", usage = "submit the patch set")
private boolean submitChange;
@@ -106,13 +119,21 @@
private ChangeControl.Factory changeControlFactory;
@Inject
- private FunctionState.Factory functionStateFactory;
+ private AbandonedSender.Factory abandonedSenderFactory;
- private List<ApproveOption> optionList;
+ @Inject
+ private FunctionState.Factory functionStateFactory;
@Inject
private PublishComments.Factory publishCommentsFactory;
+ @Inject
+ private ChangeHookRunner hooks;
+
+ private List<ApproveOption> optionList;
+
+ private Set<PatchSet.Id> toSubmit = new HashSet<PatchSet.Id>();
+
@Override
public final void start(final Environment env) {
startThread(new CommandRunnable() {
@@ -120,6 +141,14 @@
public void run() throws Failure {
initOptionList();
parseCommandLine();
+ if (abandonChange) {
+ if (restoreChange) {
+ throw error("abandon and restore actions are mutually exclusive");
+ }
+ if (submitChange) {
+ throw error("abandon and submit actions are mutually exclusive");
+ }
+ }
boolean ok = true;
for (final PatchSet.Id patchSetId : patchSetIds) {
@@ -135,20 +164,52 @@
log.error("internal error while approving " + patchSetId, e);
}
}
+
if (!ok) {
throw new UnloggedFailure(1, "one or more approvals failed;"
+ " review output above");
}
+
+ if (!toSubmit.isEmpty()) {
+ final Set<Branch.NameKey> toMerge = new HashSet<Branch.NameKey>();
+ try {
+ for (PatchSet.Id patchSetId : toSubmit) {
+ ChangeUtil.submit(patchSetId, currentUser, db, opFactory,
+ new MergeQueue() {
+ @Override
+ public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
+ toMerge.add(branch);
+ }
+
+ @Override
+ public void schedule(Branch.NameKey branch) {
+ toMerge.add(branch);
+ }
+
+ @Override
+ public void recheckAfter(Branch.NameKey branch, long delay,
+ TimeUnit delayUnit) {
+ toMerge.add(branch);
+ }
+ });
+ }
+ for (Branch.NameKey branch : toMerge) {
+ merger.merge(opFactory, branch);
+ }
+ } catch (OrmException updateError) {
+ throw new Failure(1, "one or more submits failed", updateError);
+ }
+ }
}
});
}
private void approveOne(final PatchSet.Id patchSetId)
- throws NoSuchChangeException, UnloggedFailure, OrmException {
+ throws NoSuchChangeException, UnloggedFailure, OrmException,
+ EmailException {
final Change.Id changeId = patchSetId.getParentKey();
- final ChangeControl changeControl =
- changeControlFactory.validateFor(changeId);
+ ChangeControl changeControl = changeControlFactory.validateFor(changeId);
if (changeComment == null) {
changeComment = "";
@@ -165,12 +226,33 @@
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 (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) {
CanSubmitResult result =
changeControl.canSubmit(patchSetId, db, approvalTypes,
functionStateFactory);
if (result == CanSubmitResult.OK) {
- ChangeUtil.submit(opFactory, patchSetId, currentUser, db, merger);
+ toSubmit.add(patchSetId);
} else {
throw error(result.getMessage());
}
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 bcc9d19..95b33f7 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
@@ -42,7 +42,7 @@
final UploadPack up = new UploadPack(repo);
if (!projectControl.allRefsAreVisible()) {
- up.setRefFilter(new VisibleRefFilter(repo, projectControl, db.get()));
+ up.setRefFilter(new VisibleRefFilter(repo, projectControl, db.get(), true));
}
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 87cfaa5..2d85b74 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -99,54 +99,6 @@
</dependency>
</dependencies>
- <profiles>
- <profile>
- <id>include-documentation-profile</id>
- <activation>
- <property>
- <name>gerrit.include-documentation</name>
- </property>
- </activation>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-antrun-plugin</artifactId>
- <executions>
- <execution>
- <id>include-documentation</id>
- <phase>process-classes</phase>
- <configuration>
- <tasks>
- <property name="src" location="${basedir}/../Documentation" />
- <property name="out" location="${project.build.directory}/${project.build.finalName}" />
- <property name="dst" location="${out}/Documentation" />
-
- <exec dir="${src}" executable="make">
- <arg value="VERSION=${project.version}" />
- <arg value="clean" />
- <arg value="all" />
- </exec>
-
- <mkdir dir="${dst}" />
- <copy overwrite="true" todir="${dst}">
- <fileset dir="${src}">
- <include name="*.html" />
- </fileset>
- </copy>
- </tasks>
- </configuration>
- <goals>
- <goal>run</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- </profiles>
-
<build>
<plugins>
<plugin>
@@ -183,17 +135,44 @@
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
- <id>fix-output</id>
+ <id>copy-license</id>
<phase>process-classes</phase>
<configuration>
- <tasks>
+ <target>
<property name="src" location="${basedir}/../Documentation" />
<property name="dst" location="${project.build.directory}/${project.build.finalName}" />
<copy tofile="${dst}/LICENSES.txt"
file="${src}/licenses.txt"
overwrite="true" />
- </tasks>
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>include-documentation</id>
+ <phase>process-classes</phase>
+ <configuration>
+ <target if="gerrit.include-documentation">
+ <property name="src" location="${basedir}/../Documentation" />
+ <property name="out" location="${project.build.directory}/${project.build.finalName}" />
+ <property name="dst" location="${out}/Documentation" />
+
+ <exec dir="${src}" executable="make">
+ <arg value="VERSION=${project.version}" />
+ <arg value="clean" />
+ <arg value="all" />
+ </exec>
+
+ <mkdir dir="${dst}" />
+ <copy overwrite="true" todir="${dst}">
+ <fileset dir="${src}">
+ <include name="*.html" />
+ </fileset>
+ </copy>
+ </target>
</configuration>
<goals>
<goal>run</goal>
diff --git a/pom.xml b/pom.xml
index b02d803..ee8ad68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,14 +46,14 @@
</issueManagement>
<properties>
- <jgitVersion>0.9.3.133-gaa09599</jgitVersion>
+ <jgitVersion>0.11.3.219-gefad732</jgitVersion>
<gwtormVersion>1.1.4</gwtormVersion>
- <gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
+ <gwtjsonrpcVersion>1.2.3</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.2</gwtexpuiVersion>
- <gwtVersion>2.0.4</gwtVersion>
+ <gwtVersion>2.1.1</gwtVersion>
<slf4jVersion>1.6.1</slf4jVersion>
<guiceVersion>2.0</guiceVersion>
- <jettyVersion>7.0.2.v20100331</jettyVersion>
+ <jettyVersion>7.2.1.v20101111</jettyVersion>
<keyappletVersion>1.0</keyappletVersion>
<gwt.soyc>false</gwt.soyc>
@@ -319,25 +319,25 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>2.0.2</version>
+ <version>2.3.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
- <version>1.2</version>
+ <version>1.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
- <version>1.3</version>
+ <version>1.6</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
- <version>2.1-beta-1</version>
+ <version>2.1.1</version>
</plugin>
<plugin>
@@ -349,19 +349,19 @@
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr3-maven-plugin</artifactId>
- <version>3.1.1</version>
+ <version>3.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
- <version>1.2</version>
+ <version>2.1.0-1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
- <version>1.3</version>
+ <version>1.6</version>
</plugin>
</plugins>
</pluginManagement>
@@ -380,6 +380,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
<executions>
<execution>
<goals>
@@ -482,6 +483,12 @@
</dependency>
<dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jsch</artifactId>
+ <version>0.1.44</version>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.6.4</version>
@@ -527,7 +534,7 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
- <version>1.4</version>
+ <version>1.6</version>
</dependency>
<dependency>
@@ -579,7 +586,7 @@
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr</artifactId>
- <version>3.1.1</version>
+ <version>3.2</version>
<exclusions>
<exclusion>
<groupId>org.antlr</groupId>