Merge branch 'stable-2.5'

* stable-2.5:
  Update versions of included core plugins for Gerrit 2.5-rc1 release
  Complete release notes for Gerrit-2.5-rc1

Change-Id: Ia75dcba2019dd67842778c1610ea6dd9c8ed9cee
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 879d1ac..102975f 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -562,7 +562,7 @@
 Ownership over a particular branch subspace may be delegated by
 entering a branch pattern.  To delegate control over all branches
 that begin with `qa/` to the QA group, add `Owner` category
-for reference `refs/heads/qa/\*`.  Members of the QA group can
+for reference `refs/heads/qa/*`.  Members of the QA group can
 further refine access, but only for references that begin with
 `refs/heads/qa/`. See <<project_owners,project owners>> to find
 out more about this role.
@@ -728,6 +728,20 @@
 can still do the rebase locally and upload the rebased commit as a new
 patch set.
 
+[[category_remove_reviewer]]
+Remove Reviewer
+~~~~~~~~~~~~~~~
+
+This category permits users to remove other users from the list of
+reviewers on a change.
+
+The change owner, project owner and site administrator can always
+remove reviewers (even without having the `Remove Reviewer` access
+right assigned).
+
+Users without this access right can only remove themselves from the
+reviewer list on a change.
+
 
 [[category_submit]]
 Submit
@@ -841,8 +855,8 @@
 Suggested access rights to grant:
 
 * <<category_read,`Read`>> on 'refs/heads/\*' and 'refs/tags/*'
-* <<category_push,`Push`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
-* <<category_label-Code-Review,`Code review`>> with range '-1' to '+1'
+* <<category_push,`Push`>> to 'refs/for/refs/heads/*'
+* <<category_label-Code-Review,`Code review`>> with range '-1' to '+1' for 'refs/heads/*'
 
 
 [[examples_developer]]
@@ -865,11 +879,11 @@
 Suggested access rights to grant:
 
 * <<category_read,`Read`>> on 'refs/heads/\*' and 'refs/tags/*'
-* <<category_push,`Push`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
-* <<category_push_merge,`Push merge commit`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
-* <<category_forge_author,`Forge Author Identity`>>
-* <<category_label-Code-Review,`Label: Code review`>> with range '-2' to '+2'
-* <<category_label-Verified,`Label: Verify`>> with range '-1' to '+1'
+* <<category_push,`Push`>> to 'refs/for/refs/heads/*'
+* <<category_push_merge,`Push merge commit`>> to 'refs/for/refs/heads/*'
+* <<category_forge_author,`Forge Author Identity`>> to 'refs/heads/*'
+* <<category_label-Code-Review,`Label: Code review`>> with range '-2' to '+2' for 'refs/heads/*'
+* <<category_label-Verified,`Label: Verify`>> with range '-1' to '+1' for 'refs/heads/*'
 * <<category_submit,`Submit`>>
 
 If the project is small or the developers are seasoned it might make
@@ -921,13 +935,13 @@
 Suggested access rights to grant, that won't block changes:
 
 * <<category_read,`Read`>> on 'refs/heads/\*' and 'refs/tags/*'
-* <<category_label-Code-Review,`Label: Code review`>> with range '-1' to '0'
-* <<category_label-Verified,`Label: Verify`>> with range '0' to '+1'
+* <<category_label-Code-Review,`Label: Code review`>> with range '-1' to '0' for 'refs/heads/*'
+* <<category_label-Verified,`Label: Verify`>> with range '0' to '+1' for 'refs/heads/*'
 
 Optional access rights to grant:
 
-* <<category_label-Code-Review,`Label: Code review`>> with range '-1' to '+1'
-* <<category_push,`Push`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
+* <<category_label-Code-Review,`Label: Code review`>> with range '-1' to '+1' for 'refs/heads/*'
+* <<category_push,`Push`>> to 'refs/for/refs/heads/*'
 
 
 [[examples_integrator]]
@@ -944,7 +958,7 @@
 * <<examples_developer,Developer rights>>
 * <<category_push,`Push`>> to 'refs/heads/*'
 * <<category_push_merge,`Push merge commit`>> to 'refs/heads/*'
-* <<category_forge_committer,`Forge Committer Identity`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
+* <<category_forge_committer,`Forge Committer Identity`>> to 'refs/for/refs/heads/*'
 * <<category_create,`Create Reference`>> to 'refs/heads/*'
 * <<category_push_annotated,`Push Annotated Tag`>> to 'refs/tags/*'
 
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index ccd9ffc..e446f71 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -81,6 +81,9 @@
 link:cmd-stream-events.html[gerrit stream-events]::
 	Monitor events occurring in real time.
 
+link:cmd-version.html[gerrit version]::
+	Show the currently executing version of Gerrit.
+
 git upload-pack::
 	Standard Git server side command for client side `git fetch`.
 
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index d7d5aa5..26530bd 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -16,6 +16,7 @@
   [--format {text | json | json_compact}]
   [--all]
   [--limit <N>]
+  [--has-acl-for GROUP]
 
 DESCRIPTION
 -----------
@@ -91,6 +92,13 @@
 --limit::
 	Cap the number of results to the first N matches.
 
+--has-acl-for::
+	Display only projects on which access rights for this group are
+	directly assigned. Projects which only inherit access rights for
+	this group are not listed.
++
+With this option you can find out on which projects a group is used.
+
 HTTP
 ----
 This command is also available over HTTP, as `/projects/` for
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 2feea11..963d788 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -65,7 +65,8 @@
 
 --files::
 	Support for listing files with patch sets and their
-	attributes (ADDED, MODIFIED, DELETED, RENAMED, COPIED).
+	attributes (ADDED, MODIFIED, DELETED, RENAMED, COPIED)
+	and size information (number of insertions and deletions).
 	Note that this option requires either the --current-patch-set
 	or the --patch-sets option in order to give any file information.
 
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index a8cf3b0..03aec40 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -44,7 +44,7 @@
 *patchSet*, *account* involved, and other attributes as appropriate.
 The currently supported message types are *patchset-created*,
 *draft-published*, *change-abandoned*, *change-restored*,
-*change-merged*, *comment-added* and *ref-updated*.
+*change-merged*, *comment-added*, *ref-updated* and *reviewer-added*.
 
 Note that any field may be missing in the JSON messages, so consumers of
 this JSON stream should deal with that appropriately.
@@ -127,6 +127,16 @@
 
 refUpdate:: link:json.html#refUpdate[refUpdate attribute]
 
+Reviewer Added
+^^^^^^^^^^^^^^
+type:: "reviewer-added"
+
+change:: link:json.html#change[change attribute]
+
+patchset:: link:json.html#patchSet[patchset attribute]
+
+reviewer:: link:json.html#account[account attribute]
+
 
 SEE ALSO
 --------
diff --git a/Documentation/cmd-version.txt b/Documentation/cmd-version.txt
new file mode 100644
index 0000000..d1f94ea
--- /dev/null
+++ b/Documentation/cmd-version.txt
@@ -0,0 +1,48 @@
+gerrit version
+================
+
+NAME
+----
+gerrit version - Show the version of the currently executing Gerrit server
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit version'
+
+DESCRIPTION
+-----------
+Displays a one-line response with the string `gerrit version` followed
+by the currently executing version of Gerrit.
+
+The `git describe` command is used to generate the version string based
+on the Git commit used to build Gerrit. For official releases of Gerrit,
+the version string will be equal to the Git tag set in the Gerrit source
+code, which in turn is equal to the name of the release (for example
+2.4.2). When building Gerrit from another commit (one that doesn't have
+an official-looking tag pointing to it), the version string has the form
+`<tagname>-<n>-g<sha1>`, where `<n>` is an integer indicating the number
+of commits ahead of the `<tagname>` tag the commit is, and `<sha1>` is
+the seven-character abbreviated SHA-1 of the commit. See the `git
+describe` documentation for details on how `<tagname>` is chosen and how
+`<n>` is computed.
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+EXAMPLES
+--------
+
+=====
+	$ ssh -p 29418 review.example.com gerrit version
+	gerrit version 2.4.2
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 5e2cc18..d4259a9 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -648,16 +648,23 @@
 [[changeMerge]]Section changeMerge
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+changeMerge.checkFrequency::
++
+How often the database should be rescanned for changes that have been
+submitted but not merged due to transient errors. Values can be
+specified using standard time unit abbreviations ('ms', 'sec', 'min',
+etc.). Set to 0 to disable periodic rescanning, only scanning once on
+master node startup.
++
+Default is 300 seconds (5 minutes).
+
+changeMerge.test::
++
 Controls whether or not the mergeability test of changes is
 enabled.  If enabled, when the change page is loaded, the test is
 triggered. The submit button will be enabled or disabled according to
 the result.
-
-----
-[changeMerge]
-  test = true
-----
-
++
 By default this is false (test is not enabled).
 
 [[commentlink]]Section commentlink
@@ -1283,6 +1290,11 @@
 Optional filename for the ref updated hook, if not specified then
 `ref-updated` will be used.
 
+[[hooks.reviewerAddedHook]]hooks.reviewerAddedHook::
++
+Optional filename for the reviewer added hook, if not specified then
+`reviewer-added` will be used.
+
 [[hooks.claSignedHook]]hooks.claSignedHook::
 +
 Optional filename for the CLA signed hook, if not specified then
@@ -1702,6 +1714,21 @@
 Default is `(memberUid=${username})` for RFC 2307,
 and unset (disabled) for Active Directory.
 
+[[ldap.groupName]]ldap.groupName::
++
+_(Optional)_ Name of the attribute on the group object which contains
+the value to use as the group name in Gerrit.
++
+Typically the attribute name is `cn` for RFC 2307 and Active Directory
+servers.  For other servers the attribute name may differ, for example
+`apple-group-realname` on Apple MacOS X Server.
++
+It is also possible to specify a literal string containing a pattern of
+attribute values.  For example to create a Gerrit group name consisting of
+LDAP group name and group ID, use the pattern `${cn} (${gidNumber})`.
++
+Default is `cn`.
+
 [[ldap.localUsernameToLowerCase]]ldap.localUsernameToLowerCase::
 +
 Converts the local username, that is used to login into the Gerrit
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index 35d5c0d..e5edda8 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -1,13 +1,12 @@
-Gerrit Code Review - Gitweb Integration
-=======================================
+Gitweb Integration
+------------------
 
 Gerrit Code Review can manage and generate hyperlinks to gitweb,
 allowing users to jump from Gerrit content to the same information,
 but shown by gitweb.
 
-
 Internal/Managed gitweb
------------------------
+~~~~~~~~~~~~~~~~~~~~~~~
 
 In the internal configuration, Gerrit inspects the request, enforces
 its project level access controls, and directly executes `gitweb.cgi`
@@ -39,7 +38,7 @@
 be restarted and clients must reload the host page to see the change.
 
 Configuration
-~~~~~~~~~~~~~
+^^^^^^^^^^^^^
 
 Most of the gitweb configuration file is handled automatically
 by Gerrit Code Review.  Site specific overrides can be placed in
@@ -47,7 +46,7 @@
 part of the generated configuration file.
 
 Logo and CSS
-~~~~~~~~~~~~
+^^^^^^^^^^^^
 
 If the package-manager installed CGI (`/usr/lib/cgi-bin/gitweb.cgi`)
 is being used, the stock CSS and logo files will be served from
@@ -58,17 +57,165 @@
 the default source code distribution, and most custom installations.
 
 Access Control
-~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
 
 Access controls for internally managed gitweb page views are enforced
 using the standard project READ +1 permission.
 
-
 External/Unmanaged gitweb
--------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~
 
-In the external configuration, gitweb runs under the control of an
-external web server, and Gerrit access controls are not enforced.
+For the external configuration, gitweb runs under the control of an
+external web server, and Gerrit access controls are not enforced. Gerrit
+provides configuration parameters for integration with GitWeb.
+
+[[linuxGitWeb]]
+Linux Installation
+^^^^^^^^^^^^^^^^^^
+
+Install GitWeb
+++++++++++++++
+
+On Ubuntu:
+
+====
+  sudo apt-get install gitweb
+====
+
+With Yum:
+
+====
+  $ yum install gitweb
+====
+
+Configure GitWeb
+++++++++++++++++
+
+
+Update `/etc/gitweb.conf`, add the public GIT repositories:
+
+----
+$projectroot = "/var/www/repo/";
+
+# directory to use for temp files
+$git_temp = "/tmp";
+
+# target of the home link on top of all pages
+#$home_link = $my_uri || "/";
+
+# html text to include at home page
+$home_text = "indextext.html";
+
+# file with project list; by default, simply scan the projectroot dir.
+$projects_list = $projectroot;
+
+# stylesheet to use
+# I took off the prefix / of the following path to put these files inside gitweb directory directly
+$stylesheet = "gitweb.css";
+
+# logo to use
+$logo = "git-logo.png";
+
+# the ‘favicon’
+$favicon = "git-favicon.png";
+----
+
+Configure & Restart Apache Web Server
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Configure Apache
+++++++++++++++++
+
+
+Link gitweb to `/var/www/gitweb`, check `/etc/gitweb.conf` if unsure of paths:
+
+====
+  $ sudo ln -s /usr/share/gitweb /var/www/gitweb
+====
+
+Add the gitweb directory to the Apache configuration by creating a "gitweb"
+file inside the Apache conf.d directory:
+
+====
+  $ touch /etc/apache/conf.d/gitweb
+====
+
+Add the following to /etc/apache/conf.d/gitweb:
+
+----
+Alias /gitweb /var/www/gitweb
+
+Options Indexes FollowSymlinks ExecCGI
+DirectoryIndex /cgi-bin/gitweb.cgi
+AllowOverride None
+----
+
+*NOTE* This may have already been added by yum/apt-get. If that's the case, leave as
+is.
+
+Restart the Apache Web Server
++++++++++++++++++++++++++++++
+
+====
+$ sudo /etc/init.d/apache2 restart
+====
+
+Now you should be able to view your repository projects online:
+
+link:http://localhost/gitweb[http://localhost/gitweb]
+
+[[WindowsGitWeb]]
+Windows Installation
+^^^^^^^^^^^^^^^^^^^^
+
+Instructions are available for installing the GitWeb module distributed with
+MsysGit:
+
+link:https://github.com/msysgit/msysgit/wiki/GitWeb[GitWeb]
+
+If you don't have Apache installed, you can download the appropriate build for
+Windows from link:http://www.apachelounge.com/download[apachelounge.org].
+
+After you have installed Apache, you will want to create a link:http://httpd.apache.org/docs/2.0/platform/windows.html#winsvc[new service user
+account] to use with Apache.
+
+If you're still having difficulty setting up permissions, you may find this
+tech note useful for configuring Apache Service to run under another account.
+You must grant the new account link:http://technet.microsoft.com/en-us/library/cc794944(WS.10).aspx["run as service"] permission:
+
+The GitWeb version in msysgit is missing several important and required
+perl modules, including CGI.pm. The perl included with the msysgit distro 1.7.8
+is broken.. The link:http://groups.google.com/group/msysgit/browse_thread/thread/ba3501f1f0ed95af[unicore folder is missing along with utf8_heavy.pl and CGI.pm]. You can
+verify by checking for perl modules. From an msys console, execute the
+following to check:
+
+====
+$ perl -mCGI -mEncode -mFcntl -mFile::Find -mFile::Basename -e ""
+====
+
+You may encounter the following exception:
+
+----
+$ perl -mCGI -mEncode -mFcntl -mFile::Find -mFile::Basename -e ""
+Can't locate CGI.pm in @INC (@INC contains: /usr/lib/perl5/5.8.8/msys
+/usr/lib/p erl5/5.8.8 /usr/lib/perl5/site_perl/5.8.8/msys
+/usr/lib/perl5/site_perl/5.8.8 /u sr/lib/perl5/site_perl .). BEGIN
+failed--compilation aborted.
+----
+
+If you're missing CGI.pm, you'll have to deploy the module to the msys
+environment: You will have to retrieve them from the 5.8.8 distro on :
+
+http://strawberryperl.com/releases.html
+
+File: strawberry-perl-5.8.8.3.zip
+
+contents: `bin/` `lib/` `site/`
+
+copy the contents of lib into `msysgit/lib/perl5/5.8.8` and overwrite existing files.
+
+Enable GitWeb Integration
+^^^^^^^^^^^^^^^^^^^^^^^^^
 
 To enable the external gitweb integration, set
 link:config-gerrit.html#gitweb.url[gitweb.url] with the URL of your
@@ -79,16 +226,27 @@
 being used, ensure it uses a full mirror, so the `refs/changes/*`
 namespace is available.
 
-====
-  git config --file $site_path/etc/gerrit.config gitweb.url http://example.com/gitweb.cgi
-  git config --file $site_path/etc/gerrit.config --unset gitweb.cgi
-====
+----
+$ git config -f $site_path/etc/gerrit.config gitweb.cgi $PATH_TO_GITWEB/gitweb.cgi
+$ git config -f $site_path/etc/gerrit.config gitweb.url https://gitweb.corporation.com
+----
+
+If you're not following the traditional \{projectName\}.git project naming conventions,
+you will want to customize Gerrit to read them. Add the following:
+
+----
+$ git config -f $site_path/etc/gerrit.config gitweb.type custom
+$ git config -f $site_path/etc/gerrit.config gitweb.project ?p=\${project}\;a=summary
+$ git config -f $site_path/etc/gerrit.config gitweb.revision ?p=\${project}\;a=commit\;h=\${commit}
+$ git config -f $site_path/etc/gerrit.config gitweb.branch ?p=\${project}\;a=shortlog\;h=\${branch}
+$ git config -f $site_path/etc/gerrit.config gitweb.filehistory ?p=\${project}\;a=history\;hb=\${branch}\;f=\${file}
+----
 
 After updating `'$site_path'/etc/gerrit.config`, the Gerrit server must
 be restarted and clients must reload the host page to see the change.
 
 Access Control
-~~~~~~~~~~~~~~
+++++++++++++++
 
 Gitweb access controls can be implemented using standard web server
 access controls.  This isn't typically integrated with Gerrit's own
@@ -96,7 +254,7 @@
 consistent if access needs to be restricted.
 
 Caching Gitweb
-~~~~~~~~~~~~~~
+++++++++++++++
 
 If your repository set is large and you are expecting a lot
 of users, you may want to look at the caching forks used by
@@ -112,7 +270,7 @@
 It is also possible to define custom patterns.
 
 See Also
---------
+~~~~~~~~
 
 * link:config-gerrit.html#gitweb[Section gitweb]
 * link:http://hjemli.net/git/cgit/[cgit]
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index dfdba52..0783696 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -84,6 +84,15 @@
   ref-updated --oldrev <old rev> --newrev <new rev> --refname <ref name> --project <project name> --submitter <submitter>
 ====
 
+reviewer-added
+~~~~~~~~~~~~~~
+
+Called whenever a reviewer is added to a change.
+
+====
+  reviewer-added --change <change id> --change-url <change url> --project <project name> --branch <branch> --reviewer <reviewer>
+====
+
 cla-signed
 ~~~~~~~~~~
 
@@ -104,8 +113,8 @@
 
 For the hook filenames, Gerrit will use the values of hooks.patchsetCreatedHook,
 hooks.draftPublishedHook, hooks.commentAddedHook, hooks.changeMergedHook,
-hooks.changeAbandonedHook, hooks.changeRestoredHook, hooks.refUpdatedHook and
-hooks.claSignedHook.
+hooks.changeAbandonedHook, hooks.changeRestoredHook, hooks.refUpdatedHook,
+hooks.reviewerAddedHook and hooks.claSignedHook.
 
 Missing Change URLs
 -------------------
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
new file mode 100644
index 0000000..bc52d50
--- /dev/null
+++ b/Documentation/dev-release-deploy-config.txt
@@ -0,0 +1,135 @@
+Deploy Gerrit Artifacts
+=======================
+
+Gerrit Artifacts are stored on
+link:https://developers.google.com/storage/[Google Cloud Storage].
+Via the link:https://code.google.com/apis/console/[API Console] the
+Gerrit maintainers have access to the `Gerrit Code Review` project.
+This projects host several buckets for storing Gerrit artifacts:
+
+* `gerrit-api`:
++
+Bucket to store the Gerrit Extension API Jar and the Gerrit Plugin API
+Jar.
+
+* `gerrit-maven`:
++
+Bucket to store Gerrit Subproject Artifacts (e.g. `gwtexpui`,
+`gwtjsonrpc` etc.).
+
+* `gerrit-plugins`:
++
+Bucket to store Gerrit Core Plugin Artifacts.
+
+[[deploy-configuration-settings-xml]]
+Deploy Configuration in Maven `settings.xml`
+--------------------------------------------
+
+To upload artifacts to a bucket the user must authenticate with a
+username and password. The username and password need to be retrieved
+from the link:https://code.google.com/apis/console/[API Console]:
+
+* Go to the `Gerrit Code Review` project
+* In the menu on the left select `Google Cloud Storage` >
+`Interoperable Access`
+* Use the `Access Key` as username
+* Click under `Secret` on the `Show` button to find the password
+
+To make the username and password known to Maven, they must be
+configured in the `~/.m2/settings.xml` file.
+
+----
+  <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+    <servers>
+      <server>
+        <id>gerrit-api-repository</id>
+        <username>GOOG..EXAMPLE.....EXAMPLE</username>
+        <password>EXAMPLE..EXAMPLE..EXAMPLE</password>
+      </server>
+      <server>
+        <id>gerrit-maven-repository</id>
+        <username>GOOG..EXAMPLE.....EXAMPLE</username>
+        <password>EXAMPLE..EXAMPLE..EXAMPLE</password>
+      </server>
+      <server>
+        <id>gerrit-plugins-repository</id>
+        <username>GOOG..EXAMPLE.....EXAMPLE</username>
+        <password>EXAMPLE..EXAMPLE..EXAMPLE</password>
+      </server>
+    </servers>
+  </settings>
+----
+
+[[deploy-configuration-subprojects]]
+Gerrit Subprojects
+~~~~~~~~~~~~~~~~~~
+
+* You will need to have the following in the `pom.xml` to make it
+deployable to the `gerrit-maven` storage bucket:
+
+----
+  <distributionManagement>
+    <repository>
+      <id>gerrit-maven-repository</id>
+      <name>Gerrit Maven Repository</name>
+      <url>s3://gerrit-maven@commondatastorage.googleapis.com</url>
+      <uniqueVersion>true</uniqueVersion>
+    </repository>
+  </distributionManagement>
+----
+
+
+* Add this to the `pom.xml` to enable the wagon provider:
+
+----
+  <build>
+    <extensions>
+      <extension>
+        <groupId>net.anzix.aws</groupId>
+        <artifactId>s3-maven-wagon</artifactId>
+        <version>3.2</version>
+      </extension>
+    </extensions>
+  </build>
+----
+
+
+[[deploy-configuration-core-plugins]]
+Gerrit Core Plugins
+~~~~~~~~~~~~~~~~~~~
+
+* You will need to have the following in the `pom.xml` to make it
+deployable to the `gerrit-plugins` storage bucket:
+
+----
+  <distributionManagement>
+    <repository>
+      <id>gerrit-plugins-repository</id>
+      <name>Gerrit Plugins Repository</name>
+      <url>s3://gerrit-plugins@commondatastorage.googleapis.com</url>
+      <uniqueVersion>true</uniqueVersion>
+    </repository>
+  </distributionManagement>
+----
+
+
+* Add this to the `pom.xml` to enable the wagon provider:
+
+----
+  <build>
+    <extensions>
+      <extension>
+        <groupId>net.anzix.aws</groupId>
+        <artifactId>s3-maven-wagon</artifactId>
+        <version>3.2</version>
+      </extension>
+    </extensions>
+  </build>
+----
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index 799ff2d..5e3770d 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -1,96 +1,106 @@
 Making a Release of a Gerrit Subproject / Core Plugin
 =====================================================
 
-Preparing a New Snapshot for Publishing
----------------------------------------
+[[make-snapshot]]
+Make a Snapshot
+---------------
 
-* You will need to have the following in the `pom.xml` to make it
-  deployable to the `gerrit-maven` storage bucket:
+* Only for plugins:
+** In the `pom.xml` update the Gerrit version under `properties` >
+`Gerrit-ApiVersion` to the version of the new Gerrit
+release.
+** Make sure that the URL for the Maven repository with the id
+`gerrit-api-repository` in the `pom.xml` is correct.
++
+If `Gerrit-ApiVersion` references a released Gerrit version it must be
+`https://gerrit-api.commondatastorage.googleapis.com/release/`, if
+`Gerrit-ApiVersion` references a snapshot Gerrit version it must be
+`https://gerrit-api.commondatastorage.googleapis.com/snapshot/`.
 
