Merge branch 'stable-2.5'

* stable-2.5:
  Fix test-submit-rule.
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 879d1ac..efd1f01 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.
@@ -585,7 +585,7 @@
 ^^^^^^^^^^^
 
 Any existing branch can be fast-forwarded to a new commit.
-Creation of new branches is controlled by the 
+Creation of new branches is controlled by the
 link:access-control.html#category_create['Create Reference']
 category.  Deletion of existing branches is rejected.  This is the
 safest mode as commits cannot be discarded.
@@ -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-gsql.txt b/Documentation/cmd-gsql.txt
index 3c4f1b5..b53670b 100644
--- a/Documentation/cmd-gsql.txt
+++ b/Documentation/cmd-gsql.txt
@@ -49,7 +49,7 @@
 
 	Type '\h' for help.  Type '\r' to clear the buffer.
 
-	gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;       
+	gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;
 	UPDATE 1; 1 ms
 	gerrit> \q
 	Bye
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..ce23da6 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -44,7 +44,8 @@
 *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*, *merge-failed*, *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.
@@ -105,6 +106,18 @@
 
 submitter:: link:json.html#account[account attribute]
 
+Merge Failed
+^^^^^^^^^^^^
+type:: "merge-failed"
+
+change:: link:json.html#change[change attribute]
+
+patchSet:: link:json.html#patchSet[patchSet attribute]
+
+submitter:: link:json.html#account[account attribute]
+
+reason:: Reason that the merge failed.
+
 Comment Added
 ^^^^^^^^^^^^^
 type:: "comment-added"
@@ -127,6 +140,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 1959995..03be009 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -133,7 +133,8 @@
 The actual username used in the LDAP simple bind request is the
 account's full DN, which is discovered by first querying the
 directory using either an anonymous request, or the configured
-<<ldap.username>> identity.
+<<ldap.username,ldap.username>> identity. Gerrit can also use kerberos if
+<<ldap.authentication,ldap.authentication>> is set to `GSSAPI`.
 
 * `LDAP_BIND`
 +
@@ -144,7 +145,7 @@
 +
 Unlike LDAP above, the username used to perform the LDAP simple bind
 request is the exact string supplied by in the dialog by the user.
-The configured <<ldap.username>> identity is not used to obtain
+The configured <<ldap.username,ldap.username>> identity is not used to obtain
 account information.
 +
 * `DEVELOPMENT_BECOME_ANY_ACCOUNT`
@@ -475,6 +476,15 @@
 requires two HTTP requests, and this cache tries to carry state from
 the first request into the second to ensure it can complete.
 
+cache `"changes"`::
++
+Each item caches all changes tied to one project. Default size
+is 1024, which means all changes for up to 1024 projects can be held
+in the cache. This cache should probably be disabled in a multi-master
+setup where change updates are not communicated between servers. The
+cache should be flushed whenever the database changes table is modified
+outside of gerrit.
+
 cache `"diff"`::
 +
 Each item caches the differences between two commits, at both the
@@ -652,16 +662,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
@@ -1272,6 +1289,11 @@
 Optional filename for the change merged hook, if not specified then
 `change-merged` will be used.
 
+[[hooks.mergeFailedHook]]hooks.mergeFailedHook::
++
+Optional filename for the merge failed hook, if not specified then
+`merge-failed` will be used.
+
 [[hooks.changeAbandonedHook]]hooks.changeAbandonedHook::
 +
 Optional filename for the change abandoned hook, if not specified then
@@ -1287,6 +1309,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
@@ -1304,13 +1331,13 @@
 [[http.proxyUsername]]http.proxyUsername::
 +
 Optional username to authenticate to the HTTP proxy with.
-This property is honored only if the username does not 
+This property is honored only if the username does not
 appear in the http.proxy property above.
 
 [[http.proxyPassword]]http.proxyPassword::
 +
 Optional password to authenticate to the HTTP proxy with.
-This property is honored only if the password does not 
+This property is honored only if the password does not
 appear in the http.proxy property above.
 
 
@@ -1706,6 +1733,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
@@ -1725,6 +1767,38 @@
 +
 By default, unset/false.
 
+[[ldap.authentication]]ldap.authentication::
++
+Defines how Gerrit authenticates with the server. When set to `GSSAPI`
+Gerrit will use Kerberos. To use kerberos the
+`java.security.auth.login.config` system property must point to a
+login to a JAAS configuration file and, if Java 6 is used, the system
+property `java.security.krb5.conf` must point to the appropriate
+krb5.ini file with references to the KDC.
+
+Typical jaas.conf.
+
+----
+KerberosLogin {
+    com.sun.security.auth.module.Krb5LoginModule
+            required
+            useTicketCache=true
+            doNotPrompt=true
+            renewTGT=true;
+};
+----
+
+See Java documentation on how to create the krb5.ini file.
+
+Note the `renewTGT` property to make sure the TGT does not expire,
+and `useTicketCache` to use the TGT supplied by the operating system. As
+the whole point of using GSSAPI is to have passwordless authentication
+to the LDAP service, this option does not aquire a new TGT on its own.
+
+On Windows servers the registry key `HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters`
+must have the DWORD value `allowtgtsessionkey` set to 1 and the account must not
+have local administrator privileges.
+
 [[mimetype]]Section mimetype
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2260,6 +2334,13 @@
 New configurations should prefer the boolean value for this field
 and an enum value for `accounts.visibility`.
 
+[[suggest.from]]suggest.from::
++
+The number of characters that a user must have typed before suggestions
+are provided. If set to 0, suggestions are always provided.
++
+By default 0.
+
 [[theme]] Section theme
 ~~~~~~~~~~~~~~~~~~~~~~~
 
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..dfac6d1 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -57,6 +57,15 @@
   change-merged --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --submitter <submitter> --commit <sha1>
 ====
 
+merge-failed
+~~~~~~~~~~~~
+
+Called whenever a change has failed to merge.
+
+====
+  merge-failed --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --submitter <submitter> --commit <sha1> --reason <reason>
+====
+
 change-abandoned
 ~~~~~~~~~~~~~~~~
 
@@ -84,6 +93,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 +122,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/config-mail.txt b/Documentation/config-mail.txt
index ad0704f..fc7060d 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -49,7 +49,14 @@
 
 The `Comment.vm` template will determine the contents of the email related to
 a user submitting comments on changes.  It is a `ChangeEmail`: see
-`ChangeSubject.vm` and `ChangeFooter.vm`.
+`ChangeSubject.vm`, `ChangeFooter.vm` and `CommentFooter.vm`.
+
+CommentFooter.vm
+~~~~~~~~~~~~~~~~
+
+The `CommentFooter.vm` template will determine the contents of the footer
+text that will be appended to emails related to a user submitting comments on
+changes.  See `ChangeSubject.vm`, `Comment.vm` and `ChangeFooter.vm`.
 
 Merged.vm
 ~~~~~~~~~
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index dd0c44c..5af9d1c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -176,6 +176,69 @@
 To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
 command can be used.
 
+[[init_step]]
+Init step
+~~~~~~~~~
+
+Plugins can contribute their own "init step" during the Gerrit init
+wizard. This is useful for guiding the Gerrit administrator through
+the settings needed by the plugin to work propertly.
+
+For instance plugins to integrate Jira issues to Gerrit changes may
+contribute their own "init step" to allow configuring the Jira URL,
+credentials and possibly verify connectivity to validate them.
+
+====
+  Gerrit-InitStep: tld.example.project.MyInitStep
+====
+
+MyInitStep needs to follow the standard Gerrit InitStep syntax
+and behaviour: writing to the console using the injected ConsoleUI
+and accessing / changing configuration settings using Section.Factory.
+
+In addition to the standard Gerrit init injections, plugins receive
+the @PluginName String injection containing their own plugin name.
+
+Bear in mind that the Plugin's InitStep class will be loaded but
+the standard Gerrit runtime environment is not available and the plugin's
+own Guice modules were not initialized.
+This means the InitStep for a plugin is not executed in the same way that
+the plugin executes within the server, and may mean a plugin author cannot
+trivially reuse runtime code during init.
+
+For instance a plugin that wants to verify connectivity may need to statically
+call the constructor of their connection class, passing in values obtained
+from the Section.Factory rather than from an injected Config object.
+
+Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
+the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
+and before the DB Schema initialization or upgrade.
+Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
+objects injected at startup.
+
+====
+public class MyInitStep implements InitStep {
+  private final ConsoleUI ui;
+  private final Section.Factory sections;
+  private final String pluginName;
+
+  @Inject
+  public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
+    this.ui = ui;
+    this.sections = sections;
+    this.pluginName = pluginName;
+  }
+
+  @Override
+  public void run() throws Exception {
+    ui.header("\nMy plugin");
+
+    Section mySection = getSection("myplugin", null);
+    mySection.string("Link name", "linkname", "MyLink");
+  }
+}
+====
+
 [[classpath]]
 Classpath
 ---------
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/intro-quick.txt b/Documentation/intro-quick.txt
index 25f5d5e..8253c91 100644
--- a/Documentation/intro-quick.txt
+++ b/Documentation/intro-quick.txt
@@ -138,10 +138,10 @@
 Compressing objects: 100% (2/2), done.
 Writing objects: 100% (3/3), 542 bytes, done.
 Total 3 (delta 0), reused 0 (delta 0)
-remote: 
+remote:
 remote: New Changes:
 remote:   http://gerrithost:8080/68
-remote: 
+remote:
 To ssh://gerrithost:29418/RecipeBook.git
  * [new branch]      HEAD -> refs/for/master
 ----
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/licenses.txt b/Documentation/licenses.txt
index 7787fe8..5d62c16 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -643,10 +643,10 @@
  distribute,  sublicense, and/or sell  copies of  the Software,  and to
  permit persons to whom the Software  is furnished to do so, subject to
  the following conditions:
- 
+
  The  above  copyright  notice  and  this permission  notice  shall  be
  included in all copies or substantial portions of the Software.
- 
+
  THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
  EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
  MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
@@ -664,9 +664,9 @@
 
 ----
 (The MIT License)
- 
+
 Copyright (c) 2008 Tom Preston-Werner
- 
+
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 'Software'), to deal in the Software without restriction, including
diff --git a/Documentation/pgm-gsql.txt b/Documentation/pgm-gsql.txt
index c3a492a..37fbb74 100644
--- a/Documentation/pgm-gsql.txt
+++ b/Documentation/pgm-gsql.txt
@@ -45,7 +45,7 @@
 
 	Type '\h' for help.  Type '\r' to clear the buffer.
 
-	gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;       
+	gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;
 	UPDATE 1; 1 ms
 	gerrit> \q
 	Bye
diff --git a/Documentation/project-setup.txt b/Documentation/project-setup.txt
index 3d979d3..feb4081 100644
--- a/Documentation/project-setup.txt
+++ b/Documentation/project-setup.txt
@@ -99,6 +99,13 @@
 the right order since inter-change dependencies will not be
 enforced for them.
 
+* Rebase If Necessary
++
+If the change being submitted is a strict superset of the destination
+branch, then the branch is fast-forwarded to the change.  If not,
+then the change is automatically rebased and then the branch is
+fast-forwarded to the change.
+
 When Gerrit tries to do a merge, by default the merge will only
 succeed if there is no path conflict. By selecting the checkbox
 `Automatically resolve conflicts` Gerrit will try do a content merge
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 2f9d03f..65d8cd7 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -133,7 +133,7 @@
   HTTP/1.1 200 OK
   Content-Disposition: attachment
   Content-Type: application/json;charset=UTF-8
-   
+
   )]}'
   {
     "external/bison": {
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/Documentation/user-search.txt b/Documentation/user-search.txt
index 8a231fe..438b4e7 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -1,7 +1,7 @@
 Gerrit Code Review - Searching Changes
 ======================================
 
-Default Searches 
+Default Searches
 ----------------
 
 Most basic searches can be viewed by clicking on a link along the top
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-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 27da20f..85a051b 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -146,7 +146,7 @@
     }
   }
 
