Merge "Add OpenID SSO support."
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 5522239..4c64dfe 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -76,6 +76,7 @@
 	@$(ASCIIDOC) -a toc \
 		-a data-uri \
 		-a 'revision=$(REVISION)' \
+		-a 'newline=\n' \
 		-b xhtml11 \
 		-f asciidoc.conf \
 		$(ASCIIDOC_EXTRA) \
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 78acbd2..7027562 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -452,6 +452,9 @@
 to projects in Gerrit. It can give permission to abandon a specific
 change to a given ref.
 
+This also grants the permission to restore a change if the change
+can be uploaded.
+
 [[category_create]]
 Create reference
 ~~~~~~~~~~~~~~~~
@@ -1078,6 +1081,15 @@
 either link:cmd-create-project.html[create new git projects via ssh]
 or via the web UI.
 
+[[capability_emailReviewers]]
+Email Reviewers
+~~~~~~~~~~~~~~~
+
+Allow or deny sending email to change reviewers and watchers.  This can be used
+to deny build bots from emailing reviewers and people who watch the change.
+Instead, only the authors of the change and those who starred it will be
+emailed.  The allow rules are evaluated before deny rules, however the default
+is to allow emailing, if no explicit rule is matched.
 
 [[capability_flushCaches]]
 Flush Caches
diff --git a/Documentation/cmd-cherry-pick.txt b/Documentation/cmd-cherry-pick.txt
index 568c872..d051a9a 100644
--- a/Documentation/cmd-cherry-pick.txt
+++ b/Documentation/cmd-cherry-pick.txt
@@ -39,7 +39,7 @@
 ====
   $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
 
-  $ curl http://review.example.com/tools/bin/gerrit-cherry-pick
+  $ curl -o ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
 ====
 
 GERRIT
diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt
index 475d2c5..8d404ec 100644
--- a/Documentation/cmd-create-group.txt
+++ b/Documentation/cmd-create-group.txt
@@ -53,6 +53,13 @@
 --member::
 	User name to become initial member of the group.  Multiple --member
 	options may be specified to add more initial members.
++
+Trying to add a user that doesn't have an account in Gerrit fails,
+unless LDAP is used for authentication. If LDAP is used for
+authentication and the user is not found, Gerrit tries to authenticate
+the user against the LDAP backend. If the authentication is successful
+a user account is automatically created, so that the user can be added
+to the group.
 
 --group::
 	Group name to include in the group.  Multiple --group options may
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 936729e..c4f222b 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -12,8 +12,8 @@
   $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
   $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
 
-  $ curl http://review.example.com/tools/bin/gerrit-cherry-pick
-  $ curl http://review.example.com/tools/hooks/commit-msg
+  $ curl -o ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
+  $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 
 For more details on how to determine the correct SSH port number,
 see link:user-upload.html#test_ssh[Testing Your SSH Connection].
@@ -105,6 +105,9 @@
 link:cmd-create-project.html[gerrit create-project]::
 	Create a new project and associated Git repository.
 
+link:cmd-set-project.html[gerrit set-project]::
+    Change a project's settings.
+
 link:cmd-flush-caches.html[gerrit flush-caches]::
 	Flush some/all server caches from memory.
 
diff --git a/Documentation/cmd-ls-groups.txt b/Documentation/cmd-ls-groups.txt
index 8564db2..a50657b 100644
--- a/Documentation/cmd-ls-groups.txt
+++ b/Documentation/cmd-ls-groups.txt
@@ -30,6 +30,12 @@
 ---------
 This command is intended to be used in scripts.
 
+All non-printable characters (ASCII value 31 or less) are escaped
+according to the conventions used in languages like C, Python, and Perl,
+employing standard sequences like `\n` and `\t`, and `\xNN` for all
+others. In shell scripts, the `printf` command can be used to unescape
+the output.
+
 OPTIONS
 -------
 --project::
@@ -65,10 +71,19 @@
 +
 --
 `internal`:: Any group defined within Gerrit.
-`ldap`:: Any group defined by an external LDAP database.
 `system`:: Any system defined and managed group.
 --
 
+--verbose::
+-v::
+	Enable verbose output with tab-separated columns for the
+	group name, UUID, description, type (`SYSTEM` or `INTERNAL`),
+	owner group name, owner group UUID and whether the group is
+	visible to all (`true` or `false`).
++
+If a group has been "orphaned", i.e. its owner group UUID refers to a
+nonexistent group, the owner group name field will read `n/a`.
+
 EXAMPLES
 --------
 
@@ -91,6 +106,23 @@
 	Registered Users
 =====
 
+Extract the UUID of the 'Administrators' group:
+
+=====
+	$ ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $2}'
+	ad463411db3eec4e1efb0d73f55183c1db2fd82a
+=====
+
+Extract and expand the multi-line description of the 'Administrators'
+group:
+
+=====
+	$ printf "$(ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $3}')\n"
+	This is a
+	multi-line
+	description.
+=====
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index ac613e5..eed6902 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -52,7 +52,7 @@
 
 --force-message::
 	Option which allows Gerrit to publish the --message, even
-	when the labels could not be applied due to change being
+	when the labels could not be applied due to the change being
 	closed).
 +
 Used by some scripts/CI-systems, where the results (or links
@@ -69,11 +69,11 @@
 	complete listing of supported approval categories and values.
 
 --abandon::
-	Abandon the specified patch set(s).
+	Abandon the specified change(s).
 	(option is mutually exclusive with --submit and --restore)
 
 --restore::
-	Restore the specified abandoned patch set(s).
+	Restore the specified abandoned change(s).
 	(option is mutually exclusive with --abandon)
 
 --submit::
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
new file mode 100644
index 0000000..c4b9b4f
--- /dev/null
+++ b/Documentation/cmd-set-project.txt
@@ -0,0 +1,111 @@
+gerrit set-project
+==================
+
+NAME
+----
+gerrit set-project - Change a project's settings.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit set-project'
+  [--description <DESC> | -d <DESC>]
+  [--submit-type <TYPE> |  -t <TYPE>]
+  [--use|no-contributor-agreements | --ca|nca]
+  [--use|no-signed-off-by | --so|nso]
+  [--use|no-content-merge]
+  [--require|no-change-id | --id|nid]
+  [--project-state | --ps]
+  <NAME>
+
+DESCRIPTION
+-----------
+Modifies a given project's settings. This command can be useful to
+batch change projects.
+
+The command is argument-safe, that is, if no argument is given the
+previous settings are kept intact.
+
+ACCESS
+------
+Caller must be a member of the privileged 'Administrators' group.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+<NAME>::
+    Required; name of the project to edit.  If name ends
+    with `.git` the suffix will be automatically removed.
+
+--description::
+-d::
+    New description of the project.  If not specified,
+    the old description is kept.
++
+Description values containing spaces should be quoted in single quotes
+(').  This most likely requires double quoting the value, for example
+`--description "'A description string'"`.
+
+--submit-type::
+-t::
+    Action used by Gerrit to submit an approved change to its
+    destination branch.  Supported options are:
++
+* FAST_FORWARD_ONLY: produces a strictly linear history.
+* MERGE_IF_NECESSARY: create a merge commit when required.
+* MERGE_ALWAYS: always create a merge commit.
+* CHERRY_PICK: always cherry-pick the commit.
+
++
+For more details see
+link:project-setup.html#submit_type[Change Submit Actions].
+
+--use|no-content-merge::
+    If enabled, Gerrit will try to perform a 3-way merge of text
+    file content when a file has been modified by both the
+    destination branch and the change being submitted.  This
+    option only takes effect if submit type is not
+    FAST_FORWARD_ONLY.
+
+--use|no-contributor-agreements::
+--ca|nca::
+    If enabled, authors must complete a contributor agreement
+    on the site before pushing any commits or changes to this
+    project.
+
+--use|no-signed-off-by::
+--so|nso:
+    If enabled, each change must contain a Signed-off-by line
+    from either the author or the uploader in the commit message.
+
+--require|no-change-id::
+--id|nid::
+    Require a valid link:user-changeid.html[Change-Id] footer
+    in any commit uploaded for review. This does not apply to
+    commits pushed directly to a branch or tag.
+
+--project-state::
+--ps::
+    Set project's visibility.
++
+* ACTIVE: project is regular and is the default value.
+* READ_ONLY: users can see the project if read permission
+is granted, but all modification operations are disabled.
+* HIDDEN: the project is not visible for those who are not owners
+
+EXAMPLES
+--------
+Change project `example` to be hidden, require change id, don't use content merge
+and use 'merge if necessary' as merge strategy:
+
+====
+    $ ssh -p 29418 review.example.com gerrit set-project example --submit-type MERGE_IF_NECESSARY\
+    --require-change-id --no-content-merge --project-state HIDDEN
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
\ No newline at end of file
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index b3c6037..0fb27cc 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -43,8 +43,8 @@
 The JSON messages consist of nested objects referencing the *change*,
 *patchSet*, *account* involved, and other attributes as appropriate.
 The currently supported message types are *patchset-created*,
-*change-abandoned*, *change-restored*, *change-merged*,
-*comment-added* and *ref-updated*.
+*draft-published*, *change-abandoned*, *change-restored*,
+*change-merged*, *comment-added* and *ref-updated*.
 
 Note that any field may be missing in the JSON messages, so consumers of
 this JSON stream should deal with that appropriately.
@@ -61,6 +61,16 @@
 
 uploader:: link:json.html#account[account attribute]
 
+Draft Published
+^^^^^^^^^^^^^^^
+type:: "draft-published"
+
+change:: link:json.html#change[change attribute]
+
+patchset:: link:json.html#patchset[patchset attribute]
+
+uploader:: link:json.html#account[account attribute]
+
 Change Abandoned
 ^^^^^^^^^^^^^^^^
 type:: "change-abandoned"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8c291c7..74f5d82 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -332,6 +332,18 @@
 +
 By default this is set to false.
 
+[[auth.gitBasicAuth]]auth.gitBasicAuth::
++
+If true then Git over HTTP and HTTP/S traffic is authenticated using
+standard BasicAuth and credentials validated using the same auth
+method configured for Gerrit Web UI.
++
+This parameter only affects git over http traffic. If set to false
+then Gerrit will authenticate through DIGEST authentication and
+the randomly generated HTTP password in Gerrit DB.
++
+By default this is set to false.
+
 [[auth.userNameToLowerCase]]auth.userNameToLowerCase::
 +
 If set the username that is received to authenticate a git operation
@@ -867,6 +879,15 @@
 Default on JGit is false. Although potentially slower, it yields
 much more predictable behavior.
 
+[[core.asyncLoggingBufferSize]]core.asyncLoggingBufferSize::
++
+Size of the buffer to store logging events for asynchronous logging.
+Putting a larger value can protect threads from stalling when the
+AsyncAppender threads are not fast enough to consume the logging events
+from the buffer. It also protects from loosing log entries in this case.
++
+Default is 64 entries.
+
 [[database]]Section database
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -1193,6 +1214,11 @@
 Optional filename for the patchset created hook, if not specified then
 `patchset-created` will be used.
 
+[[hooks.draftPublishedHook]]hooks.draftPublishedHook::
++
+Optional filename for the draft published hook, if not specified then
+`draft-published` will be used.
+
 [[hooks.commentAddedHook]]hooks.commentAddedHook::
 +
 Optional filename for the comment added hook, if not specified then
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index ec45837..a5415a9 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -24,12 +24,21 @@
 ~~~~~~~~~~~~~~~~
 
 This is called whenever a patchset is created (this includes new
-changes).
+changes and drafts).
 
 ====
   patchset-created --change <change id> --change-url <change url> --project <project name> --branch <branch> --uploader <uploader> --commit <sha1> --patchset <patchset id>
 ====
 
+draft-published
+~~~~~~~~~~~~~~~
+
+This is called whenever a draft change is published.
+
+====
+  draft-published --change <change id> --change-url <change url> --project <project name> --branch <branch> --uploader <uploader> --commit <sha1> --patchset <patchset id>
+====
+
 comment-added
 ~~~~~~~~~~~~~
 
@@ -94,8 +103,9 @@
 Gerrit will use the value of hooks.path for the hooks directory.
 
 For the hook filenames, Gerrit will use the values of hooks.patchsetCreatedHook,
-hooks.commentAddedHook, hooks.changeMergedHook, hooks.changeAbandonedHook,
-hooks.changeRestoredHook, hooks.refUpdatedHook and hooks.claSignedHook.
+hooks.draftPublishedHook, hooks.commentAddedHook, hooks.changeMergedHook,
+hooks.changeAbandonedHook, hooks.changeRestoredHook, hooks.refUpdatedHook and
+hooks.claSignedHook.
 
 Missing Change URLs
 -------------------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 586ae07..7832aa9 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -192,7 +192,7 @@
 by PrintHello class will be available to users as:
 
 ----
-$ ssh -P 29418 review.example.com helloworld print
+$ ssh -p 29418 review.example.com helloworld print
 ----
 
 HTTP Servlets
@@ -312,7 +312,7 @@
   the plugin from this location to its own site path.
 +
 ----
-$ ssh -P 29418 localhost gerrit plugin install -n name $(pwd)/my-plugin.jar
+$ ssh -p 29418 localhost gerrit plugin install -n name $(pwd)/my-plugin.jar
 ----
 
 * Valid URL, including any HTTP or FTP site reachable by the
@@ -320,14 +320,14 @@
   its own site path.
 +
 ----
-$ ssh -P 29418 localhost gerrit plugin install -n name http://build-server/output/our-plugin.jar
+$ ssh -p 29418 localhost gerrit plugin install -n name http://build-server/output/our-plugin.jar
 ----
 
 * As piped input to the plugin install command. The server will
   copy input until EOF, and save a copy under its own site path.
 +
 ----
-$ ssh -P 29418 localhost gerrit plugin install -n name - <target/name-0.1.jar
+$ ssh -p 29418 localhost gerrit plugin install -n name - <target/name-0.1.jar
 ----
 
 Plugins can also be copied directly into the server's
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index b0607fd..f686d0c 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -60,15 +60,15 @@
 
 * Deploy the snapshot:
 
-----
+====
   mvn deploy
-----
+====
 
 
 Making a Gerrit Subproject Release
 ----------------------------------
 
-* First deploy (and test) the latest snapshot for this subprojects
+* First deploy (and test) the latest snapshot for the subproject
 
 * Update the top level pom.xml in the subproject to reflect the new project
 version (the exact value of the tag you will create below)
@@ -77,8 +77,18 @@
 
 * Tag the version you just pushed (and push the tag)
 
+====
+ git tag -a -m "prolog-cafe 1.3" v1.3
+ git push gerrit-review refs/tags/v1.3:refs/tags/v1.3
+====
+
 * Deploy the new release:
 
-----
+====
  mvn deploy
-----
+====
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 21dc1c7..e3a93cc 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -130,8 +130,8 @@
 * Push the New Tag
 
 ====
- git push google refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
- git push google refs/tags/v2.5:refs/tags/v2.5
+ git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
+ git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
 ====
 
 
@@ -192,8 +192,7 @@
 Mailing List
 ~~~~~~~~~~~~
 
-* Send an email to the mailing list to announce the release
-* Consider including some or all of the following in the email:
+* Send an email to the mailing list to announce the release, consider including some or all of the following in the email:
 ** A link to the release and the release notes (if a final release)
 ** A link to the docs
 ** Describe the type of release (stable, bug fix, RC)
@@ -223,6 +222,23 @@
 -Martin
 ----
 
+* Add an entry to the NEWS section of the main Gerrit project web page
+** Go to: http://code.google.com/p/gerrit/admin
+** Add entry like:
+----
+ * Jun 14, 2012 - Gerrit 2.4.1 [https://groups.google.com/d/topic/repo-discuss/jHg43gixqzs/discussion Released]
+----
+
+* Update the new discussion group announcement to be sticky
+** Go to: http://groups.google.com/group/repo-discuss/topics
+** Click on the announcement thread
+** Near the top right, click on options
+** Under options, cick the "Display this top first" checkbox
+** and Save
+
+* Update the previous discussion group announcement to no longer be sticky
+** See above (unclick checkbox)
+
 
 Merging Stable Fixes to master
 ------------------------------
@@ -237,3 +253,8 @@
  git branch -f stable origin/stable
  git merge stable
 ====
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/error-change-closed.txt b/Documentation/error-change-closed.txt
index 74e7c48..3244fb3 100644
--- a/Documentation/error-change-closed.txt
+++ b/Documentation/error-change-closed.txt
@@ -1,8 +1,11 @@
 change ... closed
 =================
 
-With this error message Gerrit rejects to push a commit to a change
-that is already closed.
+With this error message Gerrit rejects to push a commit or submit a
+review label (approval) to a change that is already closed.
+
+When Pushing a Commit
+---------------------
 
 This error occurs if you are trying to push a commit that contains
 the Change-Id of a closed change in its commit message. A change can
@@ -24,6 +27,14 @@
 'Restore Change' button). Afterwards the push should succeed and a
 new patch set for this change will be created.
 
+When Submitting a Review Label
+------------------------------
+
+This error occurs if you are trying to submit a review label (approval) using
+the link:cmd-review.html[ssh review command] after the change has been closed.
+A change can be closed because it was submitted and merged, because it was abandoned,
+or because the patchset to which you are submitting the review has been replaced
+by a newer patchset.
 
 GERRIT
 ------
diff --git a/Documentation/error-you-are-not-author.txt b/Documentation/error-invalid-author.txt
similarity index 92%
rename from Documentation/error-you-are-not-author.txt
rename to Documentation/error-invalid-author.txt
index 13de5d8..c484776 100644
--- a/Documentation/error-you-are-not-author.txt
+++ b/Documentation/error-invalid-author.txt
@@ -1,10 +1,10 @@
-you are not author ...
-======================
+invalid author
+==============
 
 For every pushed commit Gerrit verifies that the e-mail address of
 the author matches one of the registered e-mail addresses of the
 pushing user. If this is not the case pushing the commit fails with
-the error message "you are not author ...". This policy can be
+the error message "invalid author". This policy can be
 bypassed by having the access right
 link:access-control.html#category_forge_author['Forge Author'].
 
@@ -17,8 +17,8 @@
 Incorrect configuration of the e-mail address on client or server side
 ----------------------------------------------------------------------
 
-If pushing to Gerrit fails with the error message "you are not
-author ..." and you are the author of the commit for which the push
+If pushing to Gerrit fails with the error message "invalid author"
+and you are the author of the commit for which the push
 fails, then either you have not successfully registered this e-mail
 address for your Gerrit account or the author information of the
 pushed commit is incorrect.
@@ -131,8 +131,8 @@
 Missing privileges to push commits of other users
 -------------------------------------------------
 
-If pushing to Gerrit fails with the error message "you are not
-author ..." and somebody else is author of the commit for which the
+If pushing to Gerrit fails with the error message "invalid author"
+and somebody else is author of the commit for which the
 push fails, then you have no permissions to forge the author
 identity. In this case you may contact the project owner to request
 the access right '+1 Forge Author Identity' in the 'Forge Identity'
diff --git a/Documentation/error-you-are-not-committer.txt b/Documentation/error-invalid-committer.txt
similarity index 90%
rename from Documentation/error-you-are-not-committer.txt
rename to Documentation/error-invalid-committer.txt
index 57229ba..447064e 100644
--- a/Documentation/error-you-are-not-committer.txt
+++ b/Documentation/error-invalid-committer.txt
@@ -1,10 +1,10 @@
-you are not committer ...
-=========================
+invalid committer
+=================
 
 For every pushed commit Gerrit verifies that the e-mail address of
 the committer matches one of the registered e-mail addresses of the
 pushing user. If this is not the case pushing the commit fails with
-the error message "you are not committer ...". This policy can be
+the error message "invalid committer". This policy can be
 bypassed by having the access right
 link:access-control.html#category_forge_committer['Forge Committer'].
 
@@ -19,8 +19,8 @@
 Incorrect configuration of the e-mail address on client or server side
 ----------------------------------------------------------------------
 
-If pushing to Gerrit fails with the error message "you are not
-committer ..." and you committed the change for which the push fails,
+If pushing to Gerrit fails with the error message "invalid committer"
+and you committed the change for which the push fails,
 then either you have not successfully registered this e-mail address
 for your Gerrit account or the committer information of the pushed
 commit is incorrect.
@@ -96,8 +96,8 @@
 Missing privileges to push commits that were committed by other users
 ---------------------------------------------------------------------
 
-If pushing to Gerrit fails with the error message "you are not
-committer ..." and somebody else committed the change for which the
+If pushing to Gerrit fails with the error message "invalid committer"
+and somebody else committed the change for which the
 push fails, then you have no permissions to forge the committer
 identity. In this case you may contact the project owner to request
 the access right '+2 Forge Committer or Tagger Identity' in the
diff --git a/Documentation/error-messages.txt b/Documentation/error-messages.txt
index f915a14..4b62795 100644
--- a/Documentation/error-messages.txt
+++ b/Documentation/error-messages.txt
@@ -33,8 +33,8 @@
 * link:error-squash-commits-first.html[squash commits first]
 * link:error-upload-denied.html[Upload denied for project \'...']
 * link:error-not-allowed-to-upload-merges.html[you are not allowed to upload merges]
-* link:error-you-are-not-author.html[you are not author ...]
-* link:error-you-are-not-committer.html[you are not committer ...]
+* link:error-invalid-author.html[invalid author]
+* link:error-invalid-committer.html[invalid committer]
 
 
 General Hints
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 2b53772..c99d26c 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -48,6 +48,7 @@
 * link:dev-readme.html[Developer Setup]
 * link:dev-eclipse.html[Eclipse Setup]
 * link:dev-contributing.html[Contributing to Gerrit]
+* link:dev-plugins.html[Developing Plugins]
 * link:dev-design.html[System Design]
 * link:i18n-readme.html[i18n Support]
 * link:dev-release.html[Developer Release]
diff --git a/Documentation/install-quick.txt b/Documentation/install-quick.txt
index ce35484..c09c197 100644
--- a/Documentation/install-quick.txt
+++ b/Documentation/install-quick.txt
@@ -87,14 +87,28 @@
 When the init is complete, you can review your settings in the
 file `'$site_path/etc/gerrit.config'`.
 
-An important setting is the canonicalWebUrl which will
-be needed later to access Gerrit's web interface.
+Note that initialization also starts the server.  If any settings changes are
+made, the server must be restarted before they will take effect.
 
 ----
-  gerrit2@host:~$ cat ~/gerrit_testsite/etc/gerrit.config | grep canonical
-  canonicalWebUrl = http://localhost:8080/
+  gerrit2@host:~$ ~/gerrit_testsite/bin/gerrit.sh restart
+  Stopping Gerrit Code Review: OK
+  Starting Gerrit Code Review: OK
   gerrit2@host:~$
 ----
+
+The server can be also stopped and started by passing the `stop` and `start`
+commands to gerrit.sh.
+
+----
+  gerrit2@host:~$ ~/gerrit_testsite/bin/gerrit.sh stop
+  Stopping Gerrit Code Review: OK
+  gerrit2@host:~$
+  gerrit2@host:~$ ~/gerrit_testsite/bin/gerrit.sh start
+  Starting Gerrit Code Review: OK
+  gerrit2@host:~$
+----
+
 [[usersetup]]
 The first user
 --------------
@@ -154,15 +168,32 @@
 Registering your key in Gerrit
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Open a browser and enter the canonical url you got before when
-initializing Gerrit.
+Open a browser and enter the canonical url of your Gerrit server.  You can
+find the url in the settings file.
 
 ----
-  Canonical URL                [http://localhost:8080/]:
+  gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config gerrit.canonicalWebUrl
+  http://localhost:8080/
+  gerrit2@host:~$
 ----
 
 Register a new account in Gerrit through the web interface with the
 email address of your choice.
+
+The default authentication type is OpenID.  If your Gerrit server is behind a
+proxy, and you are using an external OpenID provider, you will need to add the
+proxy settings in the configuration file.
+
+----
+  gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config --add http.proxy http://proxy:8080
+  gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config --add http.proxyUsername username
+  gerrit2@host:~$ git config -f ~/gerrit_testsite/etc/gerrit.config --add http.proxyPassword password
+----
+
+Refer to the Gerrit configuration guide for more detailed information about
+link:config-gerrit.html#auth[authentication] and
+link:config-gerrit.html#http.proxy[proxy] settings.
+
 The first user to sign-in and register an account will be
 automatically placed into the fully privileged Administrators group,
 permitting server management over the web and over SSH.  Subsequent
diff --git a/Documentation/json.txt b/Documentation/json.txt
index fac1424..32bfed5 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -76,6 +76,8 @@
 
 email:: User's preferred email address.
 
+username:: User's username, if configured.
+
 [[patchSet]]
 patchSet
 --------
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index 409bb32..a3015e1 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -46,11 +46,13 @@
 Gerrit Code Review provides a standard 'commit-msg' hook which
 can be installed in the local Git repository to automatically
 create and insert a unique Change-Id line during `git commit`.
-To install the hook, copy it from Gerrit's daemon:
+To install the hook, copy it from Gerrit's daemon by executing
+one of the following commands while being in the root directory
+of the local Git repository:
 
   $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
 
-  $ curl http://review.example.com/tools/hooks/commit-msg
+  $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 
 For more details, see link:cmd-hook-commit-msg.html[commit-msg].
 
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.2.txt b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
new file mode 100644
index 0000000..db5d750
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
@@ -0,0 +1,24 @@
+Release notes for Gerrit 2.2.2.2
+================================
+
+Gerrit 2.2.2.2 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.2.2.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.2.2.war]
+
+There are no schema changes from 2.2.2, or 2.2.2.1.
+
+However, if upgrading from anything earlier, follow the upgrade
+procedure in the 2.2.2 link:ReleaseNotes-2.2.2.html[ReleaseNotes].
+
+Security Fixes
+--------------
+* Some access control sections may be ignored
++
+Gerrit sometimes ignored an access control section in a project
+if the exact same section name appeared in All-Projects. The bug
+required an unrelated project to have access.inheritFrom set to
+All-Projects and be accessed before the project that has the same
+section name as All-Projects. This is an unlikely scenario for
+most servers, as Gerrit does not normally set inheritFrom equal to
+All-Projects. The usual behavior is to not supply this property in
+project.config, and permit the implicit inheritence to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.3.1.txt b/ReleaseNotes/ReleaseNotes-2.3.1.txt
new file mode 100644
index 0000000..324a3c1
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.3.1.txt
@@ -0,0 +1,24 @@
+Release notes for Gerrit 2.3.1
+==============================
+
+Gerrit 2.3.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.3.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.3.1.war]
+
+There are no schema changes from 2.3.
+
+However, if upgrading from anything earlier, follow the upgrade
+procedure in the 2.3 link:ReleaseNotes-2.3.html[ReleaseNotes].
+
+Security Fixes
+--------------
+* Some access control sections may be ignored
++
+Gerrit sometimes ignored an access control section in a project
+if the exact same section name appeared in All-Projects. The bug
+required an unrelated project to have access.inheritFrom set to
+All-Projects and be accessed before the project that has the same
+section name as All-Projects. This is an unlikely scenario for
+most servers, as Gerrit does not normally set inheritFrom equal to
+All-Projects. The usual behavior is to not supply this property in
+project.config, and permit the implicit inheritence to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.4.1.txt b/ReleaseNotes/ReleaseNotes-2.4.1.txt
new file mode 100644
index 0000000..15dc1d3
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.4.1.txt
@@ -0,0 +1,55 @@
+Release notes for Gerrit 2.4.1
+==============================
+
+Gerrit 2.4.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.1.war]
+
+
+There are no schema changes from 2.4.  However, if upgrading from
+anything but 2.4, follow the upgrade procedure in the 2.4
+link:ReleaseNotes-2.4.html[ReleaseNotes].
+
+
+Bug Fixes
+---------
+* Catch all exceptions when async emailing
++
+This fixes email notification issues reported
+link:https://groups.google.com/group/repo-discuss/browse_thread/thread/dd157ebc55b962ef/652822d6fbe61e71[here].
+
+* Fixed cleanup of propagated SshScopes
++
+This improves error reporting in case of email notification errors.
+
+* issue 1394 Fix lookup of the 'Commit Message' file in patch set
++
+There is an assumption that the commit message is always first in the list of
+files of a patch set. However, there was another place in Gerrit code, which
+did binary search through the list of the files, without taking this assumption
+into account. In case when a patch set contained a file which lexicographically
+sorted before '/COMMIT_MSG' (like '.gitignore' for example) it could have
+happened that the commit message was not found and, as a side effect, it wasn't
+possible to review it.
+
+* issue 1162 Fix deadlock on destroy of CommandFactoryProvider
+
+* Honor the sendmail.smtpUser from gerrit.config on upgrade
++
+If sendmail.smtpUser was not present in the gerrit.config then don't set it in
+site upgrade.
+
+* issue 1420 Forge committer bypassed
++
+It was possible to forge committer even without having permission for that.
+This was a regression from 2.3.
+
+* Make sure the "Object too large..." error message is printed when an object
+larger than receive.maxObjectSizeLimit is rejected by Gerrit
+
+* Display proper error if file diff fails because content is too large
+
+* Get around a log4j bug that causes AsyncAppender-Dispatcher thread to die and
+block other threads
+** Make async logging buffer size configurable
+** Make logging events discardable, prevent NPE in AsyncAppender-Dispatcher thread
diff --git a/ReleaseNotes/ReleaseNotes-2.4.2.txt b/ReleaseNotes/ReleaseNotes-2.4.2.txt
new file mode 100644
index 0000000..afa1d96
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.4.2.txt
@@ -0,0 +1,24 @@
+Release notes for Gerrit 2.4.2
+==============================
+
+Gerrit 2.4.2 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.2.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.4.2.war]
+
+There are no schema changes from 2.4, or 2.4.1.
+
+However, if upgrading from anything earlier, follow the upgrade
+procedure in the 2.4 link:ReleaseNotes-2.4.html[ReleaseNotes].
+
+Security Fixes
+--------------
+* Some access control sections may be ignored
++
+Gerrit sometimes ignored an access control section in a project
+if the exact same section name appeared in All-Projects. The bug
+required an unrelated project to have access.inheritFrom set to
+All-Projects and be accessed before the project that has the same
+section name as All-Projects. This is an unlikely scenario for
+most servers, as Gerrit does not normally set inheritFrom equal to
+All-Projects. The usual behavior is to not supply this property in
+project.config, and permit the implicit inheritence to take place.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 30a85e8..5f8de28 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,17 +4,21 @@
 [[2_4]]
 Version 2.4.x
 -------------