-----
-  <distributionManagement>
-    <repository>
-      <id>gerrit-maven</id>
-      <name>gerrit Maven Repository</name>
-      <url>s3://gerrit-maven@commondatastorage.googleapis.com</url>
-      <uniqueVersion>true</uniqueVersion>
-    </repository>
-  </distributionManagement>
-----
+* Build the latest snapshot and install it into the local Maven
+repository:
++
+====
+  mvn clean install
+====
+
+* Test Gerrit with this snapshot locally
 
 
-* Add this to the `pom.xml` to enable the wagon provider:
+Publish Snapshot
+----------------
 
-----
-  <build>
-    <extensions>
-      <extension>
-        <groupId>net.anzix.aws</groupId>
-        <artifactId>s3-maven-wagon</artifactId>
-        <version>3.2</version>
-      </extension>
-    </extensions>
-  </build>
-----
+If a Snapshot for a Subproject was created that should be referenced by
+Gerrit while current Gerrit development is ongoing, this Snapshot needs
+to be published.
 
+* Make sure you have done the configuration needed for deployment:
+** link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
+Configuration in Maven `settings.xml`]
+** link:dev-release-deploy-config.html#deploy-configuration-subprojects[
+Configuration for Subprojects in `pom.xml`]
 
-* Add your username and password to your `~/.m2/settings.xml` file.
-  These need to come from the link:https://code.google.com/apis/console/[API Console].
-
-----
-  <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
-    <servers>
-      <server>
-        <id>gerrit-maven</id>
-        <username>GOOG..EXAMPLE.....EXAMPLE</username>
-        <password>EXAMPLE..EXAMPLE..EXAMPLE</password>
-      </server>
-    </servers>
-  </settings>
-----
-
-
-Making a Snapshot
------------------
-
-* Only for plugins: in the `pom.xml` update the Gerrit version under
-`properties` > `Gerrit-ApiVersion` to the version of the new Gerrit
-release
-* First build and deploy the latest snapshot and ensure that Gerrit
-builds/runs with this snapshot
-
-* Deploy the snapshot:
-
+* Deploy the new snapshot:
++
 ====
   mvn deploy
 ====
 
+* Change the version in the Gerrit parent `pom.xml` for the Subproject
+to the `SNAPSHOT` version
++
+When Gerrit gets released, a release of the Subproject has to be done
+and Gerrit has to reference the released Subproject version.
 
-Making a Release
-----------------
 
-* First deploy (and test) the latest snapshot for the subproject/plugin
+[[prepare-release]]
+Prepare the Release
+-------------------
+
+* link:#make-snapshot[First create (and test) the latest snapshot for
+the subproject/plugin]
 
 * Update the top level `pom.xml` in the subproject/plugin to reflect
 the new project version (the exact value of the tag you will create
 below)
 
-* Commit the pom change and push to the project's repo
-`refs/for/<master/stable>`
-
-* Tag the version you just pushed (and push the tag)
-
+* Create the Release Tag
++
 ====
  git tag -a -m "prolog-cafe 1.3" v1.3
- git push gerrit-review refs/tags/v1.3:refs/tags/v1.3
 ====
 
+* Build and install into local Maven repository:
++
+====
+  mvn clean install
+====
+
+
+[[publish-release]]
+Publish the Release
+-------------------
+
+* Make sure you have done the configuration needed for deployment:
+** link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
+Configuration in Maven `settings.xml`]
+** Configuration in `pom.xml` for
+link:dev-release-deploy-config.html#deploy-configuration-subprojects[Subprojects] or
+link:dev-release-deploy-config.html#deploy-configuration-core-plugins[Core Plugins]
+
 * Deploy the new release:
-
++
 ====
- mvn deploy
+  mvn deploy
+====
+
+* Push the pom change(s) to the project's repository
+`refs/for/<master|stable>`
+
+* Push the Release Tag
++
+====
+  git push gerrit-review refs/tags/v1.3:refs/tags/v1.3
 ====
 
 
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 5ea3042..7a963fc 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -11,7 +11,7 @@
 
 To make a Gerrit release involves a great deal of complex
 tasks and it is easy to miss a step so this document should
-hopefuly serve as both a how to for those new to the process
+hopefully serve as both a how to for those new to the process
 and as a checklist for those already familiar with these
 tasks.
 
@@ -20,106 +20,181 @@
 -------------------
 
 Here are some guidelines on release approaches depending on the
-type of release you want to make (stable-fix, stable, RC0, RC1...).
+type of release you want to make (`stable-fix`, `stable`, `RC0`,
+`RC1`...).
 
 Stable
 ~~~~~~
 
-A stable release is generally built from the master branch and may need to
-undergo some stabilization before releasing the final release.
+A `stable` release is generally built from the `master` branch and may
+need to undergo some stabilization before releasing the final release.
 
 * Propose the release with any plans/objectives to the mailing list
 
-* Create a Gerrit RC0
+* Create a Gerrit `RC0`
 
-* If needed create a Gerrit RC1
+* If needed create a Gerrit `RC1`
 
 [NOTE]
 ========================================================================
 You may let in a few features to this release
 ========================================================================
 
-* If needed create a Gerrit RC2
+* If needed create a Gerrit `RC2`
 
 [NOTE]
 ========================================================================
 There should be no new features in this release, only bug fixes
 ========================================================================
 
-* Finally create the stable release (no RC)
+* Finally create the `stable` release (no `RC`)
 
 
 Stable-Fix
 ~~~~~~~~~~
 
-Stable-fix releases should likely only contain bug fixes and doc updates.
+`stable-fix` releases should likely only contain bug fixes and doc
+updates.
 
-* Propose the release with any plans/objectives to the mailing list
-
-* This type of release does not need any RCs, release when the objectives
-  are met
-
+* This type of release does not need any RCs, release when the
+objectives are met
 
 
 Create the Actual Release
 ---------------------------
 
-In the example commands below we assume that the last release was '2.4' and that
-we are preparing '2.5' release.
+To create a Gerrit release the following steps have to be done:
 
-Prepare the Subprojects
-~~~~~~~~~~~~~~~~~~~~~~~
-
-* Publish the latest snapshot for all subprojects
-* Freeze all subprojects and link:dev-release-subproject.html[publish]
-  them!
+. link:#subproject[Release Subprojects]
+. link:#prepare-gerrit[Prepare the Gerrit Release]
+.. link:#prepare-war-and-plugin-api[Prepare the Gerrit WAR and the Plugin API Jar]
+.. link:#prepare-core-plugins[Prepare the Core Plugins]
+.. link:#prepare-war-with-plugins[Prepare Gerrit WAR with Core Plugins]
+. link:#publish-gerrit[Publish the Gerrit Release]
+.. link:#extension-and-plugin-api[Publish the Extension and Plugin API Jars]
+.. link:#publish-core-plugins[Publish the Core Plugins]
+.. link:#publish-gerrit-war[Publish the Gerrit WAR (with Core Plugins)]
+.. link:#push-stable[Push the Stable Branch]
+.. link:#push-tag[Push the Release Tag]
+.. link:#upload-documentation[Upload the Documentation]
+.. link:#update-issues[Update the Issues]
+.. link:#announce[Announce on Mailing List]
+. link:#increase-version[Increase Gerrit Version for Current Development]
+. link:#merge-stable[Merge `stable` into `master`]
 
 
+[[subproject]]
+Release Subprojects
+~~~~~~~~~~~~~~~~~~~
+
+The subprojects to be released are:
+
+* `gwtexpui`
+* `gwtjsonrpc`
+* `gwtorm`
+* `prolog-cafe`
+
+For each subproject do:
+
+* Check the dependency to the Subproject in the Gerrit parent `pom.xml`:
++
+If a `SNAPSHOT` version of the subproject is referenced the subproject
+needs to be released so that Gerrit can reference a released version of
+the subproject.
+
+* link:dev-release-subproject.html#make-snapshot[Make a snapshot and test it]
+* link:dev-release-subproject.html#prepare-release[Prepare the Release]
+* link:dev-release-subproject.html#publish-release[Publish the Release]
+
+* Update the version of the Subproject in the Gerrit parent `pom.xml`
+to the released version
+
+
+[[prepare-gerrit]]
 Prepare Gerrit
 ~~~~~~~~~~~~~~
 
-* Create a `stable-2.5` branch for making the new release
+In all example commands it is assumed that the last release was `2.4`
+and that now the `2.5` release is prepared.
 
-* In the `master` branch: Update the poms for the Gerrit version, push for
-review, get merged
 
-====
- tools/version.sh --snapshot=2.5
-====
+[[prepare-war-and-plugin-api]]
+Prepare the Gerrit WAR and the Plugin API Jar
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Checkout the `stable-2.5` branch
-* Update the top level `pom.xml` in Gerrit to ensure that none of the
-Subprojects point to snapshot releases
+* Create locally a `stable-2.5` branch for making the new release
 
-* Tag
+* Check in the Gerrit parent `pom.xml` that no `SNAPSHOT` version of a
+Subproject is referenced
++
+If there is a dependency to a `SNAPSHOT` version,
+link:#subproject[release the subproject] first.
 
+* Create a tag for the Gerrit release
++
+For an `RC` release:
++
 ====
  git tag -a -m "gerrit 2.5-rc0" v2.5-rc0
+====
++
+For a final `stable` release:
++
+====
  git tag -a -m "gerrit 2.5" v2.5
 ====
 
-* Build (without plugins)
-
+* Build the Gerrit WAR (without plugins) and the Plugin API Jar
++
 ====
  ./tools/release.sh
 ====
++
+[WARNING]
+========================================================================
+Make sure you are compiling the release for all browsers. Check in your
+Maven `~/.m2/settings.xml` file that no Maven profile is active that
+limits the compilation to a certain browser.
+========================================================================
 
-[[plugin-api]]
-Publish the Plugin API JAR File
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* Sanity check WAR
 
-* Push JAR to `commondatastorage.googleapis.com`
-** Run `tools/deploy_api.sh`
 
-Prepare the Core Plugins
-~~~~~~~~~~~~~~~~~~~~~~~~
-* link:dev-release-subproject.html[Release and publish] the core plugins
+[[prepare-core-plugins]]
+Prepare Core Plugins
+^^^^^^^^^^^^^^^^^^^^
+The core plugins to be prepared are:
 
-Package Gerrit with Plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* Ensure that the core plugins listed in `gerrit-package-plugins/pom.xml`
-point to the latest release version (no dependency to snapshot versions)
+* `plugins/replication`
+
+For each core plugin do:
+
+* link:dev-release-subproject.html#make-snapshot[Make a snapshot and test it]
+* link:dev-release-subproject.html#prepare-release[Prepare the Release]
+
+* Update the version of the Core Plugin in
+`gerrit-package-plugins/pom.xml` to the released version
+
+[WARNING]
+========================================================================
+Updating the plugin versions in `gerrit-package-plugins/pom.xml`
+invalidates the Gerrit Release Tag which was created before.
+
+If needed delete the tag and recreate it!
+========================================================================
+
+
+[[prepare-war-with-plugins]]
+Prepare Gerrit WAR with Core Plugins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* Ensure that the Core Plugins listed in `gerrit-package-plugins/pom.xml`
+point to the latest release version (no dependency to `SNAPSHOT` versions)
+
+* Ensure that the release tag points to the `HEAD` commit
+
 * Include core plugins into WAR
++
 ====
  $ ./tools/version.sh --release && mvn clean package -f gerrit-package-plugins/pom.xml
  $ ./tools/version.sh --reset
@@ -127,35 +202,91 @@
 
 * Find WAR that includes the core plugins at
 `gerrit-package-plugins\target\gerrit-full-v2.5.war`
-* Sanity check WAR
 
-Publish to the Project Locations
---------------------------------
+* Compare `gerrit-package-plugins\target\gerrit-full-v2.5.war` with
+  `gerrit-war\target\gerrit-v2.5.war`
++
+The only difference should be the core plugins jars under
+`WEB-INF\plugins`.
 
-WAR File
-~~~~~~~~
+* Test the new Gerrit version
 
-* Upload WAR to code.google.com/p/gerrit (manual web browser)
+[[publish-gerrit]]
+Publish the Gerrit Release
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+[[extension-and-plugin-api]]
+Publish the Extension and Plugin API Jars
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* Make sure you have done the
+link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
+configuration needed for deployment]
+
+* Push the Jars to `commondatastorage.googleapis.com`:
++
+----
+  ./tools/deploy_api.sh
+----
+
+
+[[publish-core-plugins]]
+Publish the Core Plugins
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+* link:dev-release-subproject.html#publish-release[Publish the Release]
+
+
+[[publish-gerrit-war]]
+Publish the Gerrit WAR (with Core Plugins)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* The WAR file to upload is `gerrit-package-plugins\target\gerrit-full-v2.5.war`
+* Upload WAR to `code.google.com/p/gerrit` (manual via web browser)
 ** Go to http://code.google.com/p/gerrit/downloads/list
-** Use the "New Download" button
+** Use the `New Download` button
 
 * Update labels:
-** new war: [release-candidate], featured...
-** old war: deprecated
+** new war: [`release-candidate`], `featured`...
+** old war: `deprecated`
 
-Tag
-~~~
 
-* Push the New Tag
+[[push-stable]]
+Push the Stable Branch
+^^^^^^^^^^^^^^^^^^^^^^
 
+* create the stable branch `stable-2.5` in the `gerrit` project
++
+Via the link:https://gerrit-review.googlesource.com/#/admin/projects/gerrit,branches[
+Gerrit WebUI] or by push.
+
+* Push the commits done on `stable-2.5` to `refs/for/stable-2.5` and
+get them merged
+
+
+[[push-tag]]
+Push the Release Tag
+^^^^^^^^^^^^^^^^^^^^
+
+* Push the new Release Tag
++
+For an `RC`:
++
 ====
  git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
+====
++
+For a final `stable` release:
++
+====
  git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
 ====
 
 
-Docs
-~~~~
+[[upload-documentation]]
+Upload the Documentation
+^^^^^^^^^^^^^^^^^^^^^^^^
 
 ====
  make -C Documentation PRIOR=2.4 update
@@ -167,14 +298,14 @@
 * Update Google Code project links
 ** Go to http://code.google.com/p/gerrit/admin
 ** Point the main page to the new docs. The link to the documentation has to be
-updated at two places: in the project description and also in the Links
+updated at two places: in the project description and also in the `Links`
 section.
 ** Point the main page to the new release notes
 
 [NOTE]
 ========================================================================
-The docs makefile does an svn cp of the prior revision of the docs to branch
-the docs so you have less to upload on the new docs.
+The docs makefile does an `svn cp` of the prior revision of the docs to
+branch the docs so you have less to upload on the new docs.
 
 User and password from here:
 
@@ -188,30 +319,33 @@
 ========================================================================
 
 
-Issues
-~~~~~~
+[[update-issues]]
+Update the Issues
+^^^^^^^^^^^^^^^^^
 
 ====
  How do the issues get updated?  Do you run a script to do
- this?  When do you do it, after the final 2.2.2 is released?
+ this?  When do you do it, after the final 2.5 is released?
 ====
 
 By hand.
 
-Our current process is an issue should be updated to say Status =
-Submitted, FixedIn-2.2.2 once the change is submitted, but before the
+Our current process is an issue should be updated to say `Status =
+Submitted, FixedIn-2.5` once the change is submitted, but before the
 release.
 
 After the release is actually made, you can search in Google Code for
-``Status=Submitted FixedIn=2.2.2'' and then batch update these changes
-to say Status=Released. Make sure the pulldown says ``All Issues''
-because Status=Submitted is considered a closed issue.
+``Status=Submitted FixedIn=2.5'' and then batch update these changes
+to say `Status=Released`. Make sure the pulldown says ``All Issues''
+because `Status=Submitted` is considered a closed issue.
 
 
-Mailing List
-~~~~~~~~~~~~
+[[announce]]
+Announce on 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)
@@ -241,7 +375,7 @@
 -Martin
 ----
 
-* Add an entry to the NEWS section of the main Gerrit project web page
+* 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:
 ----
@@ -252,18 +386,33 @@
 ** 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
+** Under options, click 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
-------------------------------
+[[increase-version]]
+Increase Gerrit Version for Current Development
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-After every stable-fix release, stable should be merged to master to
-ensure that none of the fixes ever get lost.
+All new development that is done in the `master` branch will be
+included in the next Gerrit release. Update the Gerrit version in each
+`pom.xml` file to the next `SNAPSHOT`version. Push the change for
+review and get it merged.
+
+====
+ tools/version.sh --snapshot=2.6
+====
+
+
+[[merge-stable]]
+Merge `stable` into `master`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After every release, stable should be merged to master to ensure that
+none of the changes/fixes ever get lost.
 
 ====
  git config merge.summary true
diff --git a/Documentation/json.txt b/Documentation/json.txt
index f0588ce..b4602a9 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -30,6 +30,9 @@
 
 commitMessage:: The full commit message for the change.
 
+createdOn:: Time in seconds since the UNIX epoch when this change
+was created.
+
 lastUpdated:: Time in seconds since the UNIX epoch when this change
 was last updated.
 
@@ -48,14 +51,23 @@
 
   ABANDONED;; Change was abandoned by its owner or administrator.
 
+comments:: All comments for this change in <<message,message attributes>>.
+
 trackingIds:: Issue tracking system links in
-<<trackingid,trackingid attribute>>, scraped out of the commit
+<<trackingid,trackingid attributes>>, scraped out of the commit
 message based on the server's
 link:config-gerrit.html#trackingid[trackingid] sections.
 
 currentPatchSet:: Current <<patchSet,patchSet attribute>>.
 
-patchSets:: All <<patchSet,patchSet attribute>> for this change.
+patchSets:: All <<patchSet,patchSet attributes>> for this change.
+
+dependsOn:: List of changes that this change depends on in <<dependency,dependency attributes>>.
+
+neededBy:: List of changes that depend on this change in <<dependency,dependency attributes>>.
+
+submitRecords:: The <<submitRecord,submitRecord attribute>> contains
+information about whether this change has been or can be submitted.
 
 submitRecords:: The <<submitRecord,submitRecord attribute>> contains
 information about whether this change has been or can be submitted.
@@ -90,14 +102,29 @@
 
 revision:: Git commit for this patchset.
 
+parents:: List of parent revisions.
+
 ref:: Git reference pointing at the revision.  This reference is
 available through the Gerrit Code Review server's Git interface
 for the containing change.
 
 uploader:: Uploader of the patch set in <<account,account attribute>>.
 
+author:: Author of this patchset in <<account,account attribute>>.
+
+createdOn:: Time in seconds since the UNIX epoch when this patchset
+was created.
+
 approvals:: The <<approval,approval attribute>> granted.
 
+comments:: All inline comments for this patchset in <<patchsetcomment,patchsetComment attributes>>.
+
+files:: All changed files in this patchset in <<patch,patch attributes>>.
+
+sizeInsertions:: Size information of insertions of this patchset.
+
+sizeDeletions:: Size information of deletions of this patchset.
+
 [[approval]]
 approval
 --------
@@ -123,10 +150,10 @@
 
 newRev:: The new value the ref was updated to.
 
-project:: Project path in Gerrit.
-
 refName:: Ref name within project.
 
+project:: Project path in Gerrit.
+
 [[queryLimit]]
 queryLimit
 ----------
@@ -178,6 +205,71 @@
 
 by:: The <<account,account>> that applied the label.
 
+[[dependency]]
+dependency
+----------
+Information about a change or patchset dependency.
+
+id:: Change identifier.
+
+number:: Change number.
+
+revision:: Patchset revision.
+
+ref:: Ref name.
+
+isCurrentPatchSet:: If the revision is the current patchset of the change.
+
+[[message]]
+message
+-------
+Comment added on a change by a reviewer.
+
+timestamp:: Time in seconds since the UNIX epoch when this comment
+was added.
+
+reviewer:: The <<account,account>> that added the comment.
+
+message:: The comment text.
+
+[[patchsetcomment]]
+patchsetComment
+---------------
+Comment added inline on a patchset by a reviewer.
+
+file:: The name of the file on which the comment was added.
+
+line:: The line number at which the comment was added.
+
+reviewer:: The <<account,account>> that added the comment.
+
+message:: The comment text.
+
+[[patch]]
+patch
+-----
+Information about a patch on a file.
+
+file:: The name of the file.
+
+type:: The type of change.
+
+  ADDED;; The file is being created/introduced by this patch.
+
+  MODIFIED;; The file already exists, and has updated content.
+
+  DELETED;; The file existed, but is being removed by this patch.
+
+  RENAMED;; The file is renamed.
+
+  COPIED;; The file is copied from another file.
+
+  REWRITE;; Sufficient amount of content changed to claim the file was rewritten.
+
+insertions:: number of insertions of this patch.
+
+deletions::  number of deletions of this patch.
+
 SEE ALSO
 --------
 
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index a3015e1..4e87270 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -54,6 +54,10 @@
 
   $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 
+Then ensure that the execute bit is set on the hook script:
+
+  $ chmod u+x .git/hooks/commit-msg
+
 For more details, see link:cmd-hook-commit-msg.html[commit-msg].
 
 Change Upload
diff --git a/ReleaseNotes/ReleaseNotes-2.0.21.txt b/ReleaseNotes/ReleaseNotes-2.0.21.txt
index 7dd9ef4..47ba654 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.21.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.21.txt
@@ -201,7 +201,7 @@
 Gerrit no longer forges the From header in notification emails.
 To enable the prior forging behavior, set `sendemail.from`
 to `USER` in gerrit.config.  For more details see
-[http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#sendemail.from sendemail.from]
+link:http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#sendemail.from[sendemail.from]
 
 Bug Fixes
 ---------
diff --git a/ReleaseNotes/ReleaseNotes-2.0.24.txt b/ReleaseNotes/ReleaseNotes-2.0.24.txt
index 9481a3a..7e0a617 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.24.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.24.txt
@@ -67,7 +67,7 @@
 * issue 300    Support SMTP over SSL/TLS
 +
 Encrypted SMTP is now supported natively within Gerrit, see
-[http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#sendemail.smtpEncryption sendemail.smtpEncryption]
+link:http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#sendemail.smtpEncryption[sendemail.smtpEncryption]
 
 Bug Fixes
 ---------
diff --git a/gerrit-antlr/pom.xml b/gerrit-antlr/pom.xml
index 34cb46f..314d8d2 100644
--- a/gerrit-antlr/pom.xml
+++ b/gerrit-antlr/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-antlr</artifactId>
diff --git a/gerrit-cache-h2/pom.xml b/gerrit-cache-h2/pom.xml
index 4d4303c..1a26b21 100644
--- a/gerrit-cache-h2/pom.xml
+++ b/gerrit-cache-h2/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-cache-h2</artifactId>
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index 9b3fe5f..1db3549 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-common</artifactId>
@@ -61,6 +61,11 @@
       <artifactId>gerrit-patch-jgit</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
index 3d438f2..05e86be 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
@@ -42,6 +42,7 @@
   protected Account.Id account;
   protected List<PatchSetApproval> approvals;
   protected boolean canRemove;
+  private Set<String> votable;
 
   private transient Set<String> approved;
   private transient Set<String> rejected;
@@ -112,6 +113,13 @@
     rejected.add(label);
   }
 
+  public void votable(String label) {
+    if (votable == null) {
+      votable = new HashSet<String>();
+    }
+    votable.add(label);
+  }
+
   public boolean isApproved(String label) {
     return approved != null && approved.contains(label);
   }
@@ -119,4 +127,8 @@
   public boolean isRejected(String label) {
     return rejected != null && rejected.contains(label);
   }
+
+  public boolean canVote(String label) {
+    return votable != null && votable.contains(label);
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
index 74d1962..529283b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -40,6 +41,8 @@
   protected List<PatchSet> patchSets;
   protected List<ApprovalDetail> approvals;
   protected List<SubmitRecord> submitRecords;
+  protected Project.SubmitType submitType;
+  protected SubmitTypeRecord submitTypeRecord;
   protected boolean canSubmit;
   protected List<ChangeMessage> messages;
   protected PatchSet.Id currentPatchSetId;
@@ -187,6 +190,14 @@
     return submitRecords;
   }
 
+  public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
+    this.submitTypeRecord = submitTypeRecord;
+  }
+
+  public SubmitTypeRecord getSubmitTypeRecord() {
+    return submitTypeRecord;
+  }
+
   public boolean isCurrentPatchSet(final PatchSetDetail detail) {
     return currentPatchSetId != null
         && detail.getPatchSet().getId().equals(currentPatchSetId);
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 3c5c688..f383739 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
@@ -19,6 +19,7 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -33,6 +34,7 @@
   protected List<PermissionRange> labels;
   protected List<ApprovalDetail> approvals;
   protected List<SubmitRecord> submitRecords;
+  protected SubmitTypeRecord submitTypeRecord;
   protected List<PatchSetApproval> given;
   protected boolean canSubmit;
 
@@ -61,6 +63,14 @@
     return submitRecords;
   }
 