-  @SuppressWarnings({"unchecked", "rawtypes", "cast"})
+  @SuppressWarnings({"unchecked", "cast"})
   @Override
   public <K, V> Cache<K, V> build(CacheBinding<K, V> def) {
     Preconditions.checkState(!started, "cache must be built before start");
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/AddBranchResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AddBranchResult.java
new file mode 100644
index 0000000..24e527dc
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AddBranchResult.java
@@ -0,0 +1,96 @@
+// 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;
+
+public class AddBranchResult {
+  protected ListBranchesResult listBranchesResult;
+  protected Error error;
+
+  protected AddBranchResult() {
+  }
+
+  public AddBranchResult(final Error error) {
+    this.error = error;
+  }
+
+  public AddBranchResult(final ListBranchesResult listBranchesResult) {
+    this.listBranchesResult = listBranchesResult;
+  }
+
+  public ListBranchesResult getListBranchesResult() {
+    return listBranchesResult;
+  }
+
+  public boolean hasError() {
+    return error != null;
+  }
+
+  public Error getError() {
+    return error;
+  }
+
+  public static class Error {
+    public static enum Type {
+      /** The branch cannot be created because the given branch name is invalid. */
+      INVALID_NAME,
+
+      /** The branch cannot be created because the given revision is invalid. */
+      INVALID_REVISION,
+
+      /**
+       * The branch cannot be created under the given refname prefix (e.g
+       * branches cannot be created under magic refname prefixes).
+       */
+      BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX,
+
+      /** The branch that should be created exists already. */
+      BRANCH_ALREADY_EXISTS,
+
+      /**
+       * The branch cannot be created because it conflicts with an existing
+       * branch (branches cannot be nested).
+       */
+      BRANCH_CREATION_CONFLICT
+    }
+
+    protected Type type;
+    protected String refname;
+
+    protected Error() {
+    }
+
+    public Error(final Type type) {
+      this(type, null);
+    }
+
+    public Error(final Type type, final String refname) {
+      this.type = type;
+      this.refname = refname;
+    }
+
+    public Type getType() {
+      return type;
+    }
+
+    public String getRefname() {
+      return refname;
+    }
+
+    @Override
+    public String toString() {
+      return type + " " + refname;
+    }
+  }
+}
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/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index 7c16129..bf9f9ab 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -51,6 +51,7 @@
   protected boolean documentationAvailable;
   protected boolean testChangeMerge;
   protected String anonymousCowardName;
+  protected int suggestFrom;
 
   public String getRegisterUrl() {
     return registerUrl;
@@ -238,6 +239,14 @@
     this.anonymousCowardName = anonymousCowardName;
   }
 
+  public int getSuggestFrom() {
+    return suggestFrom;
+  }
+
+  public void setSuggestFrom(final int suggestFrom) {
+    this.suggestFrom = suggestFrom;
+  }
+
   public boolean siteHasUsernames() {
     if (getAuthType() == AuthType.CUSTOM_EXTENSION
         && getHttpPasswordUrl() != null
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
index f991f4c..fc7f92d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
@@ -17,6 +17,8 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 
+import java.util.List;
+
 /** Data sent as part of the host page, to bootstrap the UI. */
 public class HostPageData {
   public Account account;
@@ -24,6 +26,7 @@
   public String xsrfToken;
   public GerritConfig config;
   public Theme theme;
+  public List<String> plugins;
 
   public static class Theme {
     public String backgroundColor;
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..cf4b28b 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
@@ -33,6 +33,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 +62,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/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index 13c0a48..a8aa045 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -66,7 +66,7 @@
   @Audit
   @SignInRequired
   void addBranch(Project.NameKey projectName, String branchName,
-      String startingRevision, AsyncCallback<ListBranchesResult> callback);
+      String startingRevision, AsyncCallback<AddBranchResult> callback);
 
   @Audit
   @SignInRequired
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-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
index 6c21553..21fa1b8 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
@@ -19,7 +19,6 @@
 import com.google.inject.Injector;
 import com.google.inject.Provider;
 import com.google.inject.TypeLiteral;
-import com.google.inject.internal.UniqueAnnotations;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -27,8 +26,6 @@
 import java.util.concurrent.atomic.AtomicReference;
 
 class DynamicSetProvider<T> implements Provider<DynamicSet<T>> {
-  private static final Class<?> UNIQUE_ANNOTATION =
-      UniqueAnnotations.create().getClass();
   private final TypeLiteral<T> type;
 
   @Inject
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/GwtPlugin.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/GwtPlugin.java
new file mode 100644
index 0000000..e741a3f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/GwtPlugin.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.webui;
+
+/** Configures a web UI plugin compiled using GWT. */
+public class GwtPlugin extends WebUiPlugin {
+  private final String moduleName;
+
+  /**
+   * @param moduleName name of GWT module. The resource
+   *        {@code static/$MODULE/$MODULE.nocache.js} will be used.
+   */
+  public GwtPlugin(String moduleName) {
+    this.moduleName = moduleName;
+  }
+
+  @Override
+  public String getJavaScriptResourcePath() {
+    return String.format("static/%s/%s.nocache.js", moduleName, moduleName);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
new file mode 100644
index 0000000..89a4f33
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/JavaScriptPlugin.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.webui;
+
+/** Configures a web UI plugin written using JavaScript. */
+public class JavaScriptPlugin extends WebUiPlugin {
+  private final String fileName;
+
+  /**
+   * @param fileName of JavaScript source file under {@code static/}
+   *        subdirectory within the plugin's JAR.
+   */
+  public JavaScriptPlugin(String fileName) {
+    this.fileName = fileName;
+  }
+
+  @Override
+  public String getJavaScriptResourcePath() {
+    return "static/" + fileName;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebUiPlugin.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebUiPlugin.java
new file mode 100644
index 0000000..5cd1981
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebUiPlugin.java
@@ -0,0 +1,69 @@
+// 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.extensions.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.inject.Inject;
+
+/**
+ * Specifies JavaScript to dynamically load into the web UI.
+ * <p>
+ * To automatically register (instead of writing a Guice module), declare the
+ * intention with {@code @Listen}, extend the correct class and define a
+ * constructor to configure the correct resource:
+ *
+ * <pre>
+ * &#064;Listen
+ * class MyJs extends JavaScriptPlugin {
+ *   MyJs() {
+ *     super(&quot;hello.js&quot;);
+ *   }
+ * }
+ * </pre>
+ *
+ * @see GwtPlugin
+ * @see JavaScriptPlugin
+ */
+@ExtensionPoint
+public abstract class WebUiPlugin {
+  public static final GwtPlugin gwt(String moduleName) {
+    return new GwtPlugin(moduleName);
+  }
+
+  public static final JavaScriptPlugin js(String scriptName) {
+    return new JavaScriptPlugin(scriptName);
+  }
+
+  private String pluginName;
+
+  /** @return installed name of the plugin that provides this UI feature.  */
+  public final String getPluginName() {
+    return pluginName;
+  }
+
+  @Inject
+  void setPluginName(@PluginName String pluginName) {
+    this.pluginName = pluginName;
+  }
+
+  /** @return path to initialization script within the plugin's JAR. */
+  public abstract String getJavaScriptResourcePath();
+
+  @Override
+  public String toString() {
+    return getJavaScriptResourcePath();
+  }
+}
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 01b93a6..9db434a 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>
@@ -73,14 +73,12 @@
     <dependency>
       <groupId>bouncycastle</groupId>
       <artifactId>bcprov-jdk15</artifactId>
-      <version>140</version>
       <scope>provided</scope>
     </dependency>
 
     <dependency>
       <groupId>bouncycastle</groupId>
       <artifactId>bcpg-jdk15</artifactId>
-      <version>140</version>
       <scope>provided</scope>
     </dependency>
 
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index b3291d1..e5496ba 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>
@@ -232,7 +232,7 @@
             <configuration>
               <target>
                 <property name="dst" location="${project.build.directory}/${project.build.finalName}"/>
-                <property name="app" location="${dst}/gerrit"/>
+                <property name="app" location="${dst}/gerrit_ui"/>
 
                 <mkdir dir="${app}"/>
                 <apply executable="gzip" addsourcefile="false">
@@ -257,7 +257,7 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-war-plugin</artifactId>
         <configuration>
-          <packagingExcludes>WEB-INF/classes/**,WEB-INF/lib/**</packagingExcludes>
+          <packagingExcludes>WEB-INF/classes/**,WEB-INF/deploy/**,WEB-INF/lib/**</packagingExcludes>
           <attachClasses>true</attachClasses>
           <archive>
             <addMavenDescriptor>false</addMavenDescriptor>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
index 8555c75..5fdc5bb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
@@ -13,7 +13,7 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<module rename-to="gerrit">
+<module rename-to="gerrit_ui">
   <inherits name='com.google.gwt.editor.Editor'/>
   <inherits name='com.google.gwt.user.User'/>
   <inherits name='com.google.gwt.resources.Resources'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIgecko1_8.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIgecko1_8.gwt.xml
index d1a9fe4..d7e835f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIgecko1_8.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIgecko1_8.gwt.xml
@@ -13,7 +13,7 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<module rename-to="gerrit">
+<module rename-to="gerrit_ui">
   <inherits name='com.google.gerrit.GerritGwtUI'/>
   <set-property name="user.agent" value="gecko1_8" />
   <set-property name="locale" value="default" />
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIsafari.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIsafari.gwt.xml
index 7655c1f..88bea84 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIsafari.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUIsafari.gwt.xml
@@ -13,7 +13,7 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<module rename-to="gerrit">
+<module rename-to="gerrit_ui">
   <inherits name='com.google.gerrit.GerritGwtUI'/>
   <set-property name="user.agent" value="safari" />
   <set-property name="locale" value="default" />
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 8fe658b..9276ba1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -42,10 +42,13 @@
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gwt.core.client.Callback;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.ScriptInjector;
 import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Document;
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.http.client.URL;
@@ -67,9 +70,12 @@
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.user.client.UserAgent;
 import com.google.gwtexpui.user.client.ViewSite;
+import com.google.gwtjsonrpc.client.CallbackHandle;
 import com.google.gwtjsonrpc.client.JsonDefTarget;
 import com.google.gwtjsonrpc.client.JsonUtil;
 import com.google.gwtjsonrpc.client.XsrfManager;
+import com.google.gwtjsonrpc.client.impl.ResultDeserializer;
+import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtorm.client.KeyUtil;
 
 import java.util.ArrayList;
@@ -370,7 +376,9 @@
 
     final HostPageDataService hpd = GWT.create(HostPageDataService.class);
     hpd.load(new GerritCallback<HostPageData>() {
+      @Override
       public void onSuccess(final HostPageData result) {
+        Document.get().getElementById("gerrit_hostpagedata").removeFromParent();
         myConfig = result.config;
         myTheme = result.theme;
         if (result.account != null) {
@@ -381,7 +389,7 @@
           myAccountDiffPref = result.accountDiffPref;
           applyUserPreferences();
         }
-        onModuleLoad2();
+        onModuleLoad2(result);
       }
     });
   }
@@ -463,7 +471,7 @@
     btmmenu.add(poweredBy);
   }
 
-  private void onModuleLoad2() {
+  private void onModuleLoad2(HostPageData hpd) {
     RESOURCES.gwt_override().ensureInjected();
     RESOURCES.css().ensureInjected();
 
@@ -541,6 +549,7 @@
     refreshMenuBar();
 
     History.addValueChangeHandler(new ValueChangeHandler<String>() {
+      @Override
       public void onValueChange(final ValueChangeEvent<String> event) {
         display(event.getValue());
       }
@@ -556,7 +565,50 @@
     if (signInAnchor != null) {
       signInAnchor.setHref(loginRedirect(token));
     }
-    display(token);
+    loadPlugins(hpd, token);
+  }
+
+  private void loadPlugins(HostPageData hpd, final String token) {
+    if (hpd.plugins != null) {
+      for (final String url : hpd.plugins) {
+        ScriptInjector.fromUrl(url)
+            .setWindow(ScriptInjector.TOP_WINDOW)
+            .setCallback(new Callback<Void, Exception>() {
+              @Override
+              public void onSuccess(Void result) {
+              }
+
+              @Override
+              public void onFailure(Exception reason) {
+                ErrorDialog d = new ErrorDialog(reason);
+                d.setTitle(M.pluginFailed(url));
+                d.center();
+              }
+            }).inject();
+      }
+    }
+
+    CallbackHandle<Void> cb = new CallbackHandle<Void>(
+        new ResultDeserializer<Void>() {
+          @Override
+          public Void fromResult(JavaScriptObject responseObject) {
+            return null;
+          }
+        },
+        new AsyncCallback<Void>() {
+          @Override
+          public void onFailure(Throwable caught) {
+          }
+
+          @Override
+          public void onSuccess(Void result) {
+            display(token);
+          }
+        });
+    cb.install();
+    ScriptInjector.fromString(cb.getFunctionName() + "();")
+        .setWindow(ScriptInjector.TOP_WINDOW)
+        .inject();
   }
 
   public static void refreshMenuBar() {
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..c9c2416 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();
@@ -59,6 +59,7 @@
   String commentCell();
   String commentEditorPanel();
   String commentHolder();
+  String commentHolderLeftmost();
   String commentPanel();
   String commentPanelBorder();
   String commentPanelAuthorCell();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
index aefa3a5..12e7402 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
@@ -22,6 +22,14 @@
   String poweredBy(String version);
 
   String noSuchAccountMessage(String who);
-
   String noSuchGroupMessage(String who);
+
+  String branchCreationFailed(String branchName, String error);
+  String invalidBranchName(String branchName);
+  String invalidRevision(String revision);
+  String branchCreationNotAllowedUnderRefnamePrefix(String refnamePrefix);
+  String branchAlreadyExists(String branchName);
+  String branchCreationConflict(String branchName, String existingBranchName);
+
+  String pluginFailed(String scriptPath);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
index 79772bd..84cf476 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
@@ -3,5 +3,13 @@
 poweredBy = Powered by <a href="http://code.google.com/p/gerrit/" target="_blank">Gerrit Code Review</a> ({0})
 
 noSuchAccountMessage = {0} is not a registered user.
-
 noSuchGroupMessage = Group {0} does not exist or is not visible to you.
+
+branchCreationFailed = Creating branch {0} failed. Error: {1}
+invalidBranchName = The branch name {0} is not valid.
+invalidRevision = The revision {0} is not valid.
+branchCreationNotAllowedUnderRefnamePrefix = Branch creation is not allowed under {0}.
+branchAlreadyExists = A branch with the name {0} already exists.
+branchCreationConflict = Cannot create branch {0} since it conflicts with branch {1}.
+
+pluginFailed = Plugin JavaScript {0} failed to load
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.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 0c18169..3966e93 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -68,6 +68,7 @@
   String projectSubmitType_FAST_FORWARD_ONLY();
   String projectSubmitType_MERGE_ALWAYS();
   String projectSubmitType_MERGE_IF_NECESSARY();
+  String projectSubmitType_REBASE_IF_NECESSARY();
   String projectSubmitType_CHERRY_PICK();
 
   String projectState_ACTIVE();
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..6737c58 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -46,6 +46,7 @@
 
 projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
 projectSubmitType_MERGE_IF_NECESSARY = Merge If Necessary
+projectSubmitType_REBASE_IF_NECESSARY = Rebase If Necessary
 projectSubmitType_MERGE_ALWAYS = Always Merge
 projectSubmitType_CHERRY_PICK = Cherry Pick
 
@@ -113,6 +114,7 @@
 	pushTag, \
 	read, \
 	rebase, \
+	removeReviewer, \
 	submit
 abandon = Abandon
 create = Create Reference
@@ -125,6 +127,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/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 56d9417..8aefb9b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -24,9 +24,8 @@
 import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.common.data.AddBranchResult;
 import com.google.gerrit.common.data.ListBranchesResult;
-import com.google.gerrit.common.errors.InvalidNameException;
-import com.google.gerrit.common.errors.InvalidRevisionException;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
@@ -44,9 +43,9 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtjsonrpc.client.RemoteJsonException;
 
 import java.util.HashSet;
 import java.util.List;
@@ -164,13 +163,13 @@
   }
 
   private void doAddNewBranch() {
-    String branchName = nameTxtBox.getText();
+    final String branchName = nameTxtBox.getText();
     if ("".equals(branchName)) {
       nameTxtBox.setFocus(true);
       return;
     }
 
-    String rev = irevTxtBox.getText();
+    final String rev = irevTxtBox.getText();
     if ("".equals(rev)) {
       irevTxtBox.setText("HEAD");
       Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@@ -183,41 +182,70 @@
       return;
     }
 
-    if (!branchName.startsWith(Branch.R_REFS)) {
-      branchName = Branch.R_HEADS + branchName;
-    }
-
     addBranch.setEnabled(false);
     Util.PROJECT_SVC.addBranch(getProjectKey(), branchName, rev,
-        new GerritCallback<ListBranchesResult>() {
-          public void onSuccess(final ListBranchesResult result) {
+        new GerritCallback<AddBranchResult>() {
+          public void onSuccess(final AddBranchResult result) {
             addBranch.setEnabled(true);
-            nameTxtBox.setText("");
-            irevTxtBox.setText("");
-            display(result.getBranches());
+            if (!result.hasError()) {
+              nameTxtBox.setText("");
+              irevTxtBox.setText("");
+              display(result.getListBranchesResult().getBranches());
+            } else {
+              final AddBranchResult.Error error = result.getError();
+              final String msg;
+              switch (error.getType()) {
+                case INVALID_NAME:
+                  selectAllAndFocus(nameTxtBox);
+                  msg = Gerrit.M.invalidBranchName(branchName);
+                  break;
+
+                case INVALID_REVISION:
+                  selectAllAndFocus(irevTxtBox);
+                  msg = Gerrit.M.invalidRevision(rev);
+                  break;
+
+                case BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX:
+                  selectAllAndFocus(nameTxtBox);
+                  msg =
+                      Gerrit.M.branchCreationNotAllowedUnderRefnamePrefix(error
+                          .getRefname());
+                  break;
+
+                case BRANCH_ALREADY_EXISTS:
+                  selectAllAndFocus(nameTxtBox);
+                  msg = Gerrit.M.branchAlreadyExists(error.getRefname());
+                  break;
+
+                case BRANCH_CREATION_CONFLICT:
+                  selectAllAndFocus(nameTxtBox);
+                  msg =
+                      Gerrit.M.branchCreationConflict(branchName,
+                          error.getRefname());
+                  break;
+
+                default:
+                  msg =
+                      Gerrit.M.branchCreationFailed(branchName,
+                          error.toString());
+              }
+              new ErrorDialog(msg).center();
+            }
           }
 
           @Override
           public void onFailure(final Throwable caught) {
-            if (caught instanceof InvalidNameException
-                || caught instanceof RemoteJsonException
-                && caught.getMessage().equals(InvalidNameException.MESSAGE)) {
-              nameTxtBox.selectAll();
-              nameTxtBox.setFocus(true);
-
-            } else if (caught instanceof InvalidRevisionException
-                || caught instanceof RemoteJsonException
-                && caught.getMessage().equals(InvalidRevisionException.MESSAGE)) {
-              irevTxtBox.selectAll();
-              irevTxtBox.setFocus(true);
-            }
-
             addBranch.setEnabled(true);
             super.onFailure(caught);
           }
         });
   }
 
+  private static void selectAllAndFocus(final TextBox textBox) {
+    textBox.selectAll();
+    textBox.setFocus(true);
+  }
+
   private class BranchesTable extends FancyFlexTable<Branch> {
     boolean canDelete;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index aa5b7b6..4f8dd99 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -45,6 +45,8 @@
         return C.projectSubmitType_FAST_FORWARD_ONLY();
       case MERGE_IF_NECESSARY:
         return C.projectSubmitType_MERGE_IF_NECESSARY();
+      case REBASE_IF_NECESSARY:
+        return C.projectSubmitType_REBASE_IF_NECESSARY();
       case MERGE_ALWAYS:
         return C.projectSubmitType_MERGE_ALWAYS();
       case CHERRY_PICK:
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..32d92fe 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,6 +15,7 @@
 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.gwt.user.client.ui.Composite;
@@ -36,8 +37,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..b161897 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;
@@ -659,9 +655,11 @@
   padding-left: 0;
   padding-right: 0;
   border-top: 1px solid black;
-  border-left: 1px solid black;
   border-right: 1px solid black;
 }
+.patchContentTable td.commentHolderLeftmost {
+  border-left: 1px solid black;
+}
 .patchContentTable td.commentHolder.commentPanelLast {
   border-bottom: 1px solid black;
 }
@@ -871,6 +869,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/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index 24a2ae5..dc4db73 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -427,7 +427,9 @@
         spans[col] = table.getFlexCellFormatter().getRowSpan(row, cell);
         if (col == column) {
           final Widget w = table.getWidget(row, cell);
-          if (w instanceof CommentEditorPanel) {
+          if (w instanceof CommentEditorPanel
+              && ((CommentEditorPanel) w).getComment().getKey().getParentKey()
+                  .equals(newComment.getKey().getParentKey())) {
             // Don't insert two editors on the same position, it doesn't make
             // any sense to the user.
             //
@@ -441,6 +443,7 @@
               break FIND_ROW;
             }
             row++;
+            cell--;
           } else {
             break FIND_ROW;
           }
@@ -632,6 +635,9 @@
         Gerrit.RESOURCES.css().commentPanelLast());
     fmt.setStyleName(row, col, Gerrit.RESOURCES.css().commentHolder());
     fmt.addStyleName(row, col, Gerrit.RESOURCES.css().commentPanelLast());
+    if (!fmt.getStyleName(row, col - 1).contains(Gerrit.RESOURCES.css().commentHolder())) {
+      fmt.addStyleName(row, col, Gerrit.RESOURCES.css().commentHolderLeftmost());
+    }
   }
 
   protected static class CommentList {
@@ -808,10 +814,10 @@
     }
 
     private void cannedReply(String message) {
-      CommentEditorPanel p = createEditor(null);
+      final PatchLineComment newComment = newComment();
+      newComment.setMessage(message);
+      CommentEditorPanel p = createEditor(newComment);
       if (p == null) {
-        final PatchLineComment newComment = newComment();
-        newComment.setMessage(message);
 
         enableButtons(false);
         PatchUtil.DETAIL_SVC.saveDraft(newComment,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
index b0d825b..b609f15 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
@@ -198,6 +198,10 @@
     return comment.getKey().get() == null;
   }
 
+  public PatchLineComment getComment() {
+    return comment;
+  }
+
   @Override
   public void onDoubleClick(final DoubleClickEvent event) {
     edit();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
index 5da00cd..278e159 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.user.client.ui.SuggestOracle;
-import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -28,14 +27,14 @@
 import java.util.Map;
 
 /** Suggestion Oracle for AccountGroup entities. */
-public class AccountGroupSuggestOracle extends HighlightSuggestOracle {
+public class AccountGroupSuggestOracle extends SuggestAfterTypingNCharsOracle {
   private Map<String, AccountGroup.UUID> priorResults =
       new HashMap<String, AccountGroup.UUID>();
 
   private Project.NameKey projectName;
 
   @Override
-  public void onRequestSuggestions(final Request req, final Callback callback) {
+  public void _onRequestSuggestions(final Request req, final Callback callback) {
     RpcStatus.hide(new Runnable() {
       public void run() {
         SuggestUtil.SVC.suggestAccountGroupForProject(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
index bcf6438..b113a3b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
@@ -19,15 +19,14 @@
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.common.data.AccountInfo;
 import com.google.gwt.user.client.ui.SuggestOracle;
-import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Suggestion Oracle for Account entities. */
-public class AccountSuggestOracle extends HighlightSuggestOracle {
+public class AccountSuggestOracle extends SuggestAfterTypingNCharsOracle {
   @Override
-  public void onRequestSuggestions(final Request req, final Callback callback) {
+  public void _onRequestSuggestions(final Request req, final Callback callback) {
     RpcStatus.hide(new Runnable() {
       public void run() {
         SuggestUtil.SVC.suggestAccount(req.getQuery(), Boolean.TRUE,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java
index 25ed258..40a6834 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectNameSuggestOracle.java
@@ -17,12 +17,11 @@
 import com.google.gerrit.client.RpcStatus;
 import com.google.gerrit.client.projects.ProjectMap;
 import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
 
 /** Suggestion Oracle for Project.NameKey entities. */
-public class ProjectNameSuggestOracle extends HighlightSuggestOracle {
+public class ProjectNameSuggestOracle extends SuggestAfterTypingNCharsOracle {
   @Override
-  public void onRequestSuggestions(final Request req, final Callback callback) {
+  public void _onRequestSuggestions(final Request req, final Callback callback) {
     RpcStatus.hide(new Runnable() {
       @Override
       public void run() {
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-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
index 747ef40..f3b1439 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
@@ -22,18 +22,17 @@
 import com.google.gerrit.common.data.ReviewerInfo;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.user.client.ui.SuggestOracle;
-import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Suggestion Oracle for reviewers. */
-public class ReviewerSuggestOracle extends HighlightSuggestOracle {
+public class ReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
 
   private Change.Id changeId;
 
   @Override
-  protected void onRequestSuggestions(final Request req, final Callback callback) {
+  protected void _onRequestSuggestions(final Request req, final Callback callback) {
     RpcStatus.hide(new Runnable() {
       public void run() {
         SuggestUtil.SVC.suggestChangeReviewer(changeId, req.getQuery(),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
new file mode 100644
index 0000000..4f54ba5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
@@ -0,0 +1,36 @@
+// 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.client.ui;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+/**
+ * Suggest oracle that only provides suggestions if the user has typed at least
+ * as many characters as configured by 'suggest.from'. If 'suggest.from' is set
+ * to 0, suggestions will always be provided.
+ */
+public abstract class SuggestAfterTypingNCharsOracle extends HighlightSuggestOracle {
+
+  @Override
+  protected void onRequestSuggestions(final Request request, final Callback done) {
+    final int suggestFrom = Gerrit.getConfig().getSuggestFrom();
+    if (suggestFrom == 0 || request.getQuery().length() >= suggestFrom) {
+      _onRequestSuggestions(request, done);
+    }
+  }
+
+  protected abstract void _onRequestSuggestions(Request request, Callback done);
+}
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/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index c1f3ae4..b5a4b3c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -121,6 +121,7 @@
     config.setTestChangeMerge(cfg.getBoolean("changeMerge",
         "test", false));
     config.setAnonymousCowardName(anonymousCowardName);
+    config.setSuggestFrom(cfg.getInt("suggest", "from", 0));
 
     config.setReportBugUrl(cfg.getString("gerrit", null, "reportBugUrl"));
     if (config.getReportBugUrl() == null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index aea42f4..e6a05c6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
+import com.google.gerrit.server.git.ChangeCache;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ReceiveCommits;
 import com.google.gerrit.server.git.TagCache;
@@ -198,11 +199,13 @@
   static class UploadFilter implements Filter {
     private final Provider<ReviewDb> db;
     private final TagCache tagCache;
+    private final ChangeCache changeCache;
 
     @Inject
-    UploadFilter(Provider<ReviewDb> db, TagCache tagCache) {
+    UploadFilter(Provider<ReviewDb> db, TagCache tagCache, ChangeCache changeCache) {
       this.db = db;
       this.tagCache = tagCache;
+      this.changeCache = changeCache;
     }
 
     @Override
@@ -221,7 +224,7 @@
       }
 
       if (!pc.allRefsAreVisible()) {
-        up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, repo, pc, db.get(), true));
+        up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo, pc, db.get(), true));
       }
 
       next.doFilter(request, response);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 1a48bb5..591d97c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -18,6 +18,8 @@
 import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
 
 import com.google.gerrit.common.data.GerritConfig;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
 import com.google.gerrit.httpd.auth.container.HttpAuthModule;
 import com.google.gerrit.httpd.auth.container.HttpsClientSslCertModule;
@@ -130,6 +132,7 @@
         SINGLETON);
     bind(GerritConfigProvider.class);
     bind(GerritConfig.class).toProvider(GerritConfigProvider.class);
+    DynamicSet.setOf(binder(), WebUiPlugin.class);
 
     bind(AccountManager.class);
     bind(ChangeUserName.CurrentUser.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 0821496..d088150 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -152,7 +152,7 @@
       throw new FileNotFoundException("No " + pageName + " in webapp");
     }
     if (!IS_DEV) {
-      final Element devmode = HtmlDomUtil.find(doc, "gerrit_gwtdevmode");
+      final Element devmode = HtmlDomUtil.find(doc, "gwtdevmode");
       if (devmode != null) {
         devmode.getParentNode().removeChild(devmode);
       }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index e737700..d009261 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -501,6 +501,10 @@
     }
     if (contentType == null) {
       contentType = mimeUtil.getMimeType(entry.getName(), data).toString();
+      if ("application/octet-stream".equals(contentType)
+          && entry.getName().endsWith(".js")) {
+        contentType = "application/javascript";
+      }
     }
 
     long time = entry.getTime();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 6a43d95..fdaca60 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -14,25 +14,29 @@
 
 package com.google.gerrit.httpd.raw;
 
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Bytes;
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.common.data.HostPageData;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.httpd.HtmlDomUtil;
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gwtjsonrpc.server.RPCServletUtils;
 import com.google.gwtexpui.linker.server.Permutation;
 import com.google.gwtexpui.linker.server.PermutationSelector;
 import com.google.gwtjsonrpc.server.JsonServlet;
+import com.google.gwtjsonrpc.server.RPCServletUtils;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -44,8 +48,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StringWriter;
-import java.security.MessageDigest;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.servlet.ServletContext;
@@ -66,6 +70,7 @@
   private final Provider<CurrentUser> currentUser;
   private final Provider<WebSession> session;
   private final GerritConfig config;
+  private final DynamicSet<WebUiPlugin> plugins;
   private final HostPageData.Theme signedOutTheme;
   private final HostPageData.Theme signedInTheme;
   private final SitePaths site;
@@ -79,15 +84,18 @@
   HostPageServlet(final Provider<CurrentUser> cu, final Provider<WebSession> w,
       final SitePaths sp, final ThemeFactory themeFactory,
       final GerritConfig gc, final ServletContext servletContext,
+      final DynamicSet<WebUiPlugin> webUiPlugins,
       @GerritServerConfig final Config cfg)
       throws IOException, ServletException {
     currentUser = cu;
     session = w;
     config = gc;
+    plugins = webUiPlugins;
     signedOutTheme = themeFactory.getSignedOutTheme();
     signedInTheme = themeFactory.getSignedInTheme();
     site = sp;
     refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
+    boolean checkUserAgent = cfg.getBoolean("site", "checkUserAgent", true);
 
     final String pageName = "HostPage.html";
     template = HtmlDomUtil.parseFile(getClass(), pageName);
@@ -102,40 +110,40 @@
       throw new ServletException("No " + HPD_ID + " in " + pageName);
     }
 
-    final String src = "gerrit/gerrit.nocache.js";
-    selector = new PermutationSelector("gerrit");
-    if (IS_DEV || !cfg.getBoolean("site", "checkUserAgent", true)) {
-      noCacheName = src;
-    } else {
-      final Element devmode = HtmlDomUtil.find(template, "gerrit_gwtdevmode");
+    String src = "gerrit_ui/gerrit_ui.nocache.js";
+    if (!IS_DEV) {
+      Element devmode = HtmlDomUtil.find(template, "gwtdevmode");
       if (devmode != null) {
         devmode.getParentNode().removeChild(devmode);
       }
 
       InputStream in = servletContext.getResourceAsStream("/" + src);
-      if (in == null) {
-        throw new IOException("No " + src + " in webapp root");
-      }
-
-      final MessageDigest md = Constants.newMessageDigest();
-      try {
+      if (in != null) {
+        Hasher md = Hashing.md5().newHasher();
         try {
-          final byte[] buf = new byte[1024];
-          int n;
-          while ((n = in.read(buf)) > 0) {
-            md.update(buf, 0, n);
+          try {
+            final byte[] buf = new byte[1024];
+            int n;
+            while ((n = in.read(buf)) > 0) {
+              md.putBytes(buf, 0, n);
+            }
+          } finally {
+            in.close();
           }
-        } finally {
-          in.close();
+        } catch (IOException e) {
+          throw new IOException("Failed reading " + src, e);
         }
-      } catch (IOException e) {
-        throw new IOException("Failed reading " + src, e);
+        src += "?content=" + md.hash().toString();
+      } else {
+        log.debug("No " + src + " in webapp root; keeping noncache.js URL");
       }
-      final String id = ObjectId.fromRaw(md.digest()).name();
-      noCacheName = src + "?content=" + id;
-      selector.init(servletContext);
     }
 
+    noCacheName = src;
+    selector = new PermutationSelector("gerrit_ui");
+    if (checkUserAgent && !IS_DEV) {
+      selector.init(servletContext);
+    }
     page = new Page();
   }
 
@@ -163,11 +171,9 @@
   protected void doGet(final HttpServletRequest req,
       final HttpServletResponse rsp) throws IOException {
     final Page.Content page = get().get(select(req));
-    final byte[] raw;
-
+    final StringWriter w = new StringWriter();
     final CurrentUser user = currentUser.get();
     if (user instanceof IdentifiedUser) {
-      final StringWriter w = new StringWriter();
       w.write(HPD_ID + ".account=");
       json(((IdentifiedUser) user).getAccount(), w);
       w.write(";");
@@ -183,17 +189,19 @@
       w.write(HPD_ID + ".theme=");
       json(signedInTheme, w);
       w.write(";");
-
-      final byte[] userData = w.toString().getBytes("UTF-8");
-      raw = concat(page.part1, userData, page.part2);
     } else {
-      raw = page.full;
+      w.write(HPD_ID + ".theme=");
+      json(signedOutTheme, w);
+      w.write(";");
     }
+    plugins(w);
 
+    final byte[] hpd = w.toString().getBytes("UTF-8");
+    final byte[] raw = Bytes.concat(page.part1, hpd, page.part2);
     final byte[] tosend;
     if (RPCServletUtils.acceptsGzipEncoding(req)) {
       rsp.setHeader("Content-Encoding", "gzip");
-      tosend = raw == page.full ? page.full_gz : HtmlDomUtil.compress(raw);
+      tosend = HtmlDomUtil.compress(raw);
     } else {
       tosend = raw;
     }
@@ -212,6 +220,20 @@
     }
   }
 
+  private void plugins(StringWriter w) {
+    List<String> urls = Lists.newArrayList();
+    for (WebUiPlugin u : plugins) {
+      urls.add(String.format("plugins/%s/%s",
+          u.getPluginName(),
+          u.getJavaScriptResourcePath()));
+    }
+    if (!urls.isEmpty()) {
+      w.write(HPD_ID + ".plugins=");
+      json(urls, w);
+      w.write(";");
+    }
+  }
+
   private Permutation select(final HttpServletRequest req) {
     if ("0".equals(req.getParameter("s"))) {
       // If s=0 is used in the URL, the user has explicitly asked us
@@ -223,20 +245,6 @@
     return selector.select(req);
   }
 
-  private static byte[] concat(byte[] p1, byte[] p2, byte[] p3) {
-    final byte[] r = new byte[p1.length + p2.length + p3.length];
-    int p = 0;
-    p = append(p1, r, p);
-    p = append(p2, r, p);
-    p = append(p3, r, p);
-    return r;
-  }
-
-  private static int append(byte[] src, final byte[] dst, int p) {
-    System.arraycopy(src, 0, dst, p, src.length);
-    return p + src.length;
-  }
-
   private static class FileInfo {
     private final File path;
     private final long time;
@@ -288,6 +296,7 @@
 
       Element nocache = HtmlDomUtil.find(hostDoc, "gerrit_module");
       asScript(nocache);
+      nocache.removeAttribute("id");
       nocache.setAttribute("src", noCacheName);
       permutations.put(null, new Content(hostDoc));
     }
@@ -305,7 +314,6 @@
     }
 
     private void asScript(final Element scriptNode) {
-      scriptNode.removeAttribute("id");
       scriptNode.setAttribute("type", "text/javascript");
       scriptNode.setAttribute("language", "javascript");
     }
@@ -313,8 +321,6 @@
     class Content {
       final byte[] part1;
       final byte[] part2;
-      final byte[] full;
-      final byte[] full_gz;
 
       Content(Document hostDoc) throws IOException {
         final String raw = HtmlDomUtil.toString(hostDoc);
@@ -324,15 +330,6 @@
         }
         part1 = raw.substring(0, p).getBytes("UTF-8");
         part2 = raw.substring(raw.indexOf('>', p) + 1).getBytes("UTF-8");
-
-        final StringWriter w = new StringWriter();
-        w.write(HPD_ID + ".theme=");
-        json(signedOutTheme, w);
-        w.write(";");
-
-        final byte[] themeData = w.toString().getBytes("UTF-8");
-        full = concat(part1, themeData, part2);
-        full_gz = HtmlDomUtil.compress(full);
       }
     }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/RpcServletModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/RpcServletModule.java
index 876fee3..953bc71 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/RpcServletModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/RpcServletModule.java
@@ -22,7 +22,7 @@
 
 /** Binds {@link RemoteJsonService} implementations to a JSON servlet. */
 public abstract class RpcServletModule extends ServletModule {
-  public static final String PREFIX = "/gerrit/rpc/";
+  public static final String PREFIX = "/gerrit_ui/rpc/";
 
   private final String prefix;
 
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/ChangeManageServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
index f99921b..7ec5f34 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
@@ -24,7 +24,7 @@
 class ChangeManageServiceImpl implements ChangeManageService {
   private final SubmitAction.Factory submitAction;
   private final AbandonChangeHandler.Factory abandonChangeHandlerFactory;
-  private final RebaseChange.Factory rebaseChangeFactory;
+  private final RebaseChangeHandler.Factory rebaseChangeFactory;
   private final RestoreChangeHandler.Factory restoreChangeHandlerFactory;
   private final RevertChange.Factory revertChangeFactory;
   private final PublishAction.Factory publishAction;
@@ -33,7 +33,7 @@
   @Inject
   ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction,
       final AbandonChangeHandler.Factory abandonChangeHandlerFactory,
-      final RebaseChange.Factory rebaseChangeFactory,
+      final RebaseChangeHandler.Factory rebaseChangeFactory,
       final RestoreChangeHandler.Factory restoreChangeHandlerFactory,
       final RevertChange.Factory revertChangeFactory,
       final PublishAction.Factory publishAction,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
index b672a439..01e0262 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
@@ -31,7 +31,7 @@
         factory(AbandonChangeHandler.Factory.class);
         factory(RestoreChangeHandler.Factory.class);
         factory(RevertChange.Factory.class);
-        factory(RebaseChange.Factory.class);
+        factory(RebaseChangeHandler.Factory.class);
         factory(ChangeDetailFactory.Factory.class);
         factory(IncludedInDetailFactory.Factory.class);
         factory(PatchSetDetailFactory.Factory.class);
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-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChange.java
deleted file mode 100644
index e71e302..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChange.java
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.ChangeHookRunner;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mail.EmailException;
-import com.google.gerrit.server.mail.RebasedPatchSetSender;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.PersonIdent;
-
-import java.io.IOException;
-
-class RebaseChange extends Handler<ChangeDetail> {
-  interface Factory {
-    RebaseChange create(PatchSet.Id patchSetId);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final IdentifiedUser currentUser;
-  private final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory;
-
-  private final ChangeDetailFactory.Factory changeDetailFactory;
-  private final GitReferenceUpdated replication;
-
-  private final PatchSet.Id patchSetId;
-
-  private final ChangeHookRunner hooks;
-
-  private final GitRepositoryManager gitManager;
-  private final PatchSetInfoFactory patchSetInfoFactory;
-
-  private final PersonIdent myIdent;
-
-  private final ApprovalsUtil approvalsUtil;
-
-  @Inject
-  RebaseChange(final ChangeControl.Factory changeControlFactory,
-      final ReviewDb db, final IdentifiedUser currentUser,
-      final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
-      final ChangeDetailFactory.Factory changeDetailFactory,
-      @Assisted final PatchSet.Id patchSetId, final ChangeHookRunner hooks,
-      final GitRepositoryManager gitManager,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      final GitReferenceUpdated replication,
-      @GerritPersonIdent final PersonIdent myIdent,
-      final ApprovalsUtil approvalsUtil) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.currentUser = currentUser;
-    this.rebasedPatchSetSenderFactory = rebasedPatchSetSenderFactory;
-    this.changeDetailFactory = changeDetailFactory;
-
-    this.patchSetId = patchSetId;
-    this.hooks = hooks;
-    this.gitManager = gitManager;
-
-    this.patchSetInfoFactory = patchSetInfoFactory;
-    this.replication = replication;
-    this.myIdent = myIdent;
-
-    this.approvalsUtil = approvalsUtil;
-  }
-
-  @Override
-  public ChangeDetail call() throws NoSuchChangeException, OrmException,
-      EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      InvalidChangeOperationException {
-
-    ChangeUtil.rebaseChange(patchSetId, currentUser, db,
-        rebasedPatchSetSenderFactory, hooks, gitManager, patchSetInfoFactory,
-        replication, myIdent, changeControlFactory, approvalsUtil);
-
-    return changeDetailFactory.create(patchSetId.getParentKey()).call();
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
new file mode 100644
index 0000000..5d17073
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.changedetail;
+
+import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.mail.EmailException;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+import java.io.IOException;
+
+class RebaseChangeHandler extends Handler<ChangeDetail> {
+  interface Factory {
+    RebaseChangeHandler create(PatchSet.Id patchSetId);
+  }
+
+  private final RebaseChange rebaseChange;
+  private final IdentifiedUser currentUser;
+  private final ChangeDetailFactory.Factory changeDetailFactory;
+
+  private final PatchSet.Id patchSetId;
+
+  @Inject
+  RebaseChangeHandler(final RebaseChange.Factory rebaseChangeFactory,
+      final IdentifiedUser currentUser,
+      final ChangeDetailFactory.Factory changeDetailFactory,
+      @Assisted final PatchSet.Id patchSetId) {
+    this.rebaseChange = rebaseChangeFactory.create();
+    this.currentUser = currentUser;
+    this.changeDetailFactory = changeDetailFactory;
+
+    this.patchSetId = patchSetId;
+  }
+
+  @Override
+  public ChangeDetail call() throws NoSuchChangeException, OrmException,
+      EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
+      MissingObjectException, IncorrectObjectTypeException, IOException,
+      InvalidChangeOperationException {
+    rebaseChange.rebase(patchSetId, currentUser.getAccountId());
+    return changeDetailFactory.create(patchSetId.getParentKey()).call();
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
index 97e9bb4..4e14a39 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
@@ -15,8 +15,7 @@
 package com.google.gerrit.httpd.rpc.project;
 
 import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.data.ListBranchesResult;
-import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.common.data.AddBranchResult;
 import com.google.gerrit.common.errors.InvalidRevisionException;
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -46,7 +45,7 @@
 
 import java.io.IOException;
 
-class AddBranch extends Handler<ListBranchesResult> {
+class AddBranch extends Handler<AddBranchResult> {
   private static final Logger log = LoggerFactory.getLogger(AddBranch.class);
 
   interface Factory {
@@ -90,9 +89,7 @@
   }
 
   @Override
-  public ListBranchesResult call() throws NoSuchProjectException,
-      InvalidNameException, InvalidRevisionException, IOException,
-      BranchCreationNotAllowedException {
+  public AddBranchResult call() throws NoSuchProjectException, IOException {
     final ProjectControl projectControl =
         projectControlFactory.controlFor(projectName);
 
@@ -104,10 +101,14 @@
       refname = Constants.R_HEADS + refname;
     }
     if (!Repository.isValidRefName(refname)) {
-      throw new InvalidNameException();
+      return new AddBranchResult(new AddBranchResult.Error(
+          AddBranchResult.Error.Type.INVALID_NAME, refname));
     }
     if (MagicBranch.isMagicBranch(refname)) {
-      throw new BranchCreationNotAllowedException(refname);
+      return new AddBranchResult(
+          new AddBranchResult.Error(
+              AddBranchResult.Error.Type.BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX,
+              MagicBranch.getMagicRefNamePrefix(refname)));
     }
 
     final Branch.NameKey name = new Branch.NameKey(projectName, refname);
@@ -147,6 +148,19 @@
             referenceUpdated.fire(name.getParentKey(), refname);
             hooks.doRefUpdatedHook(name, u, identifiedUser.getAccount());
             break;
+          case LOCK_FAILURE:
+            if (repo.getRef(refname) != null) {
+              return new AddBranchResult(new AddBranchResult.Error(
+                  AddBranchResult.Error.Type.BRANCH_ALREADY_EXISTS, refname));
+            }
+            String refPrefix = getRefPrefix(refname);
+            while (!Constants.R_HEADS.equals(refPrefix)) {
+              if (repo.getRef(refPrefix) != null) {
+                return new AddBranchResult(new AddBranchResult.Error(
+                    AddBranchResult.Error.Type.BRANCH_CREATION_CONFLICT, refPrefix));
+              }
+              refPrefix = getRefPrefix(refPrefix);
+            }
           default: {
             throw new IOException(result.name());
           }
@@ -155,11 +169,22 @@
         log.error("Cannot create branch " + name, err);
         throw err;
       }
+    } catch (InvalidRevisionException e) {
+      return new AddBranchResult(new AddBranchResult.Error(
+          AddBranchResult.Error.Type.INVALID_REVISION));
     } finally {
       repo.close();
     }
 
-    return listBranchesFactory.create(projectName).call();
+    return new AddBranchResult(listBranchesFactory.create(projectName).call());
+  }
+
+  private static String getRefPrefix(final String refName) {
+    final int i = refName.lastIndexOf('/');
+    if (i > Constants.R_HEADS.length() - 1) {
+      return refName.substring(0, i);
+    }
+    return Constants.R_HEADS;
   }
 
   private ObjectId parseStartingRevision(final Repository repo)
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index 983f1fc..35014d2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.httpd.rpc.project;
 
 import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.AddBranchResult;
 import com.google.gerrit.common.data.ListBranchesResult;
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.common.data.ProjectAdminService;
@@ -126,7 +127,7 @@
   @Override
   public void addBranch(final Project.NameKey projectName,
       final String branchName, final String startingRevision,
-      final AsyncCallback<ListBranchesResult> callback) {
+      final AsyncCallback<AddBranchResult> callback) {
     addBranchFactory.create(projectName, branchName, startingRevision).to(
         callback);
   }
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/become/BecomeAnyAccount.html b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/become/BecomeAnyAccount.html
index c3c7503..a4e3cca 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/become/BecomeAnyAccount.html
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/become/BecomeAnyAccount.html
@@ -1,10 +1,10 @@
 <html>
   <head>
     <title>Gerrit Code Review</title>
-    <script id="gerrit_gwtdevmode">
+    <script id="gwtdevmode">
       (function () {
         var pn = 'gwt.codesvr';
-        var cn = 'gerrit.' + pn;
+        var cn = 'gerrit_ui.' + pn;
 
         var p_start = window.location.search.indexOf(pn + '=');
         if (p_start != -1) {
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html
index ff09f50..abc3a4a 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html
@@ -2,10 +2,10 @@
   <head>
     <title>Gerrit Code Review</title>
     <meta name="gwt:property" content="locale=en_US" />
-    <script id="gerrit_gwtdevmode">
+    <script id="gwtdevmode">
       (function () {
         var pn = 'gwt.codesvr';
-        var cn = 'gerrit.' + pn;
+        var cn = 'gerrit_ui.' + pn;
 
         var p_start = window.location.search.indexOf(pn + '=');
         if (p_start != -1) {
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/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 5823940..dded71b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -491,7 +491,7 @@
     for (File e : entries) {
       if (e.isDirectory() /* must be a directory */
           && e.getName().startsWith("gerrit-gwtui-")
-          && new File(e, "gerrit/gerrit.nocache.js").isFile()) {
+          && new File(e, "gerrit_ui/gerrit_ui.nocache.js").isFile()) {
         return Resource.newResource(e.toURI());
       }
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
new file mode 100644
index 0000000..7ac1ed6
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
@@ -0,0 +1,25 @@
+// 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.pgm.init;
+
+/** Abstraction of initializer for the database section */
+interface DatabaseConfigInitializer {
+
+  /**
+   * Performs database platform specific configuration steps and writes
+   * configuration parameters into the given database section
+   */
+  public void initConfig(Section databaseSection);
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
new file mode 100644
index 0000000..32f8c2e
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
@@ -0,0 +1,41 @@
+// 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.pgm.init;
+
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class DatabaseConfigModule extends AbstractModule {
+
+  private final SitePaths site;
+
+  public DatabaseConfigModule(final SitePaths site) {
+    this.site = site;
+  }
+
+  @Override
+  protected void configure() {
+    bind(SitePaths.class).toInstance(site);
+    bind(DatabaseConfigInitializer.class).annotatedWith(
+        Names.named("h2")).to(H2Initializer.class);
+    bind(DatabaseConfigInitializer.class).annotatedWith(
+        Names.named("jdbc")).to(JDBCInitializer.class);
+    bind(DatabaseConfigInitializer.class).annotatedWith(
+        Names.named("mysql")).to(MySqlInitializer.class);
+    bind(DatabaseConfigInitializer.class).annotatedWith(
+        Names.named("postgresql")).to(PostgreSQLInitializer.class);
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
new file mode 100644
index 0000000..0ea3ff0
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/H2Initializer.java
@@ -0,0 +1,48 @@
+// 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.pgm.init;
+
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+
+import java.io.File;
+
+class H2Initializer implements DatabaseConfigInitializer {
+
+  private final SitePaths site;
+
+  @Inject
+  H2Initializer(final SitePaths site) {
+    this.site = site;
+  }
+
+  @Override
+  public void initConfig(Section databaseSection) {
+    String path = databaseSection.get("database");
+    if (path == null) {
+      path = "db/ReviewDB";
+      databaseSection.set("database", path);
+    }
+    File db = site.resolve(path);
+    if (db == null) {
+      throw InitUtil.die("database.database must be supplied for H2");
+    }
+    db = db.getParentFile();
+    if (!db.exists() && !db.mkdirs()) {
+      throw InitUtil.die("cannot create database.database "
+          + db.getAbsolutePath());
+    }
+  }
+}
\ No newline at end of file
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index fa4dc14..ea12008 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -32,8 +32,8 @@
   @Inject
   InitAuth(final ConsoleUI ui, final Section.Factory sections) {
     this.ui = ui;
-    this.auth = sections.get("auth");
-    this.ldap = sections.get("ldap");
+    this.auth = sections.get("auth", null);
+    this.ldap = sections.get("ldap", null);
   }
 
   public void run() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
index fb1a924..2b5729f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
@@ -31,7 +31,7 @@
   @Inject
   InitCache(final SitePaths site, final Section.Factory sections) {
     this.site = site;
-    this.cache = sections.get("cache");
+    this.cache = sections.get("cache", null);
   }
 
   public void run() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
index 7063f54..f34e22d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -44,7 +44,7 @@
       final Section.Factory sections) {
     this.ui = ui;
     this.site = site;
-    this.container = sections.get("container");
+    this.container = sections.get("container", null);
   }
 
   public void run() throws FileNotFoundException, IOException {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index d501ea5..4ce963a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -14,17 +14,24 @@
 
 package com.google.gerrit.pgm.init;
 
-import static com.google.gerrit.pgm.init.InitUtil.die;
-import static com.google.gerrit.pgm.init.InitUtil.username;
-import static com.google.gerrit.server.schema.DataSourceProvider.Type.H2;
+import static com.google.inject.Stage.PRODUCTION;
 
 import com.google.gerrit.pgm.util.ConsoleUI;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.schema.DataSourceProvider;
+import com.google.inject.Binding;
+import com.google.inject.Guice;
 import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
 import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
 
-import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 
 /** Initialize the {@code database} configuration section. */
 @Singleton
@@ -40,65 +47,34 @@
     this.ui = ui;
     this.site = site;
     this.libraries = libraries;
-    this.database = sections.get("database");
+    this.database = sections.get("database", null);
   }
 
   public void run() {
     ui.header("SQL Database");
 
-    final DataSourceProvider.Type db_type =
-        database.select("Database server type", "type", H2);
+    Set<String> allowedValues = new TreeSet<String>();
+    Injector i = Guice.createInjector(PRODUCTION, new DatabaseConfigModule(site));
+    List<Binding<DatabaseConfigInitializer>> dbConfigBindings =
+        i.findBindingsByType(new TypeLiteral<DatabaseConfigInitializer>() {});
+    for (Binding<DatabaseConfigInitializer> binding : dbConfigBindings) {
+      Annotation annotation = binding.getKey().getAnnotation();
+      if (annotation instanceof Named) {
+        allowedValues.add(((Named) annotation).value());
+      }
+    }
 
-    switch (db_type) {
-      case MYSQL:
+    String dbType =
+        database.select("Database server type", "type", "h2", allowedValues);
+
+    DatabaseConfigInitializer dci =
+        i.getInstance(Key.get(DatabaseConfigInitializer.class,
+            Names.named(dbType.toLowerCase())));
+
+    if (dci instanceof MySqlInitializer) {
         libraries.mysqlDriver.downloadRequired();
-        break;
     }
 
-    final boolean userPassAuth;
-    switch (db_type) {
-      case H2: {
-        userPassAuth = false;
-        String path = database.get("database");
-        if (path == null) {
-          path = "db/ReviewDB";
-          database.set("database", path);
-        }
-        File db = site.resolve(path);
-        if (db == null) {
-          throw die("database.database must be supplied for H2");
-        }
-        db = db.getParentFile();
-        if (!db.exists() && !db.mkdirs()) {
-          throw die("cannot create database.database " + db.getAbsolutePath());
-        }
-        break;
-      }
-
-      case JDBC: {
-        userPassAuth = true;
-        database.string("Driver class name", "driver", null);
-        database.string("URL", "url", null);
-        break;
-      }
-
-      case POSTGRESQL:
-      case MYSQL: {
-        userPassAuth = true;
-        final String defPort = "(" + db_type.toString() + " default)";
-        database.string("Server hostname", "hostname", "localhost");
-        database.string("Server port", "port", defPort, true);
-        database.string("Database name", "database", "reviewdb");
-        break;
-      }
-
-      default:
-        throw die("internal bug, database " + db_type + " not supported");
-    }
-
-    if (userPassAuth) {
-      database.string("Database username", "username", username());
-      database.password("username", "password");
-    }
+    dci.initConfig(database);
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
index f0cd31f..b6f8519 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
@@ -31,7 +31,7 @@
   @Inject
   InitGitManager(final ConsoleUI ui, final Section.Factory sections) {
     this.ui = ui;
-    this.gerrit = sections.get("gerrit");
+    this.gerrit = sections.get("gerrit", null);
   }
 
   public void run() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
index 98d2e47..feb00e1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -48,8 +48,8 @@
     this.ui = ui;
     this.site = site;
     this.flags = flags;
-    this.httpd = sections.get("httpd");
-    this.gerrit = sections.get("gerrit");
+    this.httpd = sections.get("httpd", null);
+    this.gerrit = sections.get("gerrit", null);
   }
 
   public void run() throws IOException, InterruptedException {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
new file mode 100644
index 0000000..08422fa
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -0,0 +1,132 @@
+// 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.pgm.init;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+
+@Singleton
+public class InitPluginStepsLoader {
+  private final File pluginsDir;
+  private final Injector initInjector;
+  final ConsoleUI ui;
+
+  @Inject
+  public InitPluginStepsLoader(final ConsoleUI ui, final SitePaths sitePaths,
+      final Injector initInjector) {
+    this.pluginsDir = sitePaths.plugins_dir;
+    this.initInjector = initInjector;
+    this.ui = ui;
+  }
+
+  public Collection<InitStep> getInitSteps() {
+    List<File> jars = scanJarsInPluginsDirectory();
+    ArrayList<InitStep> pluginsInitSteps = new ArrayList<InitStep>();
+
+    for (File jar : jars) {
+      InitStep init = loadInitStep(jar);
+      if (init != null) {
+        pluginsInitSteps.add(init);
+      }
+    }
+    return pluginsInitSteps;
+  }
+
+  private InitStep loadInitStep(File jar) {
+    try {
+      ClassLoader pluginLoader =
+          new URLClassLoader(new URL[] {jar.toURI().toURL()},
+              InitPluginStepsLoader.class.getClassLoader());
+      JarFile jarFile = new JarFile(jar);
+      Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
+      String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
+      if (initClassName == null) {
+        return null;
+      }
+      @SuppressWarnings("unchecked")
+      Class<? extends InitStep> initStepClass =
+          (Class<? extends InitStep>) pluginLoader.loadClass(initClassName);
+      return getPluginInjector(jar).getInstance(initStepClass);
+    } catch (ClassCastException e) {
+      ui.message(
+          "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
+          jar.getName(), InitStep.class.getName(), e.getMessage());
+      return null;
+    } catch (Exception e) {
+      ui.message(
+          "WARN: Cannot load and get plugin init step for %s (Exception: %s)",
+          jar, e.getMessage());
+      return null;
+    }
+  }
+
+  private Injector getPluginInjector(File jarFile) {
+    String jarFileName = jarFile.getName();
+    final String pluginName =
+        jarFileName.substring(0, jarFileName.lastIndexOf('.'));
+    return initInjector.createChildInjector(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(String.class).annotatedWith(PluginName.class).toInstance(
+            pluginName);
+      }
+    });
+  }
+
+  private List<File> scanJarsInPluginsDirectory() {
+    if (pluginsDir == null || !pluginsDir.exists()) {
+      return Collections.emptyList();
+    }
+    File[] matches = pluginsDir.listFiles(new FileFilter() {
+      @Override
+      public boolean accept(File pathname) {
+        String n = pathname.getName();
+        return (n.endsWith(".jar") && pathname.isFile());
+      }
+    });
+    if (matches == null) {
+      ui.message("WARN: Cannot list %s", pluginsDir.getAbsolutePath());
+      return Collections.emptyList();
+    }
+    Arrays.sort(matches, new Comparator<File>() {
+      @Override
+      public int compare(File o1, File o2) {
+        return o1.getName().compareTo(o2.getName());
+      }
+    });
+    return Arrays.asList(matches);
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index 155fe4c..aecb808 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -25,6 +25,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
@@ -39,17 +40,24 @@
 
   private final ConsoleUI ui;
   private final SitePaths site;
+  private InitPluginStepsLoader pluginLoader;
 
   @Inject
-  InitPlugins(final ConsoleUI ui, final SitePaths site) {
+  InitPlugins(final ConsoleUI ui, final SitePaths site, InitPluginStepsLoader pluginLoader) {
     this.ui = ui;
     this.site = site;
+    this.pluginLoader = pluginLoader;
   }
 
   @Override
   public void run() throws Exception {
     ui.header("Plugins");
 
+    installPlugins();
+    initPlugins();
+  }
+
+  private void installPlugins() throws IOException {
     final File myWar;
     try {
       myWar = GerritLauncher.getDistributionArchive();
@@ -127,6 +135,12 @@
     }
   }
 
+  private void initPlugins() throws Exception {
+    for (InitStep initStep : pluginLoader.getInitSteps()) {
+      initStep.run();
+    }
+  }
+
   private static String getVersion(final File plugin) throws IOException {
     final JarFile jarFile = new JarFile(plugin);
     try {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index c5732e9..e4b827d1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -34,7 +34,7 @@
   InitSendEmail(final ConsoleUI ui, final SitePaths site,
       final Section.Factory sections) {
     this.ui = ui;
-    this.sendemail = sections.get("sendemail");
+    this.sendemail = sections.get("sendemail", null);
     this.site = site;
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index bfc0eaf..0c2a3c6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -45,7 +45,7 @@
     this.ui = ui;
     this.site = site;
     this.libraries = libraries;
-    this.sshd = sections.get("sshd");
+    this.sshd = sections.get("sshd", null);
   }
 
   public void run() throws Exception {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
new file mode 100644
index 0000000..94425ac1
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/JDBCInitializer.java
@@ -0,0 +1,28 @@
+// 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.pgm.init;
+
+import static com.google.gerrit.pgm.init.InitUtil.username;
+
+class JDBCInitializer implements DatabaseConfigInitializer {
+
+  @Override
+  public void initConfig(Section databaseSection) {
+    databaseSection.string("Driver class name", "driver", null);
+    databaseSection.string("URL", "url", null);
+    databaseSection.string("Database username", "username", username());
+    databaseSection.password("username", "password");
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
new file mode 100644
index 0000000..fe6a4d9
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/MySqlInitializer.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import static com.google.gerrit.pgm.init.InitUtil.username;
+
+class MySqlInitializer implements DatabaseConfigInitializer {
+
+  @Override
+  public void initConfig(Section databaseSection) {
+    final String defPort = "(mysql default)";
+    databaseSection.string("Server hostname", "hostname", "localhost");
+    databaseSection.string("Server port", "port", defPort, true);
+    databaseSection.string("Database name", "database", "reviewdb");
+    databaseSection.string("Database username", "username", username());
+    databaseSection.password("username", "password");
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
new file mode 100644
index 0000000..1425663
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import static com.google.gerrit.pgm.init.InitUtil.username;
+
+class PostgreSQLInitializer implements DatabaseConfigInitializer {
+
+  @Override
+  public void initConfig(Section databaseSection) {
+    final String defPort = "(postgresql default)";
+    databaseSection.string("Server hostname", "hostname", "localhost");
+    databaseSection.string("Server port", "port", defPort, true);
+    databaseSection.string("Database name", "database", "reviewdb");
+    databaseSection.string("Database username", "username", username());
+    databaseSection.password("username", "password");
+  }
+}
\ No newline at end of file
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
index 02ed991..387b93a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
@@ -23,53 +23,60 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Set;
+
+import javax.annotation.Nullable;
 
 /** Helper to edit a section of the configuration files. */
-class Section {
-  interface Factory {
-    Section get(String name);
+public class Section {
+  public interface Factory {
+    Section get(@Assisted("section") String section,
+        @Assisted("subsection") String subsection);
   }
 
   private final InitFlags flags;
   private final SitePaths site;
   private final ConsoleUI ui;
   private final String section;
+  private final String subsection;
 
   @Inject
-  Section(final InitFlags flags, final SitePaths site, final ConsoleUI ui,
-      @Assisted final String section) {
+  public Section(final InitFlags flags, final SitePaths site,
+      final ConsoleUI ui, @Assisted("section") final String section,
+      @Assisted("subsection") @Nullable final String subsection) {
     this.flags = flags;
     this.site = site;
     this.ui = ui;
     this.section = section;
+    this.subsection = subsection;
   }
 
   String get(String name) {
     return flags.cfg.getString(section, null, name);
   }
 
-  void set(final String name, final String value) {
+  public void set(final String name, final String value) {
     final ArrayList<String> all = new ArrayList<String>();
-    all.addAll(Arrays.asList(flags.cfg.getStringList(section, null, name)));
+    all.addAll(Arrays.asList(flags.cfg.getStringList(section, subsection, name)));
 
     if (value != null) {
       if (all.size() == 0 || all.size() == 1) {
-        flags.cfg.setString(section, null, name, value);
+        flags.cfg.setString(section, subsection, name, value);
       } else {
         all.set(0, value);
-        flags.cfg.setStringList(section, null, name, all);
+        flags.cfg.setStringList(section, subsection, name, all);
       }
 
     } else if (all.size() == 0) {
     } else if (all.size() == 1) {
-      flags.cfg.unset(section, null, name);
+      flags.cfg.unset(section, subsection, name);
     } else {
       all.remove(0);
-      flags.cfg.setStringList(section, null, name, all);
+      flags.cfg.setStringList(section, subsection, name, all);
     }
   }
 
-  <T extends Enum<?>> void set(final String name, final T value) {
+  public <T extends Enum<?>> void set(final String name, final T value) {
     if (value != null) {
       set(name, value.name());
     } else {
@@ -77,15 +84,15 @@
     }
   }
 
-  void unset(String name) {
+  public void unset(String name) {
     set(name, (String) null);
   }
 
-  String string(final String title, final String name, final String dv) {
+  public String string(final String title, final String name, final String dv) {
     return string(title, name, dv, false);
   }
 
-  String string(final String title, final String name, final String dv,
+  public String string(final String title, final String name, final String dv,
       final boolean nullIfDefault) {
     final String ov = get(name);
     String nv = ui.readString(ov != null ? ov : dv, "%s", title);
@@ -98,19 +105,19 @@
     return nv;
   }
 
-  File path(final String title, final String name, final String defValue) {
+  public File path(final String title, final String name, final String defValue) {
     return site.resolve(string(title, name, defValue));
   }
 
-  <T extends Enum<?>> T select(final String title, final String name,
+  public <T extends Enum<?>> T select(final String title, final String name,
       final T defValue) {
     return select(title, name, defValue, false);
   }
 
-  <T extends Enum<?>> T select(final String title, final String name,
+  public <T extends Enum<?>> T select(final String title, final String name,
       final T defValue, final boolean nullIfDefault) {
     final boolean set = get(name) != null;
-    T oldValue = ConfigUtil.getEnum(flags.cfg, section, null, name, defValue);
+    T oldValue = ConfigUtil.getEnum(flags.cfg, section, subsection, name, defValue);
     T newValue = ui.readEnum(oldValue, "%s", title);
     if (nullIfDefault && newValue == defValue) {
       newValue = null;
@@ -125,16 +132,26 @@
     return newValue;
   }
 
-  String password(final String username, final String password) {
+  public String select(final String title, final String name, final String dv,
+      Set<String> allowedValues) {
+    final String ov = get(name);
+    String nv = ui.readString(ov != null ? ov : dv, allowedValues, "%s", title);
+    if (!eq(ov, nv)) {
+      set(name, nv);
+    }
+    return nv;
+  }
+
+  public String password(final String username, final String password) {
     final String ov = getSecure(password);
 
-    String user = flags.sec.getString(section, null, username);
+    String user = flags.sec.getString(section, subsection, username);
     if (user == null) {
       user = get(username);
     }
 
     if (user == null) {
-      flags.sec.unset(section, null, password);
+      flags.sec.unset(section, subsection, password);
       return null;
     }
 
@@ -154,18 +171,22 @@
     return nv;
   }
 
-  String getSecure(String name) {
-    return flags.sec.getString(section, null, name);
+  public String getSecure(String name) {
+    return flags.sec.getString(section, subsection, name);
   }
 
-  void setSecure(String name, String value) {
+  public void setSecure(String name, String value) {
     if (value != null) {
-      flags.sec.setString(section, null, name, value);
+      flags.sec.setString(section, subsection, name, value);
     } else {
-      flags.sec.unset(section, null, name);
+      flags.sec.unset(section, subsection, name);
     }
   }
 
+  String getName() {
+    return section;
+  }
+
   private static boolean eq(final String a, final String b) {
     if (a == null && b == null) {
       return true;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index f8ef637..ca3bf84 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -89,6 +89,7 @@
     extractMailExample("ChangeFooter.vm");
     extractMailExample("ChangeSubject.vm");
     extractMailExample("Comment.vm");
+    extractMailExample("CommentFooter.vm");
     extractMailExample("Merged.vm");
     extractMailExample("MergeFail.vm");
     extractMailExample("NewChange.vm");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
index 9f62fc5..b982ae1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -20,7 +20,6 @@
 
 import com.google.gerrit.pgm.util.ConsoleUI;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.schema.DataSourceProvider;
 import com.google.gerrit.server.util.SocketUtil;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -118,11 +117,11 @@
 
     final Properties oldprop = readGerritServerProperties();
     if (oldprop != null) {
-      final Section database = sections.get("database");
+      final Section database = sections.get("database", null);
 
       String url = oldprop.getProperty("url");
       if (url != null && !convertUrl(database, url)) {
-        database.set("type", DataSourceProvider.Type.JDBC);
+        database.set("type", "jdbc");
         database.set("driver", oldprop.getProperty("driver"));
         database.set("url", url);
       }
@@ -189,7 +188,7 @@
 
     if (url.startsWith("jdbc:h2:file:")) {
       url = url.substring("jdbc:h2:file:".length());
-      database.set("type", DataSourceProvider.Type.H2);
+      database.set("type", "h2");
       database.set("database", url);
       return true;
     }
@@ -202,7 +201,7 @@
       }
 
       final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
-      database.set("type", DataSourceProvider.Type.POSTGRESQL);
+      database.set("type", "postgresql");
       sethost(database, addr);
       database.set("database", url.substring(sl + 1));
       setuser(database, username, password);
@@ -211,7 +210,7 @@
 
     if (url.startsWith("jdbc:postgresql:")) {
       url = url.substring("jdbc:postgresql:".length());
-      database.set("type", DataSourceProvider.Type.POSTGRESQL);
+      database.set("type", "postgresql");
       database.set("hostname", "localhost");
       database.set("database", url);
       setuser(database, username, password);
@@ -226,7 +225,7 @@
       }
 
       final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
-      database.set("type", DataSourceProvider.Type.MYSQL);
+      database.set("type", "mysql");
       sethost(database, addr);
       database.set("database", url.substring(sl + 1));
       setuser(database, username, password);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
index b4e0fad..e8cf0ab 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
@@ -18,6 +18,7 @@
 
 import java.io.Console;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Set;
 
 /** Console based interaction with the invoking user. */
 public abstract class ConsoleUI {
@@ -73,6 +74,10 @@
   /** Prompt the user for a string, suggesting a default, and returning choice. */
   public abstract String readString(String def, String fmt, Object... args);
 
+  /** Prompt the user to make a choice from an allowed list of values. */
+  public abstract String readString(String def, Set<String> allowedValues,
+      String fmt, Object... args);
+
   /** Prompt the user for an integer value, suggesting a default. */
   public int readInt(int def, String fmt, Object... args) {
     for (;;) {
@@ -162,6 +167,24 @@
     }
 
     @Override
+    public String readString(String def, Set<String> allowedValues, String fmt,
+        Object... args) {
+      for (;;) {
+        String r = readString(def, fmt, args);
+        if (allowedValues.contains(r.toLowerCase())) {
+          return r.toLowerCase();
+        }
+        if (!"?".equals(r)) {
+          console.printf("error: '%s' is not a valid choice\n", r);
+        }
+        console.printf("       Supported options are:\n");
+        for (final String v : allowedValues) {
+          console.printf("         %s\n", v.toString().toLowerCase());
+        }
+      }
+    }
+
+    @Override
     public String password(String fmt, Object... args) {
       final String prompt = String.format(fmt, args);
       for (;;) {
@@ -242,6 +265,12 @@
     }
 
     @Override
+    public String readString(String def, Set<String> allowedValues, String fmt,
+        Object... args) {
+      return def;
+    }
+
+    @Override
     public void waitForUser() {
     }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index 4893579..242ca28 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -18,10 +18,13 @@
 import static com.google.inject.Stage.PRODUCTION;
 
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GerritServerConfigModule;
 import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.schema.DataSourceModule;
 import com.google.gerrit.server.schema.DataSourceProvider;
+import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.DatabaseModule;
 import com.google.gerrit.server.schema.SchemaModule;
 import com.google.gwtorm.server.OrmException;
@@ -34,6 +37,7 @@
 import com.google.inject.name.Names;
 import com.google.inject.spi.Message;
 
+import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
 
 import java.io.File;
@@ -147,12 +151,15 @@
 
     final File sitePath = getSitePath();
     final List<Module> modules = new ArrayList<Module>();
-    modules.add(new AbstractModule() {
+
+    Module sitePathModule = new AbstractModule() {
       @Override
       protected void configure() {
         bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
       }
-    });
+    };
+    modules.add(sitePathModule);
+
     modules.add(new LifecycleModule() {
       @Override
       protected void configure() {
@@ -162,7 +169,21 @@
         listener().to(DataSourceProvider.class);
       }
     });
-    modules.add(new GerritServerConfigModule());
+    Module configModule = new GerritServerConfigModule();
+    modules.add(configModule);
+    Injector cfgInjector = Guice.createInjector(sitePathModule, configModule);
+    Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+    String dbType = cfg.getString("database", null, "type");
+
+    final DataSourceType dst = Guice.createInjector(new DataSourceModule(), configModule,
+            sitePathModule).getInstance(
+            Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
+
+    modules.add(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(DataSourceType.class).toInstance(dst);
+      }});
     modules.add(new DatabaseModule());
     modules.add(new SchemaModule());
     modules.add(new LocalDiskRepositoryManager.Module());
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-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
index 493a440..86f0260 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
@@ -72,8 +72,8 @@
     final ConsoleUI ui = createStrictMock(ConsoleUI.class);
     Section.Factory sections = new Section.Factory() {
       @Override
-      public Section get(String name) {
-        return new Section(flags, site, ui, name);
+      public Section get(String name, String subsection) {
+        return new Section(flags, site, ui, name, subsection);
       }
     };
 
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 84f6f7b..81a50984 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>
@@ -46,6 +46,18 @@
     </dependency>
 
     <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-pgm</artifactId>
+      <version>${project.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-servlet</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.tomcat</groupId>
       <artifactId>servlet-api</artifactId>
     </dependency>
@@ -66,8 +78,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-plugin-gwt-archetype/.gitignore b/gerrit-plugin-gwt-archetype/.gitignore
new file mode 100644
index 0000000..80d6257
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/.gitignore
@@ -0,0 +1,5 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
new file mode 100644
index 0000000..91cd142
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.gerrit</groupId>
+    <artifactId>gerrit-parent</artifactId>
+    <version>2.6-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>gerrit-plugin-gwt-archetype</artifactId>
+  <name>Gerrit Code Review - Web Ui GWT Plugin Archetype</name>
+
+  <properties>
+    <defaultGerritApiVersion>${project.version}</defaultGerritApiVersion>
+  </properties>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <includes>
+          <include>META-INF/maven/archetype-metadata.xml</include>
+        </includes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <excludes>
+          <exclude>META-INF/maven/archetype-metadata.xml</exclude>
+        </excludes>
+      </resource>
+    </resources>
+  </build>
+
+</project>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
new file mode 100644
index 0000000..78f2941
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<archetype-descriptor name="Gerrit Plugin">
+  <requiredProperties>
+    <requiredProperty key="pluginName"/>
+
+    <requiredProperty key="Implementation-Vendor"/>
+    <requiredProperty key="Implementation-Url"/>
+    <requiredProperty key="Gwt-Version"/>
+
+    <requiredProperty key="gerritApiVersion">
+      <defaultValue>${defaultGerritApiVersion}</defaultValue>
+    </requiredProperty>
+  </requiredProperties>
+
+  <fileSets>
+    <fileSet filtered="true" packaged="true">
+      <directory>src/main/java</directory>
+      <includes>
+        <include>**/*.css</include>
+        <include>**/*.png</include>
+        <include>**/*.java</include>
+        <include>**/*.gwt.xml</include>
+      </includes>
+    </fileSet>
+
+    <fileSet filtered="true">
+      <directory>src/main/resources/Documentation</directory>
+      <includes>
+        <include>**/*.md</include>
+      </includes>
+    </fileSet>
+
+    <fileSet>
+      <directory></directory>
+      <includes>
+        <include>.gitignore</include>
+        <include>LICENSE</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</archetype-descriptor>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore
new file mode 100644
index 0000000..80d6257
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.gitignore
@@ -0,0 +1,5 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/LICENSE b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/LICENSE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 0000000..3904e2b
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,131 @@
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>${groupId}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <packaging>jar</packaging>
+  <version>${version}</version>
+  <name>${pluginName}</name>
+
+  <properties>
+    <Gerrit-ApiType>extension</Gerrit-ApiType>
+    <Gerrit-ApiVersion>${gerritApiVersion}</Gerrit-ApiVersion>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.4</version>
+        <configuration>
+          <includes>
+            <include>**/*.*</include>
+          </includes>
+          <archive>
+            <manifestEntries>
+              <Gerrit-Module>${package}.Module</Gerrit-Module>
+
+              <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
+              <Implementation-URL>${Implementation-Url}</Implementation-URL>
+
+              <Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
+              <Implementation-Version>${project.version}</Implementation-Version>
+
+              <Gerrit-ApiType>${Gerrit-ApiType}</Gerrit-ApiType>
+              <Gerrit-ApiVersion>${Gerrit-ApiVersion}</Gerrit-ApiVersion>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <encoding>UTF-8</encoding>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>gwt-maven-plugin</artifactId>
+        <version>${Gwt-Version}</version>
+        <configuration>
+          <module>${package}.HelloPlugins</module>
+          <disableClassMetadata>true</disableClassMetadata>
+          <disableCastChecking>true</disableCastChecking>
+          <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-${Gerrit-ApiType}-api</artifactId>
+      <version>${Gerrit-ApiVersion}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-plugin-gwtui</artifactId>
+      <version>${Gerrit-ApiVersion}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-user</artifactId>
+      <version>${Gwt-Version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <repositories>
+    <repository>
+      <id>gerrit-api-repository</id>
+#if ($gerritApiVersion.endsWith("SNAPSHOT"))
+      <url>https://gerrit-api.commondatastorage.googleapis.com/snapshot/</url>
+#else
+      <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
+#end
+    </repository>
+  </repositories>
+</project>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugins.gwt.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugins.gwt.xml
new file mode 100644
index 0000000..4c70fcc
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugins.gwt.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<module rename-to="hello_gwt_plugins">
+  <!-- Inherit the core Web Toolkit stuff.                        -->
+  <inherits name="com.google.gwt.user.User"/>
+  <!-- Other module inherits                                      -->
+  <inherits name="com.google.gerrit.Plugin"/>
+  <inherits name="com.google.gwt.http.HTTP"/>
+  <!-- Using GWT built-in themes adds a number of static          -->
+  <!-- resources to the plugin. No theme inherits lines were      -->
+  <!-- added in order to make this plugin as simple as possible   -->
+  <!-- Specify the app entry point class.                         -->
+  <entry-point class="${package}.client.HelloPlugins"/>
+  <stylesheet src="hello.css"/>
+</module>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
new file mode 100644
index 0000000..d42545a
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2012 Google
+//
+// 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 ${package};
+
+import com.google.inject.AbstractModule;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.extensions.webui.GwtPlugin;
+
+class Module extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(WebUiPlugin.class).toInstance(new GwtPlugin("hello_gwt_plugins"));
+  }
+}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugins.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugins.java
new file mode 100644
index 0000000..5584d85
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugins.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2012 Google Inc
+//
+// 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 ${package}.client;
+
+import com.google.gerrit.client.Plugin;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+/**
+ * HelloWorld Plugins.
+ */
+public class HelloPlugins extends Plugin {
+
+  @Override
+  public void onModuleLoad() {
+    Image img = new Image("http://code.google.com/webtoolkit/logo-185x175.png");
+    Button button = new Button("Click me");
+
+    VerticalPanel vPanel = new VerticalPanel();
+    vPanel.setWidth("100%");
+    vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
+    vPanel.add(img);
+    vPanel.add(button);
+
+    RootPanel.get().add(vPanel);
+
+    // Create the dialog box
+    final DialogBox dialogBox = new DialogBox();
+
+    // The content of the dialog comes from a User specified Preference
+    dialogBox.setText("Hello from GWT Gerrit UI plugin");
+    dialogBox.setAnimationEnabled(true);
+    Button closeButton = new Button("Close");
+    VerticalPanel dialogVPanel = new VerticalPanel();
+    dialogVPanel.setWidth("100%");
+    dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
+    dialogVPanel.add(closeButton);
+
+    closeButton.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        dialogBox.hide();
+      }
+    });
+
+    // Set the contents of the Widget
+    dialogBox.setWidget(dialogVPanel);
+
+    button.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        dialogBox.center();
+        dialogBox.show();
+      }
+    });
+  }
+}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png
new file mode 100644
index 0000000..e970774
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png
Binary files differ
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png
new file mode 100644
index 0000000..c041149
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png
Binary files differ
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css
new file mode 100644
index 0000000..a88059d
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css
@@ -0,0 +1,103 @@
+/**
+ * The file contains styles for a sample plugin derived from the
+ * GWT standard theme.
+ * Images in the stylesheet have been removed, as well as styles for widgets
+ * not currently in use.
+ */
+
+body, table td, select {
+  font-family: Arial Unicode MS, Arial, sans-serif;
+  font-size: small;
+}
+pre {
+  font-family: "courier new", courier;
+  font-size: small;
+}
+body {
+  color: black;
+  margin: 0px;
+  border: 0px;
+  padding: 0px;
+  background: #fff;
+  direction: ltr;
+}
+a, a:visited, a:hover {
+  color: #0000AA;
+}
+
+/**
+ * The reference theme can be used to determine when this style sheet has
+ * loaded.  Create a hidden div element with absolute position, assign the style
+ * name below, and attach it to the DOM.  Use a timer to detect when the
+ * element's height and width are set to 5px.
+ */
+.gwt-Reference-standard {
+  height: 5px;
+  width: 5px;
+  zoom: 1;
+}
+
+.gwt-Button {
+  margin: 0;
+  padding: 3px 5px;
+  text-decoration: none;
+  font-size: small;
+  cursor: pointer;
+  cursor: hand;
+  border: 1px outset #ccc;
+}
+.gwt-Button:active {
+  border: 1px inset #ccc;
+}
+.gwt-Button:hover {
+  border-color: #9cf #69e #69e #7af;
+}
+.gwt-Button[disabled] {
+  cursor: default;
+  color: #888;
+}
+.gwt-Button[disabled]:hover {
+  border: 1px outset #ccc;
+}
+
+.gwt-DialogBox .Caption {
+  background: #e3e8f3;
+  padding: 4px 4px 4px 8px;
+  cursor: default;
+  border-bottom: 1px solid #bbbbbb;
+  border-top: 5px solid #d0e4f6;
+  border-left: 5px solid #d0e4f6;
+  border-right: 5px solid #d0e4f6;
+}
+
+.gwt-DialogBox .dialogContent {
+}
+
+.gwt-DialogBox .dialogMiddleCenter {
+  padding: 3px;
+  background: white;
+  border-left: 5px solid #d0e4f6;
+  border-right: 5px solid #d0e4f6;
+  border-bottom: 5px solid #d0e4f6;
+}
+
+.gwt-DialogBox .dialogTopLeftInner {
+  width: 5px;
+  zoom: 1;
+}
+.gwt-DialogBox .dialogTopRightInner {
+  width: 8px;
+  zoom: 1;
+}
+.gwt-DialogBox .dialogBottomLeftInner {
+  width: 5px;
+  height: 8px;
+  zoom: 1;
+}
+
+.gwt-DialogBox .dialogBottomRightInner {
+  width: 5px;
+  height: 8px;
+  zoom: 1;
+}
+
diff --git a/gerrit-plugin-gwtui/.gitignore b/gerrit-plugin-gwtui/.gitignore
new file mode 100644
index 0000000..2dcf1ed
--- /dev/null
+++ b/gerrit-plugin-gwtui/.gitignore
@@ -0,0 +1,6 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-gwtui-plugin.iml
diff --git a/gerrit-plugin-gwtui/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-gwtui/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..839d647
--- /dev/null
+++ b/gerrit-plugin-gwtui/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding/<project>=UTF-8
diff --git a/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..955e208
--- /dev/null
+++ b/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,295 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+org.eclipse.jdt.core.codeComplete.fieldPrefixes=
+org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+org.eclipse.jdt.core.codeComplete.localPrefixes=
+org.eclipse.jdt.core.codeComplete.localSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.ui.prefs b/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..c018821
--- /dev/null
+++ b/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+formatter_profile=_Google Format
+formatter_settings_version=12
+org.eclipse.jdt.ui.exception.name=e
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.overrideannotation=true
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
new file mode 100644
index 0000000..aa6ba8f
--- /dev/null
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.gerrit</groupId>
+    <artifactId>gerrit-parent</artifactId>
+    <version>2.6-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>gerrit-plugin-gwtui</artifactId>
+  <name>Gerrit Code Review - Plugin GWT UI</name>
+
+  <description>
+    API for UI plugins to build with GWT and integrate with Gerrit
+  </description>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-user</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-dev</artifactId>
+    </dependency>
+  </dependencies>
+
+   <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>gwt-maven-plugin</artifactId>
+        <configuration>
+          <module>com.google.gerrit.Plugin</module>
+          <disableClassMetadata>true</disableClassMetadata>
+          <disableCastChecking>true</disableCastChecking>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>resources</goal>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
\ No newline at end of file
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml
new file mode 100644
index 0000000..03edf67
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml
@@ -0,0 +1,23 @@
+<!--
+ 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.
+-->
+<module>
+  <define-linker name="gerrit_plugin" class="com.google.gerrit.linker.GerritPluginLinker"/>
+  <add-linker name="gerrit_plugin"/>
+  <generate-with class="com.google.gerrit.rebind.PluginGenerator">
+    <when-type-assignable class="com.google.gerrit.client.Plugin"/>
+  </generate-with>
+  <source path="client"/>
+</module>
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/client/Plugin.java
new file mode 100644
index 0000000..1291b79
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/client/Plugin.java
@@ -0,0 +1,29 @@
+// 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.client;
+
+import com.google.gwt.core.client.EntryPoint;
+
+/**
+ * Base class for writing Gerrit Web UI plugins
+ *
+ * Writing a plugin:
+ * <ol>
+ * <li>Declare subtype of Plugin</li>
+ * <li>Bind WebUiPlugin to GwtPlugin implementation in Gerrit-Module</li>
+ * </ol>
+ */
+public abstract class Plugin implements EntryPoint {
+}
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/linker/GerritPluginLinker.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/linker/GerritPluginLinker.java
new file mode 100644
index 0000000..e50334e
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/linker/GerritPluginLinker.java
@@ -0,0 +1,31 @@
+// 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.linker;
+
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.linker.CrossSiteIframeLinker;
+
+/** Finalizes the module manifest file with the selection script. */
+public final class GerritPluginLinker extends CrossSiteIframeLinker {
+  @Override
+  public String getDescription() {
+    return "Gerrit GWT UI plugin";
+  }
+
+  @Override
+  protected String getJsComputeUrlForResource(LinkerContext context) {
+    return "com/google/gerrit/linker/computeUrlForPluginResource.js";
+  }
+}
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/rebind/PluginGenerator.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/rebind/PluginGenerator.java
new file mode 100644
index 0000000..71666f9
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/rebind/PluginGenerator.java
@@ -0,0 +1,95 @@
+// Copyright (C) 2012 The Android Open Source Project
+// Copyright 2008 Google Inc.
+//
+// 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.rebind;
+
+import java.io.PrintWriter;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+/**
+ * Write the top layer in the Gadget bootstrap sandwich and generate a stub
+ * manifest that will be completed by the linker.
+ *
+ * Based on gwt-gadgets GadgetGenerator class
+ */
+public class PluginGenerator extends Generator {
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext context,
+      String typeName) throws UnableToCompleteException {
+
+    // The TypeOracle knows about all types in the type system
+    TypeOracle typeOracle = context.getTypeOracle();
+
+    // Get a reference to the type that the generator should implement
+    JClassType sourceType = typeOracle.findType(typeName);
+
+    // Ensure that the requested type exists
+    if (sourceType == null) {
+      logger.log(TreeLogger.ERROR, "Could not find requested typeName", null);
+      throw new UnableToCompleteException();
+    }
+
+    // Make sure the Gadget type is correctly defined
+    validateType(logger, sourceType);
+
+    // Pick a name for the generated class to not conflict.
+    String generatedSimpleSourceName = sourceType.getSimpleSourceName()
+        + "PluginImpl";
+
+    // Begin writing the generated source.
+    ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
+        sourceType.getPackage().getName(), generatedSimpleSourceName);
+    f.addImport(GWT.class.getName());
+    f.setSuperclass(typeName);
+
+    // All source gets written through this Writer
+    PrintWriter out = context.tryCreate(logger,
+        sourceType.getPackage().getName(), generatedSimpleSourceName);
+
+    // If an implementation already exists, we don't need to do any work
+    if (out != null) {
+
+      // We really use a SourceWriter since it's convenient
+      SourceWriter sw = f.createSourceWriter(context, out);
+      sw.println("public " + generatedSimpleSourceName + "() {");
+      sw.indent();
+      sw.println("onModuleLoad();");
+      sw.outdent();
+      sw.println("}");
+
+      sw.commit(logger);
+    }
+
+    return f.getCreatedClassName();
+  }
+
+  protected void validateType(TreeLogger logger, JClassType type)
+      throws UnableToCompleteException {
+    if (!type.isDefaultInstantiable()) {
+      logger.log(TreeLogger.ERROR, "Plugin types must be default instantiable",
+          null);
+      throw new UnableToCompleteException();
+    }
+  }
+}
diff --git a/gerrit-plugin-gwtui/src/main/resources/com/google/gerrit/linker/computeUrlForPluginResource.js b/gerrit-plugin-gwtui/src/main/resources/com/google/gerrit/linker/computeUrlForPluginResource.js
new file mode 100644
index 0000000..3e20e94
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/resources/com/google/gerrit/linker/computeUrlForPluginResource.js
@@ -0,0 +1,3 @@
+function computeUrlForResource(resource) {
+  return __MODULE_FUNC__.__moduleBase + resource;
+}
diff --git a/gerrit-plugin-js-archetype/.gitignore b/gerrit-plugin-js-archetype/.gitignore
new file mode 100644
index 0000000..80d6257
--- /dev/null
+++ b/gerrit-plugin-js-archetype/.gitignore
@@ -0,0 +1,5 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
new file mode 100644
index 0000000..1f0f53a
--- /dev/null
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.gerrit</groupId>
+    <artifactId>gerrit-parent</artifactId>
+    <version>2.6-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>gerrit-plugin-js-archetype</artifactId>
+  <name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name>
+
+  <properties>
+    <defaultGerritApiVersion>${project.version}</defaultGerritApiVersion>
+  </properties>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <includes>
+          <include>META-INF/maven/archetype-metadata.xml</include>
+        </includes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <excludes>
+          <exclude>META-INF/maven/archetype-metadata.xml</exclude>
+        </excludes>
+      </resource>
+    </resources>
+  </build>
+
+</project>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
new file mode 100644
index 0000000..054caae
--- /dev/null
+++ b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<archetype-descriptor name="Gerrit Plugin">
+  <requiredProperties>
+    <requiredProperty key="pluginName"/>
+
+    <requiredProperty key="Implementation-Vendor"/>
+    <requiredProperty key="Implementation-Url"/>
+
+    <requiredProperty key="gerritApiType">
+      <defaultValue>js</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="gerritApiVersion">
+      <defaultValue>${defaultGerritApiVersion}</defaultValue>
+    </requiredProperty>
+  </requiredProperties>
+
+  <fileSets>
+    <fileSet filtered="true" packaged="true">
+      <directory>src/main/java</directory>
+      <includes>
+        <include>**/*.java</include>
+      </includes>
+    </fileSet>
+
+    <fileSet>
+      <directory>src/main/js</directory>
+      <includes>
+        <include>**/*.js</include>
+      </includes>
+    </fileSet>
+
+    <fileSet filtered="true">
+      <directory>src/main/resources/Documentation</directory>
+      <includes>
+        <include>**/*.md</include>
+      </includes>
+    </fileSet>
+
+    <fileSet>
+      <directory></directory>
+      <includes>
+        <include>.gitignore</include>
+        <include>LICENSE</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</archetype-descriptor>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.gitignore b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.gitignore
new file mode 100644
index 0000000..80d6257
--- /dev/null
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.gitignore
@@ -0,0 +1,5 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/LICENSE b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/LICENSE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 0000000..9fe8d0f
--- /dev/null
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,123 @@
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>${groupId}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <packaging>jar</packaging>
+  <version>${version}</version>
+  <name>${pluginName}</name>
+
+  <properties>
+    <Gerrit-ApiType>${gerritApiType}</Gerrit-ApiType>
+    <Gerrit-ApiVersion>${gerritApiVersion}</Gerrit-ApiVersion>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.4</version>
+        <configuration>
+          <includes>
+            <include>**/*.js</include>
+            <include>**/*.class</include>
+          </includes>
+          <archive>
+            <manifestEntries>
+              <Gerrit-Module>${package}.Module</Gerrit-Module>
+
+              <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
+              <Implementation-URL>${Implementation-Url}</Implementation-URL>
+
+              <Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
+              <Implementation-Version>${project.version}</Implementation-Version>
+
+              <Gerrit-ApiType>${Gerrit-ApiType}</Gerrit-ApiType>
+              <Gerrit-ApiVersion>${Gerrit-ApiVersion}</Gerrit-ApiVersion>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <encoding>UTF-8</encoding>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-resources-plugin</artifactId>
+        <version>2.6</version>
+        <executions>
+          <execution>
+            <id>copy-resources</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${basedir}/target/classes/static</outputDirectory>
+              <resources>
+                <resource>
+                  <directory>src/main/js</directory>
+                  <filtering>true</filtering>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-${Gerrit-ApiType}-api</artifactId>
+      <version>${Gerrit-ApiVersion}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <repositories>
+    <repository>
+      <id>gerrit-api-repository</id>
+#if ($gerritApiVersion.endsWith("SNAPSHOT"))
+      <url>https://gerrit-api.commondatastorage.googleapis.com/snapshot/</url>
+#else
+      <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
+#end
+    </repository>
+  </repositories>
+</project>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/Module.java b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
new file mode 100644
index 0000000..056afeb
--- /dev/null
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/Module.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 ${package};
+
+import com.google.inject.AbstractModule;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+
+class Module extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(WebUiPlugin.class).toInstance(new JavaScriptPlugin("hello-js-plugins.js"));
+  }
+}
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/js/hello-js-plugins.js b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/js/hello-js-plugins.js
new file mode 100644
index 0000000..fd51a42
--- /dev/null
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/js/hello-js-plugins.js
@@ -0,0 +1 @@
+alert("Greeting from JavaScript Gerrit plugin!");
\ No newline at end of file
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-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index a2fc46f..38d41b36 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -70,6 +70,8 @@
 
     MERGE_IF_NECESSARY,
 
+    REBASE_IF_NECESSARY,
+
     MERGE_ALWAYS,
 
     CHERRY_PICK;
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index 2c91db4..675c5bc 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -47,30 +47,6 @@
 
 DROP FUNCTION make_plpgsql();
 
--- Define our schema upgrade support function.
---
-
-delimiter //
-
-CREATE OR REPLACE FUNCTION
-check_schema_version (exp INT)
-RETURNS VARCHAR(255)
-AS $$
-DECLARE
-  l_act INT;
-BEGIN
-  SELECT version_nbr INTO l_act
-  FROM schema_version;
-
-  IF l_act <> exp
-  THEN
-    RAISE EXCEPTION 'expected schema %, found %', exp, l_act;
-  END IF;
-  RETURN 'OK';
-END;
-$$ LANGUAGE plpgsql;
-//
-
 delimiter ;
 
 -- Indexes to support @Query
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index af18173..0b13fbd 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>
@@ -80,7 +80,6 @@
     <dependency>
       <groupId>bouncycastle</groupId>
       <artifactId>bcpg-jdk15</artifactId>
-      <version>140</version>
       <scope>provided</scope>
     </dependency>
 
@@ -162,7 +161,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..177ec65 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
@@ -39,8 +39,10 @@
 import com.google.gerrit.server.events.CommentAddedEvent;
 import com.google.gerrit.server.events.DraftPublishedEvent;
 import com.google.gerrit.server.events.EventFactory;
+import com.google.gerrit.server.events.MergeFailedEvent;
 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;
@@ -108,6 +110,9 @@
     /** Filename of the change merged hook. */
     private final File changeMergedHook;
 
+    /** Filename of the merge failed hook. */
+    private final File mergeFailedHook;
+
     /** Filename of the change abandoned hook. */
     private final File changeAbandonedHook;
 
@@ -117,6 +122,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;
 
@@ -170,9 +178,11 @@
         draftPublishedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "draftPublishedHook", "draft-published")).getPath());
         commentAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "commentAddedHook", "comment-added")).getPath());
         changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath());
+        mergeFailedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "mergeFailed", "merge-failed")).getPath());
         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());
     }
 
@@ -324,6 +334,30 @@
         runHook(change.getProject(), changeMergedHook, args);
     }
 
+    public void doMergeFailedHook(final Change change, final Account account,
+          final PatchSet patchSet, final String reason,
+          final ReviewDb db) throws OrmException {
+        final MergeFailedEvent event = new MergeFailedEvent();
+
+        event.change = eventFactory.asChangeAttribute(change);
+        event.submitter = eventFactory.asAccountAttribute(account);
+        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+        event.reason = reason;
+        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, "--topic", event.change.topic);
+        addArg(args, "--submitter", getDisplayName(account));
+        addArg(args, "--commit", event.patchSet.revision);
+        addArg(args, "--reason",  reason == null ? "" : reason);
+
+        runHook(change.getProject(), mergeFailedHook, args);
+    }
+
     public void doChangeAbandonedHook(final Change change, final Account account,
           final String reason, final ReviewDb db) throws OrmException {
         final ChangeAbandonedEvent event = new ChangeAbandonedEvent();
@@ -391,6 +425,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..0d8dfb8 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,
@@ -83,6 +83,18 @@
       PatchSet patchSet, ReviewDb db) throws OrmException;
 
   /**
+   * Fire the Merge Failed Hook.
+   *
+   * @param change The change itself.
+   * @param account The gerrit user who attempted to submit the change.
+   * @param patchSet The patchset that failed to merge.
+   * @param reason The reason that the change failed to merge.
+   * @throws OrmException
+   */
+  public void doMergeFailedHook(Change change, Account account,
+      PatchSet patchSet, String reason, ReviewDb db) throws OrmException;
+
+  /**
    * Fire the Change Abandoned Hook.
    *
    * @param change The change itself.
@@ -125,5 +137,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..57ccba7 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
@@ -46,6 +46,11 @@
   }
 
   @Override
+  public void doMergeFailedHook(Change change, Account account,
+      PatchSet patchSet, String reason, ReviewDb db) {
+  }
+
+  @Override
   public void doChangeRestoredHook(Change change, Account account,
       String reason, ReviewDb db) {
   }
@@ -81,6 +86,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/rules/PredicateClassLoader.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateClassLoader.java
new file mode 100644
index 0000000..eb2d264
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateClassLoader.java
@@ -0,0 +1,65 @@
+// 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.rules;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.gerrit.extensions.registration.DynamicSet;
+
+import java.util.Collection;
+
+/**
+ * Loads the classes for Prolog predicates.
+ */
+public class PredicateClassLoader extends ClassLoader {
+
+  private final Multimap<String, ClassLoader> packageClassLoaderMap =
+      LinkedHashMultimap.create();
+
+  public PredicateClassLoader(
+      final DynamicSet<PredicateProvider> predicateProviders,
+      final ClassLoader parent) {
+    super(parent);
+
+    for (PredicateProvider predicateProvider : predicateProviders) {
+      for (String pkg : predicateProvider.getPackages()) {
+        packageClassLoaderMap.put(pkg, predicateProvider.getClass()
+            .getClassLoader());
+      }
+    }
+  }
+
+  @Override
+  protected Class<?> findClass(final String className)
+      throws ClassNotFoundException {
+    final Collection<ClassLoader> classLoaders =
+        packageClassLoaderMap.get(getPackageName(className));
+    for (final ClassLoader cl : classLoaders) {
+      try {
+        return Class.forName(className, true, cl);
+      } catch (ClassNotFoundException e) {
+        // ignore
+      }
+    }
+    throw new ClassNotFoundException(className);
+  }
+
+  private static String getPackageName(String className) {
+    final int pos = className.lastIndexOf('.');
+    if (pos < 0) {
+      return "";
+    }
+    return className.substring(0, pos);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java
new file mode 100644
index 0000000..4bd9423
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java
@@ -0,0 +1,34 @@
+// 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.rules;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+import com.googlecode.prolog_cafe.lang.Predicate;
+
+/**
+ * Provides additional packages that contain Prolog predicates that should be
+ * made available in the Prolog environment. The predicates can e.g. be used in
+ * the project submit rules.
+ *
+ * Each Java class defining a Prolog predicate must be in one of the provided
+ * packages and its name must apply to the 'PRED_[functor]_[arity]' format. In
+ * addition it must extend {@link Predicate}.
+ */
+@ExtensionPoint
+public interface PredicateProvider {
+  /** Return set of packages that contain Prolog predicates */
+  public ImmutableSet<String> getPackages();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java
index d361790..92b8b1b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologModule.java
@@ -14,11 +14,13 @@
 
 package com.google.gerrit.rules;
 
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.config.FactoryModule;
 
 public class PrologModule extends FactoryModule {
   @Override
   protected void configure() {
+    DynamicSet.setOf(binder(), PredicateProvider.class);
     factory(PrologEnvironment.Factory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
index fbee145..3b35626 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
@@ -16,6 +16,9 @@
 
 import static com.googlecode.prolog_cafe.lang.PrologMachineCopy.save;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -52,6 +55,7 @@
 import java.net.URLClassLoader;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -69,10 +73,8 @@
   /** Default size of the internal Prolog database within each interpreter. */
   private static final int DB_MAX = 256;
 
-  private static final String[] PACKAGE_LIST = {
-      Prolog.BUILTIN,
-      "gerrit",
-    };
+  private static final List<String> PACKAGE_LIST = ImmutableList.of(
+      Prolog.BUILTIN, "gerrit");
 
   private final Map<ObjectId, MachineRef> machineCache =
       new HashMap<ObjectId, MachineRef>();
@@ -94,16 +96,18 @@
   private final File cacheDir;
   private final File rulesDir;
   private final GitRepositoryManager gitMgr;
+  private final DynamicSet<PredicateProvider> predicateProviders;
   private final ClassLoader systemLoader;
   private final PrologMachineCopy defaultMachine;
 
   @Inject
   protected RulesCache(@GerritServerConfig Config config, SitePaths site,
-      GitRepositoryManager gm) {
+      GitRepositoryManager gm, DynamicSet<PredicateProvider> predicateProviders) {
     enableProjectRules = config.getBoolean("rules", null, "enable", true);
     cacheDir = site.resolve(config.getString("cache", null, "directory"));
     rulesDir = cacheDir != null ? new File(cacheDir, "rules") : null;
     gitMgr = gm;
+    this.predicateProviders = predicateProviders;
 
     systemLoader = getClass().getClassLoader();
     defaultMachine = save(newEmptyMachine(systemLoader));
@@ -207,15 +211,22 @@
     }
   }
 
-  private static BufferingPrologControl newEmptyMachine(ClassLoader cl) {
+  private BufferingPrologControl newEmptyMachine(ClassLoader cl) {
     BufferingPrologControl ctl = new BufferingPrologControl();
     ctl.setMaxArity(PrologEnvironment.MAX_ARITY);
     ctl.setMaxDatabaseSize(DB_MAX);
-    ctl.setPrologClassLoader(new PrologClassLoader(cl));
+    ctl.setPrologClassLoader(new PrologClassLoader(new PredicateClassLoader(
+        predicateProviders, cl)));
     ctl.setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
 
+    List<String> packages = Lists.newArrayList();
+    packages.addAll(PACKAGE_LIST);
+    for (PredicateProvider predicateProvider : predicateProviders) {
+      packages.addAll(predicateProvider.getPackages());
+    }
+
     // Bootstrap the interpreter and ensure there is clean state.
-    ctl.initialize(PACKAGE_LIST);
+    ctl.initialize(packages.toArray(new String[packages.size()]));
     return ctl;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 535ad8e..45dba4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -14,17 +14,11 @@
 
 package com.google.gerrit.server;
 
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.ChangeHookRunner;
 import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
 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.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.client.TrackingId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -34,15 +28,10 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeOp;
 import com.google.gerrit.server.mail.EmailException;
-import com.google.gerrit.server.mail.RebasedPatchSetSender;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.mail.RevertedSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmConcurrencyException;
 import com.google.gwtorm.server.OrmException;
 
@@ -53,11 +42,8 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.MergeStrategy;
-import org.eclipse.jgit.merge.ThreeWayMerger;
 import org.eclipse.jgit.revwalk.FooterLine;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -68,9 +54,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.sql.Timestamp;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -193,270 +177,6 @@
     db.patchSetAncestors().insert(toInsert);
   }
 
-  /**
-   * Rebases a commit
-   *
-   * @param git Repository to find commits in
-   * @param inserter inserter to handle new trees and blobs.
-   * @param original The commit to rebase
-   * @param base Base to rebase against
-   * @return CommitBuilder the newly rebased commit
-   * @throws IOException Merged failed
-   */
-  public static CommitBuilder rebaseCommit(Repository git,
-      final ObjectInserter inserter, RevCommit original, RevCommit base,
-      PersonIdent committerIdent) throws IOException {
-
-    if (original.getParentCount() == 0) {
-      throw new IOException(
-          "Commits with no parents cannot be rebased (is this the initial commit?).");
-    }
-
-    if (original.getParentCount() > 1) {
-      throw new IOException(
-          "Patch sets with multiple parents cannot be rebased (merge commits)."
-              + " Parents: " + Arrays.toString(original.getParents()));
-    }
-
-    final RevCommit parentCommit = original.getParent(0);
-
-    if (base.equals(parentCommit)) {
-      throw new IOException("Change is already up to date.");
-    }
-
-    final ThreeWayMerger merger = MergeStrategy.RESOLVE.newMerger(git, true);
-    merger.setObjectInserter(new ObjectInserter.Filter() {
-      @Override
-      protected ObjectInserter delegate() {
-        return inserter;
-      }
-
-      @Override
-      public void flush() {
-      }
-
-      @Override
-      public void release() {
-      }
-    });
-    merger.setBase(parentCommit);
-    merger.merge(original, base);
-
-    if (merger.getResultTreeId() == null) {
-      throw new IOException(
-          "The rebase failed since conflicts occured during the merge.");
-    }
-
-    final CommitBuilder rebasedCommitBuilder = new CommitBuilder();
-
-    rebasedCommitBuilder.setTreeId(merger.getResultTreeId());
-    rebasedCommitBuilder.setParentId(base);
-    rebasedCommitBuilder.setAuthor(original.getAuthorIdent());
-    rebasedCommitBuilder.setMessage(original.getFullMessage());
-    rebasedCommitBuilder.setCommitter(committerIdent);
-
-    return rebasedCommitBuilder;
-  }
-
-  public static void rebaseChange(final PatchSet.Id patchSetId,
-      final IdentifiedUser user, final ReviewDb db,
-      RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
-      final ChangeHookRunner hooks, GitRepositoryManager gitManager,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      final GitReferenceUpdated replication, PersonIdent myIdent,
-      final ChangeControl.Factory changeControlFactory,
-      final ApprovalsUtil approvalsUtil) throws NoSuchChangeException,
-      EmailException, OrmException, MissingObjectException,
-      IncorrectObjectTypeException, IOException,
-      InvalidChangeOperationException {
-
-    final Change.Id changeId = patchSetId.getParentKey();
-    final ChangeControl changeControl =
-        changeControlFactory.validateFor(changeId);
-
-    if (!changeControl.canRebase()) {
-      throw new InvalidChangeOperationException(
-          "Cannot rebase: New patch sets are not allowed to be added to change: "
-              + changeId.toString());
-    }
-
-    Change change = changeControl.getChange();
-    final Repository git = gitManager.openRepository(change.getProject());
-    try {
-      final RevWalk revWalk = new RevWalk(git);
-      try {
-        final PatchSet originalPatchSet = db.patchSets().get(patchSetId);
-        RevCommit branchTipCommit = null;
-
-        List<PatchSetAncestor> patchSetAncestors =
-            db.patchSetAncestors().ancestorsOf(patchSetId).toList();
-        if (patchSetAncestors.size() > 1) {
-          throw new IOException(
-              "The patch set you are trying to rebase is dependent on several other patch sets: "
-                  + patchSetAncestors.toString());
-        }
-        if (patchSetAncestors.size() == 1) {
-          List<PatchSet> depPatchSetList = db.patchSets()
-                  .byRevision(patchSetAncestors.get(0).getAncestorRevision())
-                  .toList();
-          if (!depPatchSetList.isEmpty()) {
-            PatchSet depPatchSet = depPatchSetList.get(0);
-
-            Change.Id depChangeId = depPatchSet.getId().getParentKey();
-            Change depChange = db.changes().get(depChangeId);
-
-            if (depChange.getStatus() == Status.ABANDONED) {
-              throw new IOException("Cannot rebase against an abandoned change: "
-                  + depChange.getKey().toString());
-            }
-            if (depChange.getStatus().isOpen()) {
-              PatchSet latestDepPatchSet =
-                  db.patchSets().get(depChange.currentPatchSetId());
-              if (!depPatchSet.getId().equals(depChange.currentPatchSetId())) {
-                branchTipCommit =
-                    revWalk.parseCommit(ObjectId
-                        .fromString(latestDepPatchSet.getRevision().get()));
-              } else {
-                throw new IOException(
-                    "Change is already based on the latest patch set of the dependent change.");
-              }
-            }
-          }
-        }
-
-        if (branchTipCommit == null) {
-          // We are dependent on a merged PatchSet or have no PatchSet
-          // dependencies at all.
-          Ref destRef = git.getRef(change.getDest().get());
-          if (destRef == null) {
-            throw new IOException(
-                "The destination branch does not exist: "
-                    + change.getDest().get());
-          }
-          branchTipCommit = revWalk.parseCommit(destRef.getObjectId());
-        }
-
-        final RevCommit rebasedCommit;
-        final ObjectInserter oi = git.newObjectInserter();
-        try {
-          ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
-          ObjectId newId = oi.insert(rebaseCommit(
-              git, oi, revWalk.parseCommit(oldId), branchTipCommit, myIdent));
-          oi.flush();
-          rebasedCommit = revWalk.parseCommit(newId);
-        } finally {
-          oi.release();
-        }
-
-        change.nextPatchSetId();
-        final PatchSet newPatchSet = new PatchSet(change.currPatchSetId());
-        newPatchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
-        newPatchSet.setUploader(user.getAccountId());
-        newPatchSet.setRevision(new RevId(rebasedCommit.name()));
-        newPatchSet.setDraft(originalPatchSet.isDraft());
-
-        final PatchSetInfo info =
-            patchSetInfoFactory.get(rebasedCommit, newPatchSet.getId());
-
-        RefUpdate ru = git.updateRef(newPatchSet.getRefName());
-        ru.setExpectedOldObjectId(ObjectId.zeroId());
-        ru.setNewObjectId(rebasedCommit);
-        ru.disableRefLog();
-        if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-          throw new IOException(String.format(
-              "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
-              change.getDest().getParentKey().get(), ru.getResult()));
-        }
-        replication.fire(change.getProject(), ru.getName());
-
-        final Set<Account.Id> oldReviewers = Sets.newHashSet();
-        final Set<Account.Id> oldCC = Sets.newHashSet();
-        db.changes().beginTransaction(change.getId());
-        try {
-          Change updatedChange;
-
-          updatedChange = db.changes().atomicUpdate(changeId,
-              new AtomicUpdate<Change>() {
-                @Override
-                public Change update(Change change) {
-                  if (change.getStatus().isOpen()) {
-                    change.updateNumberOfPatchSets(newPatchSet.getPatchSetId());
-                    return change;
-                  } else {
-                    return null;
-                  }
-                }
-              });
-          if (updatedChange != null) {
-            change = updatedChange;
-          } else {
-            throw new InvalidChangeOperationException(
-                String.format("Change %s is closed", change.getId()));
-          }
-
-          insertAncestors(db, newPatchSet.getId(), rebasedCommit);
-          db.patchSets().insert(Collections.singleton(newPatchSet));
-          updatedChange = db.changes().atomicUpdate(changeId,
-              new AtomicUpdate<Change>() {
-                @Override
-                public Change update(Change change) {
-                  if (change.getStatus().isClosed()) {
-                    return null;
-                  }
-                  if (!change.currentPatchSetId().equals(patchSetId)) {
-                    return null;
-                  }
-                  if (change.getStatus() != Change.Status.DRAFT) {
-                    change.setStatus(Change.Status.NEW);
-                  }
-                  change.setLastSha1MergeTested(null);
-                  change.setCurrentPatchSet(info);
-                  ChangeUtil.updated(change);
-                  return change;
-                }
-              });
-          if (updatedChange != null) {
-            change = updatedChange;
-          } else {
-            throw new InvalidChangeOperationException(
-                String.format("Change %s was modified", change.getId()));
-          }
-
-          for (PatchSetApproval a : approvalsUtil.copyVetosToLatestPatchSet(change)) {
-            if (a.getValue() != 0) {
-              oldReviewers.add(a.getAccountId());
-            } else {
-              oldCC.add(a.getAccountId());
-            }
-          }
-
-          final ChangeMessage cmsg =
-              new ChangeMessage(new ChangeMessage.Key(changeId,
-                  ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
-          cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
-          db.changeMessages().insert(Collections.singleton(cmsg));
-          db.commit();
-        } finally {
-          db.rollback();
-        }
-
-        final ReplacePatchSetSender cm =
-            rebasedPatchSetSenderFactory.create(change);
-        cm.setFrom(user.getAccountId());
-        cm.setPatchSet(newPatchSet);
-        cm.addReviewers(oldReviewers);
-        cm.addExtraCC(oldCC);
-        cm.send();
-
-        hooks.doPatchsetCreatedHook(change, newPatchSet, db);
-      } finally {
-        revWalk.release();
-      }
-    } finally {
-      git.close();
-    }
-  }
-
   public static Change.Id revert(final PatchSet.Id patchSetId,
       final IdentifiedUser user, final String message, final ReviewDb db,
       final RevertedSender.Factory revertedSenderFactory,
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 644f5df..ae049a0 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.auth.ldap;
 
+import com.google.common.base.Throwables;
 import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.ParameterizedString;
@@ -28,6 +29,8 @@
 
 import org.eclipse.jgit.lib.Config;
 
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -47,6 +50,9 @@
 import javax.naming.directory.DirContext;
 import javax.naming.directory.InitialDirContext;
 import javax.net.ssl.SSLSocketFactory;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 
 @Singleton class Helper {
   static final String LDAP_UUID = "ldap:";
@@ -58,6 +64,7 @@
   private final String password;
   private final String referral;
   private final boolean sslVerify;
+  private final String authentication;
   private volatile LdapSchema ldapSchema;
   private final String readTimeOutMillis;
 
@@ -71,6 +78,7 @@
     this.password = LdapRealm.optional(config, "password");
     this.referral = LdapRealm.optional(config, "referral");
     this.sslVerify = config.getBoolean("ldap", "sslverify", true);
+    this.authentication = LdapRealm.optional(config, "authentication");
     String timeout = LdapRealm.optional(config, "readTimeout");
     if (timeout != null) {
       readTimeOutMillis =
@@ -96,15 +104,39 @@
     return env;
   }
 
-  DirContext open() throws NamingException {
+  DirContext open() throws NamingException, LoginException {
     final Properties env = createContextProperties();
-    if (username != null) {
-      env.put(Context.SECURITY_AUTHENTICATION, "simple");
-      env.put(Context.SECURITY_PRINCIPAL, username);
-      env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
-      env.put(Context.REFERRAL, referral != null ? referral : "ignore");
+    env.put(Context.SECURITY_AUTHENTICATION, authentication != null ? authentication : "simple");
+    env.put(Context.REFERRAL, referral != null ? referral : "ignore");
+    if ("GSSAPI".equals(authentication)) {
+      return kerberosOpen(env);
+    } else {
+       env.put(Context.SECURITY_PRINCIPAL, username);
+       env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
+       return new InitialDirContext(env);
     }
-    return new InitialDirContext(env);
+  }
+
+  private DirContext kerberosOpen(final Properties env) throws LoginException,
+      NamingException {
+    LoginContext ctx = new LoginContext("KerberosLogin");
+    ctx.login();
+    Subject subject = ctx.getSubject();
+    try {
+      return Subject.doAs(subject, new PrivilegedExceptionAction<DirContext>() {
+          @Override
+          public DirContext run() throws NamingException {
+            return new InitialDirContext(env);
+          }
+        });
+    } catch (PrivilegedActionException e) {
+      Throwables.propagateIfPossible(e.getException(), NamingException.class);
+      Throwables.propagateIfPossible(e.getException(), RuntimeException.class);
+      LdapRealm.log.warn("Internal error", e.getException());
+      return null;
+    } finally {
+      ctx.logout();
+    }
   }
 
   DirContext authenticate(String dn, String password) throws AccountException {
@@ -256,6 +288,7 @@
     final List<String> groupBases;
     final SearchScope groupScope;
     final ParameterizedString groupPattern;
+    final ParameterizedString groupName;
     final List<LdapQuery> groupMemberQueryList;
 
     LdapSchema(final DirContext ctx) {
@@ -271,6 +304,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..b2f9443 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;
@@ -50,6 +51,7 @@
 import javax.naming.directory.DirContext;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
+import javax.security.auth.login.LoginException;
 
 /**
  * Implementation of GroupBackend for the LDAP group system.
@@ -81,11 +83,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 +205,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 {
@@ -221,6 +224,8 @@
       }
     } catch (NamingException e) {
       log.warn("Cannot query LDAP for groups matching requested name", e);
+    } catch (LoginException e) {
+      log.warn("Cannot query LDAP for groups matching requested name", e);
     }
     return out;
   }
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..fc1102e 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
@@ -56,6 +56,7 @@
 import javax.naming.Name;
 import javax.naming.NamingException;
 import javax.naming.directory.DirContext;
+import javax.security.auth.login.LoginException;
 
 @Singleton
 class LdapRealm implements Realm {
@@ -167,7 +168,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;
@@ -240,6 +241,9 @@
     } catch (NamingException e) {
       log.error("Cannot query LDAP to autenticate user", e);
       throw new AuthenticationUnavailableException("Cannot query LDAP for account", e);
+    } catch (LoginException e) {
+      log.error("Cannot authenticate server via JAAS", e);
+      throw new AuthenticationUnavailableException("Cannot query LDAP for account", e);
     }
   }
 
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-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/BranchCreationNotAllowedException.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PathConflictException.java
similarity index 62%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/BranchCreationNotAllowedException.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PathConflictException.java
index 98cdc32..7e2f2d7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/BranchCreationNotAllowedException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PathConflictException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// 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.
@@ -12,15 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.httpd.rpc.project;
+package com.google.gerrit.server.changedetail;
 
-public class BranchCreationNotAllowedException extends Exception {
-
+/** Indicates a path conflict during rebase or merge */
+public class PathConflictException extends Exception {
   private static final long serialVersionUID = 1L;
 
-  public static final String MESSAGE = "Branch creation is not allowed under: ";
-
-  public BranchCreationNotAllowedException(final String refnamePrefix) {
-    super(MESSAGE + refnamePrefix);
+  public PathConflictException(String msg) {
+    super(msg);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
new file mode 100644
index 0000000..2698768
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -0,0 +1,438 @@
+// 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.changedetail;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
+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.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.mail.EmailException;
+import com.google.gerrit.server.mail.RebasedPatchSetSender;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class RebaseChange {
+
+  public interface Factory {
+    RebaseChange create();
+  }
+
+  private final ChangeControl.Factory changeControlFactory;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final ReviewDb db;
+  private final GitRepositoryManager gitManager;
+  private final PersonIdent myIdent;
+  private final GitReferenceUpdated replication;
+  private final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory;
+  private final ChangeHookRunner hooks;
+  private final ApprovalsUtil approvalsUtil;
+
+  @Inject
+  RebaseChange(final ChangeControl.Factory changeControlFactory,
+      final PatchSetInfoFactory patchSetInfoFactory, final ReviewDb db,
+      @GerritPersonIdent final PersonIdent myIdent,
+      final GitRepositoryManager gitManager,
+      final GitReferenceUpdated replication,
+      final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
+      final ChangeHookRunner hooks, final ApprovalsUtil approvalsUtil) {
+    this.changeControlFactory = changeControlFactory;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.db = db;
+    this.gitManager = gitManager;
+    this.myIdent = myIdent;
+    this.replication = replication;
+    this.rebasedPatchSetSenderFactory = rebasedPatchSetSenderFactory;
+    this.hooks = hooks;
+    this.approvalsUtil = approvalsUtil;
+  }
+
+  /**
+   * Rebases the change of the given patch set.
+   *
+   * If the patch set has no dependency to an open change, then the change is
+   * rebased on the tip of the destination branch.
+   *
+   * If the patch set depends on an open change, it is rebased on the latest
+   * patch set of this change.
+   *
+   * The rebased commit is added as new patch set to the change.
+   *
+   * E-mail notification and triggering of hooks happens for the creation of the
+   * new patch set.
+   *
+   * @param patchSetId the id of the patch set
+   * @param uploader the user that creates the rebased patch set
+   * @throws NoSuchChangeException thrown if the change to which the patch set
+   *         belongs does not exist or is not visible to the user
+   * @throws EmailException thrown if sending the e-mail to notify about the new
+   *         patch set fails
+   * @throws OrmException thrown in case accessing the database fails
+   * @throws IOException thrown if rebase is not possible or not needed
+   * @throws InvalidChangeOperationException thrown if rebase is not allowed
+   */
+  public void rebase(final PatchSet.Id patchSetId, final Account.Id uploader)
+      throws NoSuchChangeException, EmailException, OrmException, IOException,
+      InvalidChangeOperationException {
+    final Change.Id changeId = patchSetId.getParentKey();
+    final ChangeControl changeControl =
+        changeControlFactory.validateFor(changeId);
+    final Change change = changeControl.getChange();
+    Repository git = null;
+    RevWalk rw = null;
+    ObjectInserter inserter = null;
+    try {
+      git = gitManager.openRepository(change.getProject());
+      rw = new RevWalk(git);
+      inserter = git.newObjectInserter();
+
+      final List<PatchSetApproval> oldPatchSetApprovals =
+          db.patchSetApprovals().byChange(change.getId()).toList();
+
+      final RevCommit baseCommit =
+          findBaseCommit(git, rw, patchSetId, change.getDest());
+      final PatchSet newPatchSet =
+          rebase(git, rw, inserter, patchSetId, change, uploader, baseCommit,
+              true);
+
+      final Set<Account.Id> oldReviewers = Sets.newHashSet();
+      final Set<Account.Id> oldCC = Sets.newHashSet();
+      for (PatchSetApproval a : oldPatchSetApprovals) {
+        if (a.getValue() != 0) {
+          oldReviewers.add(a.getAccountId());
+        } else {
+          oldCC.add(a.getAccountId());
+        }
+      }
+      final ReplacePatchSetSender cm =
+          rebasedPatchSetSenderFactory.create(change);
+      cm.setFrom(uploader);
+      cm.setPatchSet(newPatchSet);
+      cm.addReviewers(oldReviewers);
+      cm.addExtraCC(oldCC);
+      cm.send();
+
+      hooks.doPatchsetCreatedHook(change, newPatchSet, db);
+    } catch (PathConflictException e) {
+      throw new IOException(e.getMessage());
+    } finally {
+      if (inserter != null) {
+        inserter.release();
+      }
+      if (rw != null) {
+        rw.release();
+      }
+      if (git != null) {
+        git.close();
+      }
+    }
+  }
+
+  /**
+   * Finds the commit on which the given patch set should be based.
+   *
+   * @param git the repository
+   * @param rw the RevWalk
+   * @param patchSetId the id of the patch set for which the new base commit
+   *        should be found
+   * @param destBranch the destination branch
+   * @return the commit on which the given patch set should be based
+   * @throws IOException thrown if rebase is not possible or not needed
+   * @throws OrmException thrown in case accessing the database fails
+   */
+  private RevCommit findBaseCommit(final Repository git, final RevWalk rw,
+      final PatchSet.Id patchSetId, final Branch.NameKey destBranch)
+      throws IOException, OrmException {
+    RevCommit baseCommit = null;
+
+    final List<PatchSetAncestor> patchSetAncestors =
+        db.patchSetAncestors().ancestorsOf(patchSetId).toList();
+    if (patchSetAncestors.size() > 1) {
+      throw new IOException(
+          "The patch set you are trying to rebase is dependent on several other patch sets: "
+              + patchSetAncestors.toString());
+    }
+    if (patchSetAncestors.size() == 1) {
+      final List<PatchSet> depPatchSetList =
+          db.patchSets()
+              .byRevision(patchSetAncestors.get(0).getAncestorRevision())
+              .toList();
+      if (!depPatchSetList.isEmpty()) {
+        final PatchSet depPatchSet = depPatchSetList.get(0);
+
+        final Change.Id depChangeId = depPatchSet.getId().getParentKey();
+        final Change depChange = db.changes().get(depChangeId);
+
+        if (depChange.getStatus() == Status.ABANDONED) {
+          throw new IOException("Cannot rebase against an abandoned change: "
+              + depChange.getKey().toString());
+        }
+        if (depChange.getStatus().isOpen()) {
+          final PatchSet latestDepPatchSet =
+              db.patchSets().get(depChange.currentPatchSetId());
+          if (!depPatchSet.getId().equals(depChange.currentPatchSetId())) {
+            baseCommit =
+                rw.parseCommit(ObjectId.fromString(latestDepPatchSet
+                    .getRevision().get()));
+          } else {
+            throw new IOException(
+                "Change is already based on the latest patch set of the dependent change.");
+          }
+        }
+      }
+    }
+
+    if (baseCommit == null) {
+      // We are dependent on a merged PatchSet or have no PatchSet
+      // dependencies at all.
+      final Ref destRef = git.getRef(destBranch.get());
+      if (destRef == null) {
+        throw new IOException("The destination branch does not exist: "
+            + destBranch.get());
+      }
+      baseCommit = rw.parseCommit(destRef.getObjectId());
+    }
+
+    return baseCommit;
+  }
+
+  /**
+   * Rebases the change of the given patch set on the given base commit.
+   *
+   * The rebased commit is added as new patch set to the change.
+   *
+   * E-mail notification and triggering of hooks is NOT done for the creation of
+   * the new patch set.
+   *
+   * @param git the repository
+   * @param revWalk the RevWalk
+   * @param inserter the object inserter
+   * @param patchSetId the id of the patch set
+   * @param chg the change that should be rebased
+   * @param uploader the user that creates the rebased patch set
+   * @param baseCommit the commit that should be the new base
+   * @param useContentMerge flag that decides if content merge should be done
+   * @return the new patch set which is based on the given base commit
+   * @throws NoSuchChangeException thrown if the change to which the patch set
+   *         belongs does not exist or is not visible to the user
+   * @throws OrmException thrown in case accessing the database fails
+   * @throws IOException thrown if rebase is not possible or not needed
+   * @throws InvalidChangeOperationException thrown if rebase is not allowed
+   */
+  public PatchSet rebase(final Repository git, final RevWalk revWalk,
+      final ObjectInserter inserter, final PatchSet.Id patchSetId,
+      final Change chg, final Account.Id uploader, final RevCommit baseCommit,
+      final boolean useContentMerge) throws NoSuchChangeException,
+      OrmException, IOException, InvalidChangeOperationException,
+      PathConflictException {
+    Change change = chg;
+    final ChangeControl changeControl =
+        changeControlFactory.validateFor(change);
+    if (!changeControl.canRebase()) {
+      throw new InvalidChangeOperationException(
+          "Cannot rebase: New patch sets are not allowed to be added to change: "
+              + change.getId().toString());
+    }
+
+    final PatchSet originalPatchSet = db.patchSets().get(patchSetId);
+
+    final RevCommit rebasedCommit;
+    ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
+    ObjectId newId =
+        rebaseCommit(git, inserter, revWalk.parseCommit(oldId), baseCommit,
+            useContentMerge, myIdent);
+
+    rebasedCommit = revWalk.parseCommit(newId);
+
+    change.nextPatchSetId();
+    final PatchSet newPatchSet = new PatchSet(change.currPatchSetId());
+    newPatchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+    newPatchSet.setUploader(uploader);
+    newPatchSet.setRevision(new RevId(rebasedCommit.name()));
+    newPatchSet.setDraft(originalPatchSet.isDraft());
+
+    final PatchSetInfo info =
+        patchSetInfoFactory.get(rebasedCommit, newPatchSet.getId());
+
+    final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
+    ru.setExpectedOldObjectId(ObjectId.zeroId());
+    ru.setNewObjectId(rebasedCommit);
+    ru.disableRefLog();
+    if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+      throw new IOException(String.format("Failed to create ref %s in %s: %s",
+          newPatchSet.getRefName(), change.getDest().getParentKey().get(),
+          ru.getResult()));
+    }
+    replication.fire(change.getProject(), ru.getName());
+
+    db.changes().beginTransaction(change.getId());
+    try {
+      Change updatedChange;
+
+      updatedChange =
+          db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+            @Override
+            public Change update(Change change) {
+              if (change.getStatus().isOpen()) {
+                change.updateNumberOfPatchSets(newPatchSet.getPatchSetId());
+                return change;
+              } else {
+                return null;
+              }
+            }
+          });
+      if (updatedChange != null) {
+        change = updatedChange;
+      } else {
+        throw new InvalidChangeOperationException(String.format(
+            "Change %s is closed", change.getId()));
+      }
+
+      ChangeUtil.insertAncestors(db, newPatchSet.getId(), rebasedCommit);
+      db.patchSets().insert(Collections.singleton(newPatchSet));
+      updatedChange =
+          db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+            @Override
+            public Change update(Change change) {
+              if (change.getStatus().isClosed()) {
+                return null;
+              }
+              if (!change.currentPatchSetId().equals(patchSetId)) {
+                return null;
+              }
+              if (change.getStatus() != Change.Status.DRAFT) {
+                change.setStatus(Change.Status.NEW);
+              }
+              change.setLastSha1MergeTested(null);
+              change.setCurrentPatchSet(info);
+              ChangeUtil.updated(change);
+              return change;
+            }
+          });
+      if (updatedChange != null) {
+        change = updatedChange;
+      } else {
+        throw new InvalidChangeOperationException(String.format(
+            "Change %s was modified", change.getId()));
+      }
+
+      approvalsUtil.copyVetosToLatestPatchSet(change);
+
+      final ChangeMessage cmsg =
+          new ChangeMessage(new ChangeMessage.Key(change.getId(),
+              ChangeUtil.messageUUID(db)), uploader, patchSetId);
+      cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
+      db.changeMessages().insert(Collections.singleton(cmsg));
+      db.commit();
+    } finally {
+      db.rollback();
+    }
+
+    return newPatchSet;
+  }
+
+  /**
+   * Rebases a commit.
+   *
+   * @param git repository to find commits in
+   * @param inserter inserter to handle new trees and blobs
+   * @param original The commit to rebase
+   * @param base Base to rebase against
+   * @param useContentMerge flag to decide if content merge should be done
+   * @param committerIdent committer identity
+   * @return the id of the rebased commit
+   * @throws IOException Merged failed
+   * @throws PathConflictException the rebase failed due to a path conflict
+   */
+  private static ObjectId rebaseCommit(final Repository git,
+      final ObjectInserter inserter, final RevCommit original,
+      final RevCommit base, final boolean useContentMerge,
+      final PersonIdent committerIdent) throws IOException,
+      PathConflictException {
+    if (original.getParentCount() == 0) {
+      throw new IOException(
+          "Commits with no parents cannot be rebased (is this the initial commit?).");
+    }
+
+    if (original.getParentCount() > 1) {
+      throw new IOException(
+          "Patch sets with multiple parents cannot be rebased (merge commits)."
+              + " Parents: " + Arrays.toString(original.getParents()));
+    }
+
+    final RevCommit parentCommit = original.getParent(0);
+
+    if (base.equals(parentCommit)) {
+      throw new IOException("Change is already up to date.");
+    }
+
+    final ThreeWayMerger merger = MergeUtil.newThreeWayMerger(git, inserter, useContentMerge);
+    merger.setBase(parentCommit);
+    merger.merge(original, base);
+
+    if (merger.getResultTreeId() == null) {
+      throw new PathConflictException(
+          "The rebase failed since conflicts occured during the merge.");
+    }
+
+    final CommitBuilder cb = new CommitBuilder();
+    cb.setTreeId(merger.getResultTreeId());
+    cb.setParentId(base);
+    cb.setAuthor(original.getAuthorIdent());
+    cb.setMessage(original.getFullMessage());
+    cb.setCommitter(committerIdent);
+    final ObjectId objectId = inserter.insert(cb);
+    inserter.flush();
+    return objectId;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigSection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigSection.java
new file mode 100644
index 0000000..057ce99
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigSection.java
@@ -0,0 +1,37 @@
+// 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.config;
+
+import org.eclipse.jgit.lib.Config;
+
+/** Provides access to one section from {@link Config} */
+public class ConfigSection {
+
+  private final Config cfg;
+  private final String section;
+
+  public ConfigSection(Config cfg, String section) {
+    this.cfg = cfg;
+    this.section = section;
+  }
+
+  public String optional(String name) {
+    return cfg.getString(section, null, name);
+  }
+
+  public String required(String name) {
+    return ConfigUtil.getRequired(cfg, section, name);
+  }
+}
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..0182c40 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;
     }
@@ -286,6 +286,15 @@
     }
   }
 
+  public static String getRequired(Config cfg, String section, String name) {
+    final String v = cfg.getString(section, null, name);
+    if (v == null || "".equals(v)) {
+      throw new IllegalArgumentException("No " + section + "." + name
+          + " configured");
+    }
+    return v;
+  }
+
   private static boolean match(final String a, final String... cases) {
     for (final String b : cases) {
       if (equalsIgnoreCase(a, b)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 2a66706..1fbdf85 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -52,6 +52,7 @@
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.ChangeCache;
 import com.google.gerrit.server.git.ChangeMergeQueue;
 import com.google.gerrit.server.git.GitModule;
 import com.google.gerrit.server.git.MergeQueue;
@@ -125,6 +126,8 @@
     install(ProjectCacheImpl.module());
     install(SectionSortCache.module());
     install(TagCache.module());
+    install(ChangeCache.module());
+
     install(new AccessControlModule());
     install(new GitModule());
     install(new PrologModule());
@@ -176,6 +179,7 @@
     DynamicSet.setOf(binder(), CacheRemovalListener.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
+    DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
 
     bind(AnonymousUser.class);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index ba54c56..b41155b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.account.VisibleGroups;
 import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.changedetail.PublishDraft;
+import com.google.gerrit.server.changedetail.RebaseChange;
 import com.google.gerrit.server.changedetail.Submit;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
 import com.google.gerrit.server.git.BanCommit;
@@ -95,6 +96,7 @@
     factory(DeleteDraftPatchSet.Factory.class);
     factory(PublishComments.Factory.class);
     factory(PublishDraft.Factory.class);
+    factory(RebaseChange.Factory.class);
     factory(ReplacePatchSetSender.Factory.class);
     factory(RebasedPatchSetSender.Factory.class);
     factory(AbandonedSender.Factory.class);
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/documentation/MarkdownFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
index 6bb7ba8..af08d1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
@@ -136,8 +136,12 @@
     InputStream in = url.openStream();
     try {
       TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024);
-      tmp.copy(in);
-      return new String(tmp.toByteArray(), "UTF-8");
+      try {
+        tmp.copy(in);
+        return new String(tmp.toByteArray(), "UTF-8");
+      } finally {
+        tmp.close();
+      }
     } finally {
       in.close();
     }
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 f07ae0c..4846b82 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;
@@ -35,6 +36,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;
@@ -48,8 +51,8 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Map;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nullable;
 
@@ -61,12 +64,14 @@
   private final ApprovalTypes approvalTypes;
   private final PatchListCache patchListCache;
   private final SchemaFactory<ReviewDb> schema;
+  private final PatchSetInfoFactory psInfoFactory;
   private final PersonIdent myIdent;
 
   @Inject
   EventFactory(AccountCache accountCache,
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       ApprovalTypes approvalTypes,
+      final PatchSetInfoFactory psif,
       PatchListCache patchListCache, SchemaFactory<ReviewDb> schema,
       @GerritPersonIdent PersonIdent myIdent) {
     this.accountCache = accountCache;
@@ -74,6 +79,7 @@
     this.approvalTypes = approvalTypes;
     this.patchListCache = patchListCache;
     this.schema = schema;
+    this.psInfoFactory = psif;
     this.myIdent = myIdent;
   }
 
@@ -291,6 +297,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) {
@@ -328,6 +336,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 {
@@ -336,11 +345,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/MergeFailedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java
new file mode 100644
index 0000000..e6ff525
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/MergeFailedEvent.java
@@ -0,0 +1,23 @@
+// 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 MergeFailedEvent extends ChangeEvent {
+    public final String type = "merge-failed";
+    public ChangeAttribute change;
+    public PatchSetAttribute patchSet;
+    public AccountAttribute submitter;
+    public String reason;
+}
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/ChangeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
new file mode 100644
index 0000000..bf2a19f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
@@ -0,0 +1,101 @@
+// 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;
+
+package com.google.gerrit.server.git;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+@Singleton
+public class ChangeCache implements GitReferenceUpdatedListener {
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeCache.class);
+  private static final String ID_CACHE = "changes";
+
+  public static Module module() {
+    return new CacheModule() {
+      @Override
+      protected void configure() {
+        cache(ID_CACHE,
+            Project.NameKey.class,
+            new TypeLiteral<List<Change>>() {})
+          .maximumWeight(1024)
+          .loader(Loader.class);
+      }
+    };
+  }
+
+  private final LoadingCache<Project.NameKey, List<Change>> cache;
+
+  @Inject
+  ChangeCache(@Named(ID_CACHE) LoadingCache<Project.NameKey, List<Change>> cache) {
+    this.cache = cache;
+  }
+
+  List<Change> get(Project.NameKey name) {
+    try {
+      return cache.get(name);
+    } catch (ExecutionException e) {
+      log.warn("Cannot fetch changes for " + name, e);
+      return Collections.emptyList();
+    }
+  }
+
+  @Override
+  public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
+    for (GitReferenceUpdatedListener.Update u : event.getUpdates()) {
+      if (u.getRefName().startsWith("refs/changes/")) {
+        cache.invalidate(new Project.NameKey(event.getProjectName()));
+        break;
+      }
+    }
+  }
+
+  static class Loader extends CacheLoader<Project.NameKey, List<Change>> {
+    private final SchemaFactory<ReviewDb> schema;
+
+    @Inject
+    Loader(SchemaFactory<ReviewDb> schema) {
+      this.schema = schema;
+    }
+
+    @Override
+    public List<Change> load(Project.NameKey key) throws Exception {
+      final ReviewDb db = schema.open();
+      try {
+        return Collections.unmodifiableList(db.changes().byProject(key).toList());
+      } finally {
+        db.close();
+      }
+    }
+  }
+}
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..f6c5f0a 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
@@ -243,6 +243,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..acf45909
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
@@ -0,0 +1,383 @@
+// 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.canCherryPick;
+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;
+  }
+
+  @Override
+  public boolean dryRun(final CodeReviewCommit mergeTip,
+      final CodeReviewCommit toMerge) throws MergeException {
+    return canCherryPick(args.mergeSorter, args.repo, args.useContentMerge,
+        mergeTip, args.rw, toMerge);
+  }
+
+  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..caa027e 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
@@ -22,6 +22,9 @@
   CLEAN_PICK("Change has been successfully cherry-picked"),
 
   /** */
+  CLEAN_REBASE("Change has been successfully rebased"),
+
+  /** */
   ALREADY_MERGED(""),
 
   /** */
@@ -39,6 +42,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."),
@@ -49,6 +55,11 @@
                   + "Please merge the change locally and upload the merge commit for review."),
 
   /** */
+  CANNOT_REBASE_ROOT("Cannot rebase an initial commit onto an existing branch.\n"
+                   + "\n"
+                   + "Please merge the change locally and upload the merge commit for review."),
+
+  /** */
   NOT_FAST_FORWARD("Project policy requires all submissions to be a fast-forward.\n"
                   + "\n"
                   + "Please rebase the change locally and upload again 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..3027b42
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java
@@ -0,0 +1,61 @@
+// 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.canFastForward;
+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;
+  }
+
+  public boolean dryRun(final CodeReviewCommit mergeTip,
+      final CodeReviewCommit toMerge) throws MergeException {
+    return canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge);
+  }
+}
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..501defb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java
@@ -0,0 +1,60 @@
+// 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.canMerge;
+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;
+  }
+
+  @Override
+  public boolean dryRun(final CodeReviewCommit mergeTip,
+      final CodeReviewCommit toMerge) throws MergeException {
+    return canMerge(args.mergeSorter, args.repo, args.useContentMerge,
+        mergeTip, toMerge);
+  }
+}
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..04b3f2b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java
@@ -0,0 +1,65 @@
+// 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.canFastForward;
+import static com.google.gerrit.server.git.MergeUtil.canMerge;
+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;
+  }
+
+  @Override
+  public boolean dryRun(final CodeReviewCommit mergeTip,
+      final CodeReviewCommit toMerge) throws MergeException {
+    return canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge)
+        || canMerge(args.mergeSorter, args.repo, args.useContentMerge,
+            mergeTip, toMerge);
+  }
+}
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..896e2f7 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,29 +14,34 @@
 
 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;
+import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
 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.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 +59,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 +71,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 +79,15 @@
 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.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 +109,14 @@
   }
 
   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 long LOCK_FAILURE_RETRY_DELAY =
+      MILLISECONDS.convert(15, SECONDS);
+
   private final GitRepositoryManager repoManager;
   private final SchemaFactory<ReviewDb> schemaFactory;
   private final ProjectCache projectCache;
@@ -138,7 +124,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 +133,23 @@
   private final PersonIdent myIdent;
   private final Branch.NameKey destBranch;
   private Project destProject;
-  private final List<CodeReviewCommit> toMerge;
-  private List<Change> submitted;
+  private final ListMultimap<SubmitType, 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 +159,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 +166,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 +177,6 @@
     replication = rq;
     mergedSenderFactory = msf;
     mergeFailSenderFactory = mfsf;
-    urlProvider = cwu;
     this.approvalTypes = approvalTypes;
     patchSetInfoFactory = psif;
     identifiedUserFactory = iuf;
@@ -202,12 +186,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 = ArrayListMultimap.create();
+    potentiallyStillSubmittable = new ArrayList<CodeReviewCommit>();
     commits = new HashMap<Change.Id, CodeReviewCommit>();
   }
 
@@ -216,8 +202,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,17 +211,27 @@
           || (destBranchRef != null && !destBranchRef.getObjectId().getName()
               .equals(change.getLastSha1MergeTested().get()))) {
         openSchema();
-        preMerge();
+        openBranch();
+        validateChangeList(Collections.singletonList(change));
+        if (!toMerge.isEmpty()) {
+          final Entry<SubmitType, CodeReviewCommit> e =
+              toMerge.entries().iterator().next();
+          final boolean isMergeable =
+              createStrategy(e.getKey()).dryRun(branchTip, e.getValue());
 
-        // update sha1 tested merge.
-        if (destBranchRef != null) {
-          change.setLastSha1MergeTested(new RevId(destBranchRef
-              .getObjectId().getName()));
+          // update sha1 tested merge.
+          if (destBranchRef != null) {
+            change.setLastSha1MergeTested(new RevId(destBranchRef
+                .getObjectId().getName()));
+          } else {
+            change.setLastSha1MergeTested(new RevId(""));
+          }
+          change.setMergeable(isMergeable);
+          db.changes().update(Collections.singleton(change));
         } else {
-          change.setLastSha1MergeTested(new RevId(""));
+          log.error("Test merge attempt for change: " + change.getId()
+              + " failed");
         }
-        change.setMergeable(isMergeable(change));
-        db.changes().update(Collections.singleton(change));
       }
     } catch (MergeException e) {
       log.error("Test merge attempt for change: " + change.getId()
@@ -277,11 +271,54 @@
     try {
       openSchema();
       openRepository();
-      submitted = db.changes().submitted(destBranch).toList();
-      preMerge();
-      updateBranch();
-      updateChangeStatus();
-      updateSubscriptions();
+      openBranch();
+      final ListMultimap<SubmitType, Change> toSubmit =
+          validateChangeList(db.changes().submitted(destBranch).toList());
+
+      final ListMultimap<SubmitType, CodeReviewCommit> toMergeNextTurn =
+          ArrayListMultimap.create();
+      final List<CodeReviewCommit> potentiallyStillSubmittableOnNextRun =
+          new ArrayList<CodeReviewCommit>();
+      while (!toMerge.isEmpty()) {
+        toMergeNextTurn.clear();
+        final Set<SubmitType> submitTypes =
+            new HashSet<Project.SubmitType>(toMerge.keySet());
+        for (final SubmitType submitType : submitTypes) {
+          final RefUpdate branchUpdate = openBranch();
+          final SubmitStrategy strategy = createStrategy(submitType);
+          preMerge(strategy, toMerge.get(submitType));
+          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;
+              toMergeNextTurn.put(submitType, 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 +337,59 @@
     }
   }
 
-  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 ListMultimap<SubmitType, CodeReviewCommit> map,
+      final CodeReviewCommit commit) {
+    if (!isSubmitForMissingCommitsStillPossible(commit)) {
+      return false;
     }
+
+    for (final CodeReviewCommit missingCommit : commit.missing) {
+      if (!map.containsValue(missingCommit)) {
+        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 +412,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 +442,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 +468,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 ListMultimap<SubmitType, Change> validateChangeList(
+      final List<Change> submitted) throws MergeException {
+    final ListMultimap<SubmitType, Change> toSubmit =
+        ArrayListMultimap.create();
+
     final Set<ObjectId> tips = new HashSet<ObjectId>();
     for (final Ref r : repo.getAllRefs().values()) {
       tips.add(r.getObjectId());
@@ -458,7 +548,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 +561,44 @@
         }
       }
 
-      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);
+      toMerge.put(submitType, commit);
+      toSubmit.put(submitType, 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 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 +611,7 @@
         }
       }
 
+      branchUpdate.setRefLogIdent(refLogIdent);
       branchUpdate.setForceUpdate(false);
       branchUpdate.setNewObjectId(mergeTip);
       branchUpdate.setRefLogMessage("merged", true);
@@ -1086,6 +643,16 @@
             hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
             break;
 
+          case LOCK_FAILURE:
+            String msg;
+            if (strategy.retryOnLockFailure()) {
+              mergeQueue.recheckAfter(destBranch, LOCK_FAILURE_RETRY_DELAY,
+                  MILLISECONDS);
+              msg = "will retry";
+            } else {
+              msg = "will not retry";
+            }
+            throw new IOException(branchUpdate.getResult().name() + ", " + msg);
           default:
             throw new IOException(branchUpdate.getResult().name());
         }
@@ -1095,21 +662,7 @@
     }
   }
 
-  private boolean isMergeable(Change c) {
-    final CodeReviewCommit commit = commits.get(c.getId());
-    final CommitMergeStatus s = commit != null ? commit.statusCode : null;
-    boolean isMergeable = false;
-    if (s != null
-        && (s.equals(CommitMergeStatus.CLEAN_MERGE)
-            || s.equals(CommitMergeStatus.CLEAN_PICK) || s
-            .equals(CommitMergeStatus.ALREADY_MERGED))) {
-      isMergeable = true;
-    }
-
-    return isMergeable;
-  }
-
-  private void updateChangeStatus() {
+  private void updateChangeStatus(final List<Change> submitted) {
     List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
 
     for (final Change c : submitted) {
@@ -1131,6 +684,7 @@
           break;
         }
 
+        case CLEAN_REBASE:
         case CLEAN_PICK: {
           setMerged(c, message(c, txt + " as " + commit.name()));
           merged.add(commit);
@@ -1151,10 +705,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 +718,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 +729,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 +747,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 +868,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 +987,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);
+      }
     }
   }
 
@@ -1527,17 +1034,23 @@
       }
     }
 
+    PatchSetApproval submitter = null;
+    try {
+      submitter = getSubmitter(db, c.currentPatchSetId());
+    } catch (Exception e) {
+      log.error("Cannot get submitter", e);
+    }
+
+    final PatchSetApproval from = submitter;
     workQueue.getDefaultQueue()
         .submit(requestScopePropagator.wrap(new Runnable() {
       @Override
       public void run() {
         PatchSet patchSet;
-        PatchSetApproval submitter;
         try {
           ReviewDb reviewDb = schemaFactory.open();
           try {
             patchSet = reviewDb.patchSets().get(c.currentPatchSetId());
-            submitter = getSubmitter(reviewDb, c.currentPatchSetId());
           } finally {
             reviewDb.close();
           }
@@ -1548,8 +1061,8 @@
 
         try {
           final MergeFailSender cm = mergeFailSenderFactory.create(c);
-          if (submitter != null) {
-            cm.setFrom(submitter.getAccountId());
+          if (from != null) {
+            cm.setFrom(from.getAccountId());
           }
           cm.setPatchSet(patchSet);
           cm.setChangeMessage(msg);
@@ -1564,5 +1077,15 @@
         return "send-email merge-failed";
       }
     }));
+
+    if (submitter != null) {
+      try {
+        hooks.doMergeFailedHook(c,
+            accountCache.get(submitter.getAccountId()).getAccount(),
+            db.patchSets().get(c.currentPatchSetId()), msg.getMessage(), db);
+      } catch (OrmException ex) {
+        log.error("Cannot run hook for merge failed " + 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..227dbee
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -0,0 +1,473 @@
+// 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.MutableObjectId;
+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.eclipse.jgit.transport.PackParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+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 canMerge(final MergeSorter mergeSorter,
+      final Repository repo, final boolean useContentMerge,
+      final CodeReviewCommit mergeTip, final CodeReviewCommit toMerge)
+      throws MergeException {
+    if (hasMissingDependencies(mergeSorter, toMerge)) {
+      return false;
+    }
+
+    final ThreeWayMerger m =
+        newThreeWayMerger(repo, createDryRunInserter(), useContentMerge);
+    try {
+      return m.merge(new AnyObjectId[] {mergeTip, toMerge});
+    } catch (IOException e) {
+      if (e.getMessage().startsWith("Multiple merge bases for")) {
+        return false;
+      }
+      throw new MergeException("Cannot merge " + toMerge.name(), e);
+    }
+  }
+
+  public static boolean canFastForward(final MergeSorter mergeSorter,
+      final CodeReviewCommit mergeTip, final RevWalk rw,
+      final CodeReviewCommit toMerge) throws MergeException {
+    if (hasMissingDependencies(mergeSorter, toMerge)) {
+      return false;
+    }
+
+    try {
+      return mergeTip == null || rw.isMergedInto(mergeTip, toMerge);
+    } catch (IOException e) {
+      throw new MergeException("Cannot fast-forward test during merge", e);
+    }
+  }
+
+  public static boolean canCherryPick(final MergeSorter mergeSorter,
+      final Repository repo, final boolean useContentMerge,
+      final CodeReviewCommit mergeTip, final RevWalk rw,
+      final CodeReviewCommit toMerge) throws MergeException {
+    if (mergeTip == null) {
+      // The branch is unborn. Fast-forward is possible.
+      //
+      return true;
+    }
+
+    if (toMerge.getParentCount() == 0) {
+      // Refuse to merge a root commit into an existing branch,
+      // we cannot obtain a delta for the cherry-pick to apply.
+      //
+      return false;
+    }
+
+    if (toMerge.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.
+      //
+      try {
+        final ThreeWayMerger m =
+            newThreeWayMerger(repo, createDryRunInserter(), useContentMerge);
+        m.setBase(toMerge.getParent(0));
+        return m.merge(mergeTip, toMerge);
+      } catch (IOException e) {
+        throw new MergeException("Cannot merge " + toMerge.name(), e);
+      }
+    }
+
+    // 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.
+    //
+    return canFastForward(mergeSorter, mergeTip, rw, toMerge)
+        || canMerge(mergeSorter, repo, useContentMerge, mergeTip, toMerge);
+  }
+
+  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 ObjectInserter createDryRunInserter() {
+    return new ObjectInserter() {
+      private final MutableObjectId buf = new MutableObjectId();
+      private final static int LAST_BYTE = Constants.OBJECT_ID_LENGTH - 1;
+
+      @Override
+      public ObjectId insert(int objectType, long length, InputStream in)
+          throws IOException {
+        // create non-existing dummy ID
+        buf.setByte(LAST_BYTE, buf.getByte(LAST_BYTE) + 1);
+        return buf.copy();
+      }
+
+      @Override
+      public PackParser newPackParser(InputStream in) throws IOException {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public void flush() throws IOException {
+        // Do nothing.
+      }
+
+      @Override
+      public void release() {
+        // Do nothing.
+      }
+    };
+  }
+
+  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/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
new file mode 100644
index 0000000..1f3d6f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
@@ -0,0 +1,164 @@
+// 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.canCherryPick;
+import static com.google.gerrit.server.git.MergeUtil.canFastForward;
+import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
+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 com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.changedetail.PathConflictException;
+import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RebaseIfNecessary extends SubmitStrategy {
+
+  private final RebaseChange rebaseChange;
+  private final Map<Change.Id, CodeReviewCommit> newCommits;
+
+  RebaseIfNecessary(final SubmitStrategy.Arguments args,
+      final RebaseChange.Factory rebaseChangeFactory) {
+    super(args);
+    this.rebaseChange = rebaseChangeFactory.create();
+    this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
+  }
+
+  @Override
+  protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
+      final List<CodeReviewCommit> toMerge) throws MergeException {
+    CodeReviewCommit newMergeTip = mergeTip;
+    sort(toMerge);
+
+    while (!toMerge.isEmpty()) {
+      final CodeReviewCommit n = toMerge.remove(0);
+
+      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 rebase to apply.
+        //
+        n.statusCode = CommitMergeStatus.CANNOT_REBASE_ROOT;
+
+      } else if (n.getParentCount() == 1) {
+        if (canFastForward(args.mergeSorter, newMergeTip, args.rw, n)) {
+          newMergeTip = n;
+          n.statusCode = CommitMergeStatus.CLEAN_MERGE;
+
+        } else {
+          try {
+            final PatchSet newPatchSet =
+                rebaseChange.rebase(args.repo, args.rw, args.inserter,
+                    n.patchsetId, n.change, getSubmitter(args.db, n.patchsetId)
+                        .getAccountId(), newMergeTip, args.useContentMerge);
+            newMergeTip =
+                (CodeReviewCommit) args.rw.parseCommit(ObjectId
+                    .fromString(newPatchSet.getRevision().get()));
+            newMergeTip.copyFrom(n);
+            newMergeTip.patchsetId = newPatchSet.getId();
+            newMergeTip.change =
+                args.db.changes().get(newPatchSet.getId().getParentKey());
+            newMergeTip.statusCode = CommitMergeStatus.CLEAN_REBASE;
+            newCommits.put(newPatchSet.getId().getParentKey(), newMergeTip);
+            setRefLogIdent(getSubmitter(args.db, n.patchsetId));
+          } catch (PathConflictException e) {
+            n.statusCode = CommitMergeStatus.PATH_CONFLICT;
+          } catch (NoSuchChangeException e) {
+            throw new MergeException("Cannot rebase " + n.name(), e);
+          } catch (OrmException e) {
+            throw new MergeException("Cannot rebase " + n.name(), e);
+          } catch (IOException e) {
+            throw new MergeException("Cannot rebase " + n.name(), e);
+          } catch (InvalidChangeOperationException e) {
+            throw new MergeException("Cannot rebase " + n.name(), e);
+          }
+        }
+
+      } else if (n.getParentCount() > 1) {
+        // There are multiple parents, so this is a merge commit. We
+        // don't want to rebase 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.
+        //
+        try {
+          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);
+        } catch (IOException e) {
+          throw new MergeException("Cannot merge " + n.name(), e);
+        }
+      }
+
+      args.alreadyAccepted.add(newMergeTip);
+    }
+
+    return newMergeTip;
+  }
+
+  private void sort(final List<CodeReviewCommit> toSort) throws MergeException {
+    try {
+      final List<CodeReviewCommit> sorted =
+          new RebaseSorter(args.rw, args.alreadyAccepted, args.canMergeFlag)
+              .sort(toSort);
+      toSort.clear();
+      toSort.addAll(sorted);
+    } catch (IOException e) {
+      throw new MergeException("Commit sorting failed", e);
+    }
+  }
+
+  @Override
+  public Map<Change.Id, CodeReviewCommit> getNewCommits() {
+    return newCommits;
+  }
+
+  @Override
+  public boolean dryRun(final CodeReviewCommit mergeTip,
+      final CodeReviewCommit toMerge) throws MergeException {
+    return !hasMissingDependencies(args.mergeSorter, toMerge)
+        && canCherryPick(args.mergeSorter, args.repo, args.useContentMerge,
+            mergeTip, args.rw, toMerge);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
new file mode 100644
index 0000000..ac1929b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -0,0 +1,91 @@
+// 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 org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+class RebaseSorter {
+
+  private final RevWalk rw;
+  private final RevFlag canMergeFlag;
+  private final Set<RevCommit> accepted;
+
+  RebaseSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
+      final RevFlag canMergeFlag) {
+    this.rw = rw;
+    this.canMergeFlag = canMergeFlag;
+    this.accepted = alreadyAccepted;
+  }
+
+  List<CodeReviewCommit> sort(Collection<CodeReviewCommit> incoming)
+      throws IOException {
+    final List<CodeReviewCommit> sorted = new ArrayList<CodeReviewCommit>();
+    final Set<CodeReviewCommit> sort = new HashSet<CodeReviewCommit>(incoming);
+    while (!sort.isEmpty()) {
+      final CodeReviewCommit n = removeOne(sort);
+
+      rw.resetRetain(canMergeFlag);
+      rw.markStart(n);
+      for (RevCommit c : accepted) {
+        rw.markUninteresting(c);
+      }
+
+      CodeReviewCommit c;
+      final List<CodeReviewCommit> contents = new ArrayList<CodeReviewCommit>();
+      while ((c = (CodeReviewCommit) rw.next()) != null) {
+        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.
+          //
+          if (n.missing == null) {
+            n.statusCode = CommitMergeStatus.MISSING_DEPENDENCY;
+            n.missing = new ArrayList<CodeReviewCommit>();
+          }
+          n.missing.add((CodeReviewCommit) c);
+        } else {
+          contents.add(c);
+        }
+      }
+
+      if (n.statusCode == CommitMergeStatus.MISSING_DEPENDENCY) {
+        continue;
+      }
+
+      sort.removeAll(contents);
+      Collections.reverse(contents);
+      sorted.removeAll(contents);
+      sorted.addAll(contents);
+    }
+    return sorted;
+  }
+
+  private static <T> T removeOne(final Collection<T> c) {
+    final Iterator<T> i = c.iterator();
+    final T r = i.next();
+    i.remove();
+    return r;
+  }
+}
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..ebe6b43 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
@@ -68,6 +68,7 @@
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.gwtorm.server.AtomicUpdate;
@@ -77,6 +78,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import com.jcraft.jsch.HostKey;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -100,10 +102,13 @@
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.transport.ReceiveCommand.Result;
 import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.util.SystemReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -247,9 +252,11 @@
   private final PersonIdent gerritIdent;
   private final TrackingFooters trackingFooters;
   private final TagCache tagCache;
+  private final ChangeCache changeCache;
   private final WorkQueue workQueue;
   private final ListeningExecutorService changeUpdateExector;
   private final RequestScopePropagator requestScopePropagator;
+  private final SshInfo sshInfo;
 
   private final ProjectControl projectControl;
   private final Project project;
@@ -296,12 +303,14 @@
       final ProjectCache projectCache,
       final GitRepositoryManager repoManager,
       final TagCache tagCache,
+      final ChangeCache changeCache,
       @CanonicalWebUrl @Nullable final String canonicalWebUrl,
       @GerritPersonIdent final PersonIdent gerritIdent,
       final TrackingFooters trackingFooters,
       final WorkQueue workQueue,
       @ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
       final RequestScopePropagator requestScopePropagator,
+      final SshInfo sshInfo,
 
       @Assisted final ProjectControl projectControl,
       @Assisted final Repository repo,
@@ -323,9 +332,11 @@
     this.gerritIdent = gerritIdent;
     this.trackingFooters = trackingFooters;
     this.tagCache = tagCache;
+    this.changeCache = changeCache;
     this.workQueue = workQueue;
     this.changeUpdateExector = changeUpdateExector;
     this.requestScopePropagator = requestScopePropagator;
+    this.sshInfo = sshInfo;
 
     this.projectControl = projectControl;
     this.project = projectControl.getProject();
@@ -344,7 +355,7 @@
 
     if (!projectControl.allRefsAreVisible()) {
       rp.setCheckReferencedObjectsAreReachable(true);
-      rp.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, repo, projectControl, db, false));
+      rp.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo, projectControl, db, false));
     }
     List<AdvertiseRefsHook> advHooks = new ArrayList<AdvertiseRefsHook>(2);
     advHooks.add(rp.getAdvertiseRefsHook());
@@ -888,7 +899,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 +998,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;
     }
 
@@ -2022,6 +2033,76 @@
     return true;
   }
 
+  /**
+   * Get the Gerrit hostname.
+   * @return the hostname from the canonical URL if it is configured,
+   * otherwise whatever the OS says the hostname is.
+   */
+  private String getGerritHost() {
+    String host;
+    if (canonicalWebUrl != null) {
+      try {
+        host = new URL(canonicalWebUrl).getHost();
+      } catch (MalformedURLException e) {
+        host = SystemReader.getInstance().getHostname();
+      }
+    } else {
+      host = SystemReader.getInstance().getHostname();
+    }
+    return host;
+  }
+
+  /**
+   * Get the Gerrit URL.
+   * @return the canonical URL (with any trailing slash removed) if it is
+   * configured, otherwise fall back to "http://hostname" where hostname is
+   * the value returned by {@link #getGerritHost()}.
+   */
+  private String getGerritUrl() {
+    if (canonicalWebUrl != null) {
+      if (canonicalWebUrl.endsWith("/")) {
+        return canonicalWebUrl.substring(0, canonicalWebUrl.lastIndexOf("/"));
+      }
+      return canonicalWebUrl;
+    } else {
+      return "http://" + getGerritHost();
+    }
+  }
+
+  /**
+   * Get the text with instructions for installing the commit-msg hook, specific
+   * to the server hostname and transport protocol.
+   * @return commit-msg hook installation instructions as a String.
+   */
+  private String getCommitMessageHookInstallationHint() {
+    final List<HostKey> hostKeys = sshInfo.getHostKeys();
+
+    // If there are no SSH keys, the commit-msg hook must be installed via HTTP(S)
+    if (hostKeys.isEmpty()) {
+      return "$ curl -o .git/hooks/commit-msg " + getGerritUrl() + "/tools/hooks/commit-msg\n"
+          + "$ chmod +x .git/hooks/commit-msg";
+    }
+
+    // SSH keys exist, so the hook can be installed with scp.
+    String sshHost;
+    int sshPort;
+    String host = hostKeys.get(0).getHost();
+    int c = host.lastIndexOf(':');
+    if (0 <= c) {
+      if (host.startsWith("*:")) {
+        sshHost = getGerritHost();
+      } else {
+        sshHost = host.substring(0, c);
+      }
+      sshPort = Integer.parseInt(host.substring(c+1));
+    } else {
+      sshHost = host;
+      sshPort = 22;
+    }
+
+    return "$ scp -p -P " + sshPort + " " + currentUser.getUserName() + "@" + sshHost + ":hooks/commit-msg .git/hooks/";
+  }
+
   private String getFixedCommitMsgWithChangeId(String errMsg, RevCommit c) {
     // We handle 3 cases:
     // 1. No change id in the commit message at all.
@@ -2056,6 +2137,9 @@
         sb.append("\nHint: A potential Change-Id was found, but it was not in the footer of the commit message.");
       }
     }
+    sb.append("\n");
+    sb.append("Hint: To automatically add a Change-Id to commit messages, install the commit-msg hook:\n");
+    sb.append(getCommitMessageHookInstallationHint());
 
     return sb.toString();
   }
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..e4da46d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
@@ -0,0 +1,187 @@
+// 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;
+
+  /**
+   * Checks whether the given commit can be merged.
+   *
+   * Subclasses must ensure that invoking this method does neither modify the
+   * git repository nor the Gerrit database.
+   *
+   * @param mergeTip the mergeTip
+   * @param toMerge the commit for which it should be checked whether it can be
+   *        merged or not
+   * @return <code>true</code> if the given commit can be merged, otherwise
+   *         <code>false</code>
+   * @throws MergeException
+   */
+  public abstract boolean dryRun(CodeReviewCommit mergeTip,
+      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..c5a990a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
@@ -0,0 +1,100 @@
+// 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.changedetail.RebaseChange;
+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;
+  private final RebaseChange.Factory rebaseChangeFactory;
+
+  @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,
+      final RebaseChange.Factory rebaseChangeFactory) {
+    this.identifiedUserFactory = identifiedUserFactory;
+    this.myIdent = myIdent;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.urlProvider = urlProvider;
+    this.approvalTypes = approvalTypes;
+    this.replication = replication;
+    this.rebaseChangeFactory = rebaseChangeFactory;
+  }
+
+  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);
+      case REBASE_IF_NECESSARY:
+        return new RebaseIfNecessary(args, rebaseChangeFactory);
+      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/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index ccb91a3..7129b86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -217,7 +217,8 @@
           for (final Change chg : submitted) {
             final CodeReviewCommit c = commits.get(chg.getId());
             if (c != null
-                && (c.statusCode == CommitMergeStatus.CLEAN_MERGE || c.statusCode == CommitMergeStatus.CLEAN_PICK)) {
+                && (c.statusCode == CommitMergeStatus.CLEAN_MERGE
+                    || c.statusCode == CommitMergeStatus.CLEAN_PICK || c.statusCode == CommitMergeStatus.CLEAN_REBASE)) {
               msgbuf += "\n";
               msgbuf += c.getFullMessage();
             }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index bc9d9f7..6ea5b11 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -22,7 +22,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gwtorm.server.OrmException;
-
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -44,16 +43,19 @@
       LoggerFactory.getLogger(VisibleRefFilter.class);
 
   private final TagCache tagCache;
+  private final ChangeCache changeCache;
   private final Repository db;
   private final Project.NameKey projectName;
   private final ProjectControl projectCtl;
   private final ReviewDb reviewDb;
   private final boolean showChanges;
 
-  public VisibleRefFilter(final TagCache tagCache, final Repository db,
+  public VisibleRefFilter(final TagCache tagCache, final ChangeCache changeCache,
+      final Repository db,
       final ProjectControl projectControl, final ReviewDb reviewDb,
       final boolean showChanges) {
     this.tagCache = tagCache;
+    this.changeCache = changeCache;
     this.db = db;
     this.projectName = projectControl.getProject().getNameKey();
     this.projectCtl = projectControl;
@@ -133,7 +135,7 @@
     final Project project = projectCtl.getProject();
     try {
       final Set<Change.Id> visibleChanges = new HashSet<Change.Id>();
-      for (Change change : reviewDb.changes().byProject(project.getNameKey())) {
+      for (Change change : changeCache.get(project.getNameKey())) {
         if (projectCtl.controlFor(change).isVisible(reviewDb)) {
           visibleChanges.add(change.getId());
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 31c8bd5..18683ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -121,11 +121,16 @@
       }
     } catch (OrmException e) {
     }
+    formatFooter();
   }
 
   /** Format the message body by calling {@link #appendText(String)}. */
   protected abstract void formatChange() throws EmailException;
 
+  /** Format the message footer by calling {@link #appendText(String)}. */
+  protected void formatFooter() throws EmailException {
+  }
+
   /** Setup the message headers and envelope (TO, CC, BCC). */
   protected void init() throws EmailException {
     if (args.projectCache != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index e7cc1ff..bfba1b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -77,6 +77,15 @@
     appendText(velocifyFile("Comment.vm"));
   }
 
+  @Override
+  public void formatFooter() throws EmailException {
+    appendText(velocifyFile("CommentFooter.vm"));
+  }
+
+  public boolean hasInlineComments() {
+    return !inlineComments.isEmpty();
+  }
+
   public String getInlineComments() {
     return getInlineComments(1);
   }
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..46f0ffa 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>();
@@ -428,7 +433,12 @@
 
   public static String shortestExample(String pattern) {
     if (isRE(pattern)) {
-      return toRegExp(pattern).toAutomaton().getShortestExample(true);
+      // Since Brics will substitute dot [.] with \0 when generating
+      // shortest example, any usage of dot will fail in
+      // Repository.isValidRefName() if not combined with star [*].
+      // To get around this, we substitute the \0 with an arbitrary
+      // accepted character.
+      return toRegExp(pattern).toAutomaton().getShortestExample(true).replace('\0', '-');
     } else if (pattern.endsWith("/*")) {
       return pattern.substring(0, pattern.length() - 1) + '1';
     } else {
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/QueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
index b3c10ef..2a6bae0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
@@ -478,28 +478,6 @@
     return r;
   }
 
-  private static <T> void expand(final List<Predicate<T>> out,
-      final List<Predicate<T>> allOR, final List<Predicate<T>> tmp,
-      final List<Predicate<T>> nonOR) {
-    if (tmp.size() == allOR.size()) {
-      final int sz = nonOR.size() + tmp.size();
-      final List<Predicate<T>> newList = new ArrayList<Predicate<T>>(sz);
-      newList.addAll(nonOR);
-      newList.addAll(tmp);
-      out.add(Predicate.and(newList));
-
-    } else {
-      for (final Predicate<T> c : allOR.get(tmp.size()).getChildren()) {
-        try {
-          tmp.add(c);
-          expand(out, allOR, tmp, nonOR);
-        } finally {
-          tmp.remove(tmp.size() - 1);
-        }
-      }
-    }
-  }
-
   private static <T> boolean isAND(final Predicate<T> p) {
     return p instanceof AndPredicate;
   }
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..49059c9 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,15 +265,15 @@
         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));
 
           if (includeSubmitRecords) {
             PatchSet.Id psId = d.getChange().currentPatchSetId();
             PatchSet patchSet = db.get().patchSets().get(psId);
-            Change.Id changeId = psId.getParentKey();
             List<SubmitRecord> submitResult = d.changeControl().canSubmit( //
                 db.get(), patchSet, null, false, true);
             eventFactory.addSubmitRecords(c, submitResult);
@@ -320,6 +325,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/com/google/gerrit/server/schema/BaseDataSourceType.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/BaseDataSourceType.java
new file mode 100644
index 0000000..1d12fea
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/BaseDataSourceType.java
@@ -0,0 +1,66 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class BaseDataSourceType implements DataSourceType {
+
+  private final String driver;
+
+  protected BaseDataSourceType(String driver) {
+    this.driver = driver;
+  }
+
+  @Override
+  public final String getDriver() {
+    return driver;
+  }
+
+  @Override
+  public boolean usePool() {
+    return true;
+  }
+
+  @Override
+  public ScriptRunner getIndexScript() throws IOException {
+    return getScriptRunner("index_generic.sql");
+  }
+
+  @Override
+  public ScriptRunner getNextValScript() throws IOException {
+    return ScriptRunner.NOOP;
+  }
+
+  protected static final ScriptRunner getScriptRunner(String path) throws IOException {
+    if (path == null) {
+      return ScriptRunner.NOOP;
+    }
+    InputStream in =  ReviewDb.class.getResourceAsStream(path);
+    if (in == null) {
+      throw new IllegalStateException("SQL script " + path + " not found");
+    }
+    ScriptRunner runner;
+    try {
+      runner = new ScriptRunner(path, in);
+    } finally {
+      in.close();
+    }
+    return runner;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
new file mode 100644
index 0000000..4066ad3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
@@ -0,0 +1,29 @@
+// 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.schema;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class DataSourceModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    bind(DataSourceType.class).annotatedWith(Names.named("h2")).to(H2.class);
+    bind(DataSourceType.class).annotatedWith(Names.named("jdbc")).to(JDBC.class);
+    bind(DataSourceType.class).annotatedWith(Names.named("mysql")).to(MySql.class);
+    bind(DataSourceType.class).annotatedWith(Names.named("postgresql")).to(PostgreSQL.class);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
index cc48019..0c54883 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
@@ -14,11 +14,12 @@
 
 package com.google.gerrit.server.schema;
 
-import static com.google.gerrit.server.config.ConfigUtil.getEnum;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.ConfigSection;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -31,8 +32,6 @@
 import org.apache.commons.dbcp.BasicDataSource;
 import org.eclipse.jgit.lib.Config;
 
-import java.io.File;
-import java.io.IOException;
 import java.sql.SQLException;
 import java.util.Properties;
 
@@ -46,8 +45,8 @@
 
   @Inject
   DataSourceProvider(final SitePaths site,
-      @GerritServerConfig final Config cfg, Context ctx) {
-    ds = open(site, cfg, ctx);
+      @GerritServerConfig final Config cfg, Context ctx, DataSourceType dst) {
+    ds = open(site, cfg, ctx, dst);
   }
 
   @Override
@@ -74,100 +73,27 @@
     SINGLE_USER, MULTI_USER;
   }
 
-  public static enum Type {
-    H2, POSTGRESQL, MYSQL, JDBC;
-  }
-
   private DataSource open(final SitePaths site, final Config cfg,
-      final Context context) {
-    Type type = getEnum(cfg, "database", null, "type", Type.values(), null);
-    String driver = optional(cfg, "driver");
-    String url = optional(cfg, "url");
-    String username = optional(cfg, "username");
-    String password = optional(cfg, "password");
-
-    if (url == null || url.isEmpty()) {
-      if (type == null) {
-        type = Type.H2;
-      }
-
-      switch (type) {
-        case H2: {
-          String database = optional(cfg, "database");
-          if (database == null || database.isEmpty()) {
-            database = "db/ReviewDB";
-          }
-          File db = site.resolve(database);
-          try {
-            db = db.getCanonicalFile();
-          } catch (IOException e) {
-            db = db.getAbsoluteFile();
-          }
-          url = "jdbc:h2:" + db.toURI().toString();
-          break;
-        }
-
-        case POSTGRESQL: {
-          final StringBuilder b = new StringBuilder();
-          b.append("jdbc:postgresql://");
-          b.append(hostname(optional(cfg, "hostname")));
-          b.append(port(optional(cfg, "port")));
-          b.append("/");
-          b.append(required(cfg, "database"));
-          url = b.toString();
-          break;
-        }
-
-        case MYSQL: {
-          final StringBuilder b = new StringBuilder();
-          b.append("jdbc:mysql://");
-          b.append(hostname(optional(cfg, "hostname")));
-          b.append(port(optional(cfg, "port")));
-          b.append("/");
-          b.append(required(cfg, "database"));
-          url = b.toString();
-          break;
-        }
-
-        case JDBC:
-          driver = required(cfg, "driver");
-          url = required(cfg, "url");
-          break;
-
-        default:
-          throw new IllegalArgumentException(type + " not supported");
-      }
+      final Context context, final DataSourceType dst) {
+    ConfigSection dbs = new ConfigSection(cfg, "database");
+    String driver = dbs.optional("driver");
+    if (Strings.isNullOrEmpty(driver)) {
+      driver = dst.getDriver();
     }
 
-    if (driver == null || driver.isEmpty()) {
-      if (url.startsWith("jdbc:h2:")) {
-        driver = "org.h2.Driver";
-
-      } else if (url.startsWith("jdbc:postgresql:")) {
-        driver = "org.postgresql.Driver";
-
-      } else if (url.startsWith("jdbc:mysql:")) {
-        driver = "com.mysql.jdbc.Driver";
-
-      } else {
-        throw new IllegalArgumentException("database.driver must be set");
-      }
+    String url = dbs.optional("url");
+    if (Strings.isNullOrEmpty(url)) {
+      url = dst.getUrl();
     }
 
+    String username = dbs.optional("username");
+    String password = dbs.optional("password");
+
     boolean usePool;
-    if (url.startsWith("jdbc:mysql:")) {
-      // MySQL has given us trouble with the connection pool,
-      // sometimes the backend disconnects and the pool winds
-      // up with a stale connection. Fortunately opening up
-      // a new MySQL connection is usually very fast.
-      //
-      usePool = false;
-    } else {
-      usePool = true;
-    }
-    usePool = cfg.getBoolean("database", "connectionpool", usePool);
     if (context == Context.SINGLE_USER) {
       usePool = false;
+    } else {
+      usePool = cfg.getBoolean("database", "connectionpool", dst.usePool());
     }
 
     if (usePool) {
@@ -207,33 +133,4 @@
       }
     }
   }
-
-  private static String hostname(String hostname) {
-    if (hostname == null || hostname.isEmpty()) {
-      hostname = "localhost";
-
-    } else if (hostname.contains(":") && !hostname.startsWith("[")) {
-      hostname = "[" + hostname + "]";
-    }
-    return hostname;
-  }
-
-  private static String port(String port) {
-    if (port != null && !port.isEmpty()) {
-      return ":" + port;
-    }
-    return "";
-  }
-
-  private static String optional(final Config config, final String name) {
-    return config.getString("database", null, name);
-  }
-
-  private static String required(final Config config, final String name) {
-    final String v = optional(config, name);
-    if (v == null || "".equals(v)) {
-      throw new IllegalArgumentException("No database." + name + " configured");
-    }
-    return v;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
new file mode 100644
index 0000000..513ef67
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
@@ -0,0 +1,44 @@
+// 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.schema;
+
+import java.io.IOException;
+
+
+/** Abstraction of a supported database platform */
+public interface DataSourceType {
+
+  public String getDriver();
+
+  public String getUrl();
+
+  public boolean usePool();
+
+  /**
+   * Return a ScriptRunner that runs the index script. Must not return
+   * <code>null</code>, but may return a ScriptRunner that does nothing.
+   *
+   * @throws IOException
+   */
+  public ScriptRunner getIndexScript() throws IOException;
+
+  /**
+   * Return a ScriptRunner that runs the nextVal script. Must not return
+   * <code>null</code>, but may return a ScriptRunner that does nothing.
+   *
+   * @throws IOException
+   */
+  public ScriptRunner getNextValScript() throws IOException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.java
new file mode 100644
index 0000000..f43530f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2.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.schema;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.io.File;
+import java.io.IOException;
+
+class H2 extends BaseDataSourceType {
+
+  protected final Config cfg;
+  private final SitePaths site;
+
+  @Inject
+  H2(final SitePaths site, @GerritServerConfig final Config cfg) {
+    super("org.h2.Driver");
+    this.cfg = cfg;
+    this.site = site;
+  }
+
+  @Override
+  public String getUrl() {
+    String database = cfg.getString("database", null, "database");
+    if (database == null || database.isEmpty()) {
+      database = "db/ReviewDB";
+    }
+    File db = site.resolve(database);
+    try {
+      db = db.getCanonicalFile();
+    } catch (IOException e) {
+      db = db.getAbsoluteFile();
+    }
+    return "jdbc:h2:" + db.toURI().toString();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java
new file mode 100644
index 0000000..2c2051d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java
@@ -0,0 +1,37 @@
+// 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.schema;
+
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+class JDBC extends BaseDataSourceType {
+
+  protected final Config cfg;
+
+  @Inject
+  JDBC(@GerritServerConfig final Config cfg) {
+    super(ConfigUtil.getRequired(cfg, "database", "driver"));
+    this.cfg = cfg;
+  }
+
+  @Override
+  public String getUrl() {
+    return ConfigUtil.getRequired(cfg, "database", "url");
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcUtil.java
new file mode 100644
index 0000000..90ca43d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcUtil.java
@@ -0,0 +1,35 @@
+// 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.schema;
+
+public class JdbcUtil {
+
+  public static String hostname(String hostname) {
+    if (hostname == null || hostname.isEmpty()) {
+      hostname = "localhost";
+
+    } else if (hostname.contains(":") && !hostname.startsWith("[")) {
+      hostname = "[" + hostname + "]";
+    }
+    return hostname;
+  }
+
+  static String port(String port) {
+    if (port != null && !port.isEmpty()) {
+      return ":" + port;
+    }
+    return "";
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
new file mode 100644
index 0000000..ed7aadf
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
@@ -0,0 +1,59 @@
+// 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.schema;
+
+import static com.google.gerrit.server.schema.JdbcUtil.hostname;
+import static com.google.gerrit.server.schema.JdbcUtil.port;
+
+import com.google.gerrit.server.config.ConfigSection;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+
+class MySql extends BaseDataSourceType {
+
+  private Config cfg;
+
+  @Inject
+  public MySql(@GerritServerConfig final Config cfg) {
+    super("com.mysql.jdbc.Driver");
+    this.cfg = cfg;
+  }
+
+  @Override
+  public String getUrl() {
+    final StringBuilder b = new StringBuilder();
+    final ConfigSection dbs = new ConfigSection(cfg, "database");
+    b.append("jdbc:mysql://");
+    b.append(hostname(dbs.optional("hostname")));
+    b.append(port(dbs.optional("port")));
+    b.append("/");
+    b.append(dbs.required("database"));
+    return b.toString();
+  }
+
+  @Override
+  public boolean usePool() {
+    return false;
+  }
+
+  @Override
+  public ScriptRunner getNextValScript() throws IOException {
+    return getScriptRunner("mysql_nextval.sql");
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java
new file mode 100644
index 0000000..c58d0c2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java
@@ -0,0 +1,54 @@
+// 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.schema;
+
+import static com.google.gerrit.server.schema.JdbcUtil.hostname;
+import static com.google.gerrit.server.schema.JdbcUtil.port;
+
+import com.google.gerrit.server.config.ConfigSection;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+
+class PostgreSQL extends BaseDataSourceType {
+
+  private Config cfg;
+
+  @Inject
+  public PostgreSQL(@GerritServerConfig final Config cfg) {
+    super("org.postgresql.Driver");
+    this.cfg = cfg;
+  }
+
+  @Override
+  public String getUrl() {
+    final StringBuilder b = new StringBuilder();
+    final ConfigSection dbc = new ConfigSection(cfg, "database");
+    b.append("jdbc:postgresql://");
+    b.append(hostname(dbc.optional("hostname")));
+    b.append(port(dbc.optional("port")));
+    b.append("/");
+    b.append(dbc.required("database"));
+    return b.toString();
+  }
+
+  @Override
+  public ScriptRunner getIndexScript() throws IOException {
+    return getScriptRunner("index_postgres.sql");
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index fd379b2..7ebaceb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -38,10 +38,6 @@
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gwtorm.jdbc.JdbcExecutor;
 import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
-import com.google.gwtorm.schema.sql.SqlDialect;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -65,11 +61,9 @@
   private final GitRepositoryManager mgr;
   private final AllProjectsName allProjectsName;
   private final PersonIdent serverUser;
+  private final DataSourceType dataSourceType;
 
   private final int versionNbr;
-  private final ScriptRunner index_generic;
-  private final ScriptRunner index_postgres;
-  private final ScriptRunner mysql_nextval;
 
   private AccountGroup admin;
   private AccountGroup anonymous;
@@ -81,23 +75,23 @@
       @Current SchemaVersion version,
       GitRepositoryManager mgr,
       AllProjectsName allProjectsName,
-      @GerritPersonIdent PersonIdent au) {
-    this(site.site_path, version, mgr, allProjectsName, au);
+      @GerritPersonIdent PersonIdent au,
+      DataSourceType dst) {
+    this(site.site_path, version, mgr, allProjectsName, au, dst);
   }
 
   public SchemaCreator(@SitePath File site,
       @Current SchemaVersion version,
       GitRepositoryManager gitMgr,
       AllProjectsName ap,
-      @GerritPersonIdent PersonIdent au) {
+      @GerritPersonIdent PersonIdent au,
+      DataSourceType dst) {
     site_path = site;
     mgr = gitMgr;
     allProjectsName = ap;
     serverUser = au;
+    dataSourceType = dst;
     versionNbr = version.getVersionNbr();
-    index_generic = new ScriptRunner("index_generic.sql");
-    index_postgres = new ScriptRunner("index_postgres.sql");
-    mysql_nextval = new ScriptRunner("mysql_nextval.sql");
   }
 
   public void create(final ReviewDb db) throws OrmException, IOException,
@@ -123,20 +117,8 @@
       initWildCardProject();
     }
 
-    final SqlDialect d = jdbc.getDialect();
-    if (d instanceof DialectH2) {
-      index_generic.run(db);
-
-    } else if (d instanceof DialectMySQL) {
-      index_generic.run(db);
-      mysql_nextval.run(db);
-
-    } else if (d instanceof DialectPostgreSQL) {
-      index_postgres.run(db);
-
-    } else {
-      throw new OrmException("Unsupported database " + d.getClass().getName());
-    }
+    dataSourceType.getIndexScript().run(db);
+    dataSourceType.getNextValScript().run(db);
   }
 
   private AccountGroup newGroup(ReviewDb c, String name, AccountGroup.UUID uuid)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index f127a21..89e6ee6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -83,6 +83,11 @@
       throws OrmException, SQLException {
     final JdbcSchema s = (JdbcSchema) db;
 
+    if (curr.versionNbr > versionNbr) {
+      throw new OrmException("Cannot downgrade database schema from version " + curr.versionNbr
+          + " to " + versionNbr + ".");
+    }
+
     prior.get().check(ui, curr, db, false);
 
     ui.message("Upgrading database schema from version " + curr.versionNbr
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
index 133b856..abe813d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.SitePaths;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
@@ -37,14 +38,17 @@
   }
 
   private final SchemaFactory<ReviewDb> schema;
+  private final SitePaths site;
 
   @Current
   private final Provider<SchemaVersion> version;
 
   @Inject
   public SchemaVersionCheck(SchemaFactory<ReviewDb> schemaFactory,
+      final SitePaths site,
       @Current Provider<SchemaVersion> version) {
     this.schema = schemaFactory;
+    this.site = site;
     this.version = version;
   }
 
@@ -52,17 +56,25 @@
     try {
       final ReviewDb db = schema.open();
       try {
-        final CurrentSchemaVersion sVer = getSchemaVersion(db);
-        final int eVer = version.get().getVersionNbr();
+        final CurrentSchemaVersion currentVer = getSchemaVersion(db);
+        final int expectedVer = version.get().getVersionNbr();
 
-        if (sVer == null) {
+        if (currentVer == null) {
           throw new ProvisionException("Schema not yet initialized."
-              + "  Run init to initialize the schema.");
+              + "  Run init to initialize the schema:\n"
+              + "$ java -jar gerrit.war init -d "
+              + site.site_path.getAbsolutePath());
         }
-        if (sVer.versionNbr != eVer) {
+        if (currentVer.versionNbr < expectedVer) {
           throw new ProvisionException("Unsupported schema version "
-              + sVer.versionNbr + "; expected schema version " + eVer
-              + ".  Run init to upgrade.");
+              + currentVer.versionNbr + "; expected schema version " + expectedVer
+              + ".  Run init to upgrade:\n"
+              + "$ java -jar gerrit.war init -d "
+              + site.site_path.getAbsolutePath());
+        } else if (currentVer.versionNbr > expectedVer) {
+          throw new ProvisionException("Unsupported schema version "
+              + currentVer.versionNbr + "; expected schema version " + expectedVer
+              + ". Downgrade is not supported.");
         }
       } finally {
         db.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
index 8cf2f26..b5aead8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
@@ -19,7 +19,6 @@
 import com.google.gwtorm.server.OrmException;
 
 import java.io.BufferedReader;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -34,10 +33,15 @@
   private final String name;
   private final List<String> commands;
 
-  ScriptRunner(final String name) {
+  static final ScriptRunner NOOP = new ScriptRunner(null, null) {
+    void run(final ReviewDb db) {
+    };
+  };
+
+  ScriptRunner(final String scriptName, final InputStream script) {
+    this.name = scriptName;
     try {
-      this.name = name;
-      this.commands = parse(name);
+      this.commands = script != null ? parse(script) : null;
     } catch (IOException e) {
       throw new IllegalStateException("Cannot parse " + name, e);
     }
@@ -63,12 +67,7 @@
     }
   }
 
-  private List<String> parse(final String name) throws IOException {
-    InputStream in = ReviewDb.class.getResourceAsStream(name);
-    if (in == null) {
-      throw new FileNotFoundException("SQL script " + name + " not found");
-    }
-
+  private List<String> parse(final InputStream in) throws IOException {
     BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
     try {
       String delimiter = ";";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java
index 510deaa02..4928e12 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java
@@ -54,6 +54,20 @@
     return false;
   }
 
+  /** Returns the ref name prefix for a magic branch, <code>null</code> if the branch is not magic */
+  public static String getMagicRefNamePrefix(String refName) {
+    if (refName.startsWith(NEW_DRAFT_CHANGE)) {
+      return NEW_DRAFT_CHANGE;
+    }
+    if (refName.startsWith(NEW_PUBLISH_CHANGE)) {
+      return NEW_PUBLISH_CHANGE;
+    }
+    if (refName.startsWith(NEW_CHANGE)) {
+      return NEW_CHANGE;
+    }
+    return null;
+  }
+
   /**
    * Checks if a (magic branch)/branch_name reference exists in the
    * destination repository and only returns Capable.OK if it does not match any.
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/mail/Comment.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
index 9af98a6..e64677d 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
@@ -30,9 +30,9 @@
 ## --------------
 ## The Comment.vm template will determine the contents of the email related to
 ## a user submitting comments on changes.  It is a ChangeEmail: see
-## ChangeSubject.vm and ChangeFooter.vm.
+## ChangeSubject.vm, ChangeFooter.vm and CommentFooter.vm.
 ##
-#if ($email.coverLetter || $email.inlineComments)
+#if ($email.coverLetter || $email.hasInlineComments())
 $fromName has posted comments on this change.
 
 Change subject: $change.subject
@@ -45,9 +45,9 @@
 #end
 ##
 ## It is possible to increase the span of the quoted lines by using the line
-## count parameter when calling $email.inlineComments as a function.
+## count parameter when calling $email.getInlineComments as a function.
 ##
-## Example: #if($email.inlineComments)$email.getInlineComments(5)#end
+## Example: #if($email.hasInlineComments())$email.getInlineComments(5)#end
 ##
-#if($email.inlineComments)$email.inlineComments#end
+#if($email.hasInlineComments())$email.inlineComments#end
 #end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.vm
new file mode 100644
index 0000000..0eb750c
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.vm
@@ -0,0 +1,40 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.example file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The CommentFooter.vm template will determine the contents of the footer
+## text that will be appended to emails related to a user submitting comments
+## on changes.
+##
+## See ChangeSubject.vm and ChangeFooter.vm.
+#if($email.hasInlineComments())
+Gerrit-HasInlineComments: Yes
+#else
+Gerrit-HasInlineComments: No
+#end
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-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index cc8d47d..bdd2258 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
 import com.google.gerrit.testutil.InMemoryDatabase;
+import com.google.gerrit.testutil.InMemoryH2Type;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.gwtorm.server.StatementExecutor;
@@ -94,6 +95,8 @@
         bind(String.class) //
           .annotatedWith(AnonymousCowardName.class) //
           .toProvider(AnonymousCowardNameProvider.class);
+
+        bind(DataSourceType.class).to(InMemoryH2Type.class);
       }
     }).getInstance(SchemaUpdater.class);
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
index b1f956f..58f442f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
@@ -157,7 +157,8 @@
               schemaVersion,
               null,
               new AllProjectsName("Test-Projects"),
-              new PersonIdent("name", "email@site")).create(c);
+              new PersonIdent("name", "email@site"),
+              new InMemoryH2Type()).create(c);
         } catch (IOException e) {
           throw new OrmException("Cannot create in-memory database", e);
         } catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java
new file mode 100644
index 0000000..25a6534
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testutil;
+
+import com.google.gerrit.server.schema.BaseDataSourceType;
+
+public class InMemoryH2Type extends BaseDataSourceType {
+
+  protected InMemoryH2Type() {
+    super(null);
+  }
+
+  @Override
+  public String getUrl() {
+    // not used
+    throw new UnsupportedOperationException();
+  }
+}
\ No newline at end of file
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-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 9e04f05..3abad87 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -62,7 +62,6 @@
   static final int STATUS_NOT_FOUND = PRIVATE_STATUS | 2;
   public static final int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3;
 
-  @SuppressWarnings("unused")
   @Option(name = "--", usage = "end of options", handler = EndOfOptionsHandler.class)
   private boolean endOfOptions;
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
index 46eb788..fc127ec 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.sshd.commands;
 
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.ChangeCache;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.git.TransferConfig;
 import com.google.gerrit.server.git.VisibleRefFilter;
@@ -37,6 +38,9 @@
   @Inject
   private TagCache tagCache;
 
+  @Inject
+  private ChangeCache changeCache;
+
   @Override
   protected void runImpl() throws IOException, Failure {
     if (!projectControl.canRunUploadPack()) {
@@ -45,7 +49,7 @@
 
     final UploadPack up = new UploadPack(repo);
     if (!projectControl.allRefsAreVisible()) {
-      up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, repo,
+      up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo,
           projectControl, db.get(), true));
     }
     up.setPackConfig(config.getPackConfig());
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..ed473c6 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>
@@ -59,14 +59,12 @@
     <dependency>
       <groupId>bouncycastle</groupId>
       <artifactId>bcprov-jdk15</artifactId>
-      <version>140</version>
       <scope>provided</scope>
     </dependency>
 
     <dependency>
       <groupId>bouncycastle</groupId>
       <artifactId>bcpg-jdk15</artifactId>
-      <version>140</version>
       <scope>provided</scope>
     </dependency>
 
@@ -184,6 +182,33 @@
               <goal>run</goal>
             </goals>
           </execution>
+          <execution>
+            <id>include-release-notes</id>
+            <phase>process-classes</phase>
+            <configuration>
+              <target if="gerrit.include-documentation">
+                <property name="src" location="${basedir}/../ReleaseNotes" />
+                <property name="out" location="${project.build.directory}/${project.build.finalName}" />
+                <property name="dst" location="${out}/ReleaseNotes" />
+
+                <exec dir="${src}" executable="make">
+                  <arg value="VERSION=${project.version}" />
+                  <arg value="clean" />
+                  <arg value="all" />
+                </exec>
+
+                <mkdir dir="${dst}" />
+                <copy overwrite="true" todir="${dst}">
+                  <fileset dir="${src}">
+                    <include name="*.html" />
+                  </fileset>
+                </copy>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
         </executions>
       </plugin>
     </plugins>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 1a556c2..b352d4f 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.config.AuthConfigModule;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GerritServerConfigModule;
 import com.google.gerrit.server.config.MasterNodeStartup;
 import com.google.gerrit.server.config.SitePath;
@@ -39,7 +40,9 @@
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginModule;
+import com.google.gerrit.server.schema.DataSourceModule;
 import com.google.gerrit.server.schema.DataSourceProvider;
+import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.DatabaseModule;
 import com.google.gerrit.server.schema.SchemaModule;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
@@ -56,6 +59,7 @@
 import com.google.inject.servlet.GuiceServletContextListener;
 import com.google.inject.spi.Message;
 
+import org.eclipse.jgit.lib.Config;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -144,10 +148,35 @@
   private Injector createDbInjector() {
     final List<Module> modules = new ArrayList<Module>();
     if (sitePath != null) {
-      modules.add(new LifecycleModule() {
+      Module sitePathModule = new AbstractModule() {
         @Override
         protected void configure() {
           bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+        }
+      };
+      modules.add(sitePathModule);
+
+      Module configModule = new GerritServerConfigModule();
+      modules.add(configModule);
+
+      Injector cfgInjector = Guice.createInjector(sitePathModule, configModule);
+      Config cfg = cfgInjector.getInstance(Key.get(Config.class,
+          GerritServerConfig.class));
+      String dbType = cfg.getString("database", null, "type");
+
+      final DataSourceType dst = Guice.createInjector(new DataSourceModule(),
+          configModule, sitePathModule).getInstance(
+            Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
+      modules.add(new AbstractModule() {
+        @Override
+        protected void configure() {
+          bind(DataSourceType.class).toInstance(dst);
+        }
+      });
+
+      modules.add(new LifecycleModule() {
+        @Override
+        protected void configure() {
           bind(DataSourceProvider.Context.class).toInstance(
               DataSourceProvider.Context.MULTI_USER);
           bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider(
@@ -155,7 +184,6 @@
           listener().to(DataSourceProvider.class);
         }
       });
-      modules.add(new GerritServerConfigModule());
 
     } else {
       modules.add(new LifecycleModule() {
diff --git a/pom.xml b/pom.xml
index d7b5988..df5d7ec 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,10 +47,11 @@
 
   <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>
+    <bouncyCastleVersion>140</bouncyCastleVersion>
     <slf4jVersion>1.6.1</slf4jVersion>
     <guiceVersion>3.0</guiceVersion>
     <jettyVersion>7.2.1.v20101111</jettyVersion>
@@ -98,7 +99,10 @@
       <modules>
         <module>gerrit-plugin-api</module>
         <module>gerrit-plugin-archetype</module>
-      </modules>
+        <module>gerrit-plugin-gwtui</module>
+        <module>gerrit-plugin-js-archetype</module>
+        <module>gerrit-plugin-gwt-archetype</module>
+     </modules>
     </profile>
     <profile>
       <activation>
@@ -478,7 +482,7 @@
       <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
-        <version>12.0.1</version>
+        <version>13.0.1</version>
       </dependency>
 
       <dependency>
@@ -683,7 +687,13 @@
       <dependency>
         <groupId>bouncycastle</groupId>
         <artifactId>bcpg-jdk15</artifactId>
-        <version>140</version>
+        <version>${bouncyCastleVersion}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>bouncycastle</groupId>
+        <artifactId>bcprov-jdk15</artifactId>
+        <version>${bouncyCastleVersion}</version>
       </dependency>
 
       <dependency>
@@ -762,13 +772,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 +831,7 @@
       </dependency>
 
       <dependency>
-        <groupId>com.google.gerrit</groupId>
+        <groupId>com.googlecode.juniversalchardet</groupId>
         <artifactId>juniversalchardet</artifactId>
         <version>1.0.3</version>
       </dependency>
@@ -843,6 +853,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 +884,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 +892,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>