+* link:ReleaseNotes-2.4.2.html[2.4.2]
+* link:ReleaseNotes-2.4.1.html[2.4.1]
 * link:ReleaseNotes-2.4.html[2.4]
 
 [[2_3]]
 Version 2.3.x
 -------------
 * link:ReleaseNotes-2.3.html[2.3]
+* link:ReleaseNotes-2.3.1.html[2.3.1]
 
 [[2_2]]
 Version 2.2.x
 -------------
 * link:ReleaseNotes-2.2.2.html[2.2.2],
+* link:ReleaseNotes-2.2.2.2.html[2.2.2.2],
 * link:ReleaseNotes-2.2.2.1.html[2.2.2.1],
 * link:ReleaseNotes-2.2.1.html[2.2.1],
 * link:ReleaseNotes-2.2.0.html[2.2.0]
diff --git a/gerrit-antlr/.settings/org.eclipse.core.resources.prefs b/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs b/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/test/java=UTF-8
diff --git a/gerrit-common/.settings/org.eclipse.core.resources.prefs b/gerrit-common/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-common/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-common/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/test/java=UTF-8
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index cd64b0a..a9b5e85 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -118,4 +118,13 @@
   public String toString() {
     return "AccessSection[" + getName() + "]";
   }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (!super.equals(obj) || !(obj instanceof AccessSection)) {
+      return false;
+    }
+    return new HashSet<Permission>(permissions).equals(new HashSet<Permission>(
+        ((AccessSection) obj).permissions));
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
index 9a4d9fb..92c2d6c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
@@ -65,4 +65,51 @@
   public void setPreferredEmail(final String email) {
     preferredEmail = email;
   }
+
+  /**
+   * Formats an account name.
+   * <p>
+   * If the account has a full name, it returns only the full name. Otherwise it
+   * returns a longer form that includes the email address.
+   */
+  public String getName(String anonymousCowardName) {
+    if (getFullName() != null) {
+      return getFullName();
+    }
+    if (getPreferredEmail() != null) {
+      return getPreferredEmail();
+    }
+    return getNameEmail(anonymousCowardName);
+  }
+
+  /**
+   * Formats an account as an name and an email address.
+   * <p>
+   * Example output:
+   * <ul>
+   * <li><code>A U. Thor &lt;author@example.com&gt;</code>: full populated</li>
+   * <li><code>A U. Thor (12)</code>: missing email address</li>
+   * <li><code>Anonymous Coward &lt;author@example.com&gt;</code>: missing name</li>
+   * <li><code>Anonymous Coward (12)</code>: missing name and email address</li>
+   * </ul>
+   */
+  public String getNameEmail(String anonymousCowardName) {
+    String name = getFullName();
+    if (name == null) {
+      name = anonymousCowardName;
+    }
+
+    final StringBuilder b = new StringBuilder();
+    b.append(name);
+    if (getPreferredEmail() != null) {
+      b.append(" <");
+      b.append(getPreferredEmail());
+      b.append(">");
+    } else if (getId() != null) {
+      b.append(" (");
+      b.append(getId().get());
+      b.append(")");
+    }
+    return b.toString();
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 64444b4..81d4fc9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -42,12 +42,13 @@
   public static final String CREATE_PROJECT = "createProject";
 
   /**
-   * Denotes who may email change reviewers.
+   * Denotes who may email change reviewers and watchers.
    * <p>
    * This can be used to deny build bots from emailing reviewers and people who
-   * have starred the changed. Instead, only the authors of the change will be
-   * emailed. The allow rules are evaluated before deny rules, however the
-   * default is to allow emailing, if no explicit rule is matched.
+   * watch the change. Instead, only the authors of the change and those who
+   * starred it will be emailed. The allow rules are evaluated before deny
+   * rules, however the default is to allow emailing, if no explicit rule is
+   * matched.
    */
   public static final String EMAIL_REVIEWERS = "emailReviewers";
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
index 075d558..3c5c688 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
@@ -20,6 +20,9 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 public class PatchSetPublishDetail {
@@ -28,6 +31,8 @@
   protected Change change;
   protected List<PatchLineComment> drafts;
   protected List<PermissionRange> labels;
+  protected List<ApprovalDetail> approvals;
+  protected List<SubmitRecord> submitRecords;
   protected List<PatchSetApproval> given;
   protected boolean canSubmit;
 
@@ -39,6 +44,23 @@
     this.labels = labels;
   }
 
+  public List<ApprovalDetail> getApprovals() {
+    return approvals;
+  }
+
+  public void setApprovals(Collection<ApprovalDetail> list) {
+    approvals = new ArrayList<ApprovalDetail>(list);
+    Collections.sort(approvals, ApprovalDetail.SORT);
+  }
+
+  public void setSubmitRecords(List<SubmitRecord> all) {
+    submitRecords = all;
+  }
+
+  public List<SubmitRecord> getSubmitRecords() {
+    return submitRecords;
+  }
+
   public List<PatchSetApproval> getGiven() {
     return given;
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 4067349..5cb7787 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.common.data;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 
@@ -75,6 +76,16 @@
     return LABEL + labelName;
   }
 
+  public static boolean canBeOnAllProjects(String ref, String permissionName) {
+    if (AccessSection.ALL.equals(ref)) {
+      return !OWNER.equals(permissionName);
+    }
+    if (AccessSection.REF_CONFIG.equals(ref)) {
+      return !PUSH.equals(permissionName);
+    }
+    return true;
+  }
+
   protected String name;
   protected boolean exclusiveGroup;
   protected List<PermissionRule> rules;
@@ -210,4 +221,23 @@
     int index = NAMES_LC.indexOf(a.getName().toLowerCase());
     return 0 <= index ? index : NAMES_LC.size();
   }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (!(obj instanceof Permission)) {
+      return false;
+    }
+
+    final Permission other = (Permission) obj;
+    if (!name.equals(other.name) || exclusiveGroup != other.exclusiveGroup) {
+      return false;
+    }
+    return new HashSet<PermissionRule>(rules)
+        .equals(new HashSet<PermissionRule>(other.rules));
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
index 9b6695e..5960165 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
@@ -257,4 +257,19 @@
     }
     return Integer.parseInt(value);
   }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (!(obj instanceof PermissionRule)) {
+      return false;
+    }
+    final PermissionRule other = (PermissionRule)obj;
+    return action.equals(other.action) && force == other.force
+        && min == other.min && max == other.max && group.equals(other.group);
+  }
+
+  @Override
+  public int hashCode() {
+    return group.hashCode();
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index f935c03..1893843 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -26,6 +26,7 @@
   protected List<AccessSection> local;
   protected Set<String> ownerOf;
   protected boolean isConfigVisible;
+  protected boolean canUpload;
 
   public ProjectAccess() {
   }
@@ -94,4 +95,12 @@
   public void setConfigVisible(boolean isConfigVisible) {
     this.isConfigVisible = isConfigVisible;
   }
+
+  public boolean canUpload() {
+    return canUpload;
+  }
+
+  public void setCanUpload(boolean canUpload) {
+    this.canUpload = canUpload;
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index df6728e..a650117 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -50,6 +51,11 @@
       String message, List<AccessSection> sections,
       AsyncCallback<ProjectAccess> callback);
 
+  @SignInRequired
+  void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
+      String message, List<AccessSection> sections,
+      AsyncCallback<Change.Id> callback);
+
   void listBranches(Project.NameKey projectName,
       AsyncCallback<ListBranchesResult> callback);
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java
index 490378e..810e906 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java
@@ -21,6 +21,9 @@
   /** Pattern that matches all branches in a project. */
   public static final String HEADS = "refs/heads/*";
 
+  /** Configuration settings for a project {@code refs/meta/config} */
+  public static final String REF_CONFIG = "refs/meta/config";
+
   /** Prefix that triggers a regular expression pattern. */
   public static final String REGEX_PREFIX = "^";
 
@@ -45,4 +48,17 @@
   public void setName(String name) {
     this.name = name;
   }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (!(obj instanceof RefConfigSection)) {
+      return false;
+    }
+    return name.equals(((RefConfigSection) obj).name);
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
 }
diff --git a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/test/java=UTF-8
diff --git a/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
index 36e1448..e9441bb 100644
--- a/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:38 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 2e33fd8..7a84b20 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -716,8 +716,7 @@
             return new ProjectInfoScreen(k);
           }
 
-          if (ProjectScreen.BRANCH.equals(panel)
-              && !k.equals(Gerrit.getConfig().getWildProject())) {
+          if (ProjectScreen.BRANCH.equals(panel)) {
             return new ProjectBranchesScreen(k);
           }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 091fbdc..267419f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.client;
 
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+
+import com.google.gerrit.client.account.AccountCapabilities;
 import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
 import com.google.gerrit.client.auth.openid.OpenIdSsoPanel;
 import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
@@ -283,7 +286,7 @@
     return selfRedirect("/login/" + token);
   }
 
-  private static String selfRedirect(String suffix) {
+  public static String selfRedirect(String suffix) {
     // Clean up the path. Users seem to like putting extra slashes into the URL
     // which can break redirections by misinterpreting at either client or server.
     String path = Location.getPath();
@@ -582,11 +585,18 @@
     addDiffLink(diffBar, C.menuDiffFiles(), PatchScreen.TopView.FILES);
 
     if (signedIn) {
-      m = new LinkMenuBar();
-      addLink(m, C.menuGroups(), PageLinks.ADMIN_GROUPS);
-      addLink(m, C.menuProjects(), PageLinks.ADMIN_PROJECTS);
-      addLink(m, C.menuPlugins(), PageLinks.ADMIN_PLUGINS);
-      menuLeft.add(m, C.menuAdmin());
+      final LinkMenuBar menuBar = new LinkMenuBar();
+      addLink(menuBar, C.menuGroups(), PageLinks.ADMIN_GROUPS);
+      addLink(menuBar, C.menuProjects(), PageLinks.ADMIN_PROJECTS);
+      AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
+        @Override
+        public void onSuccess(AccountCapabilities result) {
+          if (result.canPerform(ADMINISTRATE_SERVER)) {
+            addLink(menuBar, C.menuPlugins(), PageLinks.ADMIN_PLUGINS);
+          }
+        }
+      }, ADMINISTRATE_SERVER);
+      menuLeft.add(menuBar, C.menuAdmin());
     }
 
     if (getConfig().isDocumentationAvailable()) {
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 43afe1e..09e6b84 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -99,4 +99,7 @@
   String jumpMineWatched();
   String jumpMineStarred();
   String jumpMineDraftComments();
+
+  String projectAccessError();
+  String projectAccessProposeForReviewHint();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 277c380..294ba49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -81,4 +81,7 @@
 jumpMineWatched = Go to watched changes
 jumpMineDrafts = Go to drafts
 jumpMineStarred = Go to starred changes
-jumpMineDraftComments = Go to draft comments
\ No newline at end of file
+jumpMineDraftComments = Go to draft comments
+
+projectAccessError = You don't have permissions to modify the access rights for the following refs:
+projectAccessProposeForReviewHint = You may propose these modifications to the project owners by clicking on 'Save for Review'.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 490db59..8f38f51 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -168,7 +168,6 @@
   String patchSetRevision();
   String patchSetUserIdentity();
   String patchSizeCell();
-  String permalink();
   String pluginsTable();
   String posscore();
   String projectAdminApprovalCategoryRangeLine();
@@ -183,6 +182,7 @@
   String rpcStatusPanel();
   String screen();
   String screenHeader();
+  String screenNoHeader();
   String searchPanel();
   String sectionHeader();
   String sideBySideScreenLinkTable();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 93739c2..a9aa418 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -182,7 +182,7 @@
     Collections.sort(value.getPermissions());
 
     this.value = value;
-    this.readOnly = !editing || !projectAccess.isOwnerOf(value);
+    this.readOnly = !editing || !(projectAccess.isOwnerOf(value) || projectAccess.canUpload());
 
     name.setEnabled(!readOnly);
     deleteSection.setVisible(!readOnly);
@@ -223,22 +223,16 @@
 
     if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
       for (String varName : Util.C.capabilityNames().keySet()) {
-        if (value.getPermission(varName) == null) {
-          perms.add(varName);
-        }
+        addPermission(varName, perms);
       }
     } else if (RefConfigSection.isValid(value.getName())) {
       for (ApprovalType t : Gerrit.getConfig().getApprovalTypes()
           .getApprovalTypes()) {
         String varName = Permission.LABEL + t.getCategory().getLabelName();
-        if (value.getPermission(varName) == null) {
-          perms.add(varName);
-        }
+        addPermission(varName, perms);
       }
       for (String varName : Util.C.permissionNames().keySet()) {
-        if (value.getPermission(varName) == null) {
-          perms.add(varName);
-        }
+        addPermission(varName, perms);
       }
     }
     if (perms.isEmpty()) {
@@ -251,6 +245,19 @@
     }
   }
 
+  private void addPermission(final String permissionName,
+      final List<String> permissionList) {
+    if (value.getPermission(permissionName) != null) {
+      return;
+    }
+    if (Gerrit.getConfig().getWildProject()
+        .equals(projectAccess.getProjectName())
+        && !Permission.canBeOnAllProjects(value.getName(), permissionName)) {
+      return;
+    }
+    permissionList.add(permissionName);
+  }
+
   @Override
   public void flush() {
     List<Permission> src = permissions.getList();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
index 7019a06..814ae51 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.client.plugins.PluginMap;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Panel;
@@ -79,7 +80,11 @@
     }
 
     void populate(final int row, final PluginInfo plugin) {
-      table.setText(row, 1, plugin.name());
+      table.setWidget(
+          row,
+          1,
+          new Anchor(plugin.name(), Gerrit.selfRedirect("/plugins/"
+              + plugin.name() + "/")));
       table.setText(row, 2, plugin.version());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
index e3bf555..32bc469 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -120,7 +120,7 @@
       history.getStyle().setDisplay(Display.NONE);
     }
 