+  public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
+    this.submitTypeRecord = submitTypeRecord;
+  }
+
+  public SubmitTypeRecord getSubmitTypeRecord() {
+    return submitTypeRecord;
+  }
+
   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 5cb7787..29d9431 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
@@ -33,6 +33,7 @@
   public static final String PUSH_TAG = "pushTag";
   public static final String READ = "read";
   public static final String REBASE = "rebase";
+  public static final String REMOVE_REVIEWER = "removeReviewer";
   public static final String SUBMIT = "submit";
 
   private static final List<String> NAMES_LC;
@@ -52,6 +53,7 @@
     NAMES_LC.add(PUSH_TAG.toLowerCase());
     NAMES_LC.add(LABEL.toLowerCase());
     NAMES_LC.add(REBASE.toLowerCase());
+    NAMES_LC.add(REMOVE_REVIEWER.toLowerCase());
     NAMES_LC.add(SUBMIT.toLowerCase());
 
     labelIndex = NAMES_LC.indexOf(Permission.LABEL);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
new file mode 100644
index 0000000..4eea798
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import com.google.gerrit.reviewdb.client.Project;
+
+/**
+ * Describes the submit type for a change.
+ */
+public class SubmitTypeRecord {
+  public static enum Status {
+    /** The type was computed successfully */
+    OK,
+
+    /** An internal server error occurred preventing computation.
+     * <p>
+     * Additional detail may be available in {@link SubmitTypeRecord#errorMessage}
+     */
+    RULE_ERROR
+  }
+
+  public static SubmitTypeRecord OK(Project.SubmitType type) {
+    SubmitTypeRecord r = new SubmitTypeRecord();
+    r.status = Status.OK;
+    r.type = type;
+    return r;
+  }
+
+  public Status status;
+  public Project.SubmitType type;
+  public String errorMessage;
+
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(status);
+    if (status == Status.RULE_ERROR && errorMessage != null) {
+      sb.append('(').append(errorMessage).append(")");
+    }
+    if (type != null) {
+      sb.append('[');
+      sb.append(type.name());
+      sb.append(']');
+    }
+    return sb.toString();
+  }
+}
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index ff672d5..1080ed1 100644
--- a/gerrit-extension-api/pom.xml
+++ b/gerrit-extension-api/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-extension-api</artifactId>
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 01b93a6..7437174 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-gwtdebug</artifactId>
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index b3291d1..f31d6f1 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-gwtui</artifactId>
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 87c01cc..331ceb6 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
@@ -108,4 +108,6 @@
 
   String projectAccessError();
   String projectAccessProposeForReviewHint();
+
+  String userCannotVoteToolTip();
 }
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 596b3ad..ef23c8d 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
@@ -92,3 +92,5 @@
 
 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'.
+
+userCannotVoteToolTip = User cannot vote in this category
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 9cbf5cd..18a92ba 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
@@ -37,12 +37,12 @@
   String approvalhint();
   String approvalrole();
   String approvalscore();
+  String notVotable();
   String blockHeader();
   String bottomheader();
   String cAPPROVAL();
   String cID();
   String cLastUpdate();
-  String cPROJECT();
   String cSUBJECT();
   String changeComments();
   String changeInfoBlock();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
index 084ea6f..2810931 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
@@ -125,7 +125,7 @@
       agreementGroup.add(whyAgreement);
 
       choices.add(new InlineHyperlink(Util.C.newAgreement(),
-          PageLinks.SETTINGS_NEW_AGREEMENT + "," + nextToken));
+          PageLinks.SETTINGS_NEW_AGREEMENT));
       choices
           .add(new InlineHyperlink(Util.C.welcomeAgreementLater(), nextToken));
       formBody.add(agreementGroup);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 3ff3f43..91202c5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -113,6 +113,7 @@
 	pushTag, \
 	read, \
 	rebase, \
+	removeReviewer, \
 	submit
 abandon = Abandon
 create = Create Reference
@@ -125,6 +126,7 @@
 pushTag = Push Annotated Tag
 read = Read
 rebase = Rebase
+removeReviewer = Remove Reviewer
 submit = Submit
 
 refErrorEmpty = Reference must be supplied
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 73036ff..3891edd 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
@@ -397,6 +397,10 @@
 
     for (String labelName : columns) {
       fmt.setStyleName(row, col, Gerrit.RESOURCES.css().approvalscore());
+      if (!ad.canVote(labelName)) {
+        fmt.addStyleName(row, col, Gerrit.RESOURCES.css().notVotable());
+        fmt.getElement(row, col).setTitle(Gerrit.C.userCannotVoteToolTip());
+      }
 
       if (ad.isRejected(labelName)) {
         table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index d42992f..20d3fea 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -95,6 +95,7 @@
   String changeInfoBlockUploaded();
   String changeInfoBlockUpdated();
   String changeInfoBlockStatus();
+  String changeInfoBlockSubmitType();
   String changePermalink();
   String changeInfoBlockCanMerge();
   String changeInfoBlockCanMergeYes();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 8ceb74c..56f6219 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -72,6 +72,7 @@
 changeInfoBlockUploaded = Uploaded
 changeInfoBlockUpdated = Updated
 changeInfoBlockStatus = Status
+changeInfoBlockSubmitType = Submit Type
 changePermalink = Permalink
 changeInfoBlockCanMerge = Can Merge
 changeInfoBlockCanMergeYes = Yes
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 c8b2a66..361f997 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
@@ -15,8 +15,10 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.common.data.AccountInfoCache;
+import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
@@ -36,8 +38,8 @@
   }
 
   public void display(Change chg, Boolean starred, PatchSetInfo info,
-      final AccountInfoCache acc) {
-    infoBlock.display(chg, acc);
+      final AccountInfoCache acc, SubmitTypeRecord submitTypeRecord) {
+    infoBlock.display(chg, acc, submitTypeRecord);
     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 3ffacc3..c42ca8d 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
@@ -21,6 +21,7 @@
 import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.ProjectLink;
 import com.google.gerrit.common.data.AccountInfoCache;
+import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.user.client.ui.Composite;
@@ -36,9 +37,10 @@
   private static final int R_TOPIC = 4;
   private static final int R_UPLOADED = 5;
   private static final int R_UPDATED = 6;
-  private static final int R_STATUS = 7;
-  private static final int R_MERGE_TEST = 8;
-  private static final int R_CNT = 9;
+  private static final int R_SUBMIT_TYPE = 7;
+  private static final int R_STATUS = 8;
+  private static final int R_MERGE_TEST = 9;
+  private static final int R_CNT = 10;
 
   private final Grid table;
 
@@ -59,6 +61,7 @@
     initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
     initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
     initRow(R_STATUS, Util.C.changeInfoBlockStatus());
+    initRow(R_SUBMIT_TYPE, Util.C.changeInfoBlockSubmitType());
     if (Gerrit.getConfig().testChangeMerge()) {
       initRow(R_MERGE_TEST, Util.C.changeInfoBlockCanMerge());
     }
@@ -77,7 +80,8 @@
     table.getCellFormatter().addStyleName(row, 0, Gerrit.RESOURCES.css().header());
   }
 
-  public void display(final Change chg, final AccountInfoCache acc) {
+  public void display(final Change chg, final AccountInfoCache acc,
+      SubmitTypeRecord submitTypeRecord) {
     final Branch.NameKey dst = chg.getDest();
 
     CopyableLabel changeIdLabel =
@@ -94,6 +98,14 @@
     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()));
+    String submitType;
+    if (submitTypeRecord.status == SubmitTypeRecord.Status.OK) {
+      submitType = com.google.gerrit.client.admin.Util
+              .toLongString(submitTypeRecord.type);
+    } else {
+      submitType = submitTypeRecord.status.name();
+    }
+    table.setText(R_SUBMIT_TYPE, 1, submitType);
     final Change.Status status = chg.getStatus();
     if (Gerrit.getConfig().testChangeMerge()) {
       if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
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 4dd6b03..5868be4 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
@@ -282,7 +282,7 @@
     descriptionBlock.display(detail.getChange(),
         detail.isStarred(),
         detail.getCurrentPatchSetDetail().getInfo(),
-        detail.getAccounts());
+        detail.getAccounts(), detail.getSubmitTypeRecord());
     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 ef4ef52..7c6a36d 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
@@ -183,8 +183,6 @@
     }
     fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().cID());
     fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT());
-    fmt.addStyleName(row, C_PROJECT, Gerrit.RESOURCES.css().cPROJECT());
-    fmt.addStyleName(row, C_BRANCH, Gerrit.RESOURCES.css().cPROJECT());
     fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate());
     for (int i = BASE_COLUMNS; i < columns; i++) {
       fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
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 0dd0b0f..aa91af0 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
@@ -141,8 +141,6 @@
     }
     fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().cID());
     fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT());
-    fmt.addStyleName(row, C_PROJECT, Gerrit.RESOURCES.css().cPROJECT());
-    fmt.addStyleName(row, C_BRANCH, Gerrit.RESOURCES.css().cPROJECT());
     fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate());
     for (int i = BASE_COLUMNS; i < columns; i++) {
       fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
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 4705aad..f9d036a 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
@@ -274,7 +274,8 @@
   private void display(final PatchSetPublishDetail r) {
     setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
         patchSetId.get()));
-    descBlock.display(r.getChange(), null, r.getPatchSetInfo(), r.getAccounts());
+    descBlock.display(r.getChange(), null, r.getPatchSetInfo(), r.getAccounts(),
+        r.getSubmitTypeRecord());
 
     if (r.getChange().getStatus().isOpen()) {
       initApprovals(r, approvalPanel);
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 7512d8c..0d7ffc0 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
@@ -37,7 +37,7 @@
 @def black #000000;
 @def white #ffffff;
 @def norm-font  Arial Unicode MS, Arial, sans-serif;
-@def mono-font 'Lucida Console', 'Lucida Sans Typewriter', Monaco, monospace;
+@def mono-font  monospace;
 
 @eval backgroundColor com.google.gerrit.client.Gerrit.getTheme().backgroundColor;
 @eval topMenuColor com.google.gerrit.client.Gerrit.getTheme().topMenuColor;
@@ -523,10 +523,6 @@
   white-space: nowrap;
 }
 
-.changeTable .cPROJECT {
-  white-space: nowrap;
-}
-
 .changeTable .cLastUpdate {
   white-space: nowrap;
   text-align: right;
@@ -871,6 +867,9 @@
 .infoTable td.approvalscore {
   text-align: center;
 }
+.infoTable td.notVotable {
+  background: #F5F5F5;
+}
 .infoTable td.negscore {
   color: red;
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
index 0cbe194..cfc3d8e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
@@ -120,7 +120,6 @@
 
     final FlexCellFormatter fmt = table.getFlexCellFormatter();
     fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
-    fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().cPROJECT());
     fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
 
     populate(row, k);
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index ceacb66..65641aa 100644
--- a/gerrit-httpd/pom.xml
+++ b/gerrit-httpd/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-httpd</artifactId>
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
index cf443e7..da21c51 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/change/DeprecatedChangeQueryServlet.java
@@ -73,6 +73,7 @@
     p.setIncludeCurrentPatchSet(get(req, "current-patch-set", false));
     p.setIncludePatchSets(get(req, "patch-sets", false));
     p.setIncludeApprovals(get(req, "all-approvals", false));
+    p.setIncludeFiles(get(req, "files", false));
     p.setOutput(rsp.getOutputStream(), format);
     p.query(get(req, "q", "status:open"));
   }
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 6660a3d..5553468 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.ChangeInfo;
+import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.httpd.rpc.Handler;
@@ -71,6 +72,8 @@
 
   private final ApprovalTypes approvalTypes;
   private final ChangeControl.Factory changeControlFactory;
+  private final ChangeControl.GenericFactory changeControlGenericFactory;
+  private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final FunctionState.Factory functionState;
   private final PatchSetDetailFactory.Factory patchSetDetail;
   private final AccountInfoCacheFactory aic;
@@ -93,6 +96,8 @@
       final PatchSetDetailFactory.Factory patchSetDetail, final ReviewDb db,
       final GitRepositoryManager repoManager,
       final ChangeControl.Factory changeControlFactory,
+      final ChangeControl.GenericFactory changeControlGenericFactory,
+      final IdentifiedUser.GenericFactory identifiedUserFactory,
       final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
       final AnonymousUser anonymousUser,
       final MergeOp.Factory opFactory,
@@ -104,6 +109,8 @@
     this.db = db;
     this.repoManager = repoManager;
     this.changeControlFactory = changeControlFactory;
+    this.changeControlGenericFactory = changeControlGenericFactory;
+    this.identifiedUserFactory = identifiedUserFactory;
     this.anonymousUser = anonymousUser;
     this.aic = accountInfoCacheFactory.create();
 
@@ -161,6 +168,8 @@
     }
     detail.setSubmitRecords(submitRecords);
 
+    detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patch));
+
     patchsetsById = new HashMap<PatchSet.Id, PatchSet>();
     loadPatchSets();
     loadMessages();
@@ -241,6 +250,14 @@
       if (ca.getPatchSetId().equals(psId)) {
         d.add(ca);
       }
+      final ChangeControl chgCtrl =
+          changeControlGenericFactory.controlFor(detail.getChange(),
+              identifiedUserFactory.create(ca.getAccountId()));
+      for (PermissionRange pr : chgCtrl.getLabelRanges()) {
+        if (pr.getMin() != 0 || pr.getMax() != 0) {
+          d.votable(pr.getLabel());
+        }
+      }
     }
 
     final Account.Id owner = detail.getChange().getOwner();
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 50baf97..8a4320f 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
@@ -55,6 +55,8 @@
   private final ReviewDb db;
   private final FunctionState.Factory functionState;
   private final ChangeControl.Factory changeControlFactory;
+  private final ChangeControl.GenericFactory changeControlGenericFactory;
+  private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final ApprovalTypes approvalTypes;
   private final AccountInfoCacheFactory aic;
   private final IdentifiedUser user;
@@ -71,12 +73,16 @@
       final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
       final FunctionState.Factory functionState,
       final ChangeControl.Factory changeControlFactory,
+      final ChangeControl.GenericFactory changeControlGenericFactory,
+      final IdentifiedUser.GenericFactory identifiedUserFactory,
       final ApprovalTypes approvalTypes,
       final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
     this.infoFactory = infoFactory;
     this.db = db;
     this.functionState = functionState;
     this.changeControlFactory = changeControlFactory;
+    this.changeControlGenericFactory = changeControlGenericFactory;
+    this.identifiedUserFactory = identifiedUserFactory;
     this.approvalTypes = approvalTypes;
     this.aic = accountInfoCacheFactory.create();
     this.user = user;
@@ -180,6 +186,8 @@
       detail.setSubmitRecords(submitRecords);
     }
 
+    detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patchSet));
+
     detail.setLabels(allowed);
     detail.setGiven(given);
     loadApprovals(detail, control);
@@ -190,7 +198,7 @@
   }
 
   private void loadApprovals(final PatchSetPublishDetail detail,
-      final ChangeControl control) throws OrmException {
+      final ChangeControl control) throws OrmException, NoSuchChangeException {
     final PatchSet.Id psId = detail.getChange().currentPatchSetId();
     final Change.Id changeId = patchSetId.getParentKey();
     final List<PatchSetApproval> allApprovals =
@@ -221,6 +229,14 @@
       if (ca.getPatchSetId().equals(psId)) {
         d.add(ca);
       }
+      final ChangeControl chgCtrl =
+          changeControlGenericFactory.controlFor(detail.getChange(),
+              identifiedUserFactory.create(ca.getAccountId()));
+      for (PermissionRange pr : chgCtrl.getLabelRanges()) {
+        if (pr.getMin() != 0 || pr.getMax() != 0) {
+          d.votable(pr.getLabel());
+        }
+      }
     }
 
     final Account.Id owner = detail.getChange().getOwner();
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index e700351..4f33ce8 100644
--- a/gerrit-launcher/pom.xml
+++ b/gerrit-launcher/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index bb2d763..d174af1 100644
--- a/gerrit-main/pom.xml
+++ b/gerrit-main/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-main</artifactId>
diff --git a/gerrit-openid/pom.xml b/gerrit-openid/pom.xml
index fa4ab95..bb8b93d 100644
--- a/gerrit-openid/pom.xml
+++ b/gerrit-openid/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-openid</artifactId>
diff --git a/gerrit-package-plugins/pom.xml b/gerrit-package-plugins/pom.xml
index d3ddfc4..e90bb8e 100644
--- a/gerrit-package-plugins/pom.xml
+++ b/gerrit-package-plugins/pom.xml
@@ -22,7 +22,7 @@
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-package-plugins</artifactId>
   <packaging>war</packaging>
-  <version>2.5-SNAPSHOT</version>
+  <version>2.6-SNAPSHOT</version>
 
   <name>Gerrit Code Review - Package Plugins</name>
   <url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index f1a8b3e..78ca61c 100644
--- a/gerrit-patch-commonsnet/pom.xml
+++ b/gerrit-patch-commonsnet/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index 65223fb..7e50af8 100644
--- a/gerrit-patch-jgit/pom.xml
+++ b/gerrit-patch-jgit/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-patch-jgit</artifactId>
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index a015219..5e25350 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-pgm</artifactId>
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 95b8487f..c0f0c4b 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
@@ -140,8 +140,9 @@
         throw (Die) why;
       }
 
-      final StringBuilder buf = new StringBuilder();
+      final StringBuilder buf = new StringBuilder(ce.getMessage());
       while (why != null) {
+        buf.append("\n");
         buf.append(why.getMessage());
         why = why.getCause();
         if (why != null) {
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
index 3857ebd..f06bd32 100755
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
@@ -126,7 +126,7 @@
 GERRIT_INSTALL_TRACE_FILE=etc/gerrit.config
 
 ##################################################
-# No git in PATH? Needed for gerrit.confg parsing
+# No git in PATH? Needed for gerrit.config parsing
 ##################################################
 if type git >/dev/null 2>&1 ; then
   : OK
@@ -140,7 +140,17 @@
 ##################################################
 if test -z "$GERRIT_SITE" ; then
   GERRIT_SITE_1=`dirname "$0"`
+
+  ##################################################
+  # As last resort assume we started the script from
+  # the bin directory of the Gerrit installation
+  ##################################################
+  if [ "$GERRIT_SITE_1" = "." ]; then
+      GERRIT_SITE_1=`pwd`
+  fi
+
   GERRIT_SITE_1=`dirname "$GERRIT_SITE_1"`
+
   if test -f "${GERRIT_SITE_1}/${GERRIT_INSTALL_TRACE_FILE}" ; then 
     GERRIT_SITE=${GERRIT_SITE_1} 
   fi
@@ -241,7 +251,9 @@
 fi
 
 if test -z "$JAVA" ; then
-  echo >&2 "Cannot find a JRE or JDK. Please set JAVA_HOME to a >=1.6 JRE"
+  echo >&2 "Cannot find a JRE or JDK. Please set JAVA_HOME or"
+  echo >&2 "container.javaHome in $GERRIT_SITE/etc/gerrit.config"
+  echo >&2 "to a >=1.6 JRE"
   exit 1
 fi
 
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
index b99267c..f4c5808 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
@@ -19,6 +19,6 @@
   sha1 = 6327a5f7a3dc45e0fd735adb5d08c5a74c05c20c
 
 [library "mysqlDriver"]
-  name = MySQL Connector/J 5.1.10
-  url = http://repo2.maven.org/maven2/mysql/mysql-connector-java/5.1.10/mysql-connector-java-5.1.10.jar
-  sha1 = b83574124f1a00d6f70d56ba64aa52b8e1588e6d
+  name = MySQL Connector/J 5.1.21
+  url = http://repo2.maven.org/maven2/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar
+  sha1 = 7abbd19fc2e2d5b92c0895af8520f7fa30266be9
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 84f6f7b..0f95693 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/gerrit-plugin-api/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-plugin-api</artifactId>
@@ -66,8 +66,8 @@
               <exclude>com.google.gerrit:gerrit-patch-commonsnet</exclude>
               <exclude>com.google.gerrit:gerrit-patch-jgit</exclude>
               <exclude>com.google.gerrit:gerrit-util-ssl</exclude>
-              <exclude>com.google.gerrit:juniversalchardet</exclude>
 
+              <exclude>com.googlecode.juniversalchardet:juniversalchardet</exclude>
               <exclude>com.googlecode.prolog-cafe:PrologCafe</exclude>
               <exclude>org.slf4j:slf4j-log4j12</exclude>
               <exclude>log4j:log4j</exclude>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index dd1794b..71feabc 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-plugin-archetype</artifactId>
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index 9354274..4db1056 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-prettify</artifactId>
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index f9fb49e..96599b3 100644
--- a/gerrit-reviewdb/pom.xml
+++ b/gerrit-reviewdb/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-reviewdb</artifactId>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
index 00e282b..6ddd6d2 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
@@ -84,7 +84,7 @@
     /** Path was copied from {@link Patch#getSourceFileName()}. */
     COPIED('C'),
 
-    /** Sufficient amount of content changed to claim the file was written. */
+    /** Sufficient amount of content changed to claim the file was rewritten. */
     REWRITE('W');
 
     private final char code;
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index af18173..2795424 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-server</artifactId>
@@ -162,7 +162,7 @@
     </dependency>
 
     <dependency>
-      <groupId>com.google.gerrit</groupId>
+      <groupId>com.googlecode.juniversalchardet</groupId>
       <artifactId>juniversalchardet</artifactId>
     </dependency>
 
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 37be293..7299d07 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
@@ -41,6 +41,7 @@
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.events.ReviewerAddedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.project.ProjectCache;
@@ -117,6 +118,9 @@
     /** Filename of the ref updated hook. */
     private final File refUpdatedHook;
 
+    /** Filename of the reviewer added hook. */
+    private final File reviewerAddedHook;
+
     /** Filename of the cla signed hook. */
     private final File claSignedHook;
 
@@ -173,6 +177,7 @@
         changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath());
         changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath());
         refUpdatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdatedHook", "ref-updated")).getPath());
+        reviewerAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "reviewerAddedHook", "reviewer-added")).getPath());
         claSignedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "claSignedHook", "cla-signed")).getPath());
     }
 
@@ -391,6 +396,25 @@
       runHook(refName.getParentKey(), refUpdatedHook, args);
     }
 
+    public void doReviewerAddedHook(final Change change, final Account account,
+        final PatchSet patchSet, final ReviewDb db) throws OrmException {
+      final ReviewerAddedEvent event = new ReviewerAddedEvent();
+
+      event.change = eventFactory.asChangeAttribute(change);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.reviewer = eventFactory.asAccountAttribute(account);
+      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, "--reviewer", getDisplayName(account));
+
+      runHook(change.getProject(), reviewerAddedHook, args);
+    }
+
     public void doClaSignupHook(Account account, ContributorAgreement cla) {
       if (account != null) {
         final List<String> args = new ArrayList<String>();
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 134057d..028afe9 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
@@ -50,7 +50,7 @@
    * Fire the Draft Published Hook.
    *
    * @param change The change itself.
-   * @param patchSet The Patchset that was created.
+   * @param patchSet The Patchset that was published.
    * @throws OrmException
    */
   public void doDraftPublishedHook(Change change, PatchSet patchSet,
@@ -125,5 +125,15 @@
   public void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId,
       ObjectId newId, Account account);
 
+  /**
+   * Fire the Reviewer Added Hook
+   *
+   * @param change The change itself.
+   * @param patchSet The patchset that the reviewer was added on.
+   * @param account The gerrit user who was added as reviewer.
+   */
+  public void doReviewerAddedHook(Change change, Account account,
+      PatchSet patchSet, ReviewDb db) throws OrmException;
+
   public void doClaSignupHook(Account account, ContributorAgreement cla);
 }
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 f30f5ea..8794901 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
@@ -81,6 +81,11 @@
   }
 
   @Override
