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>