-    addSection.setVisible(value != null && editing && !value.getOwnerOf().isEmpty());
+    addSection.setVisible(value != null && editing && (!value.getOwnerOf().isEmpty() || value.canUpload()));
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 923a63e..4403ce6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ProjectAccess;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
@@ -31,10 +32,14 @@
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 public class ProjectAccessScreen extends ProjectScreen {
   interface Binder extends UiBinder<HTMLPanel, ProjectAccessScreen> {
@@ -60,6 +65,9 @@
   Button cancel2;
 
   @UiField
+  VerticalPanel error;
+
+  @UiField
   ProjectAccessEditor accessEditor;
 
   @UiField
@@ -71,6 +79,9 @@
   @UiField
   Button commit;
 
+  @UiField
+  Button review;
+
   private Driver driver;
 
   private ProjectAccess access;
@@ -104,8 +115,8 @@
   private void displayReadOnly(ProjectAccess access) {
     this.access = access;
     accessEditor.setEditing(false);
-    UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty());
-    edit.setEnabled(!access.getOwnerOf().isEmpty());
+    UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
+    edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
     cancel1.setVisible(false);
     UIObject.setVisible(commitTools, false);
     driver.edit(access);
@@ -118,6 +129,8 @@
     edit.setEnabled(false);
     cancel1.setVisible(true);
     UIObject.setVisible(commitTools, true);
+    commit.setVisible(!access.getOwnerOf().isEmpty());
+    review.setVisible(access.canUpload());
     accessEditor.setEditing(true);
     driver.edit(access);
   }
@@ -141,7 +154,7 @@
 
   @UiHandler("commit")
   void onCommit(ClickEvent event) {
-    ProjectAccess access = driver.flush();
+    final ProjectAccess access = driver.flush();
 
     if (driver.hasErrors()) {
       Window.alert(Util.C.errorsMustBeFixed());
@@ -161,14 +174,88 @@
         access.getLocal(), //
         new GerritCallback<ProjectAccess>() {
           @Override
-          public void onSuccess(ProjectAccess access) {
+          public void onSuccess(ProjectAccess newAccess) {
             enable(true);
             commitMessage.setText("");
-            displayReadOnly(access);
+            error.clear();
+            final Set<String> diffs = getDiffs(access, newAccess);
+            if (diffs.isEmpty()) {
+              displayReadOnly(newAccess);
+            } else {
+              error.add(new Label(Gerrit.C.projectAccessError()));
+              for (final String diff : diffs) {
+                error.add(new Label(diff));
+              }
+              if (access.canUpload()) {
+                error.add(new Label(Gerrit.C.projectAccessProposeForReviewHint()));
+              }
+            }
+          }
+
+          private Set<String> getDiffs(ProjectAccess wantedAccess,
+              ProjectAccess newAccess) {
+            final HashSet<AccessSection> same =
+                new HashSet<AccessSection>(wantedAccess.getLocal());
+            final HashSet<AccessSection> different =
+                new HashSet<AccessSection>(wantedAccess.getLocal().size()
+                    + newAccess.getLocal().size());
+            different.addAll(wantedAccess.getLocal());
+            different.addAll(newAccess.getLocal());
+            same.retainAll(newAccess.getLocal());
+            different.removeAll(same);
+
+            final Set<String> differentNames = new HashSet<String>();
+            for (final AccessSection s : different) {
+              differentNames.add(s.getName());
+            }
+            return differentNames;
           }
 
           @Override
           public void onFailure(Throwable caught) {
+            error.clear();
+            enable(true);
+            super.onFailure(caught);
+          }
+        });
+  }
+
+  @UiHandler("review")
+  void onReview(ClickEvent event) {
+    final ProjectAccess access = driver.flush();
+
+    if (driver.hasErrors()) {
+      Window.alert(Util.C.errorsMustBeFixed());
+      return;
+    }
+
+    String message = commitMessage.getText().trim();
+    if ("".equals(message)) {
+      message = null;
+    }
+
+    enable(false);
+    Util.PROJECT_SVC.reviewProjectAccess( //
+        getProjectKey(), //
+        access.getRevision(), //
+        message, //
+        access.getLocal(), //
+        new GerritCallback<Change.Id>() {
+          @Override
+          public void onSuccess(Change.Id changeId) {
+            enable(true);
+            commitMessage.setText("");
+            error.clear();
+            if (changeId != null) {
+              Gerrit.display(PageLinks.toChange(changeId));
+            } else {
+              displayReadOnly(access);
+            }
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            error.clear();
             enable(true);
             super.onFailure(caught);
           }
@@ -177,7 +264,8 @@
 
   private void enable(boolean enabled) {
     commitMessage.setEnabled(enabled);
-    commit.setEnabled(enabled);
+    commit.setEnabled(enabled ? !access.getOwnerOf().isEmpty() : false);
+    review.setEnabled(enabled ? access.canUpload() : false);
     cancel1.setEnabled(enabled);
     cancel2.setEnabled(enabled);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
index 2536159..a664191 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.ui.xml
@@ -32,6 +32,11 @@
   .commitMessage .gwt-TextArea {
     margin: 5px 5px 5px 5px;
   }
+  .errorMessage {
+    margin-top: 5px;
+    margin-bottom: 5px;
+    color: red;
+  }
 </ui:style>
 
 <g:HTMLPanel>
@@ -58,12 +63,21 @@
           spellCheck='true'
           />
     </div>
+    <g:VerticalPanel
+      styleName='{style.errorMessage}'
+      ui:field='error'>
+    </g:VerticalPanel>
     <g:Button
         ui:field='commit'
         text='Save Changes'>
       <ui:attribute name='text'/>
     </g:Button>
     <g:Button
+        ui:field='review'
+        text='Save for Review'>
+      <ui:attribute name='text'/>
+    </g:Button>
+    <g:Button
         ui:field='cancel2'
         text='Cancel'>
       <ui:attribute name='text'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
index ccfe2e6..dd5b070 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.client.Dispatcher.toProjectAdmin;
 
-import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.ui.MenuScreen;
 import com.google.gerrit.reviewdb.client.Project;
 
@@ -30,12 +29,8 @@
   public ProjectScreen(final Project.NameKey toShow) {
     name = toShow;
 
-    final boolean isWild = toShow.equals(Gerrit.getConfig().getWildProject());
-
     link(Util.C.projectAdminTabGeneral(), toProjectAdmin(name, INFO));
-    if (!isWild) {
-      link(Util.C.projectAdminTabBranches(), toProjectAdmin(name, BRANCH));
-    }
+    link(Util.C.projectAdminTabBranches(), toProjectAdmin(name, BRANCH));
     link(Util.C.projectAdminTabAccess(), toProjectAdmin(name, ACCESS));
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index 20df1df..73036ff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.common.data.ApprovalType;
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.data.PatchSetPublishDetail;
 import com.google.gerrit.common.data.ReviewerResult;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.reviewdb.client.Account;
@@ -133,12 +134,22 @@
     return AccountLink.link(accountCache, id);
   }
 
+  void display(PatchSetPublishDetail detail) {
+    doDisplay(detail.getChange(), detail.getApprovals(),
+        detail.getSubmitRecords());
+  }
+
   void display(ChangeDetail detail) {
-    changeId = detail.getChange().getId();
+    doDisplay(detail.getChange(), detail.getApprovals(),
+        detail.getSubmitRecords());
+  }
+
+  private void doDisplay(Change change, List<ApprovalDetail> approvals,
+      List<SubmitRecord> submitRecords) {
+    changeId = change.getId();
     reviewerSuggestOracle.setChange(changeId);
 
     List<String> columns = new ArrayList<String>();
-    List<ApprovalDetail> rows = detail.getApprovals();
 
     final Element missingList = missing.getElement();
     while (DOM.getChildCount(missingList) > 0) {
@@ -146,16 +157,16 @@
     }
     missing.setVisible(false);
 
-    if (detail.getSubmitRecords() != null) {
+    if (submitRecords != null) {
       HashSet<String> reportedMissing = new HashSet<String>();
 
       HashMap<Account.Id, ApprovalDetail> byUser =
           new HashMap<Account.Id, ApprovalDetail>();
-      for (ApprovalDetail ad : detail.getApprovals()) {
+      for (ApprovalDetail ad : approvals) {
         byUser.put(ad.getAccount(), ad);
       }
 
-      for (SubmitRecord rec : detail.getSubmitRecords()) {
+      for (SubmitRecord rec : submitRecords) {
         if (rec.labels == null) {
           continue;
         }
@@ -200,7 +211,7 @@
       missing.setVisible(!reportedMissing.isEmpty());
 
     } else {
-      for (ApprovalDetail ad : rows) {
+      for (ApprovalDetail ad : approvals) {
         for (PatchSetApproval psa : ad.getPatchSetApprovals()) {
           ApprovalType legacyType = types.byId(psa.getCategoryId());
           if (legacyType == null) {
@@ -236,13 +247,13 @@
       }
     }
 
-    if (rows.isEmpty()) {
+    if (approvals.isEmpty()) {
       table.setVisible(false);
     } else {
       displayHeader(columns);
-      table.resizeRows(1 + rows.size());
-      for (int i = 0; i < rows.size(); i++) {
-        displayRow(i + 1, rows.get(i), detail.getChange(), columns);
+      table.resizeRows(1 + approvals.size());
+      for (int i = 0; i < approvals.size(); i++) {
+        displayRow(i + 1, approvals.get(i), change, columns);
       }
       table.setVisible(true);
     }
@@ -250,7 +261,7 @@
     addReviewer.setVisible(Gerrit.isSignedIn());
 
     if (Gerrit.getConfig().testChangeMerge()
-        && !detail.getChange().isMergeable()) {
+        && !change.isMergeable()) {
       Element li = DOM.createElement("li");
       li.setClassName(Gerrit.RESOURCES.css().missingApproval());
       DOM.setInnerText(li, Util.C.messageNeedsRebaseOrHasDependency());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
index 9282709..c8b2a66 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
@@ -19,14 +19,15 @@
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
 
 public class ChangeDescriptionBlock extends Composite {
   private final ChangeInfoBlock infoBlock;
   private final CommitMessageBlock messageBlock;
 
-  public ChangeDescriptionBlock() {
+  public ChangeDescriptionBlock(KeyCommandSet keysAction) {
     infoBlock = new ChangeInfoBlock();
-    messageBlock = new CommitMessageBlock();
+    messageBlock = new CommitMessageBlock(keysAction);
 
     final HorizontalPanel hp = new HorizontalPanel();
     hp.add(infoBlock);
@@ -34,9 +35,9 @@
     initWidget(hp);
   }
 
-  public void display(final Change chg, final PatchSetInfo info,
+  public void display(Change chg, Boolean starred, PatchSetInfo info,
       final AccountInfoCache acc) {
     infoBlock.display(chg, acc);
-    messageBlock.display(info.getMessage());
+    messageBlock.display(chg.getId(), starred, info.getMessage());
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
index 865e389..3ffacc3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
@@ -19,13 +19,11 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.ui.AccountLink;
 import com.google.gerrit.client.ui.BranchLink;
-import com.google.gerrit.client.ui.ChangeLink;
 import com.google.gerrit.client.ui.ProjectLink;
 import com.google.gerrit.common.data.AccountInfoCache;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
@@ -40,18 +38,15 @@
   private static final int R_UPDATED = 6;
   private static final int R_STATUS = 7;
   private static final int R_MERGE_TEST = 8;
-  private final int R_PERMALINK;
-  private static final int R_CNT = 10;
+  private static final int R_CNT = 9;
 
   private final Grid table;
 
   public ChangeInfoBlock() {
     if (Gerrit.getConfig().testChangeMerge()) {
       table = new Grid(R_CNT, 2);
-      R_PERMALINK = 9;
     } else {
       table = new Grid(R_CNT - 1, 2);
-      R_PERMALINK = 8;
     }
     table.setStyleName(Gerrit.RESOURCES.css().infoBlock());
     table.addStyleName(Gerrit.RESOURCES.css().changeInfoBlock());
@@ -73,8 +68,6 @@
     fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
     fmt.addStyleName(R_CHANGE_ID, 1, Gerrit.RESOURCES.css().changeid());
     fmt.addStyleName(R_CNT - 2, 0, Gerrit.RESOURCES.css().bottomheader());
-    fmt.addStyleName(R_PERMALINK, 0, Gerrit.RESOURCES.css().permalink());
-    fmt.addStyleName(R_PERMALINK, 1, Gerrit.RESOURCES.css().permalink());
 
     initWidget(table);
   }
@@ -101,20 +94,21 @@
     table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
     table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
     table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
+    final Change.Status status = chg.getStatus();
     if (Gerrit.getConfig().testChangeMerge()) {
-      table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
-          .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
+      if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
+        table.getRowFormatter().setVisible(R_MERGE_TEST, true);
+        table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
+            .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
+      } else {
+        table.getRowFormatter().setVisible(R_MERGE_TEST, false);
+      }
     }
 
-    if (chg.getStatus().isClosed()) {
+    if (status.isClosed()) {
       table.getCellFormatter().addStyleName(R_STATUS, 1, Gerrit.RESOURCES.css().closedstate());
     } else {
       table.getCellFormatter().removeStyleName(R_STATUS, 1, Gerrit.RESOURCES.css().closedstate());
     }
-
-    final FlowPanel fp = new FlowPanel();
-    fp.add(new ChangeLink(Util.C.changePermalink(), chg.getId()));
-    fp.add(new CopyableLabel(ChangeLink.permalink(chg.getId()), false));
-    table.setWidget(R_PERMALINK, 1, fp);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index f761270..234f937 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -153,6 +153,7 @@
     detailCache.addValueChangeHandler(this);
 
     addStyleName(Gerrit.RESOURCES.css().changeScreen());
+    addStyleName(Gerrit.RESOURCES.css().screenNoHeader());
 
     keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
     keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
@@ -160,16 +161,11 @@
     keysNavigation.add(new ExpandCollapseDependencySectionKeyCommand(0, 'd', Util.C.expandCollapseDependencies()));
 
     if (Gerrit.isSignedIn()) {
-      StarredChanges.Icon star = StarredChanges.createIcon(changeId, false);
-      star.setStyleName(Gerrit.RESOURCES.css().changeScreenStarIcon());
-      setTitleWest(star);
-
-      keysAction.add(StarredChanges.newKeyCommand(star));
       keysAction.add(new PublishCommentsKeyCommand(0, 'r', Util.C
           .keyPublishComments()));
     }
 
-    descriptionBlock = new ChangeDescriptionBlock();
+    descriptionBlock = new ChangeDescriptionBlock(keysAction);
     add(descriptionBlock);
 
     approvals = new ApprovalTable();
@@ -243,6 +239,7 @@
       }
     }
     setPageTitle(titleBuf.toString());
+    setHeaderVisible(false);
   }
 
   @Override
@@ -265,8 +262,10 @@
     dependencies.setAccountInfoCache(detail.getAccounts());
     approvals.setAccountInfoCache(detail.getAccounts());
 
-    descriptionBlock.display(detail.getChange(), detail
-        .getCurrentPatchSetDetail().getInfo(), detail.getAccounts());
+    descriptionBlock.display(detail.getChange(),
+        detail.isStarred(),
+        detail.getCurrentPatchSetDetail().getInfo(),
+        detail.getAccounts());
     dependsOn.display(detail.getDependsOn());
     neededBy.display(detail.getNeededBy());
     approvals.display(detail);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 44a49a8..97bb4ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -202,10 +202,7 @@
     }
     table.setWidget(row, C_ID, new TableChangeLink(idstr, c));
 
-    String s = c.getSubject();
-    if (s.length() > 80) {
-      s = s.substring(0, 80);
-    }
+    String s = Util.cropSubject(c.getSubject());
     if (c.getStatus() != null && c.getStatus() != Change.Status.NEW) {
       s += " (" + c.getStatus().name() + ")";
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index fc2b418..0dd0b0f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -200,10 +200,7 @@
     }
     table.setWidget(row, C_ID, new TableChangeLink(c.id_abbreviated(), c));
 
-    String subject = c.subject();
-    if (subject.length() > 80) {
-      subject = subject.substring(0, 80);
-    }
+    String subject = Util.cropSubject(c.subject());
     Change.Status status = c.status();
     if (status != Change.Status.NEW) {
       subject += " (" + Util.toLongString(status) + ")";
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
index 1a6ea3e..6a364e0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
@@ -15,28 +15,96 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.ui.ChangeLink;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.PreElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 public class CommitMessageBlock extends Composite {
-  private final HTML description;
+  interface Binder extends UiBinder<HTMLPanel, CommitMessageBlock> {
+  }
+
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  private KeyCommandSet keysAction;
+
+  @UiField
+  SimplePanel starPanel;
+  @UiField
+  FlowPanel permalinkPanel;
+  @UiField
+  PreElement commitSummaryPre;
+  @UiField
+  PreElement commitBodyPre;
 
   public CommitMessageBlock() {
-    description = new HTML();
-    description.setStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
-    initWidget(description);
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  public CommitMessageBlock(KeyCommandSet keysAction) {
+    this.keysAction = keysAction;
+    initWidget(uiBinder.createAndBindUi(this));
   }
 
   public void display(final String commitMessage) {
-    SafeHtml msg = new SafeHtmlBuilder().append(commitMessage);
-    msg = msg.linkify();
-    msg = CommentLinkProcessor.apply(msg);
-    msg = new SafeHtmlBuilder().openElement("p").append(msg).closeElement("p");
-    msg = msg.replaceAll("\n\n", "</p><p>");
-    msg = msg.replaceAll("\n", "<br />");
-    SafeHtml.set(description, msg);
+    display(null, null, commitMessage);
+  }
+
+  public void display(Change.Id changeId, Boolean starred, String commitMessage) {
+    starPanel.clear();
+
+    if (changeId != null && starred != null && Gerrit.isSignedIn()) {
+      StarredChanges.Icon star = StarredChanges.createIcon(changeId, starred);
+      star.setStyleName(Gerrit.RESOURCES.css().changeScreenStarIcon());
+      starPanel.add(star);
+
+      if (keysAction != null) {
+        keysAction.add(StarredChanges.newKeyCommand(star));
+      }
+    }
+
+    permalinkPanel.clear();
+    if (changeId != null) {
+      permalinkPanel.add(new ChangeLink(Util.C.changePermalink(), changeId));
+      permalinkPanel.add(new CopyableLabel(ChangeLink.permalink(changeId), false));
+    }
+
+    String[] splitCommitMessage = commitMessage.split("\n", 2);
+
+    String commitSummary = splitCommitMessage[0];
+    String commitBody = "";
+    if (splitCommitMessage.length > 1) {
+      commitBody = splitCommitMessage[1];
+    }
+
+    // Linkify commit summary
+    SafeHtml commitSummaryLinkified = new SafeHtmlBuilder().append(commitSummary);
+    commitSummaryLinkified = commitSummaryLinkified.linkify();
+    commitSummaryLinkified = CommentLinkProcessor.apply(commitSummaryLinkified);
+    commitSummaryPre.setInnerHTML(commitSummaryLinkified.asString());
+
+    // Hide commit body if there is no body
+    if (commitBody.trim().isEmpty()) {
+      commitBodyPre.getStyle().setDisplay(Display.NONE);
+    } else {
+      // Linkify commit body
+      SafeHtml commitBodyLinkified = new SafeHtmlBuilder().append(commitBody);
+      commitBodyLinkified = commitBodyLinkified.linkify();
+      commitBodyLinkified = CommentLinkProcessor.apply(commitBodyLinkified);
+      commitBodyPre.setInnerHTML(commitBodyLinkified.asString());
+    }
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
new file mode 100644
index 0000000..16d1da5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+
+  <ui:with field='res' type='com.google.gerrit.client.GerritResources'/>
+  <ui:style>
+    @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+    @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+    @eval backgroundColor com.google.gerrit.client.Gerrit.getTheme().backgroundColor;
+
+    .commitMessageTable {
+      border-collapse: separate;
+      border-spacing: 0;
+      margin-bottom: 10px;
+    }
+
+    .header {
+      background-color: trimColor;
+      white-space: nowrap;
+      color: textColor;
+      font-size: 10pt;
+      font-style: italic;
+      padding: 2px 6px 1px;
+    }
+
+    .contents {
+      border-bottom: 1px solid trimColor;
+      border-left: 1px solid trimColor;
+      border-right: 1px solid trimColor;
+      padding: 5px;
+    }
+
+    .contents span {
+      font-weight: bold;
+    }
+
+    .contents pre {
+      margin: 0;
+    }
+
+    .commitSummary {
+      font-weight: bold;
+    }
+
+    .commitBody {
+      margin-top: 10px;
+    }
+
+    .starPanel {
+      float: left;
+    }
+
+    .boxTitle {
+      float: left;
+      margin-right: 10px;
+    }
+
+    .permalinkPanel {
+      float: right;
+    }
+
+    .permalinkPanel a {
+      float: left;
+    }
+
+    .permalinkPanel div {
+      display: inline;
+    }
+  </ui:style>
+
+  <g:HTMLPanel>
+    <table class='{style.commitMessageTable}'>
+      <tr><td class='{style.header}'>
+        <g:SimplePanel styleName='{style.starPanel}' ui:field='starPanel'></g:SimplePanel>
+        <div class='{style.boxTitle}'>Commit Message</div>
+        <g:FlowPanel styleName='{style.permalinkPanel}' ui:field='permalinkPanel'></g:FlowPanel>
+      </td></tr>
+      <tr><td class='{style.contents}'>
+        <pre class='{style.commitSummary} {res.css.changeScreenDescription}' ui:field='commitSummaryPre'/>
+        <pre class='{style.commitBody} {res.css.changeScreenDescription}' ui:field='commitBodyPre'/>
+      </td></tr>
+    </table>
+  </g:HTMLPanel>
+</ui:UiBinder>
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 00baf28..ca8aedf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -424,7 +424,8 @@
       parentsTable.setWidget(row, 0, new InlineLabel(parent.id.get()));
       ptfmt.addStyleName(row, 0, Gerrit.RESOURCES.css().noborder());
       ptfmt.addStyleName(row, 0, Gerrit.RESOURCES.css().monospace());
-      parentsTable.setWidget(row, 1, new InlineLabel(parent.shortMessage));
+      parentsTable.setWidget(row, 1,
+          new InlineLabel(Util.cropSubject(parent.shortMessage)));
       ptfmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
       row++;
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 0c08491..4705aad 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -64,6 +64,7 @@
   private final PatchSet.Id patchSetId;
   private Collection<ValueRadioButton> approvalButtons;
   private ChangeDescriptionBlock descBlock;
+  private ApprovalTable approvals;
   private Panel approvalPanel;
   private NpTextArea message;
   private FlowPanel draftsPanel;
@@ -83,9 +84,12 @@
     addStyleName(Gerrit.RESOURCES.css().publishCommentsScreen());
 
     approvalButtons = new ArrayList<ValueRadioButton>();
-    descBlock = new ChangeDescriptionBlock();
+    descBlock = new ChangeDescriptionBlock(null);
     add(descBlock);
 
+    approvals = new ApprovalTable();
+    add(approvals);
+
     final FormPanel form = new FormPanel();
     final FlowPanel body = new FlowPanel();
     form.setWidget(body);
@@ -270,10 +274,15 @@
   private void display(final PatchSetPublishDetail r) {
     setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
         patchSetId.get()));
-    descBlock.display(r.getChange(), r.getPatchSetInfo(), r.getAccounts());
+    descBlock.display(r.getChange(), null, r.getPatchSetInfo(), r.getAccounts());
 
     if (r.getChange().getStatus().isOpen()) {
       initApprovals(r, approvalPanel);
+
+      approvals.setAccountInfoCache(r.getAccounts());
+      approvals.display(r);
+    } else {
+      approvals.setVisible(false);
     }
 
     if (lastState != null && patchSetId.equals(lastState.patchSetId)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index e84cac8..590ad87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -30,6 +30,10 @@
   public static final ChangeListService LIST_SVC;
   public static final ChangeManageService MANAGE_SVC;
 
+  private static final int SUBJECT_MAX_LENGTH = 80;
+  private static final String SUBJECT_CROP_APPENDIX = "...";
+  private static final int SUBJECT_CROP_RANGE = 10;
+
   static {
     DETAIL_SVC = GWT.create(ChangeDetailService.class);
     JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
@@ -60,4 +64,40 @@
         return status.name();
     }
   }
+
+  /**
+   * Crops the given change subject if needed so that it has at most
+   * {@link #SUBJECT_MAX_LENGTH} characters.
+   *
+   * If the given subject is not longer than {@link #SUBJECT_MAX_LENGTH}
+   * characters it is returned unchanged.
+   *
+   * If the length of the given subject exceeds {@link #SUBJECT_MAX_LENGTH}
+   * characters it is cropped. In this case {@link #SUBJECT_CROP_APPENDIX} is
+   * appended to the cropped subject, the cropped subject including the appendix
+   * has at most {@link #SUBJECT_MAX_LENGTH} characters.
+   *
+   * If cropping is needed, the subject will be cropped after the last space
+   * character that is found within the last {@link #SUBJECT_CROP_RANGE}
+   * characters of the potentially visible characters. If no such space is
+   * found, the subject will be cropped so that the cropped subject including
+   * the appendix has exactly {@link #SUBJECT_MAX_LENGTH} characters.
+   *
+   * @return the subject, cropped if needed
+   */
+  @SuppressWarnings("deprecation")
+  public static String cropSubject(final String subject) {
+    if (subject.length() > SUBJECT_MAX_LENGTH) {
+      final int maxLength = SUBJECT_MAX_LENGTH - SUBJECT_CROP_APPENDIX.length();
+      for (int cropPosition = maxLength; cropPosition > maxLength - SUBJECT_CROP_RANGE; cropPosition--) {
+        // Character.isWhitespace(char) can't be used because this method is not supported by GWT,
+        // see https://developers.google.com/web-toolkit/doc/1.6/RefJreEmulation#Package_java_lang
+        if (Character.isSpace(subject.charAt(cropPosition - 1))) {
+          return subject.substring(0, cropPosition) + SUBJECT_CROP_APPENDIX;
+        }
+      }
+      return subject.substring(0, maxLength) + SUBJECT_CROP_APPENDIX;
+    }
+    return subject;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 9a1ca43..3789c6a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -401,6 +401,9 @@
   overflow: hidden;
 }
 
+.screenNoHeader {
+  margin-top: 5px;
+}
 
 /** ChangeTable **/
 .changeTable {
@@ -911,15 +914,6 @@
   font-weight: bold;
 }
 
-.infoBlock td.permalink {
-  border-right: 1px none;
-  border-bottom: 1px none;
-  text-align: right;
-}
-.infoBlock td.permalink div div {
-  display: inline;
-}
-
 .infoBlock td.useridentity {
   white-space: nowrap;
 }
@@ -952,7 +946,7 @@
   margin-top: 5px;
 }
 
-.changeScreen .approvalTable {
+.approvalTable {
   margin-top: 1em;
   margin-bottom: 1em;
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index 98ae46f..dce5bb6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -37,8 +37,11 @@
       new NotSignedInDialog().center();
 
     } else if (isNoSuchEntity(caught)) {
-      new ErrorDialog(Gerrit.C.notFoundBody()).center();
-
+      if (Gerrit.isSignedIn()) {
+        new ErrorDialog(Gerrit.C.notFoundBody()).center();
+      } else {
+        new NotSignedInDialog().center();
+      }
     } else if (isInactiveAccount(caught)) {
       new ErrorDialog(Gerrit.C.inactiveAccountBody()).center();
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
index 7f5eead..845a046 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
@@ -95,6 +95,10 @@
     }
   }
 
+  protected void setHeaderVisible(boolean value) {
+    header.setVisible(value);
+  }
+
   protected void setTitleEast(final Widget w) {
     header.setWidget(0, Cols.East.ordinal(), w);
   }
diff --git a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs b/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
index 9df523e..839d647 100644
--- a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 151a6d9..3805961 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.AuthConfig;
@@ -66,6 +67,7 @@
   private final IdentifiedUser.RequestFactory identified;
   private AccessPath accessPath = AccessPath.WEB_UI;
   private Cookie outCookie;
+  private AuthMethod authMethod;
 
   private Key key;
   private Val val;
@@ -91,6 +93,7 @@
       key = null;
       val = null;
     }
+    authMethod = isSignedIn() ? AuthMethod.COOKIE : AuthMethod.NONE;
 
     if (isSignedIn() && val.needsCookieRefresh()) {
       // Cookie is more than half old. Send the cookie again to the
@@ -142,7 +145,8 @@
     return anonymousProvider.get();
   }
 
-  public void login(final AuthResult res, final boolean rememberMe) {
+  public void login(final AuthResult res, final AuthMethod meth,
+                    final boolean rememberMe) {
     final Account.Id id = res.getAccountId();
     final AccountExternalId.Key identity = res.getExternalId();
 
@@ -153,6 +157,8 @@
     key = manager.createKey(id);
     val = manager.createVal(key, id, rememberMe, identity, null);
     saveCookie();
+
+    authMethod = meth;
   }
 
   /** Change the access path from the default of {@link AccessPath#WEB_UI}. */
@@ -161,9 +167,10 @@
   }
 
   /** Set the user account for this current request only. */
-  public void setUserAccountId(Account.Id id) {
+  public void setUserAccountId(Account.Id id, AuthMethod method) {
     key = new Key("id:" + id);
     val = new Val(id, 0, false, null, "", 0);
+    authMethod = method;
   }
 
   public void logout() {
@@ -210,4 +217,8 @@
   private static boolean isSecure(final HttpServletRequest req) {
     return req.isSecure() || "https".equals(req.getScheme());
   }
+
+  public AuthMethod getAuthMethod() {
+    return authMethod;
+  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java
index 29b5d95..9ce2298 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ContainerAuthFilter.java
@@ -19,6 +19,7 @@
 
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -99,7 +100,9 @@
       rsp.sendError(SC_UNAUTHORIZED);
       return false;
     }
-    session.get().setUserAccountId(who.getAccount().getId());
+    session.get().setUserAccountId(
+        who.getAccount().getId(),
+        AuthMethod.PASSWORD);
     return true;
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
index aa004e3..6ca9949 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -34,6 +34,8 @@
     Class<? extends Filter> authFilter;
     if (authConfig.isTrustContainerAuth()) {
       authFilter = ContainerAuthFilter.class;
+    } else if (authConfig.isGitBasichAuth()) {
+      authFilter = ProjectBasicAuthFilter.class;
     } else {
       authFilter = ProjectDigestFilter.class;
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
new file mode 100644
index 0000000..5b39cb2
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -0,0 +1,202 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthMethod;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/**
+ * Authenticates the current user by HTTP basic authentication.
+ * <p>
+ * The current HTTP request is authenticated by looking up the username and
+ * password from the Base64 encoded Authorization header and validating them
+ * against any username/password configured authentication system in Gerrit.
+ * This filter is intended only to protect the {@link ProjectServlet} and its
+ * handled URLs, which provide remote repository access over HTTP.
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>
+ */
+@Singleton
+class ProjectBasicAuthFilter implements Filter {
+  private static final Logger log = LoggerFactory
+      .getLogger(ProjectBasicAuthFilter.class);
+
+  public static final String REALM_NAME = "Gerrit Code Review";
+  private static final String AUTHORIZATION = "Authorization";
+  private static final String LIT_BASIC = "Basic ";
+
+  private final Provider<WebSession> session;
+  private final AccountCache accountCache;
+  private final AccountManager accountManager;
+  private final AuthConfig authConfig;
+
+  @Inject
+  ProjectBasicAuthFilter(Provider<WebSession> session,
+      AccountCache accountCache, AccountManager accountManager,
+      AuthConfig authConfig) {
+    this.session = session;
+    this.accountCache = accountCache;
+    this.accountManager = accountManager;
+    this.authConfig = authConfig;
+  }
+
+  @Override
+  public void init(FilterConfig config) {
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest req = (HttpServletRequest) request;
+    Response rsp = new Response((HttpServletResponse) response);
+
+    if (verify(req, rsp)) {
+      chain.doFilter(req, rsp);
+    }
+  }
+
+  private boolean verify(HttpServletRequest req, Response rsp)
+      throws IOException {
+    final String hdr = req.getHeader(AUTHORIZATION);
+    if (hdr == null) {
+      // Allow an anonymous connection through, or it might be using a
+      // session cookie instead of basic authentication.
+      //
+      return true;
+    }
+
+    final byte[] decoded =
+        Base64.decodeBase64(hdr.substring(LIT_BASIC.length()));
+    String usernamePassword = new String(decoded, encoding(req));
+    int splitPos = usernamePassword.indexOf(':');
+    if (splitPos < 1) {
+      rsp.sendError(SC_UNAUTHORIZED);
+      return false;
+    }
+
+    String username = usernamePassword.substring(0, splitPos);
+    String password = usernamePassword.substring(splitPos + 1);
+    if (Strings.isNullOrEmpty(password)) {
+      rsp.sendError(SC_UNAUTHORIZED);
+      return false;
+    }
+    if (authConfig.isUserNameToLowerCase()) {
+      username = username.toLowerCase(Locale.US);
+    }
+
+    final AccountState who = accountCache.getByUsername(username);
+    if (who == null || !who.getAccount().isActive()) {
+      log.warn("Authentication failed for " + username
+          + ": account inactive or not provisioned in Gerrit");
+      rsp.sendError(SC_UNAUTHORIZED);
+      return false;
+    }
+
+    AuthRequest whoAuth = AuthRequest.forUser(username);
+    whoAuth.setPassword(password);
+
+    try {
+      AuthResult whoAuthResult = accountManager.authenticate(whoAuth);
+      session.get().setUserAccountId(whoAuthResult.getAccountId(),
+          AuthMethod.PASSWORD);
+      return true;
+    } catch (AccountException e) {
+      log.warn("Authentication failed for " + username, e);
+      rsp.sendError(SC_UNAUTHORIZED);
+      return false;
+    }
+  }
+
+  private String encoding(HttpServletRequest req) {
+    return Objects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
+  }
+
+  class Response extends HttpServletResponseWrapper {
+    private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
+
+    Response(HttpServletResponse rsp) {
+      super(rsp);
+    }
+
+    private void status(int sc) {
+      if (sc == SC_UNAUTHORIZED) {
+        StringBuilder v = new StringBuilder();
+        v.append(LIT_BASIC);
+        v.append("realm=\"" + REALM_NAME + "\"");
+        setHeader(WWW_AUTHENTICATE, v.toString());
+      } else if (containsHeader(WWW_AUTHENTICATE)) {
+        setHeader(WWW_AUTHENTICATE, null);
+      }
+    }
+
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+      status(sc);
+      super.sendError(sc, msg);
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException {
+      status(sc);
+      super.sendError(sc);
+    }
+
+    @Override
+    public void setStatus(int sc, String sm) {
+      status(sc);
+      super.setStatus(sc, sm);
+    }
+
+    @Override
+    public void setStatus(int sc) {
+      status(sc);
+      super.setStatus(sc);
+    }
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index 9e12e8c..84aa532 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -22,6 +22,7 @@
 
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gwtjsonrpc.server.SignedToken;
@@ -164,7 +165,9 @@
     if (expect.equals(response)) {
       try {
         if (tokens.checkToken(nonce, "") != null) {
-          session.get().setUserAccountId(who.getAccount().getId());
+          session.get().setUserAccountId(
+              who.getAccount().getId(),
+              AuthMethod.PASSWORD);
           return true;
 
         } else {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
index ff41ad8..bb1ad69 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
@@ -14,9 +14,11 @@
 
 package com.google.gerrit.httpd;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.util.cli.CmdLineParser;
 import com.google.gwtjsonrpc.common.JsonConstants;
@@ -99,7 +101,9 @@
       if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
         String msg = String.format(
             "fatal: %s does not have \"%s\" capability.",
-            user.getUserName(), rc.value());
+            Objects.firstNonNull(user.getUserName(),
+                ((IdentifiedUser)user).getNameEmail()),
+            rc.value());
         throw new RequireCapabilityException(msg);
       }
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index a712f9b..9e69946 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -26,7 +26,6 @@
 import com.google.gerrit.httpd.rpc.account.AccountCapabilitiesServlet;
 import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
 import com.google.gerrit.httpd.rpc.change.ListChangesServlet;
-import com.google.gerrit.httpd.rpc.plugin.ListPluginsServlet;
 import com.google.gerrit.httpd.rpc.project.ListProjectsServlet;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
@@ -96,7 +95,6 @@
     filter("/a/*").through(RequireIdentifiedUserFilter.class);
     serveRegex("^/(?:a/)?accounts/self/capabilities$").with(AccountCapabilitiesServlet.class);
     serveRegex("^/(?:a/)?changes/$").with(ListChangesServlet.class);
-    serveRegex("^/(?:a/)?plugins/$").with(ListPluginsServlet.class);
     serveRegex("^/(?:a/)?projects/(.*)?$").with(ListProjectsServlet.class);
 
     if (cfg.deprecatedQuery) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
index 2925896..88c1420 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
@@ -18,9 +18,12 @@
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.account.AuthResult;
 
 public interface WebSession {
+  public AuthMethod getAuthMethod();
+
   public boolean isSignedIn();
 
   public String getToken();
@@ -31,13 +34,13 @@
 
   public CurrentUser getCurrentUser();
 
-  public void login(AuthResult res, boolean rememberMe);
+  public void login(AuthResult res, AuthMethod meth, boolean rememberMe);
 
   /** Change the access path from the default of {@link AccessPath#WEB_UI}. */
   public void setAccessPath(AccessPath path);
 
   /** Set the user account for this current request only. */
-  public void setUserAccountId(Account.Id id);
+  public void setUserAccountId(Account.Id id, AuthMethod method);
 
   public void logout();
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 4710c39..0821496 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gwtorm.server.OrmException;
@@ -113,7 +114,7 @@
     }
 
     if (res != null) {
-      webSession.get().login(res, false);
+      webSession.get().login(res, AuthMethod.BACKDOOR, false);
       final StringBuilder rdr = new StringBuilder();
       rdr.append(req.getContextPath());
       if (IS_DEV && req.getParameter("gwt.codesvr") != null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index 5df004e..9b7eaf5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gerrit.server.config.AuthConfig;
@@ -135,7 +136,8 @@
     }
     rdr.append(token);
 
-    webSession.get().login(arsp, true /* persistent cookie */);
+    webSession.get().login(arsp, AuthMethod.COOKIE,
+                           true /* persistent cookie */);
     rsp.sendRedirect(rdr.toString());
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
index 381daa8..ff0eb29 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.inject.Inject;
@@ -84,7 +85,7 @@
       log.error(err, e);
       throw new ServletException(err, e);
     }
-    webSession.get().login(arsp, true);
+    webSession.get().login(arsp, AuthMethod.COOKIE, true);
     chain.doFilter(req, rsp);
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
index 9d14872..348ecbb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AccountUserNameException;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gerrit.server.auth.AuthenticationUnavailableException;
@@ -29,11 +30,17 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 class UserPassAuthServiceImpl implements UserPassAuthService {
   private final Provider<WebSession> webSession;
   private final AccountManager accountManager;
   private final AuthType authType;
 
+  private static final Logger log = LoggerFactory
+      .getLogger(UserPassAuthServiceImpl.class);
+
   @Inject
   UserPassAuthServiceImpl(final Provider<WebSession> webSession,
       final AccountManager accountManager, final AuthConfig authConfig) {
@@ -72,6 +79,7 @@
       callback.onSuccess(result);
       return;
     } catch (AccountException e) {
+      log.info(String.format("'%s' failed to sign in: %s", username, e.getMessage()));
       result.setError(LoginResult.Error.INVALID_LOGIN);
       callback.onSuccess(result);
       return;
@@ -79,7 +87,8 @@
 
     result.success = true;
     result.isNew = res.isNew();
-    webSession.get().login(res, true /* persistent cookie */);
+    webSession.get().login(res, AuthMethod.PASSWORD,
+                           true /* persistent cookie */);
     callback.onSuccess(result);
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 47eae99..e737700 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.httpd.rpc.plugin.ListPluginsServlet;
 import com.google.gerrit.server.MimeUtilFileTypeRegistry;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -79,6 +80,7 @@
   private final Cache<ResourceKey, Resource> resourceCache;
   private final String sshHost;
   private final int sshPort;
+  private final ListPluginsServlet listServlet;
 
   private List<Plugin> pending = Lists.newArrayList();
   private String base;
@@ -90,10 +92,11 @@
       @CanonicalWebUrl Provider<String> webUrl,
       @Named(HttpPluginModule.PLUGIN_RESOURCES) Cache<ResourceKey, Resource> cache,
       @GerritServerConfig Config cfg,
-      SshInfo sshInfo) {
+      SshInfo sshInfo, ListPluginsServlet listServlet) {
     this.mimeUtil = mimeUtil;
     this.webUrl = webUrl;
     this.resourceCache = cache;
+    this.listServlet = listServlet;
 
     String sshHost = "review.example.com";
     int sshPort = 29418;
@@ -185,6 +188,10 @@
   public void service(HttpServletRequest req, HttpServletResponse res)
       throws IOException, ServletException {
     String name = extractName(req);
+    if (name.equals("")) {
+      listServlet.service(req, res);
+      return;
+    }
     final PluginHolder holder = plugins.get(name);
     if (holder == null) {
       noCache(res);
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 c7b4c79..aca2e05 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -31,15 +31,20 @@
 import com.google.gerrit.reviewdb.client.AccountGroupIncludeAudit;
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.account.GroupIncludeCache;
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.VoidResult;
 import com.google.gwtorm.server.OrmException;
@@ -54,6 +59,8 @@
     GroupAdminService {
   private final AccountCache accountCache;
   private final AccountResolver accountResolver;
+  private final AccountManager accountManager;
+  private final AuthType authType;
   private final GroupCache groupCache;
   private final GroupBackend groupBackend;
   private final GroupIncludeCache groupIncludeCache;
@@ -70,6 +77,8 @@
       final AccountCache accountCache,
       final GroupIncludeCache groupIncludeCache,
       final AccountResolver accountResolver,
+      final AccountManager accountManager,
+      final AuthConfig authConfig,
       final GroupCache groupCache,
       final GroupBackend groupBackend,
       final GroupControl.Factory groupControlFactory,
@@ -81,6 +90,8 @@
     this.accountCache = accountCache;
     this.groupIncludeCache = groupIncludeCache;
     this.accountResolver = accountResolver;
+    this.accountManager = accountManager;
+    this.authType = authConfig.getAuthType();
     this.groupCache = groupCache;
     this.groupBackend = groupBackend;
     this.groupControlFactory = groupControlFactory;
@@ -366,13 +377,38 @@
 
   private Account findAccount(final String nameOrEmail) throws OrmException,
       Failure {
-    final Account r = accountResolver.find(nameOrEmail);
+    Account r = accountResolver.find(nameOrEmail);
     if (r == null) {
-      throw new Failure(new NoSuchAccountException(nameOrEmail));
+      switch (authType) {
+        case HTTP_LDAP:
+        case CLIENT_SSL_CERT_LDAP:
+        case LDAP:
+          r = createAccountByLdap(nameOrEmail);
+          break;
+        default:
+      }
+      if (r == null) {
+        throw new Failure(new NoSuchAccountException(nameOrEmail));
+      }
     }
     return r;
   }
 
+  private Account createAccountByLdap(String user) {
+    if (!user.matches(Account.USER_NAME_PATTERN)) {
+      return null;
+    }
+
+    try {
+      final AuthRequest req = AuthRequest.forUser(user);
+      req.setSkipAuthentication(true);
+      return accountCache.get(accountManager.authenticate(req).getAccountId())
+          .getAccount();
+    } catch (AccountException e) {
+      return null;
+    }
+  }
+
   private AccountGroup findGroup(final String name) throws OrmException,
       Failure {
     final AccountGroup g = groupCache.get(new AccountGroup.NameKey(name));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
index 1713422..2110885 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
@@ -22,9 +22,11 @@
 import com.google.gerrit.server.changedetail.AbandonChange;
 import com.google.gerrit.server.mail.EmailException;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -39,7 +41,7 @@
     AbandonChangeHandler create(PatchSet.Id patchSetId, String message);
   }
 
-  private final AbandonChange.Factory abandonChangeFactory;
+  private final Provider<AbandonChange> abandonChangeProvider;
   private final ChangeDetailFactory.Factory changeDetailFactory;
 
   private final PatchSet.Id patchSetId;
@@ -47,11 +49,11 @@
   private final String message;
 
   @Inject
-  AbandonChangeHandler(final AbandonChange.Factory abandonChangeFactory,
+  AbandonChangeHandler(final Provider<AbandonChange> abandonChangeProvider,
       final ChangeDetailFactory.Factory changeDetailFactory,
       @Assisted final PatchSet.Id patchSetId,
       @Assisted @Nullable final String message) {
-    this.abandonChangeFactory = abandonChangeFactory;
+    this.abandonChangeProvider = abandonChangeProvider;
     this.changeDetailFactory = changeDetailFactory;
 
     this.patchSetId = patchSetId;
@@ -60,10 +62,13 @@
 
   @Override
   public ChangeDetail call() throws NoSuchChangeException, OrmException,
-      EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
-      RepositoryNotFoundException, IOException {
-    final ReviewResult result =
-        abandonChangeFactory.create(patchSetId.getParentKey(), message).call();
+      EmailException, NoSuchEntityException, InvalidChangeOperationException,
+      PatchSetInfoNotAvailableException, RepositoryNotFoundException,
+      IOException {
+    final AbandonChange abandonChange = abandonChangeProvider.get();
+    abandonChange.setChangeId(patchSetId.getParentKey());
+    abandonChange.setMessage(message);
+    final ReviewResult result = abandonChange.call();
     if (result.getErrors().size() > 0) {
       throw new NoSuchChangeException(result.getChangeId());
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index 6b5299a..6660a3d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ProjectUtil;
 import com.google.gerrit.server.account.AccountInfoCacheFactory;
@@ -41,6 +42,7 @@
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.workflow.CategoryFunction;
 import com.google.gerrit.server.workflow.FunctionState;
 import com.google.gwtorm.server.OrmException;
@@ -152,7 +154,8 @@
       }
       if (detail.getChange().getStatus().isOpen()
           && rec.status == SubmitRecord.Status.OK
-          && control.getRefControl().canSubmit()) {
+          && control.getRefControl().canSubmit()
+          && ProjectUtil.branchExists(repoManager, change.getDest())) {
         detail.setCanSubmit(true);
       }
     }
@@ -203,7 +206,9 @@
   }
 
   private void load() throws OrmException, NoSuchChangeException {
-    if (detail.getChange().getStatus().equals(Change.Status.NEW) && testMerge) {
+    final Change.Status status = detail.getChange().getStatus();
+    if ((status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) &&
+        testMerge) {
       ChangeUtil.testMerge(opFactory, detail.getChange());
     }
 
@@ -249,6 +254,15 @@
     detail.setApprovals(ad.values());
   }
 
+  private boolean isReviewer(Change change) {
+    // Return true if the currently logged in user is a reviewer of the change.
+    try {
+      return control.isReviewer(db, new ChangeData(change));
+    } catch (OrmException e) {
+        return false;
+    }
+  }
+
   private void loadCurrentPatchSet() throws OrmException,
       NoSuchEntityException, PatchSetInfoNotAvailableException,
       NoSuchChangeException {
@@ -274,15 +288,14 @@
       }
     }
 
-    final Set<Change.Id> descendants = new HashSet<Change.Id>();
+    final Set<PatchSet.Id> descendants = new HashSet<PatchSet.Id>();
     RevId cprev;
     for (PatchSet p : detail.getPatchSets()) {
       cprev = p.getRevision();
       if (cprev != null) {
         for (PatchSetAncestor a : db.patchSetAncestors().descendantsOf(cprev)) {
-          final Change.Id ck = a.getPatchSet().getParentKey();
-          if (descendants.add(ck)) {
-            changesToGet.add(ck);
+          if (descendants.add(a.getPatchSet())) {
+            changesToGet.add(a.getPatchSet().getParentKey());
           }
         }
       }
@@ -290,19 +303,33 @@
     final Map<Change.Id, Change> m =
         db.changes().toMap(db.changes().get(changesToGet));
 
+    final CurrentUser currentUser = control.getCurrentUser();
+    Account.Id currentUserId = null;
+    if (currentUser instanceof IdentifiedUser) {
+        currentUserId = ((IdentifiedUser) currentUser).getAccountId();
+    }
+
     final ArrayList<ChangeInfo> dependsOn = new ArrayList<ChangeInfo>();
     for (final Change.Id a : ancestorOrder) {
       final Change ac = m.get(a);
-      if (ac != null) {
-        dependsOn.add(newChangeInfo(ac, ancestorPatchIds));
+      if (ac != null && ac.getProject().equals(detail.getChange().getProject())) {
+        if (ac.getStatus().getCode() != Change.STATUS_DRAFT
+            || ac.getOwner().equals(currentUserId)
+            || isReviewer(ac)) {
+          dependsOn.add(newChangeInfo(ac, ancestorPatchIds));
+        }
       }
     }
 
     final ArrayList<ChangeInfo> neededBy = new ArrayList<ChangeInfo>();
-    for (final Change.Id a : descendants) {
-      final Change ac = m.get(a);
-      if (ac != null) {
-        neededBy.add(newChangeInfo(ac, null));
+    for (final PatchSet.Id a : descendants) {
+      final Change ac = m.get(a.getParentKey());
+      if (ac != null && ac.currentPatchSetId().equals(a)) {
+        if (ac.getStatus().getCode() != Change.STATUS_DRAFT
+            || ac.getOwner().equals(currentUserId)
+            || isReviewer(ac)) {
+          neededBy.add(newChangeInfo(ac, null));
+        }
       }
     }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index 37a1c92..50baf97 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -14,12 +14,14 @@
 
 package com.google.gerrit.httpd.rpc.changedetail;
 
+import com.google.gerrit.common.data.ApprovalDetail;
 import com.google.gerrit.common.data.ApprovalType;
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.common.data.PatchSetPublishDetail;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -32,6 +34,8 @@
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.workflow.CategoryFunction;
+import com.google.gerrit.server.workflow.FunctionState;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -49,6 +53,7 @@
 
   private final PatchSetInfoFactory infoFactory;
   private final ReviewDb db;
+  private final FunctionState.Factory functionState;
   private final ChangeControl.Factory changeControlFactory;
   private final ApprovalTypes approvalTypes;
   private final AccountInfoCacheFactory aic;
@@ -64,11 +69,13 @@
   PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
       final ReviewDb db,
       final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
+      final FunctionState.Factory functionState,
       final ChangeControl.Factory changeControlFactory,
       final ApprovalTypes approvalTypes,
       final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
     this.infoFactory = infoFactory;
     this.db = db;
+    this.functionState = functionState;
     this.changeControlFactory = changeControlFactory;
     this.approvalTypes = approvalTypes;
     this.aic = accountInfoCacheFactory.create();
@@ -130,6 +137,8 @@
           int ok = 0;
 
           for (SubmitRecord.Label lbl : rec.labels) {
+            aic.want(lbl.appliedBy);
+
             boolean canMakeOk = false;
             PermissionRange range = rangeByName.get(lbl.label);
             if (range != null) {
@@ -167,12 +176,60 @@
       if (couldSubmit && control.getRefControl().canSubmit()) {
         detail.setCanSubmit(true);
       }
+
+      detail.setSubmitRecords(submitRecords);
     }
 
     detail.setLabels(allowed);
     detail.setGiven(given);
+    loadApprovals(detail, control);
+
     detail.setAccounts(aic.create());
 
     return detail;
   }
+
+  private void loadApprovals(final PatchSetPublishDetail detail,
+      final ChangeControl control) throws OrmException {
+    final PatchSet.Id psId = detail.getChange().currentPatchSetId();
+    final Change.Id changeId = patchSetId.getParentKey();
+    final List<PatchSetApproval> allApprovals =
+        db.patchSetApprovals().byChange(changeId).toList();
+
+    if (detail.getChange().getStatus().isOpen()) {
+      final FunctionState fs = functionState.create(control, psId, allApprovals);
+
+      for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
+        CategoryFunction.forCategory(at.getCategory()).run(at, fs);
+      }
+    }
+
+    final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() //
+        && control.getCurrentUser() instanceof IdentifiedUser;
+    final HashMap<Account.Id, ApprovalDetail> ad =
+        new HashMap<Account.Id, ApprovalDetail>();
+    for (PatchSetApproval ca : allApprovals) {
+      ApprovalDetail d = ad.get(ca.getAccountId());
+      if (d == null) {
+        d = new ApprovalDetail(ca.getAccountId());
+        d.setCanRemove(canRemoveReviewers);
+        ad.put(d.getAccount(), d);
+      }
+      if (d.canRemove()) {
+        d.setCanRemove(control.canRemoveReviewer(ca));
+      }
+      if (ca.getPatchSetId().equals(psId)) {
+        d.add(ca);
+      }
+    }
+
+    final Account.Id owner = detail.getChange().getOwner();
+    if (ad.containsKey(owner)) {
+      // Ensure the owner always sorts to the top of the table
+      ad.get(owner).sortFirst();
+    }
+
+    aic.want(ad.keySet());
+    detail.setApprovals(ad.values());
+  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
index 12d66fd..e4571fd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
@@ -22,9 +22,11 @@
 import com.google.gerrit.server.changedetail.RestoreChange;
 import com.google.gerrit.server.mail.EmailException;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -38,7 +40,7 @@
     RestoreChangeHandler create(PatchSet.Id patchSetId, String message);
   }
 
-  private final RestoreChange.Factory restoreChangeFactory;
+  private final Provider<RestoreChange> restoreChangeProvider;
   private final ChangeDetailFactory.Factory changeDetailFactory;
 
   private final PatchSet.Id patchSetId;
@@ -46,11 +48,11 @@
   private final String message;
 
   @Inject
-  RestoreChangeHandler(final RestoreChange.Factory restoreChangeFactory,
+  RestoreChangeHandler(final Provider<RestoreChange> restoreChangeProvider,
       final ChangeDetailFactory.Factory changeDetailFactory,
       @Assisted final PatchSet.Id patchSetId,
       @Assisted @Nullable final String message) {
-    this.restoreChangeFactory = restoreChangeFactory;
+    this.restoreChangeProvider = restoreChangeProvider;
     this.changeDetailFactory = changeDetailFactory;
 
     this.patchSetId = patchSetId;
@@ -59,10 +61,13 @@
 
   @Override
   public ChangeDetail call() throws NoSuchChangeException, OrmException,
-      EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
-      RepositoryNotFoundException, IOException {
-    final ReviewResult result =
-        restoreChangeFactory.create(patchSetId.getParentKey(), message).call();
+      EmailException, NoSuchEntityException, InvalidChangeOperationException,
+      PatchSetInfoNotAvailableException, RepositoryNotFoundException,
+      IOException {
+    final RestoreChange restoreChange = restoreChangeProvider.get();
+    restoreChange.setChangeId(patchSetId.getParentKey());
+    restoreChange.setMessage(message);
+    final ReviewResult result = restoreChange.call();
     if (result.getErrors().size() > 0) {
       throw new NoSuchChangeException(result.getChangeId());
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index 8ad9a10..97d850a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountInfoCacheFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LargeObjectException;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListEntry;
@@ -121,7 +122,8 @@
   }
 
   @Override
-  public PatchScript call() throws OrmException, NoSuchChangeException {
+  public PatchScript call() throws OrmException, NoSuchChangeException,
+      LargeObjectException {
     validatePatchSetId(psa);
     validatePatchSetId(psb);
 
@@ -161,6 +163,8 @@
     } catch (IOException e) {
       log.error("File content unavailable", e);
       throw new NoSuchChangeException(changeId, e);
+    } catch (org.eclipse.jgit.errors.LargeObjectException err) {
+      throw new LargeObjectException("File content is too large", err);
     } finally {
       git.close();
     }
@@ -252,7 +256,7 @@
         break;
 
       case DELETED:
-        loadPublished(byKey, aic, oldName);
+        loadPublished(byKey, aic, newName);
         break;
 
       case COPIED:
@@ -274,7 +278,7 @@
           break;
 
         case DELETED:
-          loadDrafts(byKey, aic, me, oldName);
+          loadDrafts(byKey, aic, me, newName);
           break;
 
         case COPIED:
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index c991c47..72b5e3a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -15,41 +15,26 @@
 package com.google.gerrit.httpd.rpc.project;
 
 import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.ProjectAccess;
-import com.google.gerrit.common.errors.InvalidNameException;
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
-import com.google.gwtorm.server.OrmConcurrencyException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 import javax.annotation.Nullable;
 
-class ChangeProjectAccess extends Handler<ProjectAccess> {
+class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
   interface Factory {
     ChangeProjectAccess create(@Assisted Project.NameKey projectName,
         @Nullable @Assisted ObjectId base,
@@ -58,15 +43,7 @@
   }
 
   private final ProjectAccessFactory.Factory projectAccessFactory;
-  private final ProjectControl.Factory projectControlFactory;
   private final ProjectCache projectCache;
-  private final GroupBackend groupBackend;
-  private final MetaDataUpdate.User metaDataUpdateFactory;
-
-  private final Project.NameKey projectName;
-  private final ObjectId base;
-  private List<AccessSection> sectionList;
-  private String message;
 
   @Inject
   ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
@@ -78,132 +55,17 @@
       @Nullable @Assisted final ObjectId base,
       @Assisted List<AccessSection> sectionList,
       @Nullable @Assisted String message) {
+    super(projectControlFactory, groupBackend, metaDataUpdateFactory,
+        projectName, base, sectionList, message, true);
     this.projectAccessFactory = projectAccessFactory;
-    this.projectControlFactory = projectControlFactory;
     this.projectCache = projectCache;
-    this.groupBackend = groupBackend;
-    this.metaDataUpdateFactory = metaDataUpdateFactory;
-
-    this.projectName = projectName;
-    this.base = base;
-    this.sectionList = sectionList;
-    this.message = message;
   }
 
   @Override
-  public ProjectAccess call() throws NoSuchProjectException, IOException,
-      ConfigInvalidException, InvalidNameException, NoSuchGroupException,
-      OrmConcurrencyException {
-    final ProjectControl projectControl =
-        projectControlFactory.controlFor(projectName);
-
-    final MetaDataUpdate md;
-    try {
-      md = metaDataUpdateFactory.create(projectName);
-    } catch (RepositoryNotFoundException notFound) {
-      throw new NoSuchProjectException(projectName);
-    }
-    try {
-      ProjectConfig config = ProjectConfig.read(md, base);
-      Set<String> toDelete = scanSectionNames(config);
-
-      for (AccessSection section : mergeSections(sectionList)) {
-        String name = section.getName();
-
-        if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
-          if (!projectControl.isOwner()) {
-            continue;
-          }
-          replace(config, toDelete, section);
-
-        } else if (AccessSection.isValid(name)) {
-          if (!projectControl.controlForRef(name).isOwner()) {
-            continue;
-          }
-
-          RefControl.validateRefPattern(name);
-
-          replace(config, toDelete, section);
-        }
-      }
-
-      for (String name : toDelete) {
-        if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
-          if (projectControl.isOwner()) {
-            config.remove(config.getAccessSection(name));
-          }
-
-        } else if (projectControl.controlForRef(name).isOwner()) {
-          config.remove(config.getAccessSection(name));
-        }
-      }
-
-      if (message != null && !message.isEmpty()) {
-        if (!message.endsWith("\n")) {
-          message += "\n";
-        }
-        md.setMessage(message);
-      } else {
-        md.setMessage("Modify access rules\n");
-      }
-
-      if (config.commit(md)) {
-        projectCache.evict(config.getProject());
-        return projectAccessFactory.create(projectName).call();
-
-      } else {
-        throw new OrmConcurrencyException("Cannot update " + projectName);
-      }
-    } finally {
-      md.close();
-    }
-  }
-
-  private void replace(ProjectConfig config, Set<String> toDelete,
-      AccessSection section) throws NoSuchGroupException {
-    for (Permission permission : section.getPermissions()) {
-      for (PermissionRule rule : permission.getRules()) {
-        lookupGroup(rule);
-      }
-    }
-    config.replace(section);
-    toDelete.remove(section.getName());
-  }
-
-  private static List<AccessSection> mergeSections(List<AccessSection> src) {
-    Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
-    for (AccessSection section : src) {
-      if (section.getPermissions().isEmpty()) {
-        continue;
-      }
-
-      AccessSection prior = map.get(section.getName());
-      if (prior != null) {
-        prior.mergeFrom(section);
-      } else {
-        map.put(section.getName(), section);
-      }
-    }
-    return new ArrayList<AccessSection>(map.values());
-  }
-
-  private static Set<String> scanSectionNames(ProjectConfig config) {
-    Set<String> names = new HashSet<String>();
-    for (AccessSection section : config.getAccessSections()) {
-      names.add(section.getName());
-    }
-    return names;
-  }
-
-  private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
-    GroupReference ref = rule.getGroup();
-    if (ref.getUUID() == null) {
-      final GroupReference group =
-          GroupBackends.findBestSuggestion(groupBackend, ref.getName());
-      if (group == null) {
-        throw new NoSuchGroupException(ref.getName());
-      }
-      ref.setUUID(group.getUUID());
-    }
+  protected ProjectAccess updateProjectConfig(ProjectConfig config,
+      MetaDataUpdate md) throws IOException, NoSuchProjectException, ConfigInvalidException {
+    config.commit(md);
+    projectCache.evict(config.getProject());
+    return projectAccessFactory.create(projectName).call();
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
index a2b62cc..41354aa 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
@@ -86,10 +86,11 @@
       config.getProject().copySettingsFrom(update);
 
       md.setMessage("Modified project settings\n");
-      if (config.commit(md)) {
+      try {
+        config.commit(md);
         mgr.setProjectDescription(projectName, update.getDescription());
         userCache.get().evict(config.getProject());
-      } else {
+      } catch (IOException e) {
         throw new OrmConcurrencyException("Cannot update " + projectName);
       }
     } catch (ConfigInvalidException err) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index f934d11..250f5e3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -96,10 +96,9 @@
 
       if (config.updateGroupNames(groupBackend)) {
         md.setMessage("Update group names\n");
-        if (config.commit(md)) {
-          projectCache.evict(config.getProject());
-          pc = open();
-        }
+        config.commit(md);
+        projectCache.evict(config.getProject());
+        pc = open();
       } else if (config.getRevision() != null
           && !config.getRevision().equals(
               pc.getProjectState().getConfig().getRevision())) {
@@ -196,6 +195,8 @@
 
     detail.setLocal(local);
     detail.setOwnerOf(ownerOf);
+    detail.setCanUpload(pc.isOwner()
+        || (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
     detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
     return detail;
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
new file mode 100644
index 0000000..02b84b0
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -0,0 +1,190 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.project;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class ProjectAccessHandler<T> extends Handler<T> {
+
+  private final ProjectControl.Factory projectControlFactory;
+  protected final GroupBackend groupBackend;
+  private final MetaDataUpdate.User metaDataUpdateFactory;
+
+  protected final Project.NameKey projectName;
+  protected final ObjectId base;
+  private List<AccessSection> sectionList;
+  protected String message;
+  private boolean checkIfOwner;
+
+  protected ProjectAccessHandler(
+      final ProjectControl.Factory projectControlFactory,
+      final GroupBackend groupBackend,
+      final MetaDataUpdate.User metaDataUpdateFactory,
+      final Project.NameKey projectName, final ObjectId base,
+      final List<AccessSection> sectionList, final String message,
+      final boolean checkIfOwner) {
+    this.projectControlFactory = projectControlFactory;
+    this.groupBackend = groupBackend;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+
+    this.projectName = projectName;
+    this.base = base;
+    this.sectionList = sectionList;
+    this.message = message;
+    this.checkIfOwner = checkIfOwner;
+  }
+
+  @Override
+  public final T call() throws NoSuchProjectException, IOException,
+      ConfigInvalidException, InvalidNameException, NoSuchGroupException,
+      OrmException {
+    final ProjectControl projectControl =
+        projectControlFactory.controlFor(projectName);
+
+    final MetaDataUpdate md;
+    try {
+      md = metaDataUpdateFactory.create(projectName);
+    } catch (RepositoryNotFoundException notFound) {
+      throw new NoSuchProjectException(projectName);
+    }
+    try {
+      ProjectConfig config = ProjectConfig.read(md, base);
+      Set<String> toDelete = scanSectionNames(config);
+
+      for (AccessSection section : mergeSections(sectionList)) {
+        String name = section.getName();
+
+        if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
+          if (checkIfOwner && !projectControl.isOwner()) {
+            continue;
+          }
+          replace(config, toDelete, section);
+
+        } else if (AccessSection.isValid(name)) {
+          if (checkIfOwner && !projectControl.controlForRef(name).isOwner()) {
+            continue;
+          }
+
+          RefControl.validateRefPattern(name);
+
+          replace(config, toDelete, section);
+        }
+      }
+
+      for (String name : toDelete) {
+        if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
+          if (!checkIfOwner || projectControl.isOwner()) {
+            config.remove(config.getAccessSection(name));
+          }
+
+        } else if (!checkIfOwner ||  projectControl.controlForRef(name).isOwner()) {
+          config.remove(config.getAccessSection(name));
+        }
+      }
+
+      if (message != null && !message.isEmpty()) {
+        if (!message.endsWith("\n")) {
+          message += "\n";
+        }
+        md.setMessage(message);
+      } else {
+        md.setMessage("Modify access rules\n");
+      }
+
+      return updateProjectConfig(config, md);
+    } finally {
+      md.close();
+    }
+  }
+
+  protected abstract T updateProjectConfig(ProjectConfig config,
+      MetaDataUpdate md) throws IOException, NoSuchProjectException,
+      ConfigInvalidException, OrmException;
+
+  private void replace(ProjectConfig config, Set<String> toDelete,
+      AccessSection section) throws NoSuchGroupException {
+    for (Permission permission : section.getPermissions()) {
+      for (PermissionRule rule : permission.getRules()) {
+        lookupGroup(rule);
+      }
+    }
+    config.replace(section);
+    toDelete.remove(section.getName());
+  }
+
+  private static List<AccessSection> mergeSections(List<AccessSection> src) {
+    Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
+    for (AccessSection section : src) {
+      if (section.getPermissions().isEmpty()) {
+        continue;
+      }
+
+      AccessSection prior = map.get(section.getName());
+      if (prior != null) {
+        prior.mergeFrom(section);
+      } else {
+        map.put(section.getName(), section);
+      }
+    }
+    return new ArrayList<AccessSection>(map.values());
+  }
+
+  private static Set<String> scanSectionNames(ProjectConfig config) {
+    Set<String> names = new HashSet<String>();
+    for (AccessSection section : config.getAccessSections()) {
+      names.add(section.getName());
+    }
+    return names;
+  }
+
+  private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
+    GroupReference ref = rule.getGroup();
+    if (ref.getUUID() == null) {
+      final GroupReference group =
+          GroupBackends.findBestSuggestion(groupBackend, ref.getName());
+      if (group == null) {
+        throw new NoSuchGroupException(ref.getName());
+      }
+      ref.setUUID(group.getUUID());
+    }
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index ca7f448..15f167c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.ProjectAdminService;
 import com.google.gerrit.common.data.ProjectDetail;
 import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.VoidResult;
@@ -33,6 +34,7 @@
 class ProjectAdminServiceImpl implements ProjectAdminService {
   private final AddBranch.Factory addBranchFactory;
   private final ChangeProjectAccess.Factory changeProjectAccessFactory;
+  private final ReviewProjectAccess.Factory reviewProjectAccessFactory;
   private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
   private final DeleteBranches.Factory deleteBranchesFactory;
   private final ListBranches.Factory listBranchesFactory;
@@ -44,6 +46,7 @@
   @Inject
   ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
       final ChangeProjectAccess.Factory changeProjectAccessFactory,
+      final ReviewProjectAccess.Factory reviewProjectAccessFactory,
       final ChangeProjectSettings.Factory changeProjectSettingsFactory,
       final DeleteBranches.Factory deleteBranchesFactory,
       final ListBranches.Factory listBranchesFactory,
@@ -53,6 +56,7 @@
       final CreateProjectHandler.Factory createNewProjectFactory) {
     this.addBranchFactory = addBranchFactory;
     this.changeProjectAccessFactory = changeProjectAccessFactory;
+    this.reviewProjectAccessFactory = reviewProjectAccessFactory;
     this.changeProjectSettingsFactory = changeProjectSettingsFactory;
     this.deleteBranchesFactory = deleteBranchesFactory;
     this.listBranchesFactory = listBranchesFactory;
@@ -99,6 +103,14 @@
   }
 
   @Override
+  public void reviewProjectAccess(Project.NameKey projectName,
+      String baseRevision, String msg, List<AccessSection> sections,
+      AsyncCallback<Change.Id> cb) {
+    ObjectId base = ObjectId.fromString(baseRevision);
+    reviewProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
+  }
+
+  @Override
   public void listBranches(final Project.NameKey projectName,
       final AsyncCallback<ListBranchesResult> callback) {
     listBranchesFactory.create(projectName).to(callback);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
index efcc22f..e943e3fc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
@@ -30,6 +30,7 @@
       protected void configure() {
         factory(AddBranch.Factory.class);
         factory(ChangeProjectAccess.Factory.class);
+        factory(ReviewProjectAccess.Factory.class);
         factory(CreateProjectHandler.Factory.class);
         factory(ChangeProjectSettings.Factory.class);
         factory(DeleteBranches.Factory.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
new file mode 100644
index 0000000..c422f6d
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.project;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.patch.AddReviewer;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
+  interface Factory {
+    ReviewProjectAccess create(@Assisted Project.NameKey projectName,
+        @Nullable @Assisted ObjectId base,
+        @Assisted List<AccessSection> sectionList,
+        @Nullable @Assisted String message);
+  }
+
+  private final ReviewDb db;
+  private final IdentifiedUser user;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final AddReviewer.Factory addReviewerFactory;
+
+  @Inject
+  ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
+      final GroupBackend groupBackend,
+      final MetaDataUpdate.User metaDataUpdateFactory, final ReviewDb db,
+      final IdentifiedUser user, final PatchSetInfoFactory patchSetInfoFactory,
+      final AddReviewer.Factory addReviewerFactory,
+
+      @Assisted final Project.NameKey projectName,
+      @Nullable @Assisted final ObjectId base,
+      @Assisted List<AccessSection> sectionList,
+      @Nullable @Assisted String message) {
+    super(projectControlFactory, groupBackend, metaDataUpdateFactory,
+        projectName, base, sectionList, message, false);
+    this.db = db;
+    this.user = user;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.addReviewerFactory = addReviewerFactory;
+  }
+
+  @Override
+  protected Change.Id updateProjectConfig(ProjectConfig config, MetaDataUpdate md)
+      throws IOException, NoSuchProjectException, ConfigInvalidException, OrmException {
+    int nextChangeId = db.nextChangeId();
+    PatchSet.Id patchSetId = new PatchSet.Id(new Change.Id(nextChangeId), 1);
+    final PatchSet ps = new PatchSet(patchSetId);
+    RevCommit commit = config.commitToNewRef(md, ps.getRefName());
+    if (commit.getId().equals(base)) {
+      return null;
+    }
+    Change.Key changeKey = new Change.Key("I" + commit.name());
+    final Change change =
+        new Change(changeKey, new Change.Id(nextChangeId), user.getAccountId(),
+            new Branch.NameKey(config.getProject().getNameKey(),
+                GitRepositoryManager.REF_CONFIG));
+    change.nextPatchSetId();
+
+    ps.setCreatedOn(change.getCreatedOn());
+    ps.setUploader(user.getAccountId());
+    ps.setRevision(new RevId(commit.name()));
+
+    db.patchSets().insert(Collections.singleton(ps));
+
+    final PatchSetInfo info = patchSetInfoFactory.get(commit, ps.getId());
+    change.setCurrentPatchSet(info);
+    ChangeUtil.updated(change);
+
+    db.changes().insert(Collections.singleton(change));
+
+    addProjectOwnersAsReviewers(change.getId());
+
+    return change.getId();
+  }
+
+  private void addProjectOwnersAsReviewers(final Change.Id changeId) {
+    final String projectOwners =
+        groupBackend.get(AccountGroup.PROJECT_OWNERS).getName();
+    try {
+      addReviewerFactory.create(changeId, Collections.singleton(projectOwners),
+          false).call();
+    } catch (Exception e) {
+      // one of the owner groups is not visible to the user and this it why it
+      // can't be added as reviewer
+    }
+  }
+}
diff --git a/gerrit-launcher/.settings/org.eclipse.core.resources.prefs b/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-main/.settings/org.eclipse.core.resources.prefs b/gerrit-main/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-main/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-main/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-openid/.settings/org.eclipse.core.resources.prefs b/gerrit-openid/.settings/org.eclipse.core.resources.prefs
index fc11c3f..f9fe345 100644
--- a/gerrit-openid/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-openid/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/test/java=UTF-8
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 0593bce..09a5d10 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.UrlEncoded;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthMethod;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.ConfigUtil;
@@ -416,7 +417,7 @@
             lastId.setMaxAge(0);
           }
           rsp.addCookie(lastId);
-          webSession.get().login(arsp, remember);
+          webSession.get().login(arsp, AuthMethod.COOKIE, remember);
           if (arsp.isNew() && claimedIdentifier != null) {
             final com.google.gerrit.server.account.AuthRequest linkReq =
                 new com.google.gerrit.server.account.AuthRequest(
@@ -430,7 +431,7 @@
 
         case LINK_IDENTIY: {
           arsp = accountManager.link(identifiedUser.get().getAccountId(), areq);
-          webSession.get().login(arsp, remember);
+          webSession.get().login(arsp, AuthMethod.COOKIE, remember);
           callback(false, req, rsp);
           break;
         }
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs b/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs b/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-pgm/.settings/org.eclipse.core.resources.prefs b/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
index 9df523e..839d647 100644
--- a/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
index f100372..525360d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountCacheImpl;
@@ -35,6 +34,7 @@
 import com.google.gerrit.server.git.CreateCodeReviewNotes;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.git.NotesBranchUtil;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
@@ -44,7 +44,6 @@
 import com.google.inject.Scopes;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
@@ -104,6 +103,7 @@
           @Override
           protected void configure() {
             factory(CreateCodeReviewNotes.Factory.class);
+            factory(NotesBranchUtil.Factory.class);
           }
         });
         install(new LifecycleModule() {
@@ -172,21 +172,8 @@
     }
     try {
       CreateCodeReviewNotes notes = codeReviewNotesFactory.create(db, git);
-      try {
-        notes.loadBase();
-        for (Change change : changes) {
-          monitor.update(1);
-          PatchSet ps = db.patchSets().get(change.currentPatchSetId());
-          if (ps == null) {
-            continue;
-          }
-          notes.add(change, ObjectId.fromString(ps.getRevision().get()));
-        }
-        notes.commit("Exported prior reviews from Gerrit Code Review\n");
-        notes.updateRef();
-      } finally {
-        notes.release();
-      }
+      notes.create(changes, null,
+          "Exported prior reviews from Gerrit Code Review\n", monitor);
     } finally {
       git.close();
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 2d56453..95b8487f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -141,12 +141,12 @@
       }
 
       final StringBuilder buf = new StringBuilder();
-      buf.append(why.getMessage());
-      why = why.getCause();
       while (why != null) {
-        buf.append("\n  caused by ");
-        buf.append(why.toString());
+        buf.append(why.getMessage());
         why = why.getCause();
+        if (why != null) {
+          buf.append("\n  caused by ");
+        }
       }
       throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index 65e73bd..6f439d2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -30,6 +30,7 @@
 import org.eclipse.jetty.server.RequestLog;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jgit.lib.Config;
 
 import java.io.File;
 import java.io.IOException;
@@ -54,7 +55,7 @@
 
   private final AsyncAppender async;
 
-  HttpLog(final SitePaths site) {
+  HttpLog(final SitePaths site, final Config config) {
     final DailyRollingFileAppender dst = new DailyRollingFileAppender();
     dst.setName(LOG_NAME);
     dst.setLayout(new MyLayout());
@@ -69,7 +70,7 @@
 
     async = new AsyncAppender();
     async.setBlocking(true);
-    async.setBufferSize(64);
+    async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
     async.setLocationInfo(false);
     async.addAppender(dst);
     async.activateOptions();
@@ -93,7 +94,7 @@
   private void doLog(Request req, Response rsp, CurrentUser user) {
     final LoggingEvent event = new LoggingEvent( //
         Logger.class.getName(), // fqnOfCategoryClass
-        null, // logger (optional)
+        log, // logger
         System.currentTimeMillis(), // when
         Level.INFO, // level
         "", // message text
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index d85ff20..2e6274c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -117,7 +117,7 @@
     Handler app = makeContext(env, cfg);
     if (cfg.getBoolean("httpd", "requestlog", !reverseProxy)) {
       RequestLogHandler handler = new RequestLogHandler();
-      handler.setRequestLog(new HttpLog(site));
+      handler.setRequestLog(new HttpLog(site, cfg));
       handler.setHandler(app);
       app = handler;
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index ee7c794..2d6db63 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -97,7 +97,7 @@
     this.userProvider = userProvider;
     this.queue = queue;
     this.context = context;
-    this.maxWait = getTimeUnit(cfg, "httpd", null, "maxwait", 5, MINUTES);
+    this.maxWait = MINUTES.toMillis(getTimeUnit(cfg, "httpd", null, "maxwait", 5, MINUTES));
   }
 
   @Override
diff --git a/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..abdea9ac
--- /dev/null
+++ b/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding/<project>=UTF-8
diff --git a/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..470942d
--- /dev/null
+++ b/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,269 @@
+#Thu Jul 28 11:02:36 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-prettify/.settings/org.eclipse.core.resources.prefs b/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
index e7d6680..abdea9ac 100644
--- a/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs b/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
index e7d6680..abdea9ac 100644
--- a/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
index 2ea659d..061ef3e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
@@ -82,7 +82,7 @@
   /** @return true if the UUID is for a group managed within Gerrit. */
   public static boolean isInternalGroup(AccountGroup.UUID uuid) {
     return uuid.get().startsWith("global:")
-        || uuid.get().matches("[0-9a-f]{40}");
+        || uuid.get().matches("^[0-9a-f]{40}$");
   }
 
   /** Synthetic key to link to within the database */
diff --git a/gerrit-server/.settings/org.eclipse.core.resources.prefs b/gerrit-server/.settings/org.eclipse.core.resources.prefs
index 7d5f965..29abf99 100644
--- a/gerrit-server/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-server/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 832bd23..79c047f 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
@@ -37,6 +37,7 @@
 import com.google.gerrit.server.events.ChangeMergedEvent;
 import com.google.gerrit.server.events.ChangeRestoreEvent;
 import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.DraftPublishedEvent;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
@@ -98,6 +99,9 @@
     /** Filename of the new patchset hook. */
     private final File patchsetCreatedHook;
 
+    /** Filename of the draft published hook. */
+    private final File draftPublishedHook;
+
     /** Filename of the new comments hook. */
     private final File commentAddedHook;
 
@@ -163,6 +167,7 @@
         final File hooksPath = sitePath.resolve(getValue(config, "hooks", "path", sitePath.hooks_dir.getAbsolutePath()));
 
         patchsetCreatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "patchsetCreatedHook", "patchset-created")).getPath());
+        draftPublishedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "draftPublishedHook", "draft-published")).getPath());
         commentAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "commentAddedHook", "comment-added")).getPath());
         changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath());
         changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath());
@@ -237,6 +242,28 @@
         runHook(change.getProject(), patchsetCreatedHook, args);
     }
 
+    public void doDraftPublishedHook(final Change change, final PatchSet patchSet,
+          final ReviewDb db) throws OrmException {
+        final DraftPublishedEvent event = new DraftPublishedEvent();
+        final AccountState uploader = accountCache.get(patchSet.getUploader());
+
+        event.change = eventFactory.asChangeAttribute(change);
+        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+        event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
+        fireEvent(change, event, db);
+
+        final List<String> args = new ArrayList<String>();
+        addArg(args, "--change", event.change.id);
+        addArg(args, "--change-url", event.change.url);
+        addArg(args, "--project", event.change.project);
+        addArg(args, "--branch", event.change.branch);
+        addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
+        addArg(args, "--commit", event.patchSet.revision);
+        addArg(args, "--patchset", event.patchSet.number);
+
+        runHook(change.getProject(), draftPublishedHook, args);
+    }
+
     public void doCommentAddedHook(final Change change, final Account account,
           final PatchSet patchSet, final String comment, final Map<ApprovalCategory.Id,
           ApprovalCategoryValue.Id> approvals, final ReviewDb db) throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
index dc258ca..0c86049 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -47,6 +47,16 @@
       ReviewDb db) throws OrmException;
 
   /**
+   * Fire the Draft Published Hook.
+   *
+   * @param change The change itself.
+   * @param patchSet The Patchset that was created.
+   * @throws OrmException
+   */
+  public void doDraftPublishedHook(Change change, PatchSet patchSet,
+      ReviewDb db) throws OrmException;
+
+  /**
    * Fire the Comment Added Hook.
    *
    * @param change The change itself.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index 496a273..357a8b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -66,6 +66,11 @@
   }
 
   @Override
+  public void doDraftPublishedHook(Change change, PatchSet patchSet,
+      ReviewDb db) {
+  }
+
+  @Override
   public void doRefUpdatedHook(NameKey refName, RefUpdate refUpdate,
       Account account) {
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 8d31593..a0814f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -63,6 +63,8 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.Base64;
 import org.eclipse.jgit.util.NB;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -74,6 +76,9 @@
 import java.util.regex.Matcher;
 
 public class ChangeUtil {
+
+  private static final Logger log = LoggerFactory.getLogger(ChangeUtil.class);
+
   private static int uuidPrefix;
   private static int uuidSeq;
 
@@ -592,10 +597,14 @@
     new ApprovalsUtil(db, null).syncChangeStatus(change);
 
     // Email the reviewers
-    final ReplyToChangeSender cm = senderFactory.create(change);
-    cm.setFrom(user.getAccountId());
-    cm.setChangeMessage(cmsg);
-    cm.send();
+    try {
+      final ReplyToChangeSender cm = senderFactory.create(change);
+      cm.setFrom(user.getAccountId());
+      cm.setChangeMessage(cmsg);
+      cm.send();
+    } catch (Exception e) {
+      log.error("Cannot email update for change " + change.getChangeId(), e);
+    }
   }
 
   public static String sortKey(long lastUpdated, int id){
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 050f7e1..89cbac1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.data.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -249,6 +250,14 @@
     return emailAddresses;
   }
 
+  public String getName() {
+    return new AccountInfo(getAccount()).getName(anonymousCowardName);
+  }
+
+  public String getNameEmail() {
+    return new AccountInfo(getAccount()).getNameEmail(anonymousCowardName);
+  }
+
   @Override
   public GroupMembership getEffectiveGroups() {
     if (effectiveGroups == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
index 3a2ac56..abdf29e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.account;
 
+import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -115,7 +116,21 @@
     final int lt = nameOrEmail.indexOf('<');
     final int gt = nameOrEmail.indexOf('>');
     if (lt >= 0 && gt > lt && nameOrEmail.contains("@")) {
-      return byEmail.get(nameOrEmail.substring(lt + 1, gt));
+      Set<Account.Id> ids = byEmail.get(nameOrEmail.substring(lt + 1, gt));
+      if (ids.isEmpty() || ids.size() == 1) {
+        return ids;
+      }
+
+      // more than one match, try to return the best one
+      String name = nameOrEmail.substring(0, lt - 1);
+      Set<Account.Id> nameMatches = Sets.newHashSet();
+      for (Account.Id id : ids) {
+        Account a = byId.get(id).getAccount();
+        if (name.equals(a.getFullName())) {
+          nameMatches.add(id);
+        }
+      }
+      return nameMatches.isEmpty() ? ids : nameMatches;
     }
 
     if (nameOrEmail.contains("@")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthMethod.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthMethod.java
new file mode 100644
index 0000000..fdaabd2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AuthMethod.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+/** Method by which a user has authenticated for a given request. */
+public enum AuthMethod {
+  /** The user is not authenticated */
+  NONE,
+
+  /** The user is authenticated via a cookie. */
+  COOKIE,
+
+  /** The user authenticated with a password for this request. */
+  PASSWORD,
+
+  /** The user has used a credentialess development feature to login. */
+  BACKDOOR;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
index c1c9c50..da033e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
@@ -71,7 +71,7 @@
     final ProjectControl control;
     try {
       Project.NameKey nameKey = new Project.NameKey(projectName);
-      control = projectControlFactory.validateFor(nameKey);
+      control = projectControlFactory.validateFor(nameKey, ProjectControl.OWNER | ProjectControl.VISIBLE);
     } catch (NoSuchProjectException e) {
       throw new CmdLineException(owner, "'" + token + "': not a Gerrit project");
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
index bc0ef61..dca4a83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
@@ -26,50 +26,61 @@
 import com.google.gerrit.server.mail.AbandonedSender;
 import com.google.gerrit.server.mail.EmailException;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
 
 import java.util.concurrent.Callable;
 
-import javax.annotation.Nullable;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
 
 public class AbandonChange implements Callable<ReviewResult> {
 
-  public interface Factory {
-    AbandonChange create(Change.Id changeId, String changeComment);
-  }
-
   private final AbandonedSender.Factory abandonedSenderFactory;
   private final ChangeControl.Factory changeControlFactory;
   private final ReviewDb db;
   private final IdentifiedUser currentUser;
   private final ChangeHooks hooks;
 
-  private final Change.Id changeId;
-  private final String changeComment;
+  @Argument(index = 0, required = true, multiValued = false, usage = "change to abandon")
+  private Change.Id changeId;
+
+  public void setChangeId(final Change.Id changeId) {
+    this.changeId = changeId;
+  }
+
+  @Option(name = "--message", aliases = {"-m"},
+          usage = "optional message to append to change")
+  private String message;
+
+  public void setMessage(final String message) {
+    this.message = message;
+  }
 
   @Inject
   AbandonChange(final AbandonedSender.Factory abandonedSenderFactory,
       final ChangeControl.Factory changeControlFactory, final ReviewDb db,
-      final IdentifiedUser currentUser, final ChangeHooks hooks,
-      @Assisted final Change.Id changeId,
-      @Assisted @Nullable final String changeComment) {
+      final IdentifiedUser currentUser, final ChangeHooks hooks) {
     this.abandonedSenderFactory = abandonedSenderFactory;
     this.changeControlFactory = changeControlFactory;
     this.db = db;
     this.currentUser = currentUser;
     this.hooks = hooks;
 
-    this.changeId = changeId;
-    this.changeComment = changeComment;
+    changeId = null;
+    message = null;
   }
 
   @Override
-  public ReviewResult call() throws EmailException, NoSuchChangeException,
-      OrmException {
+  public ReviewResult call() throws EmailException,
+      InvalidChangeOperationException, NoSuchChangeException, OrmException {
+    if (changeId == null) {
+      throw new InvalidChangeOperationException("changeId is required");
+    }
+
     final ReviewResult result = new ReviewResult();
     result.setChangeId(changeId);
 
@@ -90,9 +101,9 @@
           currentUser.getAccountId(), patchSetId);
       final StringBuilder msgBuf =
           new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
-      if (changeComment != null && changeComment.length() > 0) {
+      if (message != null && message.length() > 0) {
         msgBuf.append("\n\n");
-        msgBuf.append(changeComment);
+        msgBuf.append(message);
       }
       cmsg.setMessage(msgBuf.toString());
 
@@ -120,7 +131,7 @@
       ChangeUtil.updatedChange(db, currentUser, updatedChange, cmsg,
                                abandonedSenderFactory);
       hooks.doChangeAbandonedHook(updatedChange, currentUser.getAccount(),
-                                  changeComment, db);
+                                  message, db);
     }
 
     return result;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
index 028feac..29e5dbb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
@@ -15,6 +15,7 @@
 
 package com.google.gerrit.server.changedetail;
 
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.ReviewResult;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -37,14 +38,17 @@
 
   private final ChangeControl.Factory changeControlFactory;
   private final ReviewDb db;
+  private final ChangeHooks hooks;
 
   private final PatchSet.Id patchSetId;
 
   @Inject
   PublishDraft(ChangeControl.Factory changeControlFactory,
-      ReviewDb db, @Assisted final PatchSet.Id patchSetId) {
+      ReviewDb db, @Assisted final PatchSet.Id patchSetId,
+      final ChangeHooks hooks) {
     this.changeControlFactory = changeControlFactory;
     this.db = db;
+    this.hooks = hooks;
 
     this.patchSetId = patchSetId;
   }
@@ -70,19 +74,26 @@
       result.addError(new ReviewResult.Error(
           ReviewResult.Error.Type.PUBLISH_NOT_PERMITTED));
     } else {
-      db.patchSets().atomicUpdate(patchSetId, new AtomicUpdate<PatchSet>() {
+      boolean published = false;
+      final PatchSet updatedPatch = db.patchSets().atomicUpdate(patchSetId,
+          new AtomicUpdate<PatchSet>() {
         @Override
         public PatchSet update(PatchSet patchset) {
           if (patchset.isDraft()) {
             patchset.setDraft(false);
+            return patchset;
           }
           return null;
         }
       });
 
+      if ((updatedPatch != null) && (!updatedPatch.isDraft())) {
+        published = true;
+      }
+
       final Change change = db.changes().get(changeId);
       if (change.getStatus() == Change.Status.DRAFT) {
-        db.changes().atomicUpdate(changeId,
+        final Change updatedChange = db.changes().atomicUpdate(changeId,
             new AtomicUpdate<Change>() {
           @Override
           public Change update(Change change) {
@@ -95,6 +106,15 @@
             }
           }
         });
+
+        if ((updatedChange != null) &&
+            (updatedChange.getStatus() == Change.Status.NEW)) {
+          published = true;
+        }
+      }
+
+      if (published) {
+        hooks.doDraftPublishedHook(change, patch, db);
       }
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
index 859807c..875ae06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
@@ -29,25 +29,22 @@
 import com.google.gerrit.server.mail.EmailException;
 import com.google.gerrit.server.mail.RestoredSender;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 
 import java.io.IOException;
 import java.util.concurrent.Callable;
 
-import javax.annotation.Nullable;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
 
 public class RestoreChange implements Callable<ReviewResult> {
 
-  public interface Factory {
-    RestoreChange create(Change.Id changeId, String changeComment);
-  }
-
   private final RestoredSender.Factory restoredSenderFactory;
   private final ChangeControl.Factory changeControlFactory;
   private final ReviewDb db;
@@ -55,15 +52,25 @@
   private final IdentifiedUser currentUser;
   private final ChangeHooks hooks;
 
-  private final Change.Id changeId;
-  private final String changeComment;
+  @Argument(index = 0, required = true, multiValued = false,
+            usage = "change to restore", metaVar = "CHANGE")
+  private Change.Id changeId;
+  public void setChangeId(final Change.Id changeId) {
+    this.changeId = changeId;
+  }
+
+  @Option(name = "--message", aliases = {"-m"},
+          usage = "optional message to append to change")
+  private String message;
+  public void setMessage(final String message) {
+    this.message = message;
+  }
 
   @Inject
   RestoreChange(final RestoredSender.Factory restoredSenderFactory,
       final ChangeControl.Factory changeControlFactory, final ReviewDb db,
       final GitRepositoryManager repoManager, final IdentifiedUser currentUser,
-      final ChangeHooks hooks, @Assisted final Change.Id changeId,
-      @Assisted @Nullable final String changeComment) {
+      final ChangeHooks hooks) {
     this.restoredSenderFactory = restoredSenderFactory;
     this.changeControlFactory = changeControlFactory;
     this.db = db;
@@ -71,13 +78,18 @@
     this.currentUser = currentUser;
     this.hooks = hooks;
 
-    this.changeId = changeId;
-    this.changeComment = changeComment;
+    changeId = null;
+    message = null;
   }
 
   @Override
   public ReviewResult call() throws EmailException, NoSuchChangeException,
-      OrmException, RepositoryNotFoundException, IOException {
+      InvalidChangeOperationException, OrmException,
+      RepositoryNotFoundException, IOException {
+    if (changeId == null) {
+      throw new InvalidChangeOperationException("changeId is required");
+    }
+
     final ReviewResult result = new ReviewResult();
     result.setChangeId(changeId);
 
@@ -108,9 +120,9 @@
             .messageUUID(db)), currentUser.getAccountId(), patchSetId);
     final StringBuilder msgBuf =
         new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
-    if (changeComment != null && changeComment.length() > 0) {
+    if (message != null && message.length() > 0) {
       msgBuf.append("\n\n");
-      msgBuf.append(changeComment);
+      msgBuf.append(message);
     }
     cmsg.setMessage(msgBuf.toString());
 
@@ -138,7 +150,7 @@
     ChangeUtil.updatedChange(db, currentUser, updatedChange, cmsg,
                              restoredSenderFactory);
     hooks.doChangeRestoreHook(updatedChange, currentUser.getAccount(),
-                              changeComment, db);
+                              message, db);
 
     return result;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
index 9cff992..3287aa1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
@@ -24,6 +24,8 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.ProjectUtil;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeOp;
 import com.google.gerrit.server.git.MergeQueue;
 import com.google.gerrit.server.project.ChangeControl;
@@ -34,6 +36,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -49,6 +52,7 @@
   private final MergeOp.Factory opFactory;
   private final MergeQueue merger;
   private final ReviewDb db;
+  private final GitRepositoryManager repoManager;
   private final IdentifiedUser currentUser;
 
   private final PatchSet.Id patchSetId;
@@ -56,12 +60,13 @@
   @Inject
   Submit(final ChangeControl.Factory changeControlFactory,
       final MergeOp.Factory opFactory, final MergeQueue merger,
-      final ReviewDb db, final IdentifiedUser currentUser,
-      @Assisted final PatchSet.Id patchSetId) {
+      final ReviewDb db, final GitRepositoryManager repoManager,
+      final IdentifiedUser currentUser, @Assisted final PatchSet.Id patchSetId) {
     this.changeControlFactory = changeControlFactory;
     this.opFactory = opFactory;
     this.merger = merger;
     this.db = db;
+    this.repoManager = repoManager;
     this.currentUser = currentUser;
 
     this.patchSetId = patchSetId;
@@ -69,7 +74,8 @@
 
   @Override
   public ReviewResult call() throws IllegalStateException,
-      InvalidChangeOperationException, NoSuchChangeException, OrmException {
+      InvalidChangeOperationException, NoSuchChangeException, OrmException,
+      IOException {
     final ReviewResult result = new ReviewResult();
 
     final PatchSet patch = db.patchSets().get(patchSetId);
@@ -151,6 +157,14 @@
       }
     }
 
+    if (!ProjectUtil.branchExists(repoManager, control.getChange().getDest())) {
+      result.addError(new ReviewResult.Error(
+          ReviewResult.Error.Type.DEST_BRANCH_NOT_FOUND,
+          "Destination branch \"" + control.getChange().getDest().get()
+              + "\" not found."));
+      return result;
+    }
+
     // Submit the change if we can
     if (result.getErrors().isEmpty()) {
       final List<PatchSetApproval> allApprovals =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index a37fde0..dc36988 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -36,6 +36,8 @@
   private final AuthType authType;
   private final String httpHeader;
   private final boolean trustContainerAuth;
+  private final boolean userNameToLowerCase;
+  private final boolean gitBasicAuth;
   private final String logoutUrl;
   private final String openIdSsoUrl;
   private final List<OpenIdProviderPattern> trustedOpenIDs;
@@ -58,6 +60,9 @@
     cookiePath = cfg.getString("auth", null, "cookiepath");
     cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
     trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
+    gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
+    userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
+
 
     String key = cfg.getString("auth", null, "registerEmailPrivateKey");
     if (key != null && !key.isEmpty()) {
@@ -138,6 +143,16 @@
     return trustContainerAuth;
   }
 
+  /** Whether user name should be converted to lower-case before validation */
+  public boolean isUserNameToLowerCase() {
+    return userNameToLowerCase;
+  }
+
+  /** Whether git-over-http should use Gerrit basic authentication scheme. */
+  public boolean isGitBasichAuth() {
+    return gitBasicAuth;
+  }
+
   public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) {
     switch (getAuthType()) {
       case DEVELOPMENT_BECOME_ANY_ACCOUNT:
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 226f926..ba54c56 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -27,16 +27,15 @@
 import com.google.gerrit.server.account.PerformCreateGroup;
 import com.google.gerrit.server.account.PerformRenameGroup;
 import com.google.gerrit.server.account.VisibleGroups;
-import com.google.gerrit.server.changedetail.AbandonChange;
 import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.changedetail.PublishDraft;
-import com.google.gerrit.server.changedetail.RestoreChange;
 import com.google.gerrit.server.changedetail.Submit;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
 import com.google.gerrit.server.git.BanCommit;
 import com.google.gerrit.server.git.CreateCodeReviewNotes;
 import com.google.gerrit.server.git.MergeOp;
 import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.NotesBranchUtil;
 import com.google.gerrit.server.git.SubmoduleOp;
 import com.google.gerrit.server.mail.AbandonedSender;
 import com.google.gerrit.server.mail.AddReviewerSender;
@@ -84,12 +83,12 @@
     factory(SubmoduleOp.Factory.class);
     factory(MergeOp.Factory.class);
     factory(CreateCodeReviewNotes.Factory.class);
+    factory(NotesBranchUtil.Factory.class);
     install(new AsyncReceiveCommits.Module());
 
     // Not really per-request, but dammit, I don't know where else to
     // easily park this stuff.
     //
-    factory(AbandonChange.Factory.class);
     factory(AddReviewer.Factory.class);
     factory(AddReviewerSender.Factory.class);
     factory(CreateChangeSender.Factory.class);
@@ -100,7 +99,6 @@
     factory(RebasedPatchSetSender.Factory.class);
     factory(AbandonedSender.Factory.class);
     factory(RemoveReviewer.Factory.class);
-    factory(RestoreChange.Factory.class);
     factory(RestoredSender.Factory.class);
     factory(RevertedSender.Factory.class);
     factory(CommentSender.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
index 2ad7ffe..2d88b83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/AccountAttribute.java
@@ -17,4 +17,5 @@
 public class AccountAttribute {
     public String name;
     public String email;
+    public String username;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
new file mode 100644
index 0000000..c90ac90
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/DraftPublishedEvent.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.events;
+
+public class DraftPublishedEvent extends ChangeEvent {
+    public final String type = "draft-published";
+    public ChangeAttribute change;
+    public PatchSetAttribute patchSet;
+    public AccountAttribute uploader;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 9fa582a..41cac4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -342,6 +342,7 @@
     AccountAttribute who = new AccountAttribute();
     who.name = account.getFullName();
     who.email = account.getPreferredEmail();
+    who.username = account.getUserName();
     return who;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
index 4bfee9c..da38573 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -23,34 +24,25 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.Note;
 import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.notes.NoteMapMerger;
-import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 
 public class BanCommit {
-
-  private static final int MAX_LOCK_FAILURE_CALLS = 10;
-  private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
-
   public interface Factory {
     BanCommit create();
   }
@@ -58,49 +50,37 @@
   private final Provider<IdentifiedUser> currentUser;
   private final GitRepositoryManager repoManager;
   private final PersonIdent gerritIdent;
+  private NotesBranchUtil.Factory notesBranchUtilFactory;
 
   @Inject
   BanCommit(final Provider<IdentifiedUser> currentUser,
       final GitRepositoryManager repoManager,
-      @GerritPersonIdent final PersonIdent gerritIdent) {
+      @GerritPersonIdent final PersonIdent gerritIdent,
+      final NotesBranchUtil.Factory notesBranchUtilFactory) {
     this.currentUser = currentUser;
     this.repoManager = repoManager;
     this.gerritIdent = gerritIdent;
+    this.notesBranchUtilFactory = notesBranchUtilFactory;
   }
 
   public BanCommitResult ban(final ProjectControl projectControl,
       final List<ObjectId> commitsToBan, final String reason)
       throws PermissionDeniedException, IOException,
-      InterruptedException, MergeException {
+      InterruptedException, MergeException, ConcurrentRefUpdateException {
     if (!projectControl.isOwner()) {
       throw new PermissionDeniedException(
           "No project owner: not permitted to ban commits");
     }
 
     final BanCommitResult result = new BanCommitResult();
-
-    final PersonIdent currentUserIdent = createPersonIdent();
+    NoteMap banCommitNotes = NoteMap.newEmptyMap();
+    // add a note for each banned commit to notes
     final Repository repo =
         repoManager.openRepository(projectControl.getProject().getNameKey());
     try {
       final RevWalk revWalk = new RevWalk(repo);
       final ObjectInserter inserter = repo.newObjectInserter();
       try {
-        NoteMap baseNoteMap = null;
-        RevCommit baseCommit = null;
-        final Ref notesBranch = repo.getRef(REF_REJECT_COMMITS);
-        if (notesBranch != null) {
-          baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
-          baseNoteMap = NoteMap.read(revWalk.getObjectReader(), baseCommit);
-        }
-
-        final NoteMap ourNoteMap;
-        if (baseCommit != null) {
-          ourNoteMap = NoteMap.read(repo.newObjectReader(), baseCommit);
-        } else {
-          ourNoteMap = NoteMap.newEmptyMap();
-        }
-
         for (final ObjectId commitToBan : commitsToBan) {
           try {
             revWalk.parseCommit(commitToBan);
@@ -110,31 +90,22 @@
             result.notACommit(commitToBan, e.getMessage());
             continue;
           }
+          banCommitNotes.set(commitToBan, createNoteContent(reason, inserter));
+        }
+        inserter.flush();
+        NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(repo);
+        NoteMap newlyCreated =
+            notesBranchUtil.commitNewNotes(banCommitNotes, REF_REJECT_COMMITS,
+                createPersonIdent(), buildCommitMessage(commitsToBan, reason));
 
-          final Note note = ourNoteMap.getNote(commitToBan);
-          if (note != null) {
-            result.commitAlreadyBanned(commitToBan);
-            continue;
+        for (Note n : banCommitNotes) {
+          if (newlyCreated.contains(n)) {
+            result.commitBanned(n);
+          } else {
+            result.commitAlreadyBanned(n);
           }
-
-          final String noteContent = reason != null ? reason : "";
-          final ObjectId noteContentId =
-              inserter
-                  .insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
-          ourNoteMap.set(commitToBan, noteContentId);
-          result.commitBanned(commitToBan);
         }
-
-        if (result.getNewlyBannedCommits().isEmpty()) {
-          return result;
-        }
-
-        final ObjectId ourCommit =
-            commit(ourNoteMap, inserter, currentUserIdent, baseCommit, result,
-                reason);
-
-        updateRef(repo, revWalk, inserter, ourNoteMap, ourCommit, baseNoteMap,
-            baseCommit);
+        return result;
       } finally {
         revWalk.release();
         inserter.release();
@@ -142,8 +113,15 @@
     } finally {
       repo.close();
     }
+  }
 
-    return result;
+  private ObjectId createNoteContent(String reason, ObjectInserter inserter)
+      throws UnsupportedEncodingException, IOException {
+    String noteContent = reason != null ? reason : "";
+    if (noteContent.length() > 0 && !noteContent.endsWith("\n")) {
+      noteContent = noteContent + "\n";
+    }
+    return inserter.insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
   }
 
   private PersonIdent createPersonIdent() {
@@ -152,35 +130,6 @@
     return currentUser.get().newCommitterIdent(now, tz);
   }
 
-  private static ObjectId commit(final NoteMap noteMap,
-      final ObjectInserter inserter, final PersonIdent personIdent,
-      final ObjectId baseCommit, final BanCommitResult result,
-      final String reason) throws IOException {
-    final String commitMsg =
-        buildCommitMessage(result.getNewlyBannedCommits(), reason);
-    if (baseCommit != null) {
-      return createCommit(noteMap, inserter, personIdent, commitMsg, baseCommit);
-    } else {
-      return createCommit(noteMap, inserter, personIdent, commitMsg);
-    }
-  }
-
-  private static ObjectId createCommit(final NoteMap noteMap,
-      final ObjectInserter inserter, final PersonIdent personIdent,
-      final String message, final ObjectId... parents) throws IOException {
-    final CommitBuilder b = new CommitBuilder();
-    b.setTreeId(noteMap.writeTree(inserter));
-    b.setAuthor(personIdent);
-    b.setCommitter(personIdent);
-    if (parents.length > 0) {
-      b.setParentIds(parents);
-    }
-    b.setMessage(message);
-    final ObjectId commitId = inserter.insert(b);
-    inserter.flush();
-    return commitId;
-  }
-
   private static String buildCommitMessage(final List<ObjectId> bannedCommits,
       final String reason) {
     final StringBuilder commitMsg = new StringBuilder();
@@ -205,61 +154,4 @@
     commitMsg.append(commitList);
     return commitMsg.toString();
   }
-
-  public void updateRef(final Repository repo, final RevWalk revWalk,
-      final ObjectInserter inserter, final NoteMap ourNoteMap,
-      final ObjectId oursCommit, final NoteMap baseNoteMap,
-      final ObjectId baseCommit) throws IOException, InterruptedException,
-      MissingObjectException, IncorrectObjectTypeException,
-      CorruptObjectException, MergeException {
-
-    int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-    RefUpdate refUpdate = createRefUpdate(repo, oursCommit, baseCommit);
-
-    for (;;) {
-      final Result result = refUpdate.update();
-
-      if (result == Result.LOCK_FAILURE) {
-        if (--remainingLockFailureCalls > 0) {
-          Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
-        } else {
-          throw new MergeException("Failed to lock the ref: "
-              + REF_REJECT_COMMITS);
-        }
-
-      } else if (result == Result.REJECTED) {
-        final RevCommit theirsCommit =
-            revWalk.parseCommit(refUpdate.getOldObjectId());
-        final NoteMap theirNoteMap =
-            NoteMap.read(revWalk.getObjectReader(), theirsCommit);
-        final NoteMapMerger merger = new NoteMapMerger(repo);
-        final NoteMap merged =
-            merger.merge(baseNoteMap, ourNoteMap, theirNoteMap);
-        final ObjectId mergeCommit =
-            createCommit(merged, inserter, gerritIdent,
-                "Merged note commits\n", oursCommit, theirsCommit);
-        refUpdate = createRefUpdate(repo, mergeCommit, theirsCommit);
-        remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-
-      } else if (result == Result.IO_FAILURE) {
-        throw new IOException(
-            "Couldn't create commit reject notes because of IO_FAILURE");
-      } else {
-        break;
-      }
-    }
-  }
-
-  private static RefUpdate createRefUpdate(final Repository repo,
-      final ObjectId newObjectId, final ObjectId expectedOldObjectId)
-      throws IOException {
-    RefUpdate refUpdate = repo.updateRef(REF_REJECT_COMMITS);
-    refUpdate.setNewObjectId(newObjectId);
-    if (expectedOldObjectId == null) {
-      refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
-    } else {
-      refUpdate.setExpectedOldObjectId(expectedOldObjectId);
-    }
-    return refUpdate;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
index 6fea8f1..b067a49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.reviewdb.client.ApprovalCategory;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -31,23 +32,15 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.Note;
 import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.notes.NoteMapMerger;
-import org.eclipse.jgit.notes.NoteMerger;
 import org.eclipse.jgit.revwalk.FooterKey;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -68,30 +61,22 @@
     CreateCodeReviewNotes create(ReviewDb reviewDb, Repository db);
   }
 
-  private static final int MAX_LOCK_FAILURE_CALLS = 10;
-  private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
   private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
 
-  private final ReviewDb schema;
-  private final PersonIdent gerritIdent;
   private final AccountCache accountCache;
   private final ApprovalTypes approvalTypes;
   private final String canonicalWebUrl;
   private final String anonymousCowardName;
+  private final ReviewDb schema;
   private final Repository db;
-  private final RevWalk revWalk;
-  private final ObjectInserter inserter;
-  private final ObjectReader reader;
 
-  private RevCommit baseCommit;
-  private NoteMap base;
-
-  private RevCommit oursCommit;
-  private NoteMap ours;
-
-  private List<CodeReviewCommit> commits;
   private PersonIdent author;
 
+  private RevWalk revWalk;
+  private ObjectInserter inserter;
+
+  private final NotesBranchUtil.Factory notesBranchUtilFactory;
+
   @Inject
   CreateCodeReviewNotes(
       @GerritPersonIdent final PersonIdent gerritIdent,
@@ -99,90 +84,87 @@
       final ApprovalTypes approvalTypes,
       final @Nullable @CanonicalWebUrl String canonicalWebUrl,
       final @AnonymousCowardName String anonymousCowardName,
+      final NotesBranchUtil.Factory notesBranchUtilFactory,
       final @Assisted  ReviewDb reviewDb,
       final @Assisted  Repository db) {
-    schema = reviewDb;
     this.author = gerritIdent;
-    this.gerritIdent = gerritIdent;
     this.accountCache = accountCache;
     this.approvalTypes = approvalTypes;
     this.canonicalWebUrl = canonicalWebUrl;
     this.anonymousCowardName = anonymousCowardName;
+    this.notesBranchUtilFactory = notesBranchUtilFactory;
+    schema = reviewDb;
     this.db = db;
-
-    revWalk = new RevWalk(db);
-    inserter = db.newObjectInserter();
-    reader = db.newObjectReader();
   }
 
   public void create(List<CodeReviewCommit> commits, PersonIdent author)
       throws CodeReviewNoteCreationException {
     try {
-      this.commits = commits;
-      this.author = author;
-      loadBase();
-      applyNotes();
-      updateRef();
+      revWalk = new RevWalk(db);
+      inserter = db.newObjectInserter();
+      if (author != null) {
+        this.author = author;
+      }
+
+      NoteMap notes = NoteMap.newEmptyMap();
+      StringBuilder message =
+          new StringBuilder("Update notes for submitted changes\n\n");
+      for (CodeReviewCommit c : commits) {
+        notes.set(c, createNoteContent(c.change, c));
+        message.append("* ").append(c.getShortMessage()).append("\n");
+      }
+
+      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+      notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
+          message.toString());
     } catch (IOException e) {
       throw new CodeReviewNoteCreationException(e);
-    } catch (InterruptedException e) {
+    } catch (ConcurrentRefUpdateException e) {
       throw new CodeReviewNoteCreationException(e);
     } finally {
-      release();
+      revWalk.release();
+      inserter.release();
     }
   }
 
-  public void loadBase() throws IOException {
-    Ref notesBranch = db.getRef(REFS_NOTES_REVIEW);
-    if (notesBranch != null) {
-      baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
-      base = NoteMap.read(revWalk.getObjectReader(), baseCommit);
-    }
-    if (baseCommit != null) {
-      ours = NoteMap.read(db.newObjectReader(), baseCommit);
-    } else {
-      ours = NoteMap.newEmptyMap();
+  public void create(List<Change> changes, PersonIdent author,
+      String commitMessage, ProgressMonitor monitor) throws OrmException,
+      IOException, CodeReviewNoteCreationException {
+    try {
+      revWalk = new RevWalk(db);
+      inserter = db.newObjectInserter();
+      if (author != null) {
+        this.author = author;
+      }
+      if (monitor == null) {
+        monitor = NullProgressMonitor.INSTANCE;
+      }
+
+      NoteMap notes = NoteMap.newEmptyMap();
+      for (Change c : changes) {
+        monitor.update(1);
+        PatchSet ps = schema.patchSets().get(c.currentPatchSetId());
+        ObjectId commitId = ObjectId.fromString(ps.getRevision().get());
+        notes.set(commitId, createNoteContent(c, commitId));
+      }
+
+      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+      notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
+          commitMessage);
+    } catch (ConcurrentRefUpdateException e) {
+      throw new CodeReviewNoteCreationException(e);
+    } finally {
+      revWalk.release();
+      inserter.release();
     }
   }
 
-  private void applyNotes() throws IOException, CodeReviewNoteCreationException {
-    StringBuilder message =
-        new StringBuilder("Update notes for submitted changes\n\n");
-    for (CodeReviewCommit c : commits) {
-      add(c.change, c);
-      message.append("* ").append(c.getShortMessage()).append("\n");
-    }
-    commit(message.toString());
-  }
-
-  public void commit(String message) throws IOException {
-    if (baseCommit != null) {
-      oursCommit = createCommit(ours, author, message, baseCommit);
-    } else {
-      oursCommit = createCommit(ours, author, message);
-    }
-  }
-
-  public void add(Change change, ObjectId commit)
-      throws MissingObjectException, IncorrectObjectTypeException, IOException,
-      CodeReviewNoteCreationException {
+  private ObjectId createNoteContent(Change change, ObjectId commit)
+      throws CodeReviewNoteCreationException, IOException  {
     if (!(commit instanceof RevCommit)) {
       commit = revWalk.parseCommit(commit);
     }
-
-    RevCommit c = (RevCommit) commit;
-    ObjectId noteContent = createNoteContent(change, c);
-    if (ours.contains(c)) {
-      // merge the existing and the new note as if they are both new
-      // means: base == null
-      // there is not really a common ancestry for these two note revisions
-      // use the same NoteMerger that is used from the NoteMapMerger
-      NoteMerger noteMerger = new ReviewNoteMerger();
-      Note newNote = new Note(c, noteContent);
-      noteContent = noteMerger.merge(null, newNote, ours.getNote(c),
-          reader, inserter).getData();
-    }
-    ours.set(c, noteContent);
+    return createNoteContent(change, (RevCommit) commit);
   }
 
   private ObjectId createNoteContent(Change change, RevCommit commit)
@@ -227,83 +209,4 @@
       throw new CodeReviewNoteCreationException(commit, e);
     }
   }
-
-  public void updateRef() throws IOException, InterruptedException,
-      CodeReviewNoteCreationException, MissingObjectException,
-      IncorrectObjectTypeException, CorruptObjectException {
-    if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) {
-      // If the trees are identical, there is no change in the notes.
-      // Avoid saving this commit as it has no new information.
-      return;
-    }
-
-    int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-    RefUpdate refUpdate = createRefUpdate(oursCommit, baseCommit);
-
-    for (;;) {
-      Result result = refUpdate.update();
-
-      if (result == Result.LOCK_FAILURE) {
-        if (--remainingLockFailureCalls > 0) {
-          Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
-        } else {
-          throw new CodeReviewNoteCreationException(
-              "Failed to lock the ref: " + REFS_NOTES_REVIEW);
-        }
-
-      } else if (result == Result.REJECTED) {
-        RevCommit theirsCommit =
-            revWalk.parseCommit(refUpdate.getOldObjectId());
-        NoteMap theirs =
-            NoteMap.read(revWalk.getObjectReader(), theirsCommit);
-        NoteMapMerger merger = new NoteMapMerger(db);
-        NoteMap merged = merger.merge(base, ours, theirs);
-        RevCommit mergeCommit =
-            createCommit(merged, gerritIdent, "Merged note commits\n",
-                theirsCommit, oursCommit);
-        refUpdate = createRefUpdate(mergeCommit, theirsCommit);
-        remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-
-      } else if (result == Result.IO_FAILURE) {
-        throw new CodeReviewNoteCreationException(
-            "Couldn't create code review notes because of IO_FAILURE");
-      } else {
-        break;
-      }
-    }
-  }
-
-  public void release() {
-    reader.release();
-    inserter.release();
-    revWalk.release();
-  }
-
-  private RevCommit createCommit(NoteMap map, PersonIdent author,
-      String message, RevCommit... parents) throws IOException {
-    CommitBuilder b = new CommitBuilder();
-    b.setTreeId(map.writeTree(inserter));
-    b.setAuthor(author != null ? author : gerritIdent);
-    b.setCommitter(gerritIdent);
-    if (parents.length > 0) {
-      b.setParentIds(parents);
-    }
-    b.setMessage(message);
-    ObjectId commitId = inserter.insert(b);
-    inserter.flush();
-    return revWalk.parseCommit(commitId);
-  }
-
-
-  private RefUpdate createRefUpdate(ObjectId newObjectId,
-      ObjectId expectedOldObjectId) throws IOException {
-    RefUpdate refUpdate = db.updateRef(REFS_NOTES_REVIEW);
-    refUpdate.setNewObjectId(newObjectId);
-    if (expectedOldObjectId == null) {
-      refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
-    } else {
-      refUpdate.setExpectedOldObjectId(expectedOldObjectId);
-    }
-    return refUpdate;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LargeObjectException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LargeObjectException.java
new file mode 100644
index 0000000..d08b8768
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LargeObjectException.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+/**
+ * Wrapper for {@link org.eclipse.jgit.errors.LargeObjectException}. Since
+ * org.eclipse.jgit.errors.LargeObjectException is a {@link RuntimeException}
+ * the GerritJsonServlet would treat it as internal failure and as result the
+ * web ui would just show 'Internal Server Error'. Wrapping
+ * org.eclipse.jgit.errors.LargeObjectException into a normal {@link Exception}
+ * allows to display a proper error message.
+ */
+public class LargeObjectException extends Exception {
+
+  private static final long serialVersionUID = 1L;
+
+  public LargeObjectException(final String message,
+      final org.eclipse.jgit.errors.LargeObjectException cause) {
+    super(message, cause);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index ecf98c2..fa47a2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -249,10 +249,12 @@
       log.error("Test merge attempt for change: " + change.getId()
           + " failed", e);
     } finally {
+      if (repo != null) {
+        repo.close();
+      }
       if (db != null) {
         db.close();
       }
-      db = null;
     }
   }
 
@@ -292,8 +294,9 @@
       if (repo != null) {
         repo.close();
       }
-      db.close();
-      db = null;
+      if (db != null) {
+        db.close();
+      }
     }
   }
 
@@ -355,6 +358,21 @@
         branchTip = null;
       }
 
+      try {
+        final Ref destRef = repo.getRef(destBranch.get());
+        if (destRef != null) {
+          branchUpdate.setExpectedOldObjectId(destRef.getObjectId());
+        } else if (repo.getFullBranch().equals(destBranch.get())) {
+          branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+        } else {
+          throw new MergeException("Destination branch \""
+              + branchUpdate.getRef().getName() + "\" does not exist");
+        }
+      } catch (IOException e) {
+        throw new MergeException(
+            "Failed to check existence of destination branch", e);
+      }
+
       for (final Ref r : repo.getAllRefs().values()) {
         if (r.getName().startsWith(Constants.R_HEADS)
             || r.getName().startsWith(Constants.R_TAGS)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
new file mode 100644
index 0000000..17cfea8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -0,0 +1,271 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.notes.NoteMapMerger;
+import org.eclipse.jgit.notes.NoteMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+/**
+ * A utility class for updating a notes branch with automatic merge of note
+ * trees.
+ */
+public class NotesBranchUtil {
+  public interface Factory {
+    NotesBranchUtil create(Repository db);
+  }
+
+  private static final int MAX_LOCK_FAILURE_CALLS = 10;
+  private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
+
+  private PersonIdent gerritIdent;
+  private final Repository db;
+
+  private RevCommit baseCommit;
+  private NoteMap base;
+
+  private RevCommit oursCommit;
+  private NoteMap ours;
+
+  private RevWalk revWalk;
+  private ObjectInserter inserter;
+  private ObjectReader reader;
+  private boolean overwrite;
+
+  private ReviewNoteMerger noteMerger;
+
+  @Inject
+  public NotesBranchUtil(@GerritPersonIdent final PersonIdent gerritIdent,
+      @Assisted Repository db) {
+    this.gerritIdent = gerritIdent;
+    this.db = db;
+  }
+
+  /**
+   * Create a new commit in the <code>notesBranch</code> by updating existing
+   * or creating new notes from the <code>notes</code> map.
+   *
+   * @param notes map of notes
+   * @param notesBranch notes branch to update
+   * @param commitAuthor author of the commit in the notes branch
+   * @param commitMessage for the commit in the notes branch
+   * @throws IOException
+   * @throws ConcurrentRefUpdateException
+   */
+  public final void commitAllNotes(NoteMap notes, String notesBranch,
+      PersonIdent commitAuthor, String commitMessage) throws IOException,
+      ConcurrentRefUpdateException {
+    this.overwrite = true;
+    commitNotes(notes, notesBranch, commitAuthor, commitMessage);
+  }
+
+  /**
+   * Create a new commit in the <code>notesBranch</code> by creating not yet
+   * existing notes from the <code>notes</code> map. The notes from the
+   * <code>notes</code> map which already exist in the note-tree of the
+   * tip of the <code>notesBranch</code> will not be updated.
+   *
+   * @param notes map of notes
+   * @param notesBranch notes branch to update
+   * @param commitAuthor author of the commit in the notes branch
+   * @param commitMessage for the commit in the notes branch
+   * @return map with those notes from the <code>notes</code> that were newly
+   *         created
+   * @throws IOException
+   * @throws ConcurrentRefUpdateException
+   */
+  public final NoteMap commitNewNotes(NoteMap notes, String notesBranch,
+      PersonIdent commitAuthor, String commitMessage) throws IOException,
+      ConcurrentRefUpdateException {
+    this.overwrite = false;
+    commitNotes(notes, notesBranch, commitAuthor, commitMessage);
+    NoteMap newlyCreated = NoteMap.newEmptyMap();
+    for (Note n : notes) {
+      if (base == null || !base.contains(n)) {
+        newlyCreated.set(n, n.getData());
+      }
+    }
+    return newlyCreated;
+  }
+
+  private void commitNotes(NoteMap notes, String notesBranch,
+      PersonIdent commitAuthor, String commitMessage) throws IOException,
+      ConcurrentRefUpdateException {
+    try {
+      revWalk = new RevWalk(db);
+      inserter = db.newObjectInserter();
+      reader = db.newObjectReader();
+      loadBase(notesBranch);
+      if (overwrite) {
+        addAllNotes(notes);
+      } else {
+        addNewNotes(notes);
+      }
+      if (base != null) {
+        oursCommit = createCommit(ours, commitAuthor, commitMessage, baseCommit);
+      } else {
+        oursCommit = createCommit(ours, commitAuthor, commitMessage);
+      }
+      updateRef(notesBranch);
+    } finally {
+      revWalk.release();
+      inserter.release();
+      reader.release();
+    }
+  }
+
+  private void addNewNotes(NoteMap notes) throws IOException {
+    for (Note n : notes) {
+      if (! ours.contains(n)) {
+        ours.set(n, n.getData());
+      }
+    }
+  }
+
+  private void addAllNotes(NoteMap notes) throws IOException {
+    for (Note n : notes) {
+      if (ours.contains(n)) {
+        // Merge the existing and the new note as if they are both new,
+        // means: base == null
+        // There is no really a common ancestry for these two note revisions
+        ObjectId noteContent = getNoteMerger().merge(null, n, ours.getNote(n),
+            reader, inserter).getData();
+        ours.set(n, noteContent);
+      } else {
+        ours.set(n, n.getData());
+      }
+    }
+  }
+
+  private NoteMerger getNoteMerger() {
+    if (noteMerger == null) {
+      noteMerger = new ReviewNoteMerger();
+    }
+    return noteMerger;
+  }
+
+  private void loadBase(String notesBranch) throws IOException {
+    Ref branch = db.getRef(notesBranch);
+    if (branch != null) {
+      baseCommit = revWalk.parseCommit(branch.getObjectId());
+      base = NoteMap.read(revWalk.getObjectReader(), baseCommit);
+    }
+    if (baseCommit != null) {
+      ours = NoteMap.read(revWalk.getObjectReader(), baseCommit);
+    } else {
+      ours = NoteMap.newEmptyMap();
+    }
+  }
+
+  private RevCommit createCommit(NoteMap map, PersonIdent author,
+      String message, RevCommit... parents) throws IOException {
+    CommitBuilder b = new CommitBuilder();
+    b.setTreeId(map.writeTree(inserter));
+    b.setAuthor(author != null ? author : gerritIdent);
+    b.setCommitter(gerritIdent);
+    if (parents.length > 0) {
+      b.setParentIds(parents);
+    }
+    b.setMessage(message);
+    ObjectId commitId = inserter.insert(b);
+    inserter.flush();
+    return revWalk.parseCommit(commitId);
+  }
+
+  private void updateRef(String notesBranch) throws IOException,
+      MissingObjectException, IncorrectObjectTypeException,
+      CorruptObjectException, ConcurrentRefUpdateException {
+    if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) {
+      // If the trees are identical, there is no change in the notes.
+      // Avoid saving this commit as it has no new information.
+      return;
+    }
+
+    int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+    RefUpdate refUpdate = createRefUpdate(notesBranch, oursCommit, baseCommit);
+
+    for (;;) {
+      Result result = refUpdate.update();
+
+      if (result == Result.LOCK_FAILURE) {
+        if (--remainingLockFailureCalls > 0) {
+          try {
+            Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+          } catch (InterruptedException e) {
+            // ignore
+          }
+        } else {
+          throw new ConcurrentRefUpdateException("Failed to lock the ref: "
+              + notesBranch, db.getRef(notesBranch), result);
+        }
+
+      } else if (result == Result.REJECTED) {
+        RevCommit theirsCommit =
+            revWalk.parseCommit(refUpdate.getOldObjectId());
+        NoteMap theirs =
+            NoteMap.read(revWalk.getObjectReader(), theirsCommit);
+        NoteMapMerger merger =
+            new NoteMapMerger(db, getNoteMerger(), MergeStrategy.RESOLVE);
+        NoteMap merged = merger.merge(base, ours, theirs);
+        RevCommit mergeCommit =
+            createCommit(merged, gerritIdent, "Merged note commits\n",
+                theirsCommit, oursCommit);
+        refUpdate = createRefUpdate(notesBranch, mergeCommit, theirsCommit);
+        remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+
+      } else if (result == Result.IO_FAILURE) {
+        throw new IOException("Couldn't update " + notesBranch + ". "
+            + result.name());
+      } else {
+        break;
+      }
+    }
+  }
+
+  private RefUpdate createRefUpdate(String notesBranch, ObjectId newObjectId,
+      ObjectId expectedOldObjectId) throws IOException {
+    RefUpdate refUpdate = db.updateRef(notesBranch);
+    refUpdate.setNewObjectId(newObjectId);
+    if (expectedOldObjectId == null) {
+      refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+    } else {
+      refUpdate.setExpectedOldObjectId(expectedOldObjectId);
+    }
+    return refUpdate;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
index f5e8fa8..7490006 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RenameGroupOp.java
@@ -123,11 +123,13 @@
       ref.setName(newName);
       md.getCommitBuilder().setAuthor(author);
       md.setMessage("Rename group " + oldName + " to " + newName + "\n");
-      if (config.commit(md)) {
+      try {
+        config.commit(md);
         projectCache.evict(config.getProject());
         success = true;
-
-      } else {
+      } catch (IOException e) {
+        log.error("Could not commit rename of group " + oldName + " to "
+            + newName + " in " + md.getProjectName().get(), e);
         try {
           Thread.sleep(25 /* milliseconds */);
         } catch (InterruptedException wakeUp) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 085424e..44536e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.base.Objects;
+
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -145,12 +146,12 @@
    * Update this metadata branch, recording a new commit on its reference.
    *
    * @param update helper information to define the update that will occur.
-   * @return true if the update was successful, false if it failed because of a
-   *         concurrent update to the same reference.
+   * @return the commit that was created
    * @throws IOException if there is a storage problem and the update cannot be
-   *         executed as requested.
+   *         executed as requested or if it failed because of a concurrent
+   *         update to the same reference
    */
-  public boolean commit(MetaDataUpdate update) throws IOException {
+  public RevCommit commit(MetaDataUpdate update) throws IOException {
     BatchMetaDataUpdate batch = openUpdate(update);
     try {
       batch.write(update.getCommitBuilder());
@@ -160,11 +161,32 @@
     }
   }
 
+  /**
+   * Creates a new commit and a new ref based on this commit.
+   *
+   * @param update helper information to define the update that will occur.
+   * @param refName name of the ref that should be created
+   * @return the commit that was created
+   * @throws IOException if there is a storage problem and the update cannot be
+   *         executed as requested or if it failed because of a concurrent
+   *         update to the same reference
+   */
+  public RevCommit commitToNewRef(MetaDataUpdate update, String refName) throws IOException {
+    BatchMetaDataUpdate batch = openUpdate(update);
+    try {
+      batch.write(update.getCommitBuilder());
+      return batch.createRef(refName);
+    } finally {
+      batch.close();
+    }
+  }
+
   public interface BatchMetaDataUpdate {
     void write(CommitBuilder commit) throws IOException;
     void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
-    boolean commit() throws IOException;
-    boolean commitAt(ObjectId revision) throws IOException;
+    RevCommit createRef(String refName) throws IOException;
+    RevCommit commit() throws IOException;
+    RevCommit commitAt(ObjectId revision) throws IOException;
     void close();
   }
 
@@ -224,14 +246,35 @@
       }
 
       @Override
-      public boolean commit() throws IOException {
+      public RevCommit createRef(String refName) throws IOException {
+        if (Objects.equal(src, revision)) {
+          return revision;
+        }
+
+        RefUpdate ru = db.updateRef(refName);
+        ru.setExpectedOldObjectId(ObjectId.zeroId());
+        ru.setNewObjectId(src);
+        RefUpdate.Result result = ru.update();
+        switch (result) {
+          case NEW:
+            revision = rw.parseCommit(ru.getNewObjectId());
+            update.replicate(ru.getName());
+            return revision;
+          default:
+            throw new IOException("Cannot update " + ru.getName() + " in "
+                + db.getDirectory() + ": " + ru.getResult());
+        }
+      }
+
+      @Override
+      public RevCommit commit() throws IOException {
         return commitAt(revision);
       }
 
       @Override
-      public boolean commitAt(ObjectId expected) throws IOException {
+      public RevCommit commitAt(ObjectId expected) throws IOException {
         if (Objects.equal(src, expected)) {
-          return true;
+          return revision;
         }
 
         RefUpdate ru = db.updateRef(getRefName());
@@ -249,10 +292,7 @@
           case FAST_FORWARD:
             revision = rw.parseCommit(ru.getNewObjectId());
             update.replicate(ru.getName());
-            return true;
-
-          case LOCK_FAILURE:
-            return false;
+            return revision;
 
           default:
             throw new IOException("Cannot update " + ru.getName() + " in "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 7387fd1..41f2aa6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -211,7 +211,8 @@
 
   /** Can this user restore this change? */
   public boolean canRestore() {
-    return canAbandon(); // Anyone who can abandon the change can restore it back
+    return canAbandon() // Anyone who can abandon the change can restore it back
+        && getRefControl().canUpload(); // as long as you can upload too
   }
 
   /** All value ranges of any allowed label permission. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 879f772..3dbd7b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -198,10 +198,7 @@
       }
 
       md.setMessage("Created project\n");
-      if (!config.commit(md)) {
-        throw new IOException("Cannot create "
-            + createProjectArgs.getProjectName());
-      }
+      config.commit(md);
     } finally {
       md.close();
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 7de1fc1..e06c948 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.Permission;
@@ -95,20 +96,24 @@
       ? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
       : null;
 
-    HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
-    AccessSection all = config.getAccessSection(AccessSection.ALL);
-    if (all != null) {
-      Permission owner = all.getPermission(Permission.OWNER);
-      if (owner != null) {
-        for (PermissionRule rule : owner.getRules()) {
-          GroupReference ref = rule.getGroup();
-          if (ref.getUUID() != null) {
-            groups.add(ref.getUUID());
+    if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
+      localOwners = Collections.emptySet();
+    } else {
+      HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
+      AccessSection all = config.getAccessSection(AccessSection.ALL);
+      if (all != null) {
+        Permission owner = all.getPermission(Permission.OWNER);
+        if (owner != null) {
+          for (PermissionRule rule : owner.getRules()) {
+            GroupReference ref = rule.getGroup();
+            if (ref.getUUID() != null) {
+              groups.add(ref.getUUID());
+            }
           }
         }
       }
+      localOwners = Collections.unmodifiableSet(groups);
     }
-    localOwners = Collections.unmodifiableSet(groups);
   }
 
   boolean needsRefresh(long generation) {
@@ -175,6 +180,18 @@
       Collection<AccessSection> fromConfig = config.getAccessSections();
       sm = new ArrayList<SectionMatcher>(fromConfig.size());
       for (AccessSection section : fromConfig) {
+        if (isAllProjects) {
+          List<Permission> copy =
+              Lists.newArrayListWithCapacity(section.getPermissions().size());
+          for (Permission p : section.getPermissions()) {
+            if (Permission.canBeOnAllProjects(section.getName(), p.getName())) {
+              copy.add(p);
+            }
+          }
+          section = new AccessSection(section.getName());
+          section.setPermissions(copy);
+        }
+
         SectionMatcher matcher = SectionMatcher.wrap(section);
         if (matcher != null) {
           sm.add(matcher);
@@ -197,6 +214,7 @@
 
     List<SectionMatcher> all = new ArrayList<SectionMatcher>();
     Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
+    ProjectState allProjects = projectCache.getAllProjects();
     seen.add(getProject().getNameKey());
 
     ProjectState s = this;
@@ -209,7 +227,9 @@
       }
       s = projectCache.get(parent);
     } while (s != null);
-    all.addAll(projectCache.getAllProjects().getLocalAccessSections());
+    if (seen.add(allProjects.getProject().getNameKey())) {
+      all.addAll(allProjects.getLocalAccessSections());
+    }
     return all;
   }
 
@@ -271,4 +291,8 @@
     }
     return projectCache.get(getProject().getParent(allProjectsName));
   }
+
+  public boolean isAllProjects() {
+    return isAllProjects;
+  }
 }
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 2f99271..a6182d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -155,7 +155,14 @@
       // rules. Allowing this to be done by a non-project-owner opens
       // a security hole enabling editing of access rules, and thus
       // granting of powers beyond pushing to the configuration.
-      return false;
+
+      // On the AllProjects project the owner access right cannot be assigned,
+      // this why for the AllProjects project we allow administrators to push
+      // configuration changes if they have push without being project owner.
+      if (!(projectControl.getProjectState().isAllProjects() &&
+          getCurrentUser().getCapabilities().canAdministrateServer())) {
+        return false;
+      }
     }
     return canPerform(Permission.PUSH)
         && canWrite();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
index 686ff59..db879de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
@@ -23,6 +23,9 @@
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
@@ -31,6 +34,9 @@
 /** Caches the order AccessSections should be sorted for evaluation. */
 @Singleton
 public class SectionSortCache {
+  private static final Logger log =
+      LoggerFactory.getLogger(SectionSortCache.class);
+
   private static final String CACHE_NAME = "permission_sort";
 
   public static Module module() {
@@ -70,10 +76,11 @@
       }
 
     } else {
+      boolean poison = false;
       IdentityHashMap<AccessSection, Integer> srcMap =
           new IdentityHashMap<AccessSection, Integer>();
       for (int i = 0; i < cnt; i++) {
-        srcMap.put(sections.get(i), i);
+        poison |= srcMap.put(sections.get(i), i) != null;
       }
 
       Collections.sort(sections, new MostSpecificComparator(ref));
@@ -88,7 +95,11 @@
         }
       }
 
-      cache.put(key, new EntryVal(srcIdx));
+      if (poison) {
+        log.error("Received duplicate AccessSection instances, not caching sort");
+      } else {
+        cache.put(key, new EntryVal(srcIdx));
+      }
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
index ba4a74e..d6a9d4f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
@@ -203,7 +203,7 @@
     out._number = in.getId().get();
     out._sortkey = in.getSortKey();
     out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
-    out.reviewed = isChangeReviewed(cd) ? true : null;
+    out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
     out.labels = labelsFor(cd);
     return out;
   }
@@ -297,9 +297,13 @@
 
   private boolean isChangeReviewed(ChangeData cd) throws OrmException {
     if (user instanceof IdentifiedUser) {
-      PatchSet.Id patchSetId = cd.currentPatchSet(db).getId();
+      PatchSet currentPatchSet = cd.currentPatchSet(db);
+      if (currentPatchSet == null) {
+        return false;
+      }
+
       List<ChangeMessage> messages =
-          db.get().changeMessages().byPatchSet(patchSetId).toList();
+          db.get().changeMessages().byPatchSet(currentPatchSet.getId()).toList();
 
       if (messages.isEmpty()) {
         return false;
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 ff6dc6c..fd379b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -257,9 +257,7 @@
       metaReadPermission.add(rule(config, owners));
 
       md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
-      if (!config.commit(md)) {
-        throw new IOException("Cannot create " + allProjectsName.get());
-      }
+      config.commit(md);
     } finally {
       git.close();
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
index 54ee9ab..8207c31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -184,9 +184,7 @@
         }
 
         md.setMessage("Import project configuration from SQL\n");
-        if (!config.commit(md)) {
-          throw new OrmException("Cannot export project " + name);
-        }
+        config.commit(md);
       } catch (ConfigInvalidException err) {
         throw new OrmException("Cannot read project " + name, err);
       } catch (IOException err) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
index 4699a00..3a288e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
@@ -135,9 +135,7 @@
         }
 
         md.setMessage("Upgrade to Gerrit Code Review schema 57\n");
-        if (!config.commit(md)) {
-          throw new OrmException("Cannot update " + allProjects);
-        }
+        config.commit(md);
       } finally {
         git.close();
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java
index 127f9c3..e665bdc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_64.java
@@ -106,9 +106,7 @@
       }
 
       md.setMessage("Upgrade to Gerrit Code Review schema 64\n");
-      if (!config.commit(md)) {
-        throw new OrmException("Cannot update " + allProjects);
-      }
+      config.commit(md);
     } catch (IOException e) {
       throw new OrmException(e);
     } catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
index 3383364..1cdf25c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
@@ -207,9 +207,7 @@
         batch.write(config, commit);
 
         // Save the the final metadata.
-        if (!batch.commitAt(config.getRevision())) {
-          throw new OrmException("Cannot update " + allProjects);
-        }
+        batch.commitAt(config.getRevision());
       } finally {
         batch.close();
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
index 3d6b93a..fa56966 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
@@ -181,9 +181,7 @@
         }
 
         md.setMessage("Switch LDAP group UUIDs to DNs\n");
-        if (!config.commit(md)) {
-          throw new OrmException("Cannot update " + name);
-        }
+        config.commit(md);
       } catch (IOException e) {
         throw new OrmException(e);
       } catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
index a67c38c..bb3c127 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
@@ -32,6 +32,6 @@
 ## subject line for ALL emails related to changes.
 ##
 #macro(elipses $length $str)
-#if($str.length() > $length)${str.substring(0,$length)}...#else$str#end
+#if(($str.length()+3) > $length)${str.substring(0,$length)}...#else$str#end
 #end
 Change in $projectName.replaceAll('/.*/', '...')[$branch.shortName]: #elipses(60, $change.subject)
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index 02bf815..a849e68 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -209,7 +209,7 @@
     util.tick(5);
     util.setAuthorAndCommitter(md.getCommitBuilder());
     md.setMessage("Edit\n");
-    assertTrue("commit finished", cfg.commit(md));
+    cfg.commit(md);
 
     Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
     return util.getRevWalk().parseCommit(ref.getObjectId());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index e4d9418..d4b07ae 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -144,6 +144,18 @@
         u.controlForRef("refs/heads/foobar").canUpload());
   }
 
+  public void testInheritDuplicateSections() {
+    grant(parent, READ, admin, "refs/*");
+    grant(local, READ, devs, "refs/heads/*");
+    local.getProject().setParentName(parent.getProject().getName());
+    assertTrue("a can read", user("a", admin).isVisible());
+
+    local = new ProjectConfig(new Project.NameKey("local"));
+    local.createInMemory();
+    grant(local, READ, devs, "refs/*");
+    assertTrue("d can read", user("d", devs).isVisible());
+  }
+
   public void testInheritRead_OverrideWithDeny() {
     grant(parent, READ, registered, "refs/*");
     grant(local, READ, registered, "refs/*").setDeny();
@@ -320,7 +332,6 @@
 
     local = new ProjectConfig(new Project.NameKey("local"));
     local.createInMemory();
-    local.getProject().setParentName(parent.getProject().getName());
 
     Cache<SectionSortCache.EntryKey, SectionSortCache.EntryVal> c =
         CacheBuilder.newBuilder().build();
diff --git a/gerrit-sshd/.settings/org.eclipse.core.resources.prefs b/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
index 0871ea8..839d647 100644
--- a/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Tue May 15 09:21:12 PDT 2012
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 2b4543b..9e04f05 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -371,6 +371,14 @@
     return new UnloggedFailure(1, "fatal: " + why.getMessage(), why);
   }
 
+  public void checkExclusivity(final Object arg1, final String arg1name,
+      final Object arg2, final String arg2name) throws UnloggedFailure {
+    if (arg1 != null && arg2 != null) {
+      throw new UnloggedFailure(String.format(
+          "%s and %s options are mutually exclusive.", arg1name, arg2name));
+    }
+  }
+
   private final class TaskThunk implements CancelableRunnable, ProjectRunnable {
     private final CommandRunnable thunk;
     private final Context context;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index 8fbea9d..a55d715 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.gerrit.sshd.SshScope.Context;
@@ -33,6 +34,7 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.spi.ErrorHandler;
 import org.apache.log4j.spi.LoggingEvent;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.util.QuotedString;
 
 import java.io.File;
@@ -59,7 +61,7 @@
 
   @Inject
   SshLog(final Provider<SshSession> session, final Provider<Context> context,
-      final SitePaths site) {
+      final SitePaths site, @GerritServerConfig Config config) {
     this.session = session;
     this.context = context;
 
@@ -77,7 +79,7 @@
 
     async = new AsyncAppender();
     async.setBlocking(true);
-    async.setBufferSize(64);
+    async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
     async.setLocationInfo(false);
     async.addAppender(dst);
     async.activateOptions();
@@ -99,7 +101,7 @@
   void onAuthFail(final SshSession sd) {
     final LoggingEvent event = new LoggingEvent( //
         Logger.class.getName(), // fqnOfCategoryClass
-        null, // logger (optional)
+        log, // logger
         System.currentTimeMillis(), // when
         Level.INFO, // level
         "AUTH FAILURE FROM " + sd.getRemoteAddressAsString(), // message text
@@ -168,7 +170,7 @@
 
     final LoggingEvent event = new LoggingEvent( //
         Logger.class.getName(), // fqnOfCategoryClass
-        null, // logger (optional)
+        log, // logger
         System.currentTimeMillis(), // when
         Level.INFO, // level
         msg, // message text
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index cfd917c..6483e24 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -137,9 +137,7 @@
           config.getProject().setParentName(newParentKey);
           md.setMessage("Inherit access from "
               + (newParentKey != null ? newParentKey.get() : allProjectsName.get()) + "\n");
-          if (!config.commit(md)) {
-            err.append("error: Could not update project " + name + "\n");
-          }
+          config.commit(md);
         } finally {
           md.close();
         }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index 4350d1e..939d68a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
 
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
@@ -80,6 +81,8 @@
       throw die(e);
     } catch (InterruptedException e) {
       throw die(e);
+    } catch (ConcurrentRefUpdateException e) {
+      throw die(e);
     }
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index a729f43..aa439c6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -20,9 +20,12 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.VisibleGroups;
+import com.google.gerrit.server.ioutil.ColumnFormatter;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.sshd.SshCommand;
+import com.google.gwtorm.client.KeyUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -33,6 +36,9 @@
 
 public class ListGroupsCommand extends SshCommand {
   @Inject
+  private GroupCache groupCache;
+
+  @Inject
   private VisibleGroups.Factory visibleGroupsFactory;
 
   @Inject
@@ -52,6 +58,12 @@
       usage = "user for which the groups should be listed")
   private Account.Id user;
 
+  @Option(name = "--verbose", aliases = {"-v"},
+      usage = "verbose output format with tab-separated columns for the " +
+          "group name, UUID, description, type, owner group name, " +
+          "owner group UUID, and whether the group is visible to all")
+  private boolean verboseOutput;
+
   @Override
   protected void run() throws Failure {
     try {
@@ -70,9 +82,26 @@
       } else {
         groupList = visibleGroups.get();
       }
+
+      final ColumnFormatter formatter = new ColumnFormatter(stdout, '\t');
       for (final GroupDetail groupDetail : groupList.getGroups()) {
-        stdout.print(groupDetail.group.getName() + "\n");
+        final AccountGroup g = groupDetail.group;
+        formatter.addColumn(g.getName());
+        if (verboseOutput) {
+          formatter.addColumn(KeyUtil.decode(g.getGroupUUID().toString()));
+          formatter.addColumn(
+              g.getDescription() != null ? g.getDescription() : "");
+          formatter.addColumn(g.getType().toString());
+          final AccountGroup owningGroup =
+              groupCache.get(g.getOwnerGroupUUID());
+          formatter.addColumn(
+              owningGroup != null ? owningGroup.getName() : "n/a");
+          formatter.addColumn(KeyUtil.decode(g.getOwnerGroupUUID().toString()));
+          formatter.addColumn(Boolean.toString(g.isVisibleToAll()));
+        }
+        formatter.nextLine();
       }
+      formatter.finish();
     } catch (OrmException e) {
       throw die(e);
     } catch (NoSuchGroupException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
index c5cdbd5..db4e985 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
@@ -36,5 +36,6 @@
     command(gerrit, "set-project-parent").to(AdminSetParent.class);
     command(gerrit, "review").to(ReviewCommand.class);
     command(gerrit, "set-account").to(SetAccountCommand.class);
+    command(gerrit, "set-project").to(SetProjectCommand.class);
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index 0e7ff83..b4de75b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -24,11 +24,14 @@
 import com.google.gerrit.sshd.AbstractGitCommand;
 import com.google.inject.Inject;
 
+import org.eclipse.jgit.errors.TooLargeObjectInPackException;
 import org.eclipse.jgit.errors.UnpackException;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.transport.AdvertiseRefsHook;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -39,6 +42,8 @@
 
 /** Receives change upload over SSH using the Git receive-pack protocol. */
 final class Receive extends AbstractGitCommand {
+  private static final Logger log = LoggerFactory.getLogger(Receive.class);
+
   @Inject
   private AsyncReceiveCommits.Factory factory;
 
@@ -92,6 +97,23 @@
       receive.advertiseHistory();
       rp.receive(in, out, err);
     } catch (UnpackException badStream) {
+      // In case this was caused by the user pushing an object whose size
+      // is larger than the receive.maxObjectSizeLimit gerrit.config parameter
+      // we want to present this error to the user
+      if (badStream.getCause() instanceof TooLargeObjectInPackException) {
+        StringBuilder msg = new StringBuilder();
+        msg.append("Receive error on project \""
+            + projectControl.getProject().getName() + "\"");
+        msg.append(" (user ");
+        msg.append(currentUser.getAccount().getUserName());
+        msg.append(" account ");
+        msg.append(currentUser.getAccountId());
+        msg.append("): ");
+        msg.append(badStream.getCause().getMessage());
+        log.info(msg.toString());
+        throw new UnloggedFailure(128, "error: " + badStream.getCause().getMessage());
+      }
+
       // This may have been triggered by branch level access controls.
       // Log what the heck is going on, as detailed as we can.
       //
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 236d9f1..5ebb6c7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -38,6 +38,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.kohsuke.args4j.Argument;
@@ -67,7 +68,8 @@
 
   private final Set<PatchSet.Id> patchSetIds = new HashSet<PatchSet.Id>();
 
-  @Argument(index = 0, required = true, multiValued = true, metaVar = "{COMMIT | CHANGE,PATCHSET}", usage = "patch to review")
+  @Argument(index = 0, required = true, multiValued = true, metaVar = "{COMMIT | CHANGE,PATCHSET}",
+      usage = "list of commits or patch sets to review")
   void addPatchSetId(final String token) {
     try {
       patchSetIds.addAll(parsePatchSetId(token));
@@ -78,29 +80,29 @@
     }
   }
 
-  @Option(name = "--project", aliases = "-p", usage = "project containing the patch set")
+  @Option(name = "--project", aliases = "-p", usage = "project containing the specified patch set(s)")
   private ProjectControl projectControl;
 
-  @Option(name = "--message", aliases = "-m", usage = "cover message to publish on change", metaVar = "MESSAGE")
+  @Option(name = "--message", aliases = "-m", usage = "cover message to publish on change(s)", metaVar = "MESSAGE")
   private String changeComment;
 
-  @Option(name = "--abandon", usage = "abandon the patch set")
+  @Option(name = "--abandon", usage = "abandon the specified change(s)")
   private boolean abandonChange;
 
-  @Option(name = "--restore", usage = "restore an abandoned the patch set")
+  @Option(name = "--restore", usage = "restore the specified abandoned change(s)")
   private boolean restoreChange;
 
-  @Option(name = "--submit", aliases = "-s", usage = "submit the patch set")
+  @Option(name = "--submit", aliases = "-s", usage = "submit the specified patch set(s)")
   private boolean submitChange;
 
   @Option(name = "--force-message", usage = "publish the message, "
-      + "even if the label score cannot be applied due to change being closed")
+      + "even if the label score cannot be applied due to the change being closed")
   private boolean forceMessage = false;
 
-  @Option(name = "--publish", usage = "publish a draft patch set")
+  @Option(name = "--publish", usage = "publish the specified draft patch set(s)")
   private boolean publishPatchSet;
 
-  @Option(name = "--delete", usage = "delete a draft patch set")
+  @Option(name = "--delete", usage = "delete the specified draft patch set(s)")
   private boolean deleteDraftPatchSet;
 
   @Inject
@@ -113,7 +115,7 @@
   private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
 
   @Inject
-  private AbandonChange.Factory abandonChangeFactory;
+  private Provider<AbandonChange> abandonChangeProvider;
 
   @Inject
   private PublishComments.Factory publishCommentsFactory;
@@ -122,7 +124,7 @@
   private PublishDraft.Factory publishDraftFactory;
 
   @Inject
-  private RestoreChange.Factory restoreChangeFactory;
+  private Provider<RestoreChange> restoreChangeProvider;
 
   @Inject
   private Submit.Factory submitFactory;
@@ -201,12 +203,16 @@
       publishCommentsFactory.create(patchSetId, changeComment, aps, forceMessage).call();
 
       if (abandonChange) {
-        final ReviewResult result = abandonChangeFactory.create(
-            patchSetId.getParentKey(), changeComment).call();
+        final AbandonChange abandonChange = abandonChangeProvider.get();
+        abandonChange.setChangeId(patchSetId.getParentKey());
+        abandonChange.setMessage(changeComment);
+        final ReviewResult result = abandonChange.call();
         handleReviewResultErrors(result);
       } else if (restoreChange) {
-        final ReviewResult result = restoreChangeFactory.create(
-            patchSetId.getParentKey(), changeComment).call();
+        final RestoreChange restoreChange = restoreChangeProvider.get();
+        restoreChange.setChangeId(patchSetId.getParentKey());
+        restoreChange.setMessage(changeComment);
+        final ReviewResult result = restoreChange.call();
         handleReviewResultErrors(result);
       }
       if (submitChange) {
@@ -261,7 +267,7 @@
           errMsg += "rule error";
           break;
         case NOT_A_DRAFT:
-          errMsg += "change is not a draft";
+          errMsg += "change/patch set is not a draft";
           break;
         case GIT_ERROR:
           errMsg += "error writing change to git repository";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
new file mode 100644
index 0000000..9143f5b
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -0,0 +1,171 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.State;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+final class SetProjectCommand extends SshCommand {
+  private static final Logger log = LoggerFactory
+      .getLogger(SetProjectCommand.class);
+
+  @Argument(index = 0, required = true, metaVar = "NAME", usage = "name of the project")
+  private ProjectControl projectControl;
+
+  @Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of project")
+  private String projectDescription;
+
+  @Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type\n"
+      + "(default: MERGE_IF_NECESSARY)")
+  private SubmitType submitType;
+
+  @Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required")
+  private Boolean contributorAgreements;
+
+  @Option(name = "--no-contributor-agreements", aliases = {"--nca"}, usage = "if contributor agreement is not required")
+  private Boolean noContributorAgreements;
+
+  @Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
+  private Boolean signedOffBy;
+
+  @Option(name = "--no-signed-off-by", aliases = {"--nso"}, usage = "if signed-off-by is not required")
+  private Boolean noSignedOffBy;
+
+  @Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
+  private Boolean contentMerge;
+
+  @Option(name = "--no-content-merge", usage = "don't allow automatic conflict resolving within files")
+  private Boolean noContentMerge;
+
+  @Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
+  private Boolean requireChangeID;
+
+  @Option(name = "--no-change-id", aliases = {"--nid"}, usage = "if change-id is not required")
+  private Boolean noRequireChangeID;
+
+  @Option(name = "--project-state", aliases = {"--ps"}, usage = "project's visibility state")
+  private State state;
+
+  @Inject
+  private MetaDataUpdate.User metaDataUpdateFactory;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Override
+  protected void run() throws Failure {
+    validate();
+    Project ctlProject = projectControl.getProject();
+    Project.NameKey nameKey = ctlProject.getNameKey();
+    String name = ctlProject.getName();
+    final StringBuilder err = new StringBuilder();
+
+    try {
+      MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
+      try {
+        ProjectConfig config = ProjectConfig.read(md);
+        Project project = config.getProject();
+
+        project.setRequireChangeID(requireChangeID != null ? requireChangeID
+            : project.isRequireChangeID());
+
+        project.setRequireChangeID(noRequireChangeID != null
+            ? !noRequireChangeID : project.isRequireChangeID());
+
+        project.setSubmitType(submitType != null ? submitType : project
+            .getSubmitType());
+
+        project.setUseContentMerge(contentMerge != null ? contentMerge
+            : project.isUseContentMerge());
+
+        project.setUseContentMerge(noContentMerge != null ? !noContentMerge
+            : project.isUseContentMerge());
+
+        project.setUseContributorAgreements(contributorAgreements != null
+            ? contributorAgreements : project.isUseContributorAgreements());
+
+        project.setUseContributorAgreements(noContributorAgreements != null
+            ? !noContributorAgreements : project.isUseContributorAgreements());
+
+        project.setUseSignedOffBy(signedOffBy != null ? signedOffBy : project
+            .isUseSignedOffBy());
+
+        project.setUseContentMerge(noSignedOffBy != null ? !noSignedOffBy
+            : project.isUseContentMerge());
+
+        project.setDescription(projectDescription != null ? projectDescription
+            : project.getDescription());
+
+        project.setState(state != null ? state : project.getState());
+
+        md.setMessage("Project settings updated");
+        config.commit(md);
+      } finally {
+        md.close();
+      }
+    } catch (RepositoryNotFoundException notFound) {
+      err.append("error: Project " + name + " not found\n");
+    } catch (IOException e) {
+      final String msg = "Cannot update project " + name;
+      log.error(msg, e);
+      err.append("error: " + msg + "\n");
+    } catch (ConfigInvalidException e) {
+      final String msg = "Cannot update project " + name;
+      log.error(msg, e);
+      err.append("error: " + msg + "\n");
+    }
+    projectCache.evict(ctlProject);
+
+    if (err.length() > 0) {
+      while (err.charAt(err.length() - 1) == '\n') {
+        err.setLength(err.length() - 1);
+      }
+      throw new UnloggedFailure(1, err.toString());
+    }
+  }
+
+  private void validate() throws UnloggedFailure {
+    checkExclusivity(contentMerge, "--use-content-merge",
+        noContentMerge, "--no-content-merge");
+
+    checkExclusivity(contributorAgreements, "--use-contributor-agreements",
+        noContributorAgreements, "--no-contributor-agreements");
+
+    checkExclusivity(signedOffBy, "--use-signed-off-by",
+        noSignedOffBy, "--no-signed-off-by");
+
+    checkExclusivity(requireChangeID, "--require-change-id",
+        noRequireChangeID, "--no-change-id");
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
old mode 100755
new mode 100644
diff --git a/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs b/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
index c780f44..e9441bb 100644
--- a/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:36 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs b/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
index 589908f..e9441bb 100644
--- a/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:35 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-war/.settings/org.eclipse.core.resources.prefs b/gerrit-war/.settings/org.eclipse.core.resources.prefs
index d404b00..abdea9ac 100644
--- a/gerrit-war/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-war/.settings/org.eclipse.core.resources.prefs
@@ -1,4 +1,3 @@
-#Thu Jul 28 11:02:37 PDT 2011
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
diff --git a/pom.xml b/pom.xml
index 440bd1c..d7b5988 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,7 +49,7 @@
     <jgitVersion>2.0.0.201206130900-r.23-gb3dbf19</jgitVersion>
     <gwtormVersion>1.4</gwtormVersion>
     <gwtjsonrpcVersion>1.3</gwtjsonrpcVersion>
-    <gwtexpuiVersion>1.2.5</gwtexpuiVersion>
+    <gwtexpuiVersion>1.2.6</gwtexpuiVersion>
     <gwtVersion>2.4.0</gwtVersion>
     <slf4jVersion>1.6.1</slf4jVersion>
     <guiceVersion>3.0</guiceVersion>
@@ -88,12 +88,26 @@
     <module>gerrit-war</module>
 
     <module>gerrit-extension-api</module>
-    <module>gerrit-plugin-api</module>
-    <module>gerrit-plugin-archetype</module>
 
     <module>gerrit-gwtui</module>
   </modules>
 
+  <profiles>
+    <profile>
+      <id>all</id>
+      <modules>
+        <module>gerrit-plugin-api</module>
+        <module>gerrit-plugin-archetype</module>
+      </modules>
+    </profile>
+    <profile>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <id>no-plugins</id>
+    </profile>
+  </profiles>
+
   <licenses>
     <license>
       <name>Apache License, 2.0</name>
@@ -464,7 +478,7 @@
       <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
-        <version>12.0</version>
+        <version>12.0.1</version>
       </dependency>
 
       <dependency>
diff --git a/tools/release.sh b/tools/release.sh
index 4a872a1..79e6605 100755
--- a/tools/release.sh
+++ b/tools/release.sh
@@ -25,7 +25,7 @@
 fi
 
 ./tools/version.sh --release &&
-mvn clean package $include_docs
+mvn clean package $include_docs -P all
 rc=$?
 ./tools/version.sh --reset