+  public void doReviewerAddedHook(Change change, Account account, PatchSet patchSet,
+      ReviewDb db) {
+  }
+
+  @Override
   public void removeChangeListener(ChangeListener listener) {
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 2265bc2..7cf6268 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -242,6 +242,7 @@
     final List<String> groupBases;
     final SearchScope groupScope;
     final ParameterizedString groupPattern;
+    final ParameterizedString groupName;
     final List<LdapQuery> groupMemberQueryList;
 
     LdapSchema(final DirContext ctx) {
@@ -257,6 +258,7 @@
       groupBases = LdapRealm.optionalList(config, "groupBase");
       groupScope = LdapRealm.scope(config, "groupScope");
       groupPattern = LdapRealm.paramString(config, "groupPattern", type.groupPattern());
+      groupName = LdapRealm.paramString(config, "groupName", type.groupName());
       final String groupMemberPattern =
           LdapRealm.optdef(config, "groupMemberPattern", type.groupMemberPattern());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index 5c30e5c..6b54f6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -41,6 +41,7 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -81,11 +82,11 @@
     return uuid.get().startsWith(LDAP_UUID);
   }
 
-  private static GroupReference groupReference(LdapQuery.Result res)
-      throws NamingException {
+  private static GroupReference groupReference(ParameterizedString p,
+      LdapQuery.Result res) throws NamingException {
     return new GroupReference(
         new AccountGroup.UUID(LDAP_UUID + res.getDN()),
-        LDAP_NAME + cnFor(res.getDN()));
+        LDAP_NAME + LdapRealm.apply(p, res));
   }
 
   private static String cnFor(String dn) {
@@ -203,13 +204,14 @@
         LdapSchema schema = helper.getSchema(ctx);
         ParameterizedString filter = ParameterizedString.asis(
             schema.groupPattern.replace(GROUPNAME, name).toString());
-        Set<String> returnAttrs = Collections.<String>emptySet();
+        Set<String> returnAttrs =
+            new HashSet<String>(schema.groupName.getParameterNames());
         Map<String, String> params = Collections.emptyMap();
         for (String groupBase : schema.groupBases) {
           LdapQuery query = new LdapQuery(
               groupBase, schema.groupScope, filter, returnAttrs);
           for (LdapQuery.Result res : query.query(ctx, params)) {
-            out.add(groupReference(res));
+            out.add(groupReference(schema.groupName, res));
           }
         }
       } finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 72eb7ec..4ddbc67 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -167,7 +167,7 @@
     return !readOnlyAccountFields.contains(field);
   }
 
-  private static String apply(ParameterizedString p, LdapQuery.Result m)
+  static String apply(ParameterizedString p, LdapQuery.Result m)
       throws NamingException {
     if (p == null) {
       return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
index 3c4a54b..fd5bb30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
@@ -36,6 +36,8 @@
 
   abstract String groupMemberPattern();
 
+  abstract String groupName();
+
   abstract String accountFullName();
 
   abstract String accountEmailAddress();
@@ -58,6 +60,11 @@
     }
 
     @Override
+    String groupName() {
+      return "cn";
+    }
+
+    @Override
     String accountFullName() {
       return "displayName";
     }
@@ -101,6 +108,11 @@
     }
 
     @Override
+    String groupName() {
+      return "cn";
+    }
+
+    @Override
     String groupMemberPattern() {
       return null; // Active Directory uses memberOf in the account
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index 6068c50..cc54054 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -228,7 +228,7 @@
    */
   public static long getTimeUnit(final String valueString, long defaultValue,
       TimeUnit wantUnit) {
-    Matcher m = Pattern.compile("^([1-9][0-9]*)\\s*(.*)$").matcher(valueString);
+    Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$").matcher(valueString);
     if (!m.matches()) {
       return defaultValue;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java
index bb95dca..bf96a22 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/MasterNodeStartup.java
@@ -14,35 +14,60 @@
 
 package com.google.gerrit.server.config;
 
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.git.ReloadSubmitQueueOp;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
-import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.lib.Config;
+
+import java.util.concurrent.ScheduledFuture;
 
 /** Configuration for a master node in a cluster of servers. */
 public class MasterNodeStartup extends LifecycleModule {
   @Override
   public void configure() {
-    listener().to(OnStart.class);
+    listener().to(Lifecycle.class);
   }
 
-  static class OnStart implements LifecycleListener {
+  @Singleton
+  static class Lifecycle implements LifecycleListener {
+    private static final int INITIAL_DELAY_S = 15;
+
     private final ReloadSubmitQueueOp.Factory submit;
+    private final long delay;
+    private volatile ScheduledFuture<?> handle;
 
     @Inject
-    OnStart(final ReloadSubmitQueueOp.Factory submit) {
+    Lifecycle(ReloadSubmitQueueOp.Factory submit,
+        @GerritServerConfig Config config) {
       this.submit = submit;
+      this.delay = ConfigUtil.getTimeUnit(config,
+          "changeMerge", null, "checkFrequency",
+          SECONDS.convert(5, MINUTES), SECONDS);
     }
 
     @Override
     public void start() {
-      submit.create().start(15, TimeUnit.SECONDS);
+      if (delay > 0) {
+        handle = submit.create()
+            .startWithFixedDelay(INITIAL_DELAY_S, delay, SECONDS);
+      } else {
+        handle = submit.create().start(INITIAL_DELAY_S, SECONDS);
+      }
     }
 
     @Override
     public void stop() {
+      ScheduledFuture<?> f = handle;
+      if (f != null) {
+        handle = null;
+        f.cancel(true);
+      }
     }
   }
 }
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 ab3ca56..8b25e30 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
@@ -21,6 +21,7 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetAncestor;
@@ -34,6 +35,8 @@
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListEntry;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
@@ -46,6 +49,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.List;
 
@@ -59,17 +63,20 @@
   private final ApprovalTypes approvalTypes;
   private final PatchListCache patchListCache;
   private final SchemaFactory<ReviewDb> schema;
+  private final PatchSetInfoFactory psInfoFactory;
 
   @Inject
   EventFactory(AccountCache accountCache,
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       ApprovalTypes approvalTypes,
+      final PatchSetInfoFactory psif,
       PatchListCache patchListCache, SchemaFactory<ReviewDb> schema) {
     this.accountCache = accountCache;
     this.urlProvider = urlProvider;
     this.approvalTypes = approvalTypes;
     this.patchListCache = patchListCache;
     this.schema = schema;
+    this.psInfoFactory = psif;
   }
 
   /**
@@ -286,6 +293,8 @@
         PatchAttribute p = new PatchAttribute();
         p.file = patch.getNewName();
         p.type = patch.getChangeType();
+        p.deletions -= patch.getDeletions();
+        p.insertions = patch.getInsertions();
         patchSetAttribute.files.add(p);
       }
     } catch (PatchListNotAvailableException e) {
@@ -323,6 +332,7 @@
     p.ref = patchSet.getRefName();
     p.uploader = asAccountAttribute(patchSet.getUploader());
     p.createdOn = patchSet.getCreatedOn().getTime() / 1000L;
+    final PatchSet.Id pId = patchSet.getId();
     try {
       final ReviewDb db = schema.open();
       try {
@@ -331,11 +341,28 @@
             patchSet.getId())) {
           p.parents.add(a.getAncestorRevision().get());
         }
+
+        p.author = asAccountAttribute(//
+            psInfoFactory.get(db, pId).getAuthor().getAccount());
+
+        Change change = db.changes().get(pId.getParentKey());
+        List<Patch> list =
+            patchListCache.get(change, patchSet).toPatchList(pId);
+        for (Patch pe : list) {
+          if (!Patch.COMMIT_MSG.equals(pe.getFileName())) {
+            p.sizeDeletions -= pe.getDeletions();
+            p.sizeInsertions += pe.getInsertions();
+          }
+        }
       } finally {
         db.close();
       }
     } catch (OrmException e) {
       log.error("Cannot load patch set data for " + patchSet.getId(), e);
+    } catch (PatchSetInfoNotAvailableException e) {
+      log.error(String.format("Cannot get authorEmail for %s.", pId), e);
+    } catch (PatchListNotAvailableException e) {
+      log.error(String.format("Cannot get size information for %s.", pId), e);
     }
     return p;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchAttribute.java
index 3802fdd..0d2b054 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchAttribute.java
@@ -19,4 +19,6 @@
 public class PatchAttribute {
     public String file;
     public ChangeType type;
+    public int insertions;
+    public int deletions;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java
index f726ce3..1123e5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetAttribute.java
@@ -23,8 +23,11 @@
   public String ref;
   public AccountAttribute uploader;
   public Long createdOn;
+  public AccountAttribute author;
 
   public List<ApprovalAttribute> approvals;
   public List<PatchSetCommentAttribute> comments;
   public List<PatchAttribute> files;
+  public int sizeInsertions;
+  public int sizeDeletions;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java
index 1c5e7d7..ecf2b9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/QueryStats.java
@@ -18,4 +18,5 @@
   public final String type = "stats";
   public int rowCount;
   public long runTimeMilliseconds;
+  public String resumeSortKey;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
new file mode 100644
index 0000000..a881d8d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerAddedEvent.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 ReviewerAddedEvent extends ChangeEvent {
+    public final String type = "reviewer-added";
+    public ChangeAttribute change;
+    public PatchSetAttribute patchSet;
+    public AccountAttribute reviewer;
+}
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 c0e00aa..79e35be 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
@@ -92,7 +92,8 @@
           banCommitNotes.set(commitToBan, createNoteContent(reason, inserter));
         }
         inserter.flush();
-        NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(repo);
+        NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(repo,
+            inserter);
         NoteMap newlyCreated =
             notesBranchUtil.commitNewNotes(banCommitNotes, REF_REJECT_COMMITS,
                 createPersonIdent(), buildCommitMessage(commitsToBan, reason));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
index 6d1b155..eab27f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
@@ -89,6 +89,7 @@
         });
       }
 
+      @SuppressWarnings("unused")
       @Provides
       public PerThreadRequestScope.Scoper provideScoper(
           final PerThreadRequestScope.Propagator propagator) {
@@ -243,6 +244,7 @@
       dest = d;
     }
 
+    @Override
     public void run() {
       unschedule(this);
       mergeImpl(dest);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
new file mode 100644
index 0000000..7973562
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
@@ -0,0 +1,375 @@
+// 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 static com.google.gerrit.server.git.MergeUtil.commit;
+import static com.google.gerrit.server.git.MergeUtil.hasMissingDependencies;
+import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
+import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
+import static com.google.gerrit.server.git.MergeUtil.newThreeWayMerger;
+
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.client.Account;
+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.PatchSetAncestor;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.merge.Merger;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CherryPick extends SubmitStrategy {
+  private static final Logger log = LoggerFactory.getLogger(CherryPick.class);
+
+  private static final ApprovalCategory.Id CRVW = //
+      new ApprovalCategory.Id("CRVW");
+  private static final ApprovalCategory.Id VRIF = //
+      new ApprovalCategory.Id("VRIF");
+  private static final FooterKey REVIEWED_ON = new FooterKey("Reviewed-on");
+  private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
+
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final Provider<String> urlProvider;
+  private final ApprovalTypes approvalTypes;
+  private final GitReferenceUpdated replication;
+  private final Map<Change.Id, CodeReviewCommit> newCommits;
+
+  CherryPick(final SubmitStrategy.Arguments args,
+      final PatchSetInfoFactory patchSetInfoFactory,
+      final Provider<String> urlProvider, final ApprovalTypes approvalTypes,
+      final GitReferenceUpdated replication) {
+    super(args);
+
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.urlProvider = urlProvider;
+    this.approvalTypes = approvalTypes;
+    this.replication = replication;
+    this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
+  }
+
+  @Override
+  protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    CodeReviewCommit newMergeTip = mergeTip;
+    while (!toMerge.isEmpty()) {
+      final CodeReviewCommit n = toMerge.remove(0);
+      final ThreeWayMerger m =
+          newThreeWayMerger(args.repo, args.inserter, args.useContentMerge);
+      try {
+        if (newMergeTip == null) {
+          // The branch is unborn. Take a fast-forward resolution to
+          // create the branch.
+          //
+          newMergeTip = n;
+          n.statusCode = CommitMergeStatus.CLEAN_MERGE;
+
+        } else if (n.getParentCount() == 0) {
+          // Refuse to merge a root commit into an existing branch,
+          // we cannot obtain a delta for the cherry-pick to apply.
+          //
+          n.statusCode = CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT;
+
+        } else if (n.getParentCount() == 1) {
+          // If there is only one parent, a cherry-pick can be done by
+          // taking the delta relative to that one parent and redoing
+          // that on the current merge tip.
+          //
+          m.setBase(n.getParent(0));
+          if (m.merge(newMergeTip, n)) {
+            newMergeTip = writeCherryPickCommit(m, newMergeTip, n);
+
+          } else {
+            n.statusCode = CommitMergeStatus.PATH_CONFLICT;
+          }
+
+        } else {
+          // There are multiple parents, so this is a merge commit. We
+          // don't want to cherry-pick the merge as clients can't easily
+          // rebase their history with that merge present and replaced
+          // by an equivalent merge with a different first parent. So
+          // instead behave as though MERGE_IF_NECESSARY was configured.
+          //
+          if (!hasMissingDependencies(args.mergeSorter, n)) {
+            if (args.rw.isMergedInto(newMergeTip, n)) {
+              newMergeTip = n;
+            } else {
+              newMergeTip =
+                  mergeOneCommit(args.db, args.identifiedUserFactory,
+                      args.myIdent, args.repo, args.rw, args.inserter,
+                      args.canMergeFlag, args.useContentMerge, args.destBranch,
+                      newMergeTip, n);
+           }
+            final PatchSetApproval submitApproval =
+                markCleanMerges(args.db, args.rw, args.canMergeFlag,
+                    newMergeTip, args.alreadyAccepted);
+            setRefLogIdent(submitApproval);
+
+          } else {
+            // One or more dependencies were not met. The status was
+            // already marked on the commit so we have nothing further
+            // to perform at this time.
+            //
+          }
+        }
+
+      } catch (IOException e) {
+        throw new MergeException("Cannot merge " + n.name(), e);
+      } catch (OrmException e) {
+        throw new MergeException("Cannot merge " + n.name(), e);
+      }
+    }
+    return newMergeTip;
+  }
+
+  @Override
+  public Map<Change.Id, CodeReviewCommit> getNewCommits() {
+    return newCommits;
+  }
+
+  private CodeReviewCommit writeCherryPickCommit(final Merger m,
+      final CodeReviewCommit mergeTip, final CodeReviewCommit n)
+      throws IOException, OrmException {
+    args.rw.parseBody(n);
+
+    final List<FooterLine> footers = n.getFooterLines();
+    final StringBuilder msgbuf = new StringBuilder();
+    msgbuf.append(n.getFullMessage());
+
+    if (msgbuf.length() == 0) {
+      // WTF, an empty commit message?
+      msgbuf.append("<no commit message provided>");
+    }
+    if (msgbuf.charAt(msgbuf.length() - 1) != '\n') {
+      // Missing a trailing LF? Correct it (perhaps the editor was broken).
+      msgbuf.append('\n');
+    }
+    if (footers.isEmpty()) {
+      // Doesn't end in a "Signed-off-by: ..." style line? Add another line
+      // break to start a new paragraph for the reviewed-by tag lines.
+      //
+      msgbuf.append('\n');
+    }
+
+    if (!contains(footers, CHANGE_ID, n.change.getKey().get())) {
+      msgbuf.append(CHANGE_ID.getName());
+      msgbuf.append(": ");
+      msgbuf.append(n.change.getKey().get());
+      msgbuf.append('\n');
+    }
+
+    final String siteUrl = urlProvider.get();
+    if (siteUrl != null) {
+      final String url = siteUrl + n.patchsetId.getParentKey().get();
+      if (!contains(footers, REVIEWED_ON, url)) {
+        msgbuf.append(REVIEWED_ON.getName());
+        msgbuf.append(": ");
+        msgbuf.append(url);
+        msgbuf.append('\n');
+      }
+    }
+
+    PatchSetApproval submitAudit = null;
+    List<PatchSetApproval> approvalList = null;
+    try {
+      approvalList =
+          args.db.patchSetApprovals().byPatchSet(n.patchsetId).toList();
+      Collections.sort(approvalList, new Comparator<PatchSetApproval>() {
+        @Override
+        public int compare(final PatchSetApproval a, final PatchSetApproval b) {
+          return a.getGranted().compareTo(b.getGranted());
+        }
+      });
+
+      for (final PatchSetApproval a : approvalList) {
+        if (a.getValue() <= 0) {
+          // Negative votes aren't counted.
+          continue;
+        }
+
+        if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
+          // Submit is treated specially, below (becomes committer)
+          //
+          if (submitAudit == null
+              || a.getGranted().compareTo(submitAudit.getGranted()) > 0) {
+            submitAudit = a;
+          }
+          continue;
+        }
+
+        final Account acc =
+            args.identifiedUserFactory.create(a.getAccountId()).getAccount();
+        final StringBuilder identbuf = new StringBuilder();
+        if (acc.getFullName() != null && acc.getFullName().length() > 0) {
+          if (identbuf.length() > 0) {
+            identbuf.append(' ');
+          }
+          identbuf.append(acc.getFullName());
+        }
+        if (acc.getPreferredEmail() != null
+            && acc.getPreferredEmail().length() > 0) {
+          if (isSignedOffBy(footers, acc.getPreferredEmail())) {
+            continue;
+          }
+          if (identbuf.length() > 0) {
+            identbuf.append(' ');
+          }
+          identbuf.append('<');
+          identbuf.append(acc.getPreferredEmail());
+          identbuf.append('>');
+        }
+        if (identbuf.length() == 0) {
+          // Nothing reasonable to describe them by? Ignore them.
+          continue;
+        }
+
+        final String tag;
+        if (CRVW.equals(a.getCategoryId())) {
+          tag = "Reviewed-by";
+        } else if (VRIF.equals(a.getCategoryId())) {
+          tag = "Tested-by";
+        } else {
+          final ApprovalType at = approvalTypes.byId(a.getCategoryId());
+          if (at == null) {
+            // A deprecated/deleted approval type, ignore it.
+            continue;
+          }
+          tag = at.getCategory().getName().replace(' ', '-');
+        }
+
+        if (!contains(footers, new FooterKey(tag), identbuf.toString())) {
+          msgbuf.append(tag);
+          msgbuf.append(": ");
+          msgbuf.append(identbuf);
+          msgbuf.append('\n');
+        }
+      }
+    } catch (OrmException e) {
+      log.error("Can't read approval records for " + n.patchsetId, e);
+    }
+
+    final CommitBuilder mergeCommit = new CommitBuilder();
+    mergeCommit.setTreeId(m.getResultTreeId());
+    mergeCommit.setParentId(mergeTip);
+    mergeCommit.setAuthor(n.getAuthorIdent());
+    mergeCommit.setCommitter(toCommitterIdent(submitAudit));
+    mergeCommit.setMessage(msgbuf.toString());
+
+    final ObjectId id = commit(args.inserter, mergeCommit);
+    final CodeReviewCommit newCommit =
+        (CodeReviewCommit) args.rw.parseCommit(id);
+
+    n.change.nextPatchSetId();
+
+    final PatchSet ps = new PatchSet(n.change.currPatchSetId());
+    ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+    ps.setUploader(submitAudit.getAccountId());
+    ps.setRevision(new RevId(id.getName()));
+    insertAncestors(ps.getId(), newCommit);
+    args.db.patchSets().insert(Collections.singleton(ps));
+
+    n.change.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
+    args.db.changes().update(Collections.singletonList(n.change));
+
+    if (approvalList != null) {
+      for (PatchSetApproval a : approvalList) {
+        args.db.patchSetApprovals().insert(
+            Collections.singleton(new PatchSetApproval(ps.getId(), a)));
+      }
+    }
+
+    final RefUpdate ru = args.repo.updateRef(ps.getRefName());
+    ru.setExpectedOldObjectId(ObjectId.zeroId());
+    ru.setNewObjectId(newCommit);
+    ru.disableRefLog();
+    if (ru.update(args.rw) != RefUpdate.Result.NEW) {
+      throw new IOException(String.format("Failed to create ref %s in %s: %s",
+          ps.getRefName(), n.change.getDest().getParentKey().get(),
+          ru.getResult()));
+    }
+    replication.fire(n.change.getProject(), ru.getName());
+
+    newCommit.copyFrom(n);
+    newCommit.statusCode = CommitMergeStatus.CLEAN_PICK;
+    newCommits.put(newCommit.patchsetId.getParentKey(), newCommit);
+    setRefLogIdent(submitAudit);
+    return newCommit;
+  }
+
+  private void insertAncestors(PatchSet.Id id, RevCommit src)
+      throws OrmException {
+    final int cnt = src.getParentCount();
+    List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
+    for (int p = 0; p < cnt; p++) {
+      PatchSetAncestor a;
+
+      a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
+      a.setAncestorRevision(new RevId(src.getParent(p).getId().name()));
+      toInsert.add(a);
+    }
+    args.db.patchSetAncestors().insert(toInsert);
+  }
+
+  private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
+    for (final FooterLine line : footers) {
+      if (line.matches(key) && val.equals(line.getValue())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean isSignedOffBy(List<FooterLine> footers, String email) {
+    for (final FooterLine line : footers) {
+      if (line.matches(FooterKey.SIGNED_OFF_BY)
+          && email.equals(line.getEmailAddress())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private PersonIdent toCommitterIdent(final PatchSetApproval audit) {
+    if (audit != null) {
+      return args.identifiedUserFactory.create(audit.getAccountId())
+          .newCommitterIdent(audit.getGranted(), args.myIdent.getTimeZone());
+    }
+    return args.myIdent;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
index 8790351..512944f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
@@ -39,6 +39,9 @@
   REVISION_GONE(""),
 
   /** */
+  NO_SUBMIT_TYPE(""),
+
+  /** */
   CRISS_CROSS_MERGE("Your change requires a recursive merge to resolve.\n"
                   + "\n"
                   + "Please merge (or rebase) the change locally and upload the resolution for review."),
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 9a0fe17..50412e3 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
@@ -114,7 +114,8 @@
         message.append("* ").append(c.getShortMessage()).append("\n");
       }
 
-      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db,
+          inserter);
       notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
           message.toString());
       inserter.flush();
@@ -149,7 +150,8 @@
         notes.set(commitId, createNoteContent(c, commitId));
       }
 
-      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db,
+          inserter);
       notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
           commitMessage);
       inserter.flush();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/DefaultQueueOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/DefaultQueueOp.java
index 82d0493..cff69c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/DefaultQueueOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/DefaultQueueOp.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.git;
 
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 public abstract class DefaultQueueOp implements Runnable {
@@ -23,7 +24,13 @@
     workQueue = wq;
   }
 
-  public void start(final int delay, final TimeUnit unit) {
-    workQueue.getDefaultQueue().schedule(this, delay, unit);
+  public ScheduledFuture<?> start(long delay, TimeUnit unit) {
+    return workQueue.getDefaultQueue().schedule(this, delay, unit);
+  }
+
+  public ScheduledFuture<?> startWithFixedDelay(long initialDelay, long delay,
+      TimeUnit unit) {
+    return workQueue.getDefaultQueue()
+        .scheduleWithFixedDelay(this, initialDelay, delay, unit);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java
new file mode 100644
index 0000000..ab02bf9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java
@@ -0,0 +1,55 @@
+// 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 static com.google.gerrit.server.git.MergeUtil.getFirstFastForward;
+import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
+import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
+
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+
+import java.util.List;
+
+public class FastForwardOnly extends SubmitStrategy {
+
+  FastForwardOnly(final SubmitStrategy.Arguments args) {
+    super(args);
+  }
+
+  @Override
+  protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    reduceToMinimalMerge(args.mergeSorter, toMerge);
+    final CodeReviewCommit newMergeTip =
+        getFirstFastForward(mergeTip, args.rw, toMerge);
+
+    while (!toMerge.isEmpty()) {
+      final CodeReviewCommit n = toMerge.remove(0);
+      n.statusCode = CommitMergeStatus.NOT_FAST_FORWARD;
+    }
+
+    final PatchSetApproval submitApproval =
+        markCleanMerges(args.db, args.rw, args.canMergeFlag, newMergeTip,
+            args.alreadyAccepted);
+    setRefLogIdent(submitApproval);
+
+    return newMergeTip;
+  }
+
+  @Override
+  public boolean retryOnLockFailure() {
+    return false;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java
new file mode 100644
index 0000000..2840d3c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java
@@ -0,0 +1,52 @@
+// 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 static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
+import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
+import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
+
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+
+import java.util.List;
+
+public class MergeAlways extends SubmitStrategy {
+
+  MergeAlways(final SubmitStrategy.Arguments args) {
+    super(args);
+  }
+
+  @Override
+  protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    reduceToMinimalMerge(args.mergeSorter, toMerge);
+
+    CodeReviewCommit newMergeTip = mergeTip;
+    while (!toMerge.isEmpty()) {
+      newMergeTip =
+          mergeOneCommit(args.db, args.identifiedUserFactory, args.myIdent,
+              args.repo, args.rw, args.inserter, args.canMergeFlag,
+              args.useContentMerge, args.destBranch, mergeTip,
+              toMerge.remove(0));
+    }
+
+    final PatchSetApproval submitApproval =
+        markCleanMerges(args.db, args.rw, args.canMergeFlag, newMergeTip,
+            args.alreadyAccepted);
+    setRefLogIdent(submitApproval);
+
+    return newMergeTip;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java
new file mode 100644
index 0000000..34811b8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java
@@ -0,0 +1,55 @@
+// 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 static com.google.gerrit.server.git.MergeUtil.getFirstFastForward;
+import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
+import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
+import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
+
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+
+import java.util.List;
+
+public class MergeIfNecessary extends SubmitStrategy {
+
+  MergeIfNecessary(final SubmitStrategy.Arguments args) {
+    super(args);
+  }
+
+  @Override
+  protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    reduceToMinimalMerge(args.mergeSorter, toMerge);
+    CodeReviewCommit newMergeTip =
+        getFirstFastForward(mergeTip, args.rw, toMerge);
+
+    // For every other commit do a pair-wise merge.
+    while (!toMerge.isEmpty()) {
+      newMergeTip =
+          mergeOneCommit(args.db, args.identifiedUserFactory, args.myIdent,
+              args.repo, args.rw, args.inserter, args.canMergeFlag,
+              args.useContentMerge, args.destBranch, mergeTip,
+              toMerge.remove(0));
+    }
+
+    final PatchSetApproval submitApproval =
+        markCleanMerges(args.db, args.rw, args.canMergeFlag, newMergeTip,
+            args.alreadyAccepted);
+    setRefLogIdent(submitApproval);
+
+    return newMergeTip;
+  }
+}
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 26fd2ea..c80b3e6 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
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.git;
 
+import static com.google.gerrit.server.git.MergeUtil.computeMergeCommitAuthor;
+import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 
@@ -21,22 +23,22 @@
 import com.google.gerrit.common.data.ApprovalType;
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.ApprovalCategory;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
 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.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.mail.MergeFailSender;
 import com.google.gerrit.server.mail.MergedSender;
@@ -54,14 +56,11 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -69,11 +68,6 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.MergeStrategy;
-import org.eclipse.jgit.merge.Merger;
-import org.eclipse.jgit.merge.ThreeWayMerger;
-import org.eclipse.jgit.revwalk.FooterKey;
-import org.eclipse.jgit.revwalk.FooterLine;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevSort;
@@ -82,21 +76,16 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.sql.Timestamp;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
+import java.util.Map.Entry;
 import java.util.Set;
-import java.util.TimeZone;
-
-import javax.annotation.Nullable;
 
 /**
  * Merges changes in submission order into a single branch.
@@ -118,19 +107,13 @@
   }
 
   private static final Logger log = LoggerFactory.getLogger(MergeOp.class);
-  private static final String R_HEADS_MASTER =
-      Constants.R_HEADS + Constants.MASTER;
-  private static final ApprovalCategory.Id CRVW =
-      new ApprovalCategory.Id("CRVW");
-  private static final ApprovalCategory.Id VRIF =
-      new ApprovalCategory.Id("VRIF");
-  private static final FooterKey REVIEWED_ON = new FooterKey("Reviewed-on");
-  private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
 
   /** Amount of time to wait between submit and checking for missing deps. */
   private static final long DEPENDENCY_DELAY =
       MILLISECONDS.convert(15, MINUTES);
 
+  private static final Random random = new Random();
+
   private final GitRepositoryManager repoManager;
   private final SchemaFactory<ReviewDb> schemaFactory;
   private final ProjectCache projectCache;
@@ -138,7 +121,6 @@
   private final GitReferenceUpdated replication;
   private final MergedSender.Factory mergedSenderFactory;
   private final MergeFailSender.Factory mergeFailSenderFactory;
-  private final Provider<String> urlProvider;
   private final ApprovalTypes approvalTypes;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
@@ -148,23 +130,23 @@
   private final PersonIdent myIdent;
   private final Branch.NameKey destBranch;
   private Project destProject;
-  private final List<CodeReviewCommit> toMerge;
-  private List<Change> submitted;
+  private final Map<SubmitType, List<CodeReviewCommit>> toMerge;
+  private final List<CodeReviewCommit> potentiallyStillSubmittable;
   private final Map<Change.Id, CodeReviewCommit> commits;
   private ReviewDb db;
   private Repository repo;
   private RevWalk rw;
-  private RevFlag CAN_MERGE;
+  private RevFlag canMergeFlag;
   private CodeReviewCommit branchTip;
   private CodeReviewCommit mergeTip;
-  private Set<RevCommit> alreadyAccepted;
-  private RefUpdate branchUpdate;
   private ObjectInserter inserter;
+  private PersonIdent refLogIdent;
 
   private final ChangeHooks hooks;
   private final AccountCache accountCache;
   private final TagCache tagCache;
   private final CreateCodeReviewNotes.Factory codeReviewNotesFactory;
+  private final SubmitStrategyFactory submitStrategyFactory;
   private final SubmoduleOp.Factory subOpFactory;
   private final WorkQueue workQueue;
   private final RequestScopePropagator requestScopePropagator;
@@ -174,7 +156,6 @@
       final ProjectCache pc, final FunctionState.Factory fs,
       final GitReferenceUpdated rq, final MergedSender.Factory msf,
       final MergeFailSender.Factory mfsf,
-      @CanonicalWebUrl @Nullable final Provider<String> cwu,
       final ApprovalTypes approvalTypes, final PatchSetInfoFactory psif,
       final IdentifiedUser.GenericFactory iuf,
       final ChangeControl.GenericFactory changeControlFactory,
@@ -182,6 +163,7 @@
       final MergeQueue mergeQueue, @Assisted final Branch.NameKey branch,
       final ChangeHooks hooks, final AccountCache accountCache,
       final TagCache tagCache, final CreateCodeReviewNotes.Factory crnf,
+      final SubmitStrategyFactory submitStrategyFactory,
       final SubmoduleOp.Factory subOpFactory,
       final WorkQueue workQueue,
       final RequestScopePropagator requestScopePropagator) {
@@ -192,7 +174,6 @@
     replication = rq;
     mergedSenderFactory = msf;
     mergeFailSenderFactory = mfsf;
-    urlProvider = cwu;
     this.approvalTypes = approvalTypes;
     patchSetInfoFactory = psif;
     identifiedUserFactory = iuf;
@@ -202,12 +183,14 @@
     this.accountCache = accountCache;
     this.tagCache = tagCache;
     codeReviewNotesFactory = crnf;
+    this.submitStrategyFactory = submitStrategyFactory;
     this.subOpFactory = subOpFactory;
     this.workQueue = workQueue;
     this.requestScopePropagator = requestScopePropagator;
     this.myIdent = myIdent;
     destBranch = branch;
-    toMerge = new ArrayList<CodeReviewCommit>();
+    toMerge = new HashMap<SubmitType, List<CodeReviewCommit>>();
+    potentiallyStillSubmittable = new ArrayList<CodeReviewCommit>();
     commits = new HashMap<Change.Id, CodeReviewCommit>();
   }
 
@@ -216,8 +199,6 @@
       setDestProject();
       openRepository();
       final Ref destBranchRef = repo.getRef(destBranch.get());
-      submitted = new ArrayList<Change>();
-      submitted.add(change);
 
       // Test mergeability of the change if the last merged sha1
       // in the branch is different from the last sha1
@@ -227,7 +208,11 @@
           || (destBranchRef != null && !destBranchRef.getObjectId().getName()
               .equals(change.getLastSha1MergeTested().get()))) {
         openSchema();
-        preMerge();
+        openBranch();
+        validateChangeList(Collections.singletonList(change));
+        final Entry<SubmitType, List<CodeReviewCommit>> entry =
+            toMerge.entrySet().iterator().next();
+        preMerge(createStrategy(entry.getKey()), entry.getValue());
 
         // update sha1 tested merge.
         if (destBranchRef != null) {
@@ -277,11 +262,53 @@
     try {
       openSchema();
       openRepository();
-      submitted = db.changes().submitted(destBranch).toList();
-      preMerge();
-      updateBranch();
-      updateChangeStatus();
-      updateSubscriptions();
+      openBranch();
+      final Map<SubmitType, List<Change>> toSubmit =
+          validateChangeList(db.changes().submitted(destBranch).toList());
+
+      final Map<SubmitType, List<CodeReviewCommit>> toMergeNextTurn =
+          new HashMap<SubmitType, List<CodeReviewCommit>>();
+      final List<CodeReviewCommit> potentiallyStillSubmittableOnNextRun =
+          new ArrayList<CodeReviewCommit>();
+      while (!toMerge.isEmpty()) {
+        toMergeNextTurn.clear();
+        for (final Entry<SubmitType, List<CodeReviewCommit>> e : toMerge.entrySet()) {
+          final SubmitType submitType = e.getKey();
+          final RefUpdate branchUpdate = openBranch();
+          final SubmitStrategy strategy = createStrategy(submitType);
+          preMerge(strategy, e.getValue());
+          updateBranch(strategy, branchUpdate);
+          updateChangeStatus(toSubmit.get(submitType));
+          updateSubscriptions(toSubmit.get(submitType));
+
+          for (final Iterator<CodeReviewCommit> it =
+              potentiallyStillSubmittable.iterator(); it.hasNext();) {
+            final CodeReviewCommit commit = it.next();
+            if (containsMissingCommits(toMerge, commit)
+                || containsMissingCommits(toMergeNextTurn, commit)) {
+              // change has missing dependencies, but all commits which are
+              // missing are still attempted to be merged with another submit
+              // strategy, retry to merge this commit in the next turn
+              it.remove();
+              commit.statusCode = null;
+              commit.missing = null;
+              getList(submitType, toMergeNextTurn).add(commit);
+            }
+          }
+          potentiallyStillSubmittableOnNextRun.addAll(potentiallyStillSubmittable);
+          potentiallyStillSubmittable.clear();
+        }
+        toMerge.clear();
+        toMerge.putAll(toMergeNextTurn);
+      }
+
+      for (final CodeReviewCommit commit : potentiallyStillSubmittableOnNextRun) {
+        final Capable capable = isSubmitStillPossible(commit);
+        if (capable != Capable.OK) {
+          sendMergeFail(commit.change,
+              message(commit.change, capable.getMessage()), false);
+        }
+      }
     } catch (OrmException e) {
       throw new MergeException("Cannot query the database", e);
     } finally {
@@ -300,24 +327,66 @@
     }
   }
 
-  private void preMerge() throws MergeException, OrmException {
-    openBranch();
-    validateChangeList();
-    mergeTip = branchTip;
-    switch (destProject.getSubmitType()) {
-      case CHERRY_PICK:
-        cherryPickChanges();
-        break;
-
-      case FAST_FORWARD_ONLY:
-      case MERGE_ALWAYS:
-      case MERGE_IF_NECESSARY:
-      default:
-        reduceToMinimalMerge();
-        mergeTopics();
-        markCleanMerges();
-        break;
+  private boolean containsMissingCommits(
+      final Map<SubmitType, List<CodeReviewCommit>> map,
+      final CodeReviewCommit commit) {
+    if (!isSubmitForMissingCommitsStillPossible(commit)) {
+      return false;
     }
+
+    for (final CodeReviewCommit missingCommit : commit.missing) {
+      boolean found = false;
+      for (final List<CodeReviewCommit> list : map.values()) {
+        if (list.contains(missingCommit)) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean isSubmitForMissingCommitsStillPossible(final CodeReviewCommit commit) {
+    if (commit.missing == null || commit.missing.isEmpty()) {
+      return false;
+    }
+
+    for (CodeReviewCommit missingCommit : commit.missing) {
+      loadChangeInfo(missingCommit);
+
+      if (missingCommit.patchsetId == null) {
+        // The commit doesn't have a patch set, so it cannot be
+        // submitted to the branch.
+        //
+        return false;
+      }
+
+      if (!missingCommit.change.currentPatchSetId().equals(
+          missingCommit.patchsetId)) {
+        // If the missing commit is not the current patch set,
+        // the change must be rebased to use the proper parent.
+        //
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  private void preMerge(final SubmitStrategy strategy,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    mergeTip = strategy.run(branchTip, toMerge);
+    refLogIdent = strategy.getRefLogIdent();
+    commits.putAll(strategy.getNewCommits());
+  }
+
+  private SubmitStrategy createStrategy(final SubmitType submitType) throws MergeException {
+    return submitStrategyFactory.create(submitType, db, repo, rw, inserter,
+        canMergeFlag, getAlreadyAccepted(branchTip), destBranch,
+        destProject.isUseContentMerge());
   }
 
   private void openRepository() throws MergeException {
@@ -340,20 +409,17 @@
     };
     rw.sort(RevSort.TOPO);
     rw.sort(RevSort.COMMIT_TIME_DESC, true);
-    CAN_MERGE = rw.newFlag("CAN_MERGE");
+    canMergeFlag = rw.newFlag("CAN_MERGE");
 
     inserter = repo.newObjectInserter();
   }
 
-  private void openBranch() throws MergeException {
-    alreadyAccepted = new HashSet<RevCommit>();
-
+  private RefUpdate openBranch() throws MergeException {
     try {
-      branchUpdate = repo.updateRef(destBranch.get());
+      final RefUpdate branchUpdate = repo.updateRef(destBranch.get());
       if (branchUpdate.getOldObjectId() != null) {
         branchTip =
             (CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
-        alreadyAccepted.add(branchTip);
       } else {
         branchTip = null;
       }
@@ -373,6 +439,21 @@
             "Failed to check existence of destination branch", e);
       }
 
+      return branchUpdate;
+    } catch (IOException e) {
+      throw new MergeException("Cannot open branch", e);
+    }
+  }
+
+  private Set<RevCommit> getAlreadyAccepted(final CodeReviewCommit branchTip)
+      throws MergeException {
+    final Set<RevCommit> alreadyAccepted = new HashSet<RevCommit>();
+
+    if (branchTip != null) {
+      alreadyAccepted.add(branchTip);
+    }
+
+    try {
       for (final Ref r : repo.getAllRefs().values()) {
         if (r.getName().startsWith(Constants.R_HEADS)
             || r.getName().startsWith(Constants.R_TAGS)) {
@@ -384,11 +465,17 @@
         }
       }
     } catch (IOException e) {
-      throw new MergeException("Cannot open branch", e);
+      throw new MergeException("Failed to determine already accepted commits.", e);
     }
+
+    return alreadyAccepted;
   }
 
-  private void validateChangeList() throws MergeException {
+  private Map<SubmitType, List<Change>> validateChangeList(
+      final List<Change> submitted) throws MergeException {
+    final Map<SubmitType, List<Change>> toSubmit =
+        new HashMap<Project.SubmitType, List<Change>>();
+
     final Set<ObjectId> tips = new HashSet<ObjectId>();
     for (final Ref r : repo.getAllRefs().values()) {
       tips.add(r.getObjectId());
@@ -458,7 +545,7 @@
 
       if (branchTip != null) {
         // If this commit is already merged its a bug in the queuing code
-        // that we got back here. Just mark it complete and move on. Its
+        // that we got back here. Just mark it complete and move on. It's
         // merged and that is all that mattered to the requestor.
         //
         try {
@@ -471,578 +558,53 @@
         }
       }
 
-      commit.add(CAN_MERGE);
-      toMerge.add(commit);
+      final SubmitType submitType = getSubmitType(chg, ps);
+      if (submitType == null) {
+        commits.put(changeId,
+            CodeReviewCommit.error(CommitMergeStatus.NO_SUBMIT_TYPE));
+        continue;
+      }
+
+      commit.add(canMergeFlag);
+      getList(submitType, toMerge).add(commit);
+      getList(submitType, toSubmit).add(chg);
     }
+    return toSubmit;
   }
 
-  private void reduceToMinimalMerge() throws MergeException {
-    final Collection<CodeReviewCommit> heads;
+  private SubmitType getSubmitType(final Change change, final PatchSet ps) {
     try {
-      heads = new MergeSorter(rw, alreadyAccepted, CAN_MERGE).sort(toMerge);
-    } catch (IOException e) {
-      throw new MergeException("Branch head sorting failed", e);
-    }
-
-    toMerge.clear();
-    toMerge.addAll(heads);
-    Collections.sort(toMerge, new Comparator<CodeReviewCommit>() {
-      public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
-        return a.originalOrder - b.originalOrder;
+      final SubmitTypeRecord r =
+          changeControlFactory.controlFor(change,
+              identifiedUserFactory.create(change.getOwner()))
+              .getSubmitTypeRecord(db, ps);
+      if (r.status != SubmitTypeRecord.Status.OK) {
+        log.error("Failed to get submit type for " + change.getKey());
+        return null;
       }
-    });
-  }
-
-  private void mergeTopics() throws MergeException {
-    // Take the first fast-forward available, if any is available in the set.
-    //
-    if (destProject.getSubmitType() != Project.SubmitType.MERGE_ALWAYS) {
-      for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
-        try {
-          final CodeReviewCommit n = i.next();
-          if (mergeTip == null || rw.isMergedInto(mergeTip, n)) {
-            mergeTip = n;
-            i.remove();
-            break;
-          }
-        } catch (IOException e) {
-          throw new MergeException("Cannot fast-forward test during merge", e);
-        }
-      }
-    }
-
-    if (destProject.getSubmitType() == Project.SubmitType.FAST_FORWARD_ONLY) {
-      // If this project only permits fast-forwards, abort everything else.
-      //
-      while (!toMerge.isEmpty()) {
-        final CodeReviewCommit n = toMerge.remove(0);
-        n.statusCode = CommitMergeStatus.NOT_FAST_FORWARD;
-      }
-
-    } else {
-      // For every other commit do a pair-wise merge.
-      //
-      while (!toMerge.isEmpty()) {
-        mergeOneCommit(toMerge.remove(0));
-      }
+      return r.type;
+    } catch (NoSuchChangeException e) {
+      log.error("Failed to get submit type for " + change.getKey(), e);
+      return null;
     }
   }
 
-  private void mergeOneCommit(final CodeReviewCommit n) throws MergeException {
-    final ThreeWayMerger m = newThreeWayMerger();
-    try {
-      if (m.merge(new AnyObjectId[] {mergeTip, n})) {
-        writeMergeCommit(m.getResultTreeId(), n);
-
-      } else {
-        failed(n, CommitMergeStatus.PATH_CONFLICT);
-      }
-    } catch (IOException e) {
-      if (e.getMessage().startsWith("Multiple merge bases for")) {
-        try {
-          failed(n, CommitMergeStatus.CRISS_CROSS_MERGE);
-        } catch (IOException e2) {
-          throw new MergeException("Cannot merge " + n.name(), e);
-        }
-      } else {
-        throw new MergeException("Cannot merge " + n.name(), e);
-      }
+  private static <K, T> List<T> getList(final K key, final Map<K, List<T>> map) {
+    List<T> list = map.get(key);
+    if (list == null) {
+      list = new ArrayList<T>();
+      map.put(key, list);
     }
+    return list;
   }
 
-  private ThreeWayMerger newThreeWayMerger() {
-    ThreeWayMerger m;
-    if (destProject.isUseContentMerge()) {
-      // Settings for this project allow us to try and
-      // automatically resolve conflicts within files if needed.
-      // Use ResolveMerge and instruct to operate in core.
-      m = MergeStrategy.RESOLVE.newMerger(repo, true);
-    } else {
-      // No auto conflict resolving allowed. If any of the
-      // affected files was modified, merge will fail.
-      m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
-    }
-    m.setObjectInserter(new ObjectInserter.Filter() {
-      @Override
-      protected ObjectInserter delegate() {
-        return inserter;
-      }
-
-      @Override
-      public void flush() {
-      }
-
-      @Override
-      public void release() {
-      }
-    });
-    return m;
-  }
-
-  private CodeReviewCommit failed(final CodeReviewCommit n,
-      final CommitMergeStatus failure) throws MissingObjectException,
-      IncorrectObjectTypeException, IOException {
-    rw.reset();
-    rw.markStart(n);
-    rw.markUninteresting(mergeTip);
-    CodeReviewCommit failed;
-    while ((failed = (CodeReviewCommit) rw.next()) != null) {
-      failed.statusCode = failure;
-    }
-    return failed;
-  }
-
-  private void writeMergeCommit(ObjectId treeId, CodeReviewCommit n)
-      throws IOException, MissingObjectException, IncorrectObjectTypeException {
-    final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
-    rw.reset();
-    rw.markStart(n);
-    rw.markUninteresting(mergeTip);
-    for (final RevCommit c : rw) {
-      final CodeReviewCommit crc = (CodeReviewCommit) c;
-      if (crc.patchsetId != null) {
-        merged.add(crc);
-      }
-    }
-
-    final StringBuilder msgbuf = new StringBuilder();
-    if (merged.size() == 1) {
-      final CodeReviewCommit c = merged.get(0);
-      rw.parseBody(c);
-      msgbuf.append("Merge \"");
-      msgbuf.append(c.getShortMessage());
-      msgbuf.append("\"");
-
-    } else {
-      msgbuf.append("Merge changes ");
-      for (final Iterator<CodeReviewCommit> i = merged.iterator(); i.hasNext();) {
-        msgbuf.append(i.next().change.getKey().abbreviate());
-        if (i.hasNext()) {
-          msgbuf.append(',');
-        }
-      }
-    }
-
-    if (!R_HEADS_MASTER.equals(destBranch.get())) {
-      msgbuf.append(" into ");
-      msgbuf.append(destBranch.getShortName());
-    }
-
-    if (merged.size() > 1) {
-      msgbuf.append("\n\n* changes:\n");
-      for (final CodeReviewCommit c : merged) {
-        rw.parseBody(c);
-        msgbuf.append("  ");
-        msgbuf.append(c.getShortMessage());
-        msgbuf.append("\n");
-      }
-    }
-
-    PersonIdent authorIdent = computeAuthor(merged);
-
-    final CommitBuilder mergeCommit = new CommitBuilder();
-    mergeCommit.setTreeId(treeId);
-    mergeCommit.setParentIds(mergeTip, n);
-    mergeCommit.setAuthor(authorIdent);
-    mergeCommit.setCommitter(myIdent);
-    mergeCommit.setMessage(msgbuf.toString());
-
-    mergeTip = (CodeReviewCommit) rw.parseCommit(commit(mergeCommit));
-  }
-
-  private PersonIdent computeAuthor(
-      final List<CodeReviewCommit> codeReviewCommits) {
-    PatchSetApproval submitter = null;
-    for (final CodeReviewCommit c : codeReviewCommits) {
-      PatchSetApproval s = getSubmitter(db, c.patchsetId);
-      if (submitter == null
-          || (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
-        submitter = s;
-      }
-    }
-
-    // Try to use the submitter's identity for the merge commit author.
-    // If all of the commits being merged are created by the submitter,
-    // prefer the identity line they used in the commits rather than the
-    // preferred identity stored in the user account. This way the Git
-    // commit records are more consistent internally.
-    //
-    PersonIdent authorIdent;
-    if (submitter != null) {
-      IdentifiedUser who =
-          identifiedUserFactory.create(submitter.getAccountId());
-      Set<String> emails = new HashSet<String>();
-      for (RevCommit c : codeReviewCommits) {
-        try {
-          rw.parseBody(c);
-        } catch (IOException e) {
-          log.warn("Cannot parse commit " + c.name() + " in " + destBranch, e);
-          continue;
-        }
-        emails.add(c.getAuthorIdent().getEmailAddress());
-      }
-
-      final Timestamp dt = submitter.getGranted();
-      final TimeZone tz = myIdent.getTimeZone();
-      if (emails.size() == 1
-          && who.getEmailAddresses().contains(emails.iterator().next())) {
-        authorIdent =
-            new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
-      } else {
-        authorIdent = who.newCommitterIdent(dt, tz);
-      }
-    } else {
-      authorIdent = myIdent;
-    }
-    return authorIdent;
-  }
-
-  private void markCleanMerges() throws MergeException {
-    if (mergeTip == null) {
-      // If mergeTip is null here, branchTip was null, indicating a new branch
-      // at the start of the merge process. We also elected to merge nothing,
-      // probably due to missing dependencies. Nothing was cleanly merged.
-      //
+  private void updateBranch(final SubmitStrategy strategy,
+      final RefUpdate branchUpdate) throws MergeException {
+    if ((branchTip == null && mergeTip == null) || branchTip == mergeTip) {
+      // nothing to do
       return;
     }
 
-    try {
-      rw.reset();
-      rw.sort(RevSort.TOPO);
-      rw.sort(RevSort.REVERSE, true);
-      rw.markStart(mergeTip);
-      for (RevCommit c : alreadyAccepted) {
-        rw.markUninteresting(c);
-      }
-
-      CodeReviewCommit c;
-      while ((c = (CodeReviewCommit) rw.next()) != null) {
-        if (c.patchsetId != null) {
-          c.statusCode = CommitMergeStatus.CLEAN_MERGE;
-          if (branchUpdate.getRefLogIdent() == null) {
-            setRefLogIdent(getSubmitter(db, c.patchsetId));
-          }
-        }
-      }
-    } catch (IOException e) {
-      throw new MergeException("Cannot mark clean merges", e);
-    }
-  }
-
-  private void setRefLogIdent(final PatchSetApproval submitAudit) {
-    if (submitAudit != null) {
-      branchUpdate.setRefLogIdent(identifiedUserFactory.create(
-          submitAudit.getAccountId()).newRefLogIdent());
-    }
-  }
-
-  private void cherryPickChanges() throws MergeException, OrmException {
-    while (!toMerge.isEmpty()) {
-      final CodeReviewCommit n = toMerge.remove(0);
-      final ThreeWayMerger m = newThreeWayMerger();
-      try {
-        if (mergeTip == null) {
-          // The branch is unborn. Take a fast-forward resolution to
-          // create the branch.
-          //
-          mergeTip = n;
-          n.statusCode = CommitMergeStatus.CLEAN_MERGE;
-
-        } else if (n.getParentCount() == 0) {
-          // Refuse to merge a root commit into an existing branch,
-          // we cannot obtain a delta for the cherry-pick to apply.
-          //
-          n.statusCode = CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT;
-
-        } else if (n.getParentCount() == 1) {
-          // If there is only one parent, a cherry-pick can be done by
-          // taking the delta relative to that one parent and redoing
-          // that on the current merge tip.
-          //
-          m.setBase(n.getParent(0));
-          if (m.merge(mergeTip, n)) {
-            writeCherryPickCommit(m, n);
-
-          } else {
-            n.statusCode = CommitMergeStatus.PATH_CONFLICT;
-          }
-
-        } else {
-          // There are multiple parents, so this is a merge commit. We
-          // don't want to cherry-pick the merge as clients can't easily
-          // rebase their history with that merge present and replaced
-          // by an equivalent merge with a different first parent. So
-          // instead behave as though MERGE_IF_NECESSARY was configured.
-          //
-          if (hasDependenciesMet(n)) {
-            if (rw.isMergedInto(mergeTip, n)) {
-              mergeTip = n;
-            } else {
-              mergeOneCommit(n);
-            }
-            markCleanMerges();
-
-          } else {
-            // One or more dependencies were not met. The status was
-            // already marked on the commit so we have nothing further
-            // to perform at this time.
-            //
-          }
-        }
-
-      } catch (IOException e) {
-        throw new MergeException("Cannot merge " + n.name(), e);
-      }
-    }
-  }
-
-  private boolean hasDependenciesMet(final CodeReviewCommit n)
-      throws IOException {
-    // Oddly we can determine this by running the merge sorter and
-    // look for the one commit to come out as a result. This works
-    // as the merge sorter checks the dependency chain as part of
-    // its logic trying to find a minimal merge path.
-    //
-    return new MergeSorter(rw, alreadyAccepted, CAN_MERGE).sort(
-        Collections.singleton(n)).contains(n);
-  }
-
-  private void writeCherryPickCommit(final Merger m, final CodeReviewCommit n)
-      throws IOException, OrmException {
-    rw.parseBody(n);
-
-    final List<FooterLine> footers = n.getFooterLines();
-    final StringBuilder msgbuf = new StringBuilder();
-    msgbuf.append(n.getFullMessage());
-
-    if (msgbuf.length() == 0) {
-      // WTF, an empty commit message?
-      msgbuf.append("<no commit message provided>");
-    }
-    if (msgbuf.charAt(msgbuf.length() - 1) != '\n') {
-      // Missing a trailing LF? Correct it (perhaps the editor was broken).
-      msgbuf.append('\n');
-    }
-    if (footers.isEmpty()) {
-      // Doesn't end in a "Signed-off-by: ..." style line? Add another line
-      // break to start a new paragraph for the reviewed-by tag lines.
-      //
-      msgbuf.append('\n');
-    }
-
-    if (!contains(footers, CHANGE_ID, n.change.getKey().get())) {
-      msgbuf.append(CHANGE_ID.getName());
-      msgbuf.append(": ");
-      msgbuf.append(n.change.getKey().get());
-      msgbuf.append('\n');
-    }
-
-    final String siteUrl = urlProvider.get();
-    if (siteUrl != null) {
-      final String url = siteUrl + n.patchsetId.getParentKey().get();
-      if (!contains(footers, REVIEWED_ON, url)) {
-        msgbuf.append(REVIEWED_ON.getName());
-        msgbuf.append(": ");
-        msgbuf.append(url);
-        msgbuf.append('\n');
-      }
-    }
-
-    PatchSetApproval submitAudit = null;
-    List<PatchSetApproval> approvalList = null;
-    try {
-      approvalList =
-          db.patchSetApprovals().byPatchSet(n.patchsetId).toList();
-      Collections.sort(approvalList, new Comparator<PatchSetApproval>() {
-        public int compare(final PatchSetApproval a, final PatchSetApproval b) {
-          return a.getGranted().compareTo(b.getGranted());
-        }
-      });
-
-      for (final PatchSetApproval a : approvalList) {
-        if (a.getValue() <= 0) {
-          // Negative votes aren't counted.
-          continue;
-        }
-
-        if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
-          // Submit is treated specially, below (becomes committer)
-          //
-          if (submitAudit == null
-              || a.getGranted().compareTo(submitAudit.getGranted()) > 0) {
-            submitAudit = a;
-          }
-          continue;
-        }
-
-        final Account acc =
-            identifiedUserFactory.create(a.getAccountId()).getAccount();
-        final StringBuilder identbuf = new StringBuilder();
-        if (acc.getFullName() != null && acc.getFullName().length() > 0) {
-          if (identbuf.length() > 0) {
-            identbuf.append(' ');
-          }
-          identbuf.append(acc.getFullName());
-        }
-        if (acc.getPreferredEmail() != null
-            && acc.getPreferredEmail().length() > 0) {
-          if (isSignedOffBy(footers, acc.getPreferredEmail())) {
-            continue;
-          }
-          if (identbuf.length() > 0) {
-            identbuf.append(' ');
-          }
-          identbuf.append('<');
-          identbuf.append(acc.getPreferredEmail());
-          identbuf.append('>');
-        }
-        if (identbuf.length() == 0) {
-          // Nothing reasonable to describe them by? Ignore them.
-          continue;
-        }
-
-        final String tag;
-        if (CRVW.equals(a.getCategoryId())) {
-          tag = "Reviewed-by";
-        } else if (VRIF.equals(a.getCategoryId())) {
-          tag = "Tested-by";
-        } else {
-          final ApprovalType at =
-              approvalTypes.byId(a.getCategoryId());
-          if (at == null) {
-            // A deprecated/deleted approval type, ignore it.
-            continue;
-          }
-          tag = at.getCategory().getName().replace(' ', '-');
-        }
-
-        if (!contains(footers, new FooterKey(tag), identbuf.toString())) {
-          msgbuf.append(tag);
-          msgbuf.append(": ");
-          msgbuf.append(identbuf);
-          msgbuf.append('\n');
-        }
-      }
-    } catch (OrmException e) {
-      log.error("Can't read approval records for " + n.patchsetId, e);
-    }
-
-    final CommitBuilder mergeCommit = new CommitBuilder();
-    mergeCommit.setTreeId(m.getResultTreeId());
-    mergeCommit.setParentId(mergeTip);
-    mergeCommit.setAuthor(n.getAuthorIdent());
-    mergeCommit.setCommitter(toCommitterIdent(submitAudit));
-    mergeCommit.setMessage(msgbuf.toString());
-
-    final ObjectId id = commit(mergeCommit);
-    final CodeReviewCommit newCommit = (CodeReviewCommit) rw.parseCommit(id);
-    final Change oldChange = n.change;
-
-    n.change =
-        db.changes().atomicUpdate(n.change.getId(),
-            new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                change.nextPatchSetId();
-                return change;
-              }
-            });
-
-    final PatchSet ps = new PatchSet(n.change.currPatchSetId());
-    ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
-    ps.setUploader(submitAudit.getAccountId());
-    ps.setRevision(new RevId(id.getName()));
-    insertAncestors(ps.getId(), newCommit);
-    db.patchSets().insert(Collections.singleton(ps));
-
-    n.change =
-        db.changes().atomicUpdate(n.change.getId(),
-            new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                change.setCurrentPatchSet(patchSetInfoFactory.get(newCommit,
-                    ps.getId()));
-                return change;
-              }
-            });
-
-    this.submitted.remove(oldChange);
-    this.submitted.add(n.change);
-
-    if (approvalList != null) {
-      for (PatchSetApproval a : approvalList) {
-        db.patchSetApprovals().insert(
-            Collections.singleton(new PatchSetApproval(ps.getId(), a)));
-      }
-    }
-
-    final RefUpdate ru = repo.updateRef(ps.getRefName());
-    ru.setExpectedOldObjectId(ObjectId.zeroId());
-    ru.setNewObjectId(newCommit);
-    ru.disableRefLog();
-    if (ru.update(rw) != RefUpdate.Result.NEW) {
-      throw new IOException(String.format(
-          "Failed to create ref %s in %s: %s", ps.getRefName(),
-          n.change.getDest().getParentKey().get(), ru.getResult()));
-    }
-    replication.fire(n.change.getProject(), ru.getName());
-
-    newCommit.copyFrom(n);
-    newCommit.statusCode = CommitMergeStatus.CLEAN_PICK;
-    commits.put(newCommit.patchsetId.getParentKey(), newCommit);
-    mergeTip = newCommit;
-    setRefLogIdent(submitAudit);
-  }
-
-  private void insertAncestors(PatchSet.Id id, RevCommit src)
-      throws OrmException {
-    final int cnt = src.getParentCount();
-    List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
-    for (int p = 0; p < cnt; p++) {
-      PatchSetAncestor a;
-
-      a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
-      a.setAncestorRevision(new RevId(src.getParent(p).getId().name()));
-      toInsert.add(a);
-    }
-    db.patchSetAncestors().insert(toInsert);
-  }
-
-  private ObjectId commit(CommitBuilder mergeCommit)
-      throws IOException, UnsupportedEncodingException {
-    ObjectId id = inserter.insert(mergeCommit);
-    inserter.flush();
-    return id;
-  }
-
-  private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
-    for (final FooterLine line : footers) {
-      if (line.matches(key) && val.equals(line.getValue())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private boolean isSignedOffBy(List<FooterLine> footers, String email) {
-    for (final FooterLine line : footers) {
-      if (line.matches(FooterKey.SIGNED_OFF_BY)
-          && email.equals(line.getEmailAddress())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private PersonIdent toCommitterIdent(final PatchSetApproval audit) {
-    if (audit != null) {
-      return identifiedUserFactory.create(audit.getAccountId())
-          .newCommitterIdent(audit.getGranted(), myIdent.getTimeZone());
-    }
-    return myIdent;
-  }
-
-  private void updateBranch() throws MergeException {
     if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
       if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
         try {
@@ -1055,6 +617,7 @@
         }
       }
 
+      branchUpdate.setRefLogIdent(refLogIdent);
       branchUpdate.setForceUpdate(false);
       branchUpdate.setNewObjectId(mergeTip);
       branchUpdate.setRefLogMessage("merged", true);
@@ -1086,6 +649,16 @@
             hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
             break;
 
+          case LOCK_FAILURE:
+            String msg;
+            if (strategy.retryOnLockFailure()) {
+              mergeQueue.recheckAfter(destBranch, random.nextInt(1000),
+                  MILLISECONDS);
+              msg = "will retry";
+            } else {
+              msg = "will not retry";
+            }
+            throw new IOException(branchUpdate.getResult().name() + ", " + msg);
           default:
             throw new IOException(branchUpdate.getResult().name());
         }
@@ -1109,7 +682,7 @@
     return isMergeable;
   }
 
-  private void updateChangeStatus() {
+  private void updateChangeStatus(final List<Change> submitted) {
     List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
 
     for (final Change c : submitted) {
@@ -1151,10 +724,7 @@
         }
 
         case MISSING_DEPENDENCY: {
-          final Capable capable = isSubmitStillPossible(commit);
-          if (capable != Capable.OK) {
-            sendMergeFail(c, message(c, capable.getMessage()), false);
-          }
+          potentiallyStillSubmittable.add(commit);
           break;
         }
 
@@ -1167,7 +737,10 @@
     CreateCodeReviewNotes codeReviewNotes =
         codeReviewNotesFactory.create(db, repo);
     try {
-      codeReviewNotes.create(merged, computeAuthor(merged));
+      codeReviewNotes.create(
+          merged,
+          computeMergeCommitAuthor(db, identifiedUserFactory, myIdent, rw,
+              merged));
     } catch (CodeReviewNoteCreationException e) {
       log.error(e.getMessage());
     }
@@ -1175,7 +748,7 @@
         GitRepositoryManager.REFS_NOTES_REVIEW);
   }
 
-  private void updateSubscriptions() {
+  private void updateSubscriptions(final List<Change> submitted) {
     if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
       SubmoduleOp subOp =
           subOpFactory.create(destBranch, mergeTip, rw, repo, destProject,
@@ -1193,32 +766,7 @@
   private Capable isSubmitStillPossible(final CodeReviewCommit commit) {
     final Capable capable;
     final Change c = commit.change;
-    if (commit.missing == null) {
-      commit.missing = new ArrayList<CodeReviewCommit>();
-    }
-
-    boolean submitStillPossible = commit.missing.size() > 0;
-    for (CodeReviewCommit missingCommit : commit.missing) {
-      loadChangeInfo(missingCommit);
-
-      if (missingCommit.patchsetId == null) {
-        // The commit doesn't have a patch set, so it cannot be
-        // submitted to the branch.
-        //
-        submitStillPossible = false;
-        break;
-      }
-
-      if (!missingCommit.change.currentPatchSetId().equals(
-          missingCommit.patchsetId)) {
-        // If the missing commit is not the current patch set,
-        // the change must be rebased to use the proper parent.
-        //
-        submitStillPossible = false;
-        break;
-      }
-    }
-
+    final boolean submitStillPossible = isSubmitForMissingCommitsStillPossible(commit);
     final long now = System.currentTimeMillis();
     final long waitUntil = c.getLastUpdatedOn().getTime() + DEPENDENCY_DELAY;
     if (submitStillPossible && now < waitUntil) {
@@ -1339,29 +887,6 @@
     return m;
   }
 
-  private static PatchSetApproval getSubmitter(ReviewDb reviewDb,
-      PatchSet.Id c) {
-    if (c == null) {
-      return null;
-    }
-    PatchSetApproval submitter = null;
-    try {
-      final List<PatchSetApproval> approvals =
-          reviewDb.patchSetApprovals().byPatchSet(c).toList();
-      for (PatchSetApproval a : approvals) {
-        if (a.getValue() > 0
-            && ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
-          if (submitter == null
-              || a.getGranted().compareTo(submitter.getGranted()) > 0) {
-            submitter = a;
-          }
-        }
-      }
-    } catch (OrmException e) {
-    }
-    return submitter;
-  }
-
   private void setMerged(final Change c, final ChangeMessage msg) {
     final Change.Id changeId = c.getId();
     // We must pull the patchset out of commits, because the patchset ID is
@@ -1481,13 +1006,14 @@
       }
     }));
 
-
-    try {
-      hooks.doChangeMergedHook(c, //
-          accountCache.get(submitter.getAccountId()).getAccount(), //
-          db.patchSets().get(c.currentPatchSetId()), db);
-    } catch (OrmException ex) {
-      log.error("Cannot run hook for submitted patch set " + c.getId(), ex);
+    if (submitter != null) {
+      try {
+        hooks.doChangeMergedHook(c,
+            accountCache.get(submitter.getAccountId()).getAccount(),
+            db.patchSets().get(c.currentPatchSetId()), db);
+      } catch (OrmException ex) {
+        log.error("Cannot run hook for submitted patch set " + c.getId(), ex);
+      }
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
index fe87480..3911d96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
@@ -28,14 +28,14 @@
 
 class MergeSorter {
   private final RevWalk rw;
-  private final RevFlag CAN_MERGE;
+  private final RevFlag canMergeFlag;
   private final Set<RevCommit> accepted;
 
-  MergeSorter(final RevWalk walk, final Set<RevCommit> alreadyAccepted,
-      final RevFlag flagCAN_MERGE) {
-    rw = walk;
-    CAN_MERGE = flagCAN_MERGE;
-    accepted = alreadyAccepted;
+  MergeSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
+      final RevFlag canMergeFlag) {
+    this.rw = rw;
+    this.canMergeFlag = canMergeFlag;
+    this.accepted = alreadyAccepted;
   }
 
   Collection<CodeReviewCommit> sort(final Collection<CodeReviewCommit> incoming)
@@ -45,7 +45,7 @@
     while (!sort.isEmpty()) {
       final CodeReviewCommit n = removeOne(sort);
 
-      rw.resetRetain(CAN_MERGE);
+      rw.resetRetain(canMergeFlag);
       rw.markStart(n);
       for (RevCommit c : accepted) {
         rw.markUninteresting(c);
@@ -54,7 +54,7 @@
       RevCommit c;
       final RevCommitList<RevCommit> contents = new RevCommitList<RevCommit>();
       while ((c = rw.next()) != null) {
-        if (!c.has(CAN_MERGE)) {
+        if (!c.has(canMergeFlag) || !incoming.contains(c)) {
           // We cannot merge n as it would bring something we
           // aren't permitted to merge at this time. Drop n.
           //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
new file mode 100644
index 0000000..2d305b3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -0,0 +1,364 @@
+// 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.reviewdb.client.ApprovalCategory;
+import com.google.gerrit.reviewdb.client.Branch;
+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.IdentifiedUser;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+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.Repository;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+public class MergeUtil {
+  private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
+
+  private static final String R_HEADS_MASTER =
+      Constants.R_HEADS + Constants.MASTER;
+
+  public static CodeReviewCommit getFirstFastForward(
+      final CodeReviewCommit mergeTip, final RevWalk rw,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
+      try {
+        final CodeReviewCommit n = i.next();
+        if (mergeTip == null || rw.isMergedInto(mergeTip, n)) {
+          i.remove();
+          return n;
+        }
+      } catch (IOException e) {
+        throw new MergeException("Cannot fast-forward test during merge", e);
+      }
+    }
+    return mergeTip;
+  }
+
+  public static void reduceToMinimalMerge(final MergeSorter mergeSorter,
+      final List<CodeReviewCommit> toSort) throws MergeException {
+    final Collection<CodeReviewCommit> heads;
+    try {
+      heads = mergeSorter.sort(toSort);
+    } catch (IOException e) {
+      throw new MergeException("Branch head sorting failed", e);
+    }
+
+    toSort.clear();
+    toSort.addAll(heads);
+    Collections.sort(toSort, new Comparator<CodeReviewCommit>() {
+      @Override
+      public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
+        return a.originalOrder - b.originalOrder;
+      }
+    });
+  }
+
+  public static PatchSetApproval getSubmitter(final ReviewDb reviewDb,
+      final PatchSet.Id c) {
+    if (c == null) {
+      return null;
+    }
+    PatchSetApproval submitter = null;
+    try {
+      final List<PatchSetApproval> approvals =
+          reviewDb.patchSetApprovals().byPatchSet(c).toList();
+      for (PatchSetApproval a : approvals) {
+        if (a.getValue() > 0
+            && ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
+          if (submitter == null
+              || a.getGranted().compareTo(submitter.getGranted()) > 0) {
+            submitter = a;
+          }
+        }
+      }
+    } catch (OrmException e) {
+    }
+    return submitter;
+  }
+
+  public static PersonIdent computeMergeCommitAuthor(final ReviewDb reviewDb,
+      final IdentifiedUser.GenericFactory identifiedUserFactory,
+      final PersonIdent myIdent, final RevWalk rw,
+      final List<CodeReviewCommit> codeReviewCommits) {
+    PatchSetApproval submitter = null;
+    for (final CodeReviewCommit c : codeReviewCommits) {
+      PatchSetApproval s = getSubmitter(reviewDb, c.patchsetId);
+      if (submitter == null
+          || (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
+        submitter = s;
+      }
+    }
+
+    // Try to use the submitter's identity for the merge commit author.
+    // If all of the commits being merged are created by the submitter,
+    // prefer the identity line they used in the commits rather than the
+    // preferred identity stored in the user account. This way the Git
+    // commit records are more consistent internally.
+    //
+    PersonIdent authorIdent;
+    if (submitter != null) {
+      IdentifiedUser who =
+          identifiedUserFactory.create(submitter.getAccountId());
+      Set<String> emails = new HashSet<String>();
+      for (RevCommit c : codeReviewCommits) {
+        try {
+          rw.parseBody(c);
+        } catch (IOException e) {
+          log.warn("Cannot parse commit " + c.name(), e);
+          continue;
+        }
+        emails.add(c.getAuthorIdent().getEmailAddress());
+      }
+
+      final Timestamp dt = submitter.getGranted();
+      final TimeZone tz = myIdent.getTimeZone();
+      if (emails.size() == 1
+          && who.getEmailAddresses().contains(emails.iterator().next())) {
+        authorIdent =
+            new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
+      } else {
+        authorIdent = who.newCommitterIdent(dt, tz);
+      }
+    } else {
+      authorIdent = myIdent;
+    }
+    return authorIdent;
+  }
+
+  public static boolean hasMissingDependencies(final MergeSorter mergeSorter,
+      final CodeReviewCommit toMerge) throws MergeException {
+    try {
+      return !mergeSorter.sort(Collections.singleton(toMerge)).contains(toMerge);
+    } catch (IOException e) {
+      throw new MergeException("Branch head sorting failed", e);
+    }
+  }
+
+  public static CodeReviewCommit mergeOneCommit(final ReviewDb reviewDb,
+      final IdentifiedUser.GenericFactory identifiedUserFactory,
+      final PersonIdent myIdent, final Repository repo, final RevWalk rw,
+      final ObjectInserter inserter, final RevFlag canMergeFlag,
+      final boolean useContentMerge, final Branch.NameKey destBranch,
+      final CodeReviewCommit mergeTip, final CodeReviewCommit n)
+      throws MergeException {
+    final ThreeWayMerger m = newThreeWayMerger(repo, inserter, useContentMerge);
+    try {
+      if (m.merge(new AnyObjectId[] {mergeTip, n})) {
+        return writeMergeCommit(reviewDb, identifiedUserFactory, myIdent, rw,
+            inserter, canMergeFlag, destBranch, mergeTip, m.getResultTreeId(), n);
+      } else {
+        failed(rw, canMergeFlag, mergeTip, n, CommitMergeStatus.PATH_CONFLICT);
+      }
+    } catch (IOException e) {
+      if (e.getMessage().startsWith("Multiple merge bases for")) {
+        try {
+          failed(rw, canMergeFlag, mergeTip, n,
+              CommitMergeStatus.CRISS_CROSS_MERGE);
+        } catch (IOException e2) {
+          throw new MergeException("Cannot merge " + n.name(), e);
+        }
+      } else {
+        throw new MergeException("Cannot merge " + n.name(), e);
+      }
+    }
+    return mergeTip;
+  }
+
+  private static CodeReviewCommit failed(final RevWalk rw,
+      final RevFlag canMergeFlag, final CodeReviewCommit mergeTip,
+      final CodeReviewCommit n, final CommitMergeStatus failure)
+      throws MissingObjectException, IncorrectObjectTypeException, IOException {
+    rw.resetRetain(canMergeFlag);
+    rw.markStart(n);
+    rw.markUninteresting(mergeTip);
+    CodeReviewCommit failed;
+    while ((failed = (CodeReviewCommit) rw.next()) != null) {
+      failed.statusCode = failure;
+    }
+    return failed;
+  }
+
+  public static CodeReviewCommit writeMergeCommit(final ReviewDb reviewDb,
+      final IdentifiedUser.GenericFactory identifiedUserFactory,
+      final PersonIdent myIdent, final RevWalk rw,
+      final ObjectInserter inserter, final RevFlag canMergeFlag,
+      final Branch.NameKey destBranch, final CodeReviewCommit mergeTip,
+      final ObjectId treeId, final CodeReviewCommit n) throws IOException,
+      MissingObjectException, IncorrectObjectTypeException {
+    final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
+    rw.resetRetain(canMergeFlag);
+    rw.markStart(n);
+    rw.markUninteresting(mergeTip);
+    for (final RevCommit c : rw) {
+      final CodeReviewCommit crc = (CodeReviewCommit) c;
+      if (crc.patchsetId != null) {
+        merged.add(crc);
+      }
+    }
+
+    final StringBuilder msgbuf = new StringBuilder();
+    if (merged.size() == 1) {
+      final CodeReviewCommit c = merged.get(0);
+      rw.parseBody(c);
+      msgbuf.append("Merge \"");
+      msgbuf.append(c.getShortMessage());
+      msgbuf.append("\"");
+
+    } else {
+      msgbuf.append("Merge changes ");
+      for (final Iterator<CodeReviewCommit> i = merged.iterator(); i.hasNext();) {
+        msgbuf.append(i.next().change.getKey().abbreviate());
+        if (i.hasNext()) {
+          msgbuf.append(',');
+        }
+      }
+    }
+
+    if (!R_HEADS_MASTER.equals(destBranch.get())) {
+      msgbuf.append(" into ");
+      msgbuf.append(destBranch.getShortName());
+    }
+
+    if (merged.size() > 1) {
+      msgbuf.append("\n\n* changes:\n");
+      for (final CodeReviewCommit c : merged) {
+        rw.parseBody(c);
+        msgbuf.append("  ");
+        msgbuf.append(c.getShortMessage());
+        msgbuf.append("\n");
+      }
+    }
+
+    PersonIdent authorIdent =
+        computeMergeCommitAuthor(reviewDb, identifiedUserFactory, myIdent, rw,
+            merged);
+
+    final CommitBuilder mergeCommit = new CommitBuilder();
+    mergeCommit.setTreeId(treeId);
+    mergeCommit.setParentIds(mergeTip, n);
+    mergeCommit.setAuthor(authorIdent);
+    mergeCommit.setCommitter(myIdent);
+    mergeCommit.setMessage(msgbuf.toString());
+
+    return (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
+  }
+
+  public static ThreeWayMerger newThreeWayMerger(final Repository repo,
+      final ObjectInserter inserter, final boolean useContentMerge) {
+    ThreeWayMerger m;
+    if (useContentMerge) {
+      // Settings for this project allow us to try and
+      // automatically resolve conflicts within files if needed.
+      // Use ResolveMerge and instruct to operate in core.
+      m = MergeStrategy.RESOLVE.newMerger(repo, true);
+    } else {
+      // No auto conflict resolving allowed. If any of the
+      // affected files was modified, merge will fail.
+      m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
+    }
+    m.setObjectInserter(new ObjectInserter.Filter() {
+      @Override
+      protected ObjectInserter delegate() {
+        return inserter;
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void release() {
+      }
+    });
+    return m;
+  }
+
+  public static ObjectId commit(final ObjectInserter inserter,
+      final CommitBuilder mergeCommit) throws IOException,
+      UnsupportedEncodingException {
+    ObjectId id = inserter.insert(mergeCommit);
+    inserter.flush();
+    return id;
+  }
+
+  public static PatchSetApproval markCleanMerges(final ReviewDb reviewDb,
+      final RevWalk rw, final RevFlag canMergeFlag,
+      final CodeReviewCommit mergeTip, final Set<RevCommit> alreadyAccepted)
+      throws MergeException {
+    if (mergeTip == null) {
+      // If mergeTip is null here, branchTip was null, indicating a new branch
+      // at the start of the merge process. We also elected to merge nothing,
+      // probably due to missing dependencies. Nothing was cleanly merged.
+      //
+      return null;
+    }
+
+    try {
+      PatchSetApproval submitApproval = null;
+
+      rw.resetRetain(canMergeFlag);
+      rw.sort(RevSort.TOPO);
+      rw.sort(RevSort.REVERSE, true);
+      rw.markStart(mergeTip);
+      for (RevCommit c : alreadyAccepted) {
+        rw.markUninteresting(c);
+      }
+
+      CodeReviewCommit c;
+      while ((c = (CodeReviewCommit) rw.next()) != null) {
+        if (c.patchsetId != null) {
+          c.statusCode = CommitMergeStatus.CLEAN_MERGE;
+          if (submitApproval == null) {
+            submitApproval = getSubmitter(reviewDb, c.patchsetId);
+          }
+        }
+      }
+
+      return submitApproval;
+    } catch (IOException e) {
+      throw new MergeException("Cannot mark clean merges", e);
+    }
+  }
+}
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
index 17cfea8..7887edd 100644
--- 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
@@ -47,7 +47,7 @@
  */
 public class NotesBranchUtil {
   public interface Factory {
-    NotesBranchUtil create(Repository db);
+    NotesBranchUtil create(Repository db, ObjectInserter inserter);
   }
 
   private static final int MAX_LOCK_FAILURE_CALLS = 10;
@@ -55,6 +55,7 @@
 
   private PersonIdent gerritIdent;
   private final Repository db;
+  private final ObjectInserter inserter;
 
   private RevCommit baseCommit;
   private NoteMap base;
@@ -63,7 +64,6 @@
   private NoteMap ours;
 
   private RevWalk revWalk;
-  private ObjectInserter inserter;
   private ObjectReader reader;
   private boolean overwrite;
 
@@ -71,9 +71,11 @@
 
   @Inject
   public NotesBranchUtil(@GerritPersonIdent final PersonIdent gerritIdent,
-      @Assisted Repository db) {
+      @Assisted Repository db,
+      @Assisted ObjectInserter inserter) {
     this.gerritIdent = gerritIdent;
     this.db = db;
+    this.inserter = inserter;
   }
 
   /**
@@ -128,7 +130,6 @@
       ConcurrentRefUpdateException {
     try {
       revWalk = new RevWalk(db);
-      inserter = db.newObjectInserter();
       reader = db.newObjectReader();
       loadBase(notesBranch);
       if (overwrite) {
@@ -144,7 +145,6 @@
       updateRef(notesBranch);
     } finally {
       revWalk.release();
-      inserter.release();
       reader.release();
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 6a6f908..5f1d046 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -888,7 +888,7 @@
         reject(cmd, "cannot delete project configuration");
       } else {
         errors.put(Error.DELETE, ctl.getRefName());
-        reject(cmd, "can not delete references");
+        reject(cmd, "cannot delete references");
       }
     }
   }
@@ -987,7 +987,7 @@
     destBranchCtl = projectControl.controlForRef(destBranch);
     if (!destBranchCtl.canUpload()) {
       errors.put(Error.CODE_REVIEW, cmd.getRefName());
-      reject(cmd, "can not upload review");
+      reject(cmd, "cannot upload review");
       return;
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
new file mode 100644
index 0000000..b6770a9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.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.server.git;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base class that submit strategies must extend. A submit strategy for a
+ * certain {@link SubmitType} defines how the submitted commits should be
+ * merged.
+ */
+public abstract class SubmitStrategy {
+
+  private PersonIdent refLogIdent;
+
+  static class Arguments {
+    protected final IdentifiedUser.GenericFactory identifiedUserFactory;
+    protected final PersonIdent myIdent;
+    protected final ReviewDb db;
+
+    protected final Repository repo;
+    protected final RevWalk rw;
+    protected final ObjectInserter inserter;
+    protected final RevFlag canMergeFlag;
+    protected final Set<RevCommit> alreadyAccepted;
+    protected final Branch.NameKey destBranch;
+    protected final boolean useContentMerge;
+    protected final MergeSorter mergeSorter;
+
+    Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
+        final PersonIdent myIdent, final ReviewDb db, final Repository repo,
+        final RevWalk rw, final ObjectInserter inserter,
+        final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
+        final Branch.NameKey destBranch, final boolean useContentMerge) {
+      this.identifiedUserFactory = identifiedUserFactory;
+      this.myIdent = myIdent;
+      this.db = db;
+
+      this.repo = repo;
+      this.rw = rw;
+      this.inserter = inserter;
+      this.canMergeFlag = canMergeFlag;
+      this.alreadyAccepted = alreadyAccepted;
+      this.destBranch = destBranch;
+      this.useContentMerge = useContentMerge;
+      this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
+    }
+  }
+
+  protected final Arguments args;
+
+  SubmitStrategy(final Arguments args) {
+    this.args = args;
+  }
+
+  /**
+   * Runs this submit strategy. If possible the provided commits will be merged
+   * with this submit strategy.
+   *
+   * @param mergeTip the mergeTip
+   * @param toMerge the list of submitted commits that should be merged using
+   *        this submit strategy
+   * @return the new mergeTip
+   * @throws MergeException
+   */
+  public final CodeReviewCommit run(final CodeReviewCommit mergeTip,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    refLogIdent = null;
+    return _run(mergeTip, toMerge);
+  }
+
+  /**
+   * Runs this submit strategy. If possible the provided commits will be merged
+   * with this submit strategy.
+   *
+   * @param mergeTip the mergeTip
+   * @param toMerge the list of submitted commits that should be merged using
+   *        this submit strategy
+   * @return the new mergeTip
+   * @throws MergeException
+   */
+  protected abstract CodeReviewCommit _run(CodeReviewCommit mergeTip,
+      List<CodeReviewCommit> toMerge) throws MergeException;
+
+  /**
+   * Returns the PersonIdent that should be used for the ref log entries when
+   * updating the destination branch. The ref log identity may be set after the
+   * {@link #run(CodeReviewCommit, List)} method finished.
+   *
+   * Do only call this method after the {@link #run(CodeReviewCommit, List)}
+   * method has been invoked.
+   *
+   * @return the ref log identity, may be <code>null</code>
+   */
+  public final PersonIdent getRefLogIdent() {
+    return refLogIdent;
+  }
+
+  /**
+   * Returns all commits that have been newly created for the changes that are
+   * getting merged.
+   *
+   * By default this method is returning an empty map, but subclasses may
+   * overwrite this method to provide newly created commits.
+   *
+   * Do only call this method after the {@link #run(CodeReviewCommit, List)}
+   * method has been invoked.
+   *
+   * @return new commits created for changes that are getting merged
+   */
+  public Map<Change.Id, CodeReviewCommit> getNewCommits() {
+    return Collections.emptyMap();
+  }
+
+  /**
+   * Returns whether a merge that failed with
+   * {@link RefUpdate.Result#LOCK_FAILURE} should be retried.
+   *
+   * May be overwritten by subclasses.
+   *
+   * @return <code>true</code> if a merge that failed with
+   *         {@link RefUpdate.Result#LOCK_FAILURE} should be retried, otherwise
+   *         <code>false</code>
+   */
+  public boolean retryOnLockFailure() {
+    return true;
+  }
+
+  /**
+   * Sets the ref log identity if it wasn't set yet.
+   *
+   * @param submitApproval the approval that submitted the patch set
+   */
+  protected final void setRefLogIdent(final PatchSetApproval submitApproval) {
+    if (refLogIdent == null && submitApproval != null) {
+      refLogIdent =
+          args.identifiedUserFactory.create(submitApproval.getAccountId())
+              .newRefLogIdent();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
new file mode 100644
index 0000000..e942f71
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
@@ -0,0 +1,94 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/** Factory to create a {@link SubmitStrategy} for a {@link SubmitType}. */
+public class SubmitStrategyFactory {
+  private static final Logger log = LoggerFactory
+      .getLogger(SubmitStrategyFactory.class);
+
+  private final IdentifiedUser.GenericFactory identifiedUserFactory;
+  private final PersonIdent myIdent;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final Provider<String> urlProvider;
+  private final ApprovalTypes approvalTypes;
+  private final GitReferenceUpdated replication;
+
+  @Inject
+  SubmitStrategyFactory(
+      final IdentifiedUser.GenericFactory identifiedUserFactory,
+      @GerritPersonIdent final PersonIdent myIdent,
+      final PatchSetInfoFactory patchSetInfoFactory,
+      @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
+      final ApprovalTypes approvalTypes, final GitReferenceUpdated replication) {
+    this.identifiedUserFactory = identifiedUserFactory;
+    this.myIdent = myIdent;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.urlProvider = urlProvider;
+    this.approvalTypes = approvalTypes;
+    this.replication = replication;
+  }
+
+  public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
+      final Repository repo, final RevWalk rw, final ObjectInserter inserter,
+      final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
+      final Branch.NameKey destBranch, final boolean useContentMerge)
+      throws MergeException {
+    final SubmitStrategy.Arguments args =
+        new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db, repo,
+            rw, inserter, canMergeFlag, alreadyAccepted, destBranch,
+            useContentMerge);
+    switch (submitType) {
+      case CHERRY_PICK:
+        return new CherryPick(args, patchSetInfoFactory, urlProvider,
+            approvalTypes, replication);
+      case FAST_FORWARD_ONLY:
+        return new FastForwardOnly(args);
+      case MERGE_ALWAYS:
+        return new MergeAlways(args);
+      case MERGE_IF_NECESSARY:
+        return new MergeIfNecessary(args);
+      default:
+        final String errorMsg = "No submit strategy for: " + submitType;
+        log.error(errorMsg);
+        throw new MergeException(errorMsg);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java
index df93852..53fd182 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AddReviewer.java
@@ -15,6 +15,7 @@
 
 package com.google.gerrit.server.patch;
 
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.ApprovalType;
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.common.data.ReviewerResult;
@@ -26,6 +27,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 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.AccountResolver;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupMembers;
@@ -64,6 +66,8 @@
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final ApprovalCategory.Id addReviewerCategoryId;
   private final Config cfg;
+  private final ChangeHooks hooks;
+  private final AccountCache accountCache;
 
   private final Change.Id changeId;
   private final Collection<String> reviewers;
@@ -76,7 +80,9 @@
       final ChangeControl.Factory changeControlFactory, final ReviewDb db,
       final IdentifiedUser.GenericFactory identifiedUserFactory,
       final IdentifiedUser currentUser, final ApprovalTypes approvalTypes,
-      final @GerritServerConfig Config cfg, @Assisted final Change.Id changeId,
+      final @GerritServerConfig Config cfg, final ChangeHooks hooks,
+      final AccountCache accountCache,
+      @Assisted final Change.Id changeId,
       @Assisted final Collection<String> reviewers,
       @Assisted final boolean confirmed) {
     this.addReviewerSenderFactory = addReviewerSenderFactory;
@@ -88,6 +94,8 @@
     this.identifiedUserFactory = identifiedUserFactory;
     this.currentUser = currentUser;
     this.cfg = cfg;
+    this.hooks = hooks;
+    this.accountCache = accountCache;
 
     final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
     addReviewerCategoryId =
@@ -197,7 +205,8 @@
     //
     final Set<Account.Id> added = new HashSet<Account.Id>();
     final List<PatchSetApproval> toInsert = new ArrayList<PatchSetApproval>();
-    final PatchSet.Id psid = control.getChange().currentPatchSetId();
+    final Change change = control.getChange();
+    final PatchSet.Id psid = change.currentPatchSetId();
     for (final Account.Id reviewer : reviewerIds) {
       if (!exists(psid, reviewer)) {
         // This reviewer has not entered an approval for this change yet.
@@ -210,6 +219,14 @@
     }
     db.patchSetApprovals().insert(toInsert);
 
+    // Execute hook for added reviewers
+    //
+    final PatchSet patchSet = db.patchSets().get(psid);
+    for (final Account.Id id : added) {
+      final Account account = accountCache.get(id).getAccount();
+      hooks.doReviewerAddedHook(change, account, patchSet, db);
+    }
+
     // Email the reviewers
     //
     // The user knows they added themselves, don't bother emailing them.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
index 62ed5e4..5b37b92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
@@ -82,6 +82,10 @@
     return bId;
   }
 
+  public boolean isIgnoreWhitespace() {
+    return ignoreWhitespace;
+  }
+
   Project.NameKey getProject() {
     return projectKey;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index e8af060..eacdfa0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -299,7 +299,7 @@
     return data;
   }
 
-  private static class ClassData implements ClassVisitor {
+  private static class ClassData extends ClassVisitor {
     private static final String EXPORT = Type.getType(Export.class).getDescriptor();
     private static final String LISTEN = Type.getType(Listen.class).getDescriptor();
 
@@ -308,6 +308,10 @@
     String exportedAsName;
     boolean listen;
 
+    ClassData() {
+      super(Opcodes.ASM4);
+    }
+
     boolean isConcrete() {
       return (access & Opcodes.ACC_ABSTRACT) == 0
           && (access & Opcodes.ACC_INTERFACE) == 0;
@@ -370,8 +374,12 @@
     }
   }
 
-  private static abstract class AbstractAnnotationVisitor implements
+  private static abstract class AbstractAnnotationVisitor extends
       AnnotationVisitor {
+    AbstractAnnotationVisitor() {
+      super(Opcodes.ASM4);
+    }
+
     @Override
     public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
       return null;
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 db4b021..4494853 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
@@ -16,14 +16,13 @@
 
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Account;
 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.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.rules.PrologEnvironment;
-import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -32,23 +31,18 @@
 import com.google.inject.Provider;
 import com.google.inject.util.Providers;
 
-import com.googlecode.prolog_cafe.compiler.CompileException;
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
 import com.googlecode.prolog_cafe.lang.ListTerm;
 import com.googlecode.prolog_cafe.lang.Prolog;
-import com.googlecode.prolog_cafe.lang.PrologException;
 import com.googlecode.prolog_cafe.lang.StructureTerm;
 import com.googlecode.prolog_cafe.lang.Term;
-import com.googlecode.prolog_cafe.lang.VariableTerm;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -282,9 +276,10 @@
         return true;
       }
 
-      // The branch owner, project owner, site admin can remove anyone.
-      //
-      if (getRefControl().isOwner() // branch owner
+      // Users with the remove reviewer permission, the branch owner, project
+      // owner and site admin can remove anyone
+      if (getRefControl().canRemoveReviewer() // has removal permissions
+          || getRefControl().isOwner() // branch owner
           || getProjectControl().isOwner() // project owner
           || getCurrentUser().getCapabilities().canAdministrateServer()) {
         return true;
@@ -302,6 +297,7 @@
     return canSubmit(db, patchSet, null, false, false);
   }
 
+ @SuppressWarnings("unchecked")
   public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
       @Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed) {
     if (!allowClosed && change.getStatus().isClosed()) {
@@ -333,103 +329,18 @@
       return logRuleError("Cannot read patch set " + patchSet.getId(), err);
     }
 
-    List<Term> results = new ArrayList<Term>();
-    Term submitRule;
-    ProjectState projectState = getProjectControl().getProjectState();
-    PrologEnvironment env;
-
+    List<Term> results;
+    SubmitRuleEvaluator evaluator;
     try {
-      env = projectState.newPrologEnvironment();
-    } catch (CompileException err) {
-      return logRuleError("Cannot consult rules.pl for "
-          + getProject().getName(), err);
-    }
-
-    try {
-      env.set(StoredValues.REVIEW_DB, db);
-      env.set(StoredValues.CHANGE, change);
-      env.set(StoredValues.CHANGE_DATA, cd);
-      env.set(StoredValues.PATCH_SET, patchSet);
-      env.set(StoredValues.CHANGE_CONTROL, this);
-
-      submitRule = env.once(
-        "gerrit", "locate_submit_rule",
-        new VariableTerm());
-      if (submitRule == null) {
-        return logRuleError("No user:submit_rule found for "
-            + getProject().getName());
-      }
-
-      if (fastEvalLabels) {
-        env.once("gerrit", "assume_range_from_label");
-      }
-
-      try {
-        for (Term[] template : env.all(
-            "gerrit", "can_submit",
-            submitRule,
-            new VariableTerm())) {
-          results.add(template[1]);
-        }
-      } catch (PrologException err) {
-        return logRuleError("Exception calling " + submitRule + " on change "
-            + change.getId() + " of " + getProject().getName(), err);
-      } catch (RuntimeException err) {
-        return logRuleError("Exception calling " + submitRule + " on change "
-            + change.getId() + " of " + getProject().getName(), err);
-      }
-
-      ProjectState parentState = projectState.getParentState();
-      PrologEnvironment childEnv = env;
-      Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
-      projectsSeen.add(getProject().getNameKey());
-
-      while (parentState != null) {
-        if (!projectsSeen.add(parentState.getProject().getNameKey())) {
-          //parent has been seen before, stop walk up inheritance tree
-          break;
-        }
-        PrologEnvironment parentEnv;
-        try {
-          parentEnv = parentState.newPrologEnvironment();
-        } catch (CompileException err) {
-          return logRuleError("Cannot consult rules.pl for "
-              + parentState.getProject().getName(), err);
-        }
-
-        parentEnv.copyStoredValues(childEnv);
-        Term filterRule =
-            parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
-        if (filterRule != null) {
-          try {
-            if (fastEvalLabels) {
-              env.once("gerrit", "assume_range_from_label");
-            }
-
-            Term resultsTerm = toListTerm(results);
-            results.clear();
-            Term[] template = parentEnv.once(
-                "gerrit", "filter_submit_results",
-                filterRule,
-                resultsTerm,
-                new VariableTerm());
-            @SuppressWarnings("unchecked")
-            final List<? extends Term> termList = ((ListTerm) template[2]).toJava();
-            results.addAll(termList);
-          } catch (PrologException err) {
-            return logRuleError("Exception calling " + filterRule + " on change "
-                + change.getId() + " of " + parentState.getProject().getName(), err);
-          } catch (RuntimeException err) {
-            return logRuleError("Exception calling " + filterRule + " on change "
-                + change.getId() + " of " + parentState.getProject().getName(), err);
-          }
-        }
-
-        parentState = parentState.getParentState();
-        childEnv = parentEnv;
-      }
-    } finally {
-      env.close();
+      evaluator = new SubmitRuleEvaluator(db, patchSet,
+          getProjectControl(),
+          this, change, cd,
+          fastEvalLabels,
+          "locate_submit_rule", "can_submit",
+          "locate_submit_filter", "filter_submit_results");
+      results = evaluator.evaluate().toJava();
+    } catch (RuleEvalException e) {
+      return logRuleError(e.getMessage(), e);
     }
 
     if (results.isEmpty()) {
@@ -437,12 +348,13 @@
       // at least one result informing the caller of the labels that are
       // required for this change to be submittable. Each label will indicate
       // whether or not that is actually possible given the permissions.
-      log.error("Submit rule " + submitRule + " for change " + change.getId()
-          + " of " + getProject().getName() + " has no solution.");
+      log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
+          + change.getId() + " of " + getProject().getName()
+          + " has no solution.");
       return ruleError("Project submit rule has no solution");
     }
 
-    return resultsToSubmitRecord(submitRule, results);
+    return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
   }
 
   /**
@@ -527,6 +439,65 @@
     return out;
   }
 
+  public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet) {
+    return getSubmitTypeRecord(db, patchSet, null);
+  }
+
+  @SuppressWarnings("unchecked")
+  public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet,
+      @Nullable ChangeData cd) {
+    if (!patchSet.getId().equals(change.currentPatchSetId())) {
+      return typeRuleError("Patch set " + patchSet.getPatchSetId()
+          + " is not current");
+    }
+
+    try {
+      if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db, cd)) {
+        return typeRuleError("Patch set " + patchSet.getPatchSetId()
+            + " not found");
+      }
+      if (patchSet.isDraft() && !isDraftVisible(db, cd)) {
+        return typeRuleError("Patch set " + patchSet.getPatchSetId()
+            + " not found");
+      }
+    } catch (OrmException err) {
+      return logTypeRuleError("Cannot read patch set " + patchSet.getId(),
+          err);
+    }
+
+    List<String> results;
+    SubmitRuleEvaluator evaluator;
+    try {
+      evaluator = new SubmitRuleEvaluator(db, patchSet,
+          getProjectControl(), this, change, cd,
+          false,
+          "locate_submit_type", "get_submit_type",
+          "locate_submit_type_filter", "filter_submit_type_results");
+      results = evaluator.evaluate().toJava();
+    } catch (RuleEvalException e) {
+      return logTypeRuleError(e.getMessage(), e);
+    }
+
+    if (results.isEmpty()) {
+      // Should never occur for a well written rule
+      log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
+          + change.getId() + " of " + getProject().getName()
+          + " has no solution.");
+      return typeRuleError("Project submit rule has no solution");
+    }
+
+    // Take only the first result and convert it to SubmitTypeRecord
+    // This logic will need to change once we support multiple submit types
+    // in the UI
+    String typeName = results.get(0);
+    try {
+      return SubmitTypeRecord.OK(
+          Project.SubmitType.valueOf(typeName.toUpperCase()));
+    } catch (IllegalArgumentException e) {
+      return logInvalidType(evaluator.getSubmitRule(), typeName);
+    }
+  }
+
   private List<SubmitRecord> logInvalidResult(Term rule, Term record) {
     return logRuleError("Submit rule " + rule + " for change " + change.getId()
         + " of " + getProject().getName() + " output invalid result: " + record);
@@ -549,6 +520,29 @@
     return Collections.singletonList(rec);
   }
 
+  private SubmitTypeRecord logInvalidType(Term rule, String record) {
+    return logTypeRuleError("Submit type rule " + rule + " for change "
+        + change.getId() + " of " + getProject().getName()
+        + " output invalid result: " + record);
+  }
+
+  private SubmitTypeRecord logTypeRuleError(String err, Exception e) {
+    log.error(err, e);
+    return typeRuleError("Error evaluating project type rules, check server log");
+  }
+
+  private SubmitTypeRecord logTypeRuleError(String err) {
+    log.error(err);
+    return typeRuleError("Error evaluating project type rules, check server log");
+  }
+
+  private SubmitTypeRecord typeRuleError(String err) {
+    SubmitTypeRecord rec = new SubmitTypeRecord();
+    rec.status = SubmitTypeRecord.Status.RULE_ERROR;
+    rec.errorMessage = err;
+    return rec;
+  }
+
   private void appliedBy(SubmitRecord.Label label, Term status) {
     if (status.isStructure() && status.arity() == 1) {
       Term who = status.arg(0);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index e5a11ca..98e0f6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -15,11 +15,16 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.NameKey;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.StringUtil;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.util.TreeFormatter;
 import com.google.gson.reflect.TypeToken;
@@ -86,6 +91,8 @@
 
   private final CurrentUser currentUser;
   private final ProjectCache projectCache;
+  private final GroupCache groupCache;
+  private final GroupControl.Factory groupControlFactory;
   private final GitRepositoryManager repoManager;
   private final ProjectNode.Factory projectNodeFactory;
 
@@ -115,12 +122,18 @@
 
   private String matchPrefix;
 
+  @Option(name = "--has-acl-for", metaVar = "GROUP", usage =
+      "displays only projects on which access rights for this group are directly assigned")
+  private AccountGroup.UUID groupUuid;
+
   @Inject
   protected ListProjects(CurrentUser currentUser, ProjectCache projectCache,
-      GitRepositoryManager repoManager,
-      ProjectNode.Factory projectNodeFactory) {
+      GroupCache groupCache, GroupControl.Factory groupControlFactory,
+      GitRepositoryManager repoManager, ProjectNode.Factory projectNodeFactory) {
     this.currentUser = currentUser;
     this.projectCache = projectCache;
+    this.groupCache = groupCache;
+    this.groupControlFactory = groupControlFactory;
     this.repoManager = repoManager;
     this.projectNodeFactory = projectNodeFactory;
   }
@@ -175,6 +188,22 @@
           //
           continue;
         }
+
+        final ProjectControl pctl = e.controlFor(currentUser);
+        if (groupUuid != null) {
+          try {
+            if (!groupControlFactory.controlFor(groupUuid).isVisible()) {
+              break;
+            }
+          } catch (NoSuchGroupException ex) {
+            break;
+          }
+          if (!pctl.getLocalGroups().contains(
+              GroupReference.forGroup(groupCache.get(groupUuid)))) {
+            continue;
+          }
+        }
+
         ProjectInfo info = new ProjectInfo();
         if (type == FilterType.PARENT_CANDIDATES) {
           ProjectState parentState = e.getParentState();
@@ -194,7 +223,6 @@
           }
 
         } else {
-          final ProjectControl pctl = e.controlFor(currentUser);
           final boolean isVisible = pctl.isVisible() || (all && pctl.isOwner());
           if (showTree && !format.isJson()) {
             treeMap.put(projectName,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 513f1b1..b6d53e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -120,6 +120,7 @@
   private final Collection<ContributorAgreement> contributorAgreements;
 
   private List<SectionMatcher> allSections;
+  private List<SectionMatcher> localSections;
   private Map<String, RefControl> refControls;
   private Boolean declaredOwner;
 
@@ -239,8 +240,17 @@
   }
 
   public Set<GroupReference> getAllGroups() {
+    return getGroups(access());
+  }
+
+  public Set<GroupReference> getLocalGroups() {
+    return getGroups(localAccess());
+  }
+
+  private static Set<GroupReference> getGroups(
+      final List<SectionMatcher> sectionMatcherList) {
     final Set<GroupReference> all = new HashSet<GroupReference>();
-    for (final SectionMatcher matcher : access()) {
+    for (final SectionMatcher matcher : sectionMatcherList) {
       final AccessSection section = matcher.section;
       for (final Permission permission : section.getPermissions()) {
         for (final PermissionRule rule : permission.getRules()) {
@@ -392,6 +402,13 @@
     return allSections;
   }
 
+  private List<SectionMatcher> localAccess() {
+    if (localSections == null) {
+      localSections = state.getLocalAccessSections();
+    }
+    return localSections;
+  }
+
   boolean match(PermissionRule rule) {
     return match(rule.getGroup().getUUID());
   }
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 e06c948..74c1247 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
@@ -174,7 +174,7 @@
   }
 
   /** Get the sections that pertain only to this project. */
-  private List<SectionMatcher> getLocalAccessSections() {
+  List<SectionMatcher> getLocalAccessSections() {
     List<SectionMatcher> sm = localAccessSections;
     if (sm == null) {
       Collection<AccessSection> fromConfig = config.getAccessSections();
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 a6182d1..e98f84e 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
@@ -322,6 +322,11 @@
     return canPerform(Permission.ABANDON);
   }
 
+  /** @return true if this user can remove a reviewer for a change. */
+  public boolean canRemoveReviewer() {
+    return canPerform(Permission.REMOVE_REVIEWER);
+  }
+
   /** All value ranges of any allowed label permission. */
   public List<PermissionRange> getLabelRanges() {
     List<PermissionRange> r = new ArrayList<PermissionRange>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RuleEvalException.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RuleEvalException.java
new file mode 100644
index 0000000..9dae11c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RuleEvalException.java
@@ -0,0 +1,26 @@
+// 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.project;
+
+@SuppressWarnings("serial")
+public class RuleEvalException extends Exception {
+  public RuleEvalException(String message) {
+    super(message);
+  }
+
+  RuleEvalException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
new file mode 100644
index 0000000..1977624
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -0,0 +1,207 @@
+// 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.project;
+
+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.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.query.change.ChangeData;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.ListTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.Term;
+import com.googlecode.prolog_cafe.lang.VariableTerm;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Evaluates a submit-like Prolog rule found in the rules.pl file of the current
+ * project and filters the results through rules found in the parent projects,
+ * all the way up to All-Projects.
+ */
+public class SubmitRuleEvaluator {
+  private final ReviewDb db;
+  private final PatchSet patchSet;
+  private final ProjectControl projectControl;
+  private final ChangeControl changeControl;
+  private final Change change;
+  private final ChangeData cd;
+  private final boolean fastEvalLabels;
+  private final String userRuleLocatorName;
+  private final String userRuleWrapperName;
+  private final String filterRuleLocatorName;
+  private final String filterRuleWrapperName;
+
+  private Term submitRule;
+  private String projectName;
+
+  /**
+   * @param userRuleLocatorName The name of the rule used to locate the
+   *        user-supplied rule.
+   * @param userRuleWrapperName The name of the wrapper rule used to evaluate
+   *        the user-supplied rule.
+   * @param filterRuleLocatorName The name of the rule used to locate the filter
+   *        rule.
+   * @param filterRuleWrapperName The name of the rule used to evaluate the
+   *        filter rule.
+   */
+  SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
+      ProjectControl projectControl,
+      ChangeControl changeControl, Change change, @Nullable ChangeData cd,
+      boolean fastEvalLabels,
+      String userRuleLocatorName, String userRuleWrapperName,
+      String filterRuleLocatorName, String filterRuleWrapperName) {
+    this.db = db;
+    this.patchSet = patchSet;
+    this.projectControl = projectControl;
+    this.changeControl = changeControl;
+    this.change = change;
+    this.cd = cd;
+    this.fastEvalLabels = fastEvalLabels;
+    this.userRuleLocatorName = userRuleLocatorName;
+    this.userRuleWrapperName = userRuleWrapperName;
+    this.filterRuleLocatorName = filterRuleLocatorName;
+    this.filterRuleWrapperName = filterRuleWrapperName;
+  }
+
+  /**
+   * Evaluates the given rule and filters.
+   *
+   * Sets the {@link #submitRule} to the Term found by the
+   * {@link #userRuleLocatorName}. This can be used when reporting error(s) on
+   * unexpected return value of this method.
+   *
+   * @return List of {@link Term} objects returned from the evaluated rules.
+   * @throws RuleEvalException
+   */
+  ListTerm evaluate() throws RuleEvalException {
+    List<Term> results = new ArrayList<Term>();
+    ProjectState projectState = projectControl.getProjectState();
+    PrologEnvironment env;
+
+    try {
+      env = projectState.newPrologEnvironment();
+    } catch (CompileException err) {
+      throw new RuleEvalException("Cannot consult rules.pl for "
+          + getProjectName(), err);
+    }
+
+    try {
+      env.set(StoredValues.REVIEW_DB, db);
+      env.set(StoredValues.CHANGE, change);
+      env.set(StoredValues.CHANGE_DATA, cd);
+      env.set(StoredValues.PATCH_SET, patchSet);
+      env.set(StoredValues.CHANGE_CONTROL, changeControl);
+
+      submitRule = env.once("gerrit", userRuleLocatorName, new VariableTerm());
+      if (fastEvalLabels) {
+        env.once("gerrit", "assume_range_from_label");
+      }
+
+      try {
+        for (Term[] template : env.all("gerrit", userRuleWrapperName,
+            submitRule, new VariableTerm())) {
+          results.add(template[1]);
+        }
+      } catch (PrologException err) {
+        throw new RuleEvalException("Exception calling " + submitRule
+            + " on change " + change.getId() + " of " + getProjectName(),
+            err);
+      } catch (RuntimeException err) {
+        throw new RuleEvalException("Exception calling " + submitRule
+            + " on change " + change.getId() + " of " + getProjectName(),
+            err);
+      }
+
+      ProjectState parentState = projectState.getParentState();
+      PrologEnvironment childEnv = env;
+      Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
+      projectsSeen.add(projectState.getProject().getNameKey());
+
+      Term resultsTerm = toListTerm(results);
+      while (parentState != null) {
+        if (!projectsSeen.add(parentState.getProject().getNameKey())) {
+          // parent has been seen before, stop walk up inheritance tree
+          break;
+        }
+        PrologEnvironment parentEnv;
+        try {
+          parentEnv = parentState.newPrologEnvironment();
+        } catch (CompileException err) {
+          throw new RuleEvalException("Cannot consult rules.pl for "
+              + parentState.getProject().getName(), err);
+        }
+
+        parentEnv.copyStoredValues(childEnv);
+        Term filterRule =
+            parentEnv.once("gerrit", filterRuleLocatorName, new VariableTerm());
+        try {
+          if (fastEvalLabels) {
+            env.once("gerrit", "assume_range_from_label");
+          }
+
+          Term[] template =
+              parentEnv.once("gerrit", filterRuleWrapperName, filterRule,
+                  resultsTerm, new VariableTerm());
+          resultsTerm = template[2];
+        } catch (PrologException err) {
+          throw new RuleEvalException("Exception calling " + filterRule
+              + " on change " + change.getId() + " of "
+              + parentState.getProject().getName(), err);
+        } catch (RuntimeException err) {
+          throw new RuleEvalException("Exception calling " + filterRule
+              + " on change " + change.getId() + " of "
+              + parentState.getProject().getName(), err);
+        }
+
+        parentState = parentState.getParentState();
+        childEnv = parentEnv;
+      }
+
+      return (ListTerm) resultsTerm;
+    } finally {
+      env.close();
+    }
+  }
+
+  private static Term toListTerm(List<Term> terms) {
+    Term list = Prolog.Nil;
+    for (int i = terms.size() - 1; i >= 0; i--) {
+      list = new ListTerm(terms.get(i), list);
+    }
+    return list;
+  }
+
+  Term getSubmitRule() {
+    return submitRule;
+  }
+
+  private String getProjectName() {
+    if (projectName == null) {
+      projectName = projectControl.getProjectState().getProject().getName();
+    }
+    return projectName;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index f44282a..67e1128 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -110,6 +110,7 @@
 
   private OutputStream outputStream = DisabledOutputStream.INSTANCE;
   private PrintWriter out;
+  private boolean moreResults;
 
   @Inject
   QueryProcessor(EventFactory eventFactory,
@@ -124,6 +125,7 @@
     this.maxLimit = currentUser.getCapabilities()
       .getRange(GlobalCapability.QUERY_LIMIT)
       .getMax();
+    this.moreResults = false;
   }
 
   int getLimit() {
@@ -234,6 +236,9 @@
 
     Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
     int limit = limit(s);
+    if (results.size() > maxLimit) {
+      moreResults = true;
+    }
     if (limit < results.size()) {
       results = results.subList(0, limit);
     }
@@ -260,8 +265,9 @@
         stats.runTimeMilliseconds = System.currentTimeMillis();
 
         List<ChangeData> results = queryChanges(queryString);
+        ChangeAttribute c = null;
         for (ChangeData d : results) {
-          ChangeAttribute c = eventFactory.asChangeAttribute(d.getChange());
+          c = eventFactory.asChangeAttribute(d.getChange());
           eventFactory.extend(c, d.getChange());
           eventFactory.addTrackingIds(c, d.trackingIds(db));
 
@@ -320,6 +326,9 @@
         }
 
         stats.rowCount = results.size();
+        if (moreResults) {
+          stats.resumeSortKey = c.sortKey;
+        }
         stats.runTimeMilliseconds =
             System.currentTimeMillis() - stats.runTimeMilliseconds;
         show(stats);
diff --git a/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java b/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
new file mode 100644
index 0000000..0f173c7
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
@@ -0,0 +1,58 @@
+// 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 gerrit;
+
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.project.ChangeControl;
+
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+public class PRED_project_default_submit_type_1 extends Predicate.P1 {
+
+  private static final SymbolTerm[] term;
+
+  static {
+    SubmitType[] val = SubmitType.values();
+    term = new SymbolTerm[val.length];
+    for (int i = 0; i < val.length; i++) {
+      term[i] = SymbolTerm.create(val[i].name());
+    }
+  }
+
+  public PRED_project_default_submit_type_1(Term a1, Operation n) {
+    arg1 = a1;
+    cont = n;
+  }
+
+  @Override
+  public Operation exec(Prolog engine) throws PrologException {
+    engine.setB0();
+    Term a1 = arg1.dereference();
+
+    ChangeControl control = StoredValues.CHANGE_CONTROL.get(engine);
+    SubmitType submitType = control.getProject().getSubmitType();
+
+    if (!a1.unify(term[submitType.ordinal()], engine.trail)) {
+      return engine.fail();
+    }
+    return cont;
+  }
+}
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index a75acc0..59289c0 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -140,12 +140,12 @@
 :- public can_submit/2.
 %%
 can_submit(SubmitRule, S) :-
-  call_submit_rule(SubmitRule, Tmp),
+  call_rule(SubmitRule, Tmp),
   Tmp =.. [submit | Ls],
   ( is_all_ok(Ls) -> S = ok(Tmp), ! ; S = not_ready(Tmp) ).
 
-call_submit_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
-call_submit_rule(X, Arg) :- !, F =.. [X, Arg], F.
+call_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
+call_rule(X, Arg) :- !, F =.. [X, Arg], F.
 
 is_all_ok([]).
 is_all_ok([label(_, ok(__)) | Ls]) :- is_all_ok(Ls).
@@ -155,6 +155,21 @@
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
+%% locate_helper
+%%
+%%   Returns user:Func if it exists otherwise returns gerrit:Default
+
+locate_helper(Func, Default, Arity, user:Func) :-
+    '$compiled_predicate'(user, Func, Arity), !.
+locate_helper(Func, Default, Arity, user:Func) :-
+    listN(Arity, P), C =.. [Func | P], clause(user:C, _), !.
+locate_helper(Func, Default, _, gerrit:Default).
+
+listN(0, []).
+listN(N, [_|T]) :- N > 0, N1 is N - 1, listN(N1, T).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
 %% locate_submit_rule/1:
 %%
 %%   Finds a submit_rule depending on what rules are available.
@@ -164,17 +179,32 @@
 %%
 
 locate_submit_rule(RuleName) :-
-  '$compiled_predicate'(user, submit_rule, 1),
-  !,
-  RuleName = user:submit_rule
-  .
-locate_submit_rule(RuleName) :-
-  clause(user:submit_rule(_), _),
-  !,
-  RuleName = user:submit_rule
-  .
-locate_submit_rule(RuleName) :-
-  RuleName = gerrit:default_submit.
+  locate_helper(submit_rule, default_submit, 1, RuleName).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% get_submit_type/2:
+%%
+%%   Executes the SubmitTypeRule and return the first solution
+%%
+:- public get_submit_type/2.
+%%
+get_submit_type(SubmitTypeRule, A) :-
+  call_rule(SubmitTypeRule, A), !.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% locate_submit_type/1:
+%%
+%%   Finds a submit_type_rule depending on what rules are available.
+%%   If none are available, use project_default_submit_type/1.
+%%
+:- public locate_submit_type/1.
+%%
+locate_submit_type(RuleName) :-
+  locate_helper(submit_type, project_default_submit_type, 1, RuleName).
 
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -306,6 +336,17 @@
 call_submit_filter(P:X, R, S) :- !, F =.. [X, R, S], P:F.
 call_submit_filter(X, R, S) :- F =.. [X, R, S], F.
 
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% filter_submit_type_results/3:
+%%
+%%   Executes the submit_type_filter against the result,
+%%   returns the filtered result.
+%%
+:- public filter_submit_type_results/3.
+%%
+filter_submit_type_results(Filter, In, Out) :- call_submit_filter(Filter, In, Out).
+
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
@@ -316,15 +357,26 @@
 :- public locate_submit_filter/1.
 %%
 locate_submit_filter(FilterName) :-
-  '$compiled_predicate'(user, submit_filter, 2),
-  !,
-  FilterName = user:submit_filter
-  .
-locate_submit_filter(FilterName) :-
-  clause(user:submit_filter(_,_), _),
-  FilterName = user:submit_filter
-  .
+  locate_helper(submit_filter, noop_filter, 2, FilterName).
 
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% noop_filter/2:
+%%
+:- public noop_filter/2.
+%%
+noop_filter(In, In).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% locate_submit_type_filter/1:
+%%
+%%   Finds a submit_type_filter if available.
+%%
+:- public locate_submit_type_filter/1.
+%%
+locate_submit_type_filter(FilterName) :-
+  locate_helper(submit_type_filter, noop_filter, 2, FilterName).
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
index 212ffb1..06926df 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
@@ -17,6 +17,8 @@
 # limitations under the License.
 #
 
+unset GREP_OPTIONS
+
 CHANGE_ID_AFTER="Bug|Issue"
 MSG="$1"
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
index 37197ec..5d72916 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -26,9 +26,11 @@
 
 public class ConfigUtilTest extends TestCase {
   public void testTimeUnit() {
+    assertEquals(ms(0, MILLISECONDS), parse("0"));
     assertEquals(ms(2, MILLISECONDS), parse("2ms"));
     assertEquals(ms(200, MILLISECONDS), parse("200 milliseconds"));
 
+    assertEquals(ms(0, SECONDS), parse("0s"));
     assertEquals(ms(2, SECONDS), parse("2s"));
     assertEquals(ms(231, SECONDS), parse("231sec"));
     assertEquals(ms(1, SECONDS), parse("1second"));
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 31b2422..3f3d166 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-sshd</artifactId>
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index 4886d09..5fe58eb 100644
--- a/gerrit-util-cli/pom.xml
+++ b/gerrit-util-cli/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index beedb8f..69ae3b5 100644
--- a/gerrit-util-ssl/pom.xml
+++ b/gerrit-util-ssl/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 1f3750e..a62831c 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.5-SNAPSHOT</version>
+    <version>2.6-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-war</artifactId>
diff --git a/pom.xml b/pom.xml
index d7b5988..c1e2fd8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>2.5-SNAPSHOT</version>
+  <version>2.6-SNAPSHOT</version>
 
   <name>Gerrit Code Review - Parent</name>
   <url>http://code.google.com/p/gerrit/</url>
@@ -47,7 +47,7 @@
 
   <properties>
     <jgitVersion>2.0.0.201206130900-r.23-gb3dbf19</jgitVersion>
-    <gwtormVersion>1.4</gwtormVersion>
+    <gwtormVersion>1.5</gwtormVersion>
     <gwtjsonrpcVersion>1.3</gwtjsonrpcVersion>
     <gwtexpuiVersion>1.2.6</gwtexpuiVersion>
     <gwtVersion>2.4.0</gwtVersion>
@@ -478,7 +478,7 @@
       <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
-        <version>12.0.1</version>
+        <version>13.0.1</version>
       </dependency>
 
       <dependency>
@@ -762,13 +762,13 @@
       <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
-        <version>1.2.147</version>
+        <version>1.3.168</version>
       </dependency>
 
       <dependency>
         <groupId>postgresql</groupId>
         <artifactId>postgresql</artifactId>
-        <version>9.0-801.jdbc4</version>
+        <version>9.1-901-1.jdbc4</version>
       </dependency>
 
       <dependency>
@@ -821,7 +821,7 @@
       </dependency>
 
       <dependency>
-        <groupId>com.google.gerrit</groupId>
+        <groupId>com.googlecode.juniversalchardet</groupId>
         <artifactId>juniversalchardet</artifactId>
         <version>1.0.3</version>
       </dependency>
@@ -843,6 +843,17 @@
         <artifactId>pegdown</artifactId>
         <version>1.1.0</version>
       </dependency>
+
+      <dependency>
+        <groupId>org.parboiled</groupId>
+        <artifactId>parboiled-core</artifactId>
+        <version>1.1</version>
+      </dependency>
+      <dependency>
+        <groupId>org.parboiled</groupId>
+        <artifactId>parboiled-java</artifactId>
+        <version>1.1</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
@@ -863,11 +874,6 @@
     </repository>
 
     <repository>
-      <id>objectweb-repository</id>
-      <url>http://maven.objectweb.org/maven2/</url>
-    </repository>
-
-    <repository>
       <id>clojars-repo</id>
       <url>http://clojars.org/repo</url>
     </repository>
@@ -876,5 +882,10 @@
       <id>scala-tools</id>
       <url>http://scala-tools.org/repo-releases</url>
     </repository>
+
+    <repository>
+      <id>parboiled-repo</id>
+      <url>http://repo.spray.cc/</url>
+    </repository>
   </repositories>
 </project>