Merge "Add ssh command create-branch for creating branches"
diff --git a/.buckconfig b/.buckconfig
index 1bc29ac..4eb5e85 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -5,6 +5,7 @@
   docs = //Documentation:html
   gerrit = //:gerrit
   release = //:release
+  withdocs = //:withdocs
 
 [buildfile]
   includes = //tools/default.defs
@@ -14,3 +15,7 @@
 
 [project]
   ignore = .git
+
+[cache]
+  mode = dir
+  dir = buck-out/cache
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 631fe48..e07f9cb 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -21,8 +21,9 @@
   directory = /var/cache/gerrit2
 ----
 
-[[accounts]]Section accounts
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[accounts]]
+Section accounts
+~~~~~~~~~~~~~~~~
 
 [[accounts.visibility]]accounts.visibility::
 +
@@ -42,8 +43,9 @@
 +
 Default is `ALL`.
 
-[[addreviewer]]Section addreviewer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[addreviewer]]
+Section addreviewer
+~~~~~~~~~~~~~~~~~~~
 
 [[addreviewer.maxWithoutConfirmation]]addreviewer.maxWithoutConfirmation::
 +
@@ -69,8 +71,9 @@
 +
 Default is 20.
 
-[[auth]]Section auth
-~~~~~~~~~~~~~~~~~~~~
+[[auth]]
+Section auth
+~~~~~~~~~~~~
 
 See also link:config-sso.html[SSO configuration].
 
@@ -464,8 +467,9 @@
 +
 Default is true.
 
-[[cache]]Section cache
-~~~~~~~~~~~~~~~~~~~~~~
+[[cache]]
+Section cache
+~~~~~~~~~~~~~
 
 [[cache.directory]]cache.directory::
 +
@@ -767,8 +771,9 @@
 +
 Default is 5 minutes.
 
-[[change]]Section change
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[change]]
+Section change
+~~~~~~~~~~~~~~
 
 [[change.largeChange]]change.largeChange::
 +
@@ -800,8 +805,9 @@
 +
 Default is 30 seconds.
 
-[[changeMerge]]Section changeMerge
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[changeMerge]]
+Section changeMerge
+~~~~~~~~~~~~~~~~~~~
 
 [[changeMerge.checkFrequency]]changeMerge.checkFrequency::
 +
@@ -820,8 +826,10 @@
 +
 Default is 1.
 
-[[commentlink]]Section commentlink
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[commentlink]]
+Section commentlink
+~~~~~~~~~~~~~~~~~~~
+
 Comment links are find/replace strings applied to change descriptions,
 patch comments, in-line code comments and approval category value descriptions
 to turn set strings into hyperlinks.  One common use is for linking to
@@ -900,13 +908,16 @@
 user-supplied and thus can be verified to be XSS-free, but are only
 enabled for a subset of projects.
 +
+By default, true.
++
 Note that the names and contents of disabled sections are visible even
 to anonymous users via the
 link:rest-api-projects.html#get-config[REST API].
 
 
-[[contactstore]]Section contactstore
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[contactstore]]
+Section contactstore
+~~~~~~~~~~~~~~~~~~~~
 
 [[contactstore.url]]contactstore.url::
 +
@@ -921,8 +932,9 @@
 Shared secret of the web based contact store.
 
 
-[[container]]Section container
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[container]]
+Section container
+~~~~~~~~~~~~~~~~~
 
 These settings are applied only if Gerrit is started as the container
 process through Gerrit's 'gerrit.sh' rc.d compatible wrapper script.
@@ -977,8 +989,9 @@
 '$HOME/gerrit.war'.
 
 
-[[core]]Section core
-~~~~~~~~~~~~~~~~~~~~
+[[core]]
+Section core
+~~~~~~~~~~~~
 
 [[core.packedGitWindowSize]]core.packedGitWindowSize::
 +
@@ -1084,8 +1097,9 @@
 +
 Default is false, but in a future release may default to true.
 
-[[database]]Section database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[database]]
+Section database
+~~~~~~~~~~~~~~~~
 
 The database section configures where Gerrit stores its metadata
 records about user accounts and change reviews.
@@ -1222,8 +1236,9 @@
 This setting only applies if
 <<database.connectionPool,database.connectionPool>> is true.
 
-[[download]]Section download
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[download]]
+Section download
+~~~~~~~~~~~~~~~~
 
 ----
 [download]
@@ -1303,8 +1318,9 @@
 If `download.scheme` is not specified, SSH, HTTP and Anonymous HTTP
 downloads are allowed.
 
-[[gerrit]]Section gerrit
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[gerrit]]
+Section gerrit
+~~~~~~~~~~~~~~
 
 [[gerrit.basePath]]gerrit.basePath::
 +
@@ -1389,8 +1405,9 @@
 Default change screen UI to direct users to. Valid values are
 `OLD_UI` and `CHANGE_SCREEN2`. Default is `CHANGE_SCREEN2`.
 
-[[gitweb]]Section gitweb
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[gitweb]]
+Section gitweb
+~~~~~~~~~~~~~~
 
 Gerrit can forward requests to either an internally managed gitweb
 (which allows Gerrit to enforce some access controls), or to an
@@ -1502,8 +1519,9 @@
 +
 Valid values are "true" and "false," default is "true".
 
-[[groups]]Section groups
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[groups]]
+Section groups
+~~~~~~~~~~~~~~
 
 [[groups.newGroupsVisibleToAll]]groups.newGroupsVisibleToAll::
 +
@@ -1512,8 +1530,9 @@
 +
 By default, false.
 
-[[hooks]]Section hooks
-~~~~~~~~~~~~~~~~~~~~~~
+[[hooks]]
+Section hooks
+~~~~~~~~~~~~~
 
 See also link:config-hooks.html[Hooks].
 
@@ -1586,8 +1605,9 @@
 Optional timeout value in seconds for synchronous hooks, if not specified
 then 30 seconds will be used.
 
-[[http]]Section http
-~~~~~~~~~~~~~~~~~~~~
+[[http]]
+Section http
+~~~~~~~~~~~~
 
 [[http.proxy]]http.proxy::
 +
@@ -1608,8 +1628,9 @@
 appear in the http.proxy property above.
 
 
-[[httpd]]Section httpd
-~~~~~~~~~~~~~~~~~~~~~~
+[[httpd]]
+Section httpd
+~~~~~~~~~~~~~
 
 The httpd section configures the embedded servlet container.
 
@@ -1841,8 +1862,9 @@
 If the file doesn't exist or can't be read the default robots.txt file
 bundled with the .war will be used instead.
 
-[[index]]Section index
-~~~~~~~~~~~~~~~~~~~~~~
+[[index]]
+Section index
+~~~~~~~~~~~~~
 
 The index section configures the secondary index.
 
@@ -1866,8 +1888,9 @@
 using the link:pgm-reindex.html[reindex program] before restarting the
 Gerrit server.
 
-[[ldap]]Section ldap
-~~~~~~~~~~~~~~~~~~~~
+[[ldap]]
+Section ldap
+~~~~~~~~~~~~
 
 LDAP integration is only enabled if `auth.type` is set to
 `HTTP_LDAP`, `LDAP` or `CLIENT_SSL_CERT_LDAP`.  See above for a
@@ -2153,8 +2176,9 @@
 must have the DWORD value `allowtgtsessionkey` set to 1 and the account must not
 have local administrator privileges.
 
-[[mimetype]]Section mimetype
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[mimetype]]
+Section mimetype
+~~~~~~~~~~~~~~~~
 
 [[mimetype.name.safe]]mimetype.<name>.safe::
 +
@@ -2182,8 +2206,10 @@
 ----
 
 
-[[pack]]Section pack
-~~~~~~~~~~~~~~~~~~~~
+[[pack]]
+Section pack
+~~~~~~~~~~~~
+
 Global settings controlling how Gerrit Code Review creates pack
 streams for Git clients running clone, fetch, or pull.  Most of these
 variables are per-client request, and thus should be carefully set
@@ -2207,8 +2233,9 @@
 By default, 1.
 
 
-[[plugins]]Section plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[plugins]]
+Section plugins
+~~~~~~~~~~~~~~~
 
 [[plugins.checkFrequency]]plugins.checkFrequency::
 +
@@ -2223,8 +2250,10 @@
 Default is 1 minute.
 
 
-[[receive]]Section receive
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[receive]]
+Section receive
+~~~~~~~~~~~~~~~
+
 This section is used to set who can execute the 'receive-pack' and
 to limit the maximum Git object size that 'receive-pack' will accept.
 'receive-pack' is what runs on the server during a user's push or
@@ -2324,8 +2353,10 @@
 is assumed.
 
 
-[[repository]]Section repository
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[repository]]
+Section repository
+~~~~~~~~~~~~~~~~~~
+
 Repositories in this sense are the same as projects.
 
 In the following example configuration `Registered Users` is set
@@ -2354,8 +2385,9 @@
 groups are allowed.  Each on its own line.  Groups which don't exist
 in the database are ignored.
 
-[[rules]]Section rules
-~~~~~~~~~~~~~~~~~~~~~~
+[[rules]]
+Section rules
+~~~~~~~~~~~~~
 
 [[rules.enable]]rules.enable::
 +
@@ -2365,8 +2397,9 @@
 +
 Default is true, to execute project specific rules.
 
-[[sendemail]]Section sendemail
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[sendemail]]
+Section sendemail
+~~~~~~~~~~~~~~~~~
 
 [[sendemail.enable]]sendemail.enable::
 +
@@ -2494,8 +2527,9 @@
 By default, unset, so no Expiry-Date header is generated.
 
 
-[[site]]Section site
-~~~~~~~~~~~~~~~~~~~~
+[[site]]
+Section site
+~~~~~~~~~~~~
 
 [[site.checkUserAgent]]site.checkUserAgent::
 +
@@ -2517,8 +2551,9 @@
 and text results for changes. If false, the URL is disabled and
 returns 404 to clients. Default is true, enabling `/query`.
 
-[[ssh-alias]] Section ssh-alias
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[ssh-alias]]
+Section ssh-alias
+~~~~~~~~~~~~~~~~~
 
 Variables in section ssh-alias permit the site administrator to alias
 another command from Gerrit or a plugin into the `gerrit` command
@@ -2529,8 +2564,9 @@
   replicate = replication start
 ----
 
-[[sshd]] Section sshd
-~~~~~~~~~~~~~~~~~~~~~
+[[sshd]]
+Section sshd
+~~~~~~~~~~~~
 
 [[sshd.backend]]sshd.backend::
 +
@@ -2741,8 +2777,9 @@
 +
 By default, true.
 
-[[suggest]] Section suggest
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[suggest]]
+Section suggest
+~~~~~~~~~~~~~~~
 
 [[suggest.accounts]]suggest.accounts::
 +
@@ -2768,8 +2805,9 @@
 +
 By default 0.
 
-[[theme]] Section theme
-~~~~~~~~~~~~~~~~~~~~~~~
+[[theme]]
+Section theme
+~~~~~~~~~~~~~
 
 [[theme.backgroundColor]]theme.backgroundColor::
 +
@@ -2866,8 +2904,9 @@
   backgroundColor = FFFFFF
 ----
 
-[[trackingid]] Section trackingid
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[trackingid]]
+Section trackingid
+~~~~~~~~~~~~~~~~~~
 
 Tagged footer lines containing references to external
 tracking systems, parsed out of the commit message and
@@ -2919,8 +2958,9 @@
 It is possible to have several trackingid entries for the same
 tracking system.
 
-[[transfer]] Section transfer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[transfer]]
+Section transfer
+~~~~~~~~~~~~~~~~
 
 [[transfer.timeout]]transfer.timeout::
 +
@@ -2937,8 +2977,10 @@
 Defaults to 0 seconds, wait indefinitely.
 
 
-[[upload]]Section upload
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[upload]]
+Section upload
+~~~~~~~~~~~~~~
+
 Sets the group of users allowed to execute 'upload-pack' on the
 server, 'upload-pack' is what runs on the server during a user's
 fetch, clone or repo sync command.
@@ -2958,8 +3000,9 @@
 'upload-pack' on the server.
 
 
-[[user]] Section user
-~~~~~~~~~~~~~~~~~~~~~
+[[user]]
+Section user
+~~~~~~~~~~~~
 
 [[user.name]]user.name::
 +
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index 3e9cb96..80f577d 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -62,6 +62,12 @@
 Access controls for internally managed gitweb page views are enforced
 using the standard project READ +1 permission.
 
+Also, in order for a user to be able to view any gitweb information for a
+project, the user must be able to read all referencess (including
+refs/meta/config, refs/meta/dashboards/*, etc.). If you have exclusive read
+permissions for any references, make sure to include all parties that should be
+able to read the gitweb info for any of the branches in that project.
+
 External/Unmanaged gitweb
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 1acff3b..7b4981a 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -7,7 +7,7 @@
 
 There is currently no binary distribution of Buck, so it has to be manually
 built and installed.  Apache Ant is required.  Currently only Linux and Mac
-OS are supported.
+OS are supported.  Gerrit's buck wrappers require Python version 2.6 or higher.
 
 Clone the git and build it:
 
@@ -130,7 +130,7 @@
 ----
 
 Java binaries, Java sources and Java docs are generated into corresponding
-project directories in buck-out/gen, here as example for plugin API:
+project directories in `buck-out/gen`, here as example for plugin API:
 
 ----
   buck-out/gen/gerrit-plugin-api/plugin-api.jar
@@ -144,7 +144,7 @@
   buck build api_install
 ----
 
-Deploy {extension,plugin,gwt}-api to the remote maven repository
+Deploy {extension,plugin,gwt}-api to the remote maven repository:
 
 ----
   buck build api_deploy
@@ -154,6 +154,11 @@
 * 2.9-SNAPSHOT: snapshot repo
 * 2.9: release repo
 
+Deploying to the remote repository still depends on Maven, and the credentials
+for the repository need to be
+link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
+configured in Maven's settings.xml file].
+
 Plugins
 ~~~~~~~
 
@@ -211,7 +216,7 @@
 Documentation
 ~~~~~~~~~~~~~
 
-To build the documentation:
+To build only the documentation:
 
 ----
   buck build docs
@@ -229,6 +234,18 @@
   buck-out/gen/Documentation/html.zip
 ----
 
+To build the executable WAR with the documentation included:
+
+----
+  buck build withdocs
+----
+
+The WAR file will be placed in:
+
+----
+  buck-out/gen/withdocs.war
+----
+
 [[release]]
 Gerrit Release WAR File
 ~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index df14c1d..b39adf7 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1353,10 +1353,13 @@
 </manifestEntries>
 ----
 
-It is important that the module name that is provided to the
-`GwtPlugin` matches the GWT module contained in the plugin. The name
-of the GWT module can be explicitly set in the GWT module file by
-specifying the `rename-to` attribute on the module.
+The name that is provided to the `GwtPlugin` must match the GWT
+module name compiled into the plugin. The name of the GWT module
+can be explicitly set in the GWT module XML file by specifying
+the `rename-to` attribute on the module. It is important that the
+module name be unique across all plugins installed on the server,
+as the module name determines the JavaScript namespace used by the
+compiled plugin code.
 
 [source,xml]
 ----
@@ -1364,14 +1367,14 @@
 ----
 
 The actual GWT code must be implemented in a class that extends
-`com.google.gerrit.plugin.client.Plugin`:
+`com.google.gerrit.plugin.client.PluginEntryPoint`:
 
 [source,java]
 ----
-public class HelloPlugin extends Plugin {
+public class HelloPlugin extends PluginEntryPoint {
 
   @Override
-  public void onModuleLoad() {
+  public void onPluginLoad() {
     // Create the dialog box
     final DialogBox dialogBox = new DialogBox();
 
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index e73e039..10455c0 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -28,6 +28,23 @@
 link:dev-buck.html#build[Building on the command line with Buck].
 
 
+Switching between branches
+--------------------------
+
+When switching between branches with `git checkout`, be aware that
+submodule revisions are not altered.  This may result in the wrong
+plugin revisions being present, unneeded plugins being present, or
+expected plugins being missing.
+
+After switching branches, make sure the submodules are at the correct
+revisions for the new branch with the commands:
+
+----
+  git submodule update
+  git clean -fdx
+----
+
+
 Configuring Eclipse
 -------------------
 
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index 07ca154..99520ac 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -51,7 +51,7 @@
 --list-plugins::
 	Print names of plugins that can be installed during init process.
 
---install-plugin:
+--install-plugin::
 	Automatically install plugin with given name without asking.
 	This option may be supplied more than once to install multiple
 	plugins.
diff --git a/Documentation/rest-api-documentation.txt b/Documentation/rest-api-documentation.txt
index 0defe6d..56e6c92 100644
--- a/Documentation/rest-api-documentation.txt
+++ b/Documentation/rest-api-documentation.txt
@@ -7,9 +7,9 @@
 
 Please note that this feature is only usable with documentation built-in.
 You'll need to
-`buck build :withdocs`
+`buck build withdocs`
 or
-`buck build :release`
+`buck build release`
 to test this feature.
 
 [[documentation-endpoints]]
diff --git a/ReleaseNotes/ReleaseNotes-2.9.txt b/ReleaseNotes/ReleaseNotes-2.9.txt
index 174cdef..c0e2fcb 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.txt
@@ -83,3 +83,10 @@
 
 * The number of accounts shown on the 'Become Any Account' login
 screen is increased from 5 to 100.
+
+
+Upgrades
+--------
+
+* Update gwtjsonrpc to 1.4
+* Update gwtorm to 1.8
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 68d04cb..0643d7a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
 import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectOwners;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -30,6 +29,7 @@
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.account.GroupCache;
@@ -180,7 +180,7 @@
     ProjectInput in = new ProjectInput();
     in.permissions_only = true;
     session.put("/projects/" + newProjectName, in);
-    assertHead(newProjectName, GitRepositoryManager.REF_CONFIG);
+    assertHead(newProjectName, RefNames.REFS_CONFIG);
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
index 9fd7568..a93a23a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -27,9 +27,9 @@
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.git.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.SchemaFactory;
@@ -73,7 +73,7 @@
     project = "p";
     createProject(sshSession, project, null, true);
     git = cloneProject(sshSession.getUrl() + "/" + project);
-    fetch(git, GitRepositoryManager.REF_CONFIG + ":refs/heads/config");
+    fetch(git, RefNames.REFS_CONFIG + ":refs/heads/config");
     checkout(git, "refs/heads/config");
 
     db = reviewDbProvider.open();
@@ -93,7 +93,7 @@
     PushOneCommit push =
         new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
             configName, cfg.toText());
-    push.to(git, GitRepositoryManager.REF_CONFIG);
+    push.to(git, RefNames.REFS_CONFIG);
 
     ProjectState state = projectCache.get(new Project.NameKey(project));
     assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
@@ -117,19 +117,19 @@
 
     Git parentGit =
         cloneProject(sshSession.getUrl() + "/" + allProjects.get().get(), false);
-    fetch(parentGit, GitRepositoryManager.REF_CONFIG + ":refs/heads/config");
+    fetch(parentGit, RefNames.REFS_CONFIG + ":refs/heads/config");
     checkout(parentGit, "refs/heads/config");
     PushOneCommit push =
         new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
             configName, parentCfg.toText());
-    push.to(parentGit, GitRepositoryManager.REF_CONFIG);
+    push.to(parentGit, RefNames.REFS_CONFIG);
 
     Config cfg = new Config();
     cfg.setString("s1", null, "k1", "childValue1");
     cfg.setString("s2", "ss", "k3", "childValue2");
     push = new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
         configName, cfg.toText());
-    push.to(git, GitRepositoryManager.REF_CONFIG);
+    push.to(git, RefNames.REFS_CONFIG);
 
     ProjectState state = projectCache.get(new Project.NameKey(project));
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
new file mode 100644
index 0000000..0157bd6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
@@ -0,0 +1,21 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  deps = [
+    ':util',
+    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+    '//lib:gwtjsonrpc',
+  ],
+)
+
+java_library(
+  name = 'util',
+  srcs = [
+    'ChangeAndCommit.java',
+    'RelatedInfo.java',
+  ],
+  deps = [
+    '//gerrit-server:server',
+  ],
+)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/JsUiPlugin.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ChangeAndCommit.java
similarity index 69%
copy from gerrit-gwtui/src/main/java/com/google/gerrit/client/api/JsUiPlugin.java
copy to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ChangeAndCommit.java
index 64f832b..d9959b2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/JsUiPlugin.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ChangeAndCommit.java
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.client.api;
+package com.google.gerrit.acceptance.server.change;
 
-import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gerrit.server.change.ChangeJson.CommitInfo;
 
-class JsUiPlugin extends JavaScriptObject {
-  final native String name() /*-{ return this.name }-*/;
-
-  protected JsUiPlugin() {
-  }
+public class ChangeAndCommit {
+  public String changeId;
+  public CommitInfo commit;
+  public Integer _changeNumber;
+  public Integer _revisionNumber;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
new file mode 100644
index 0000000..3ae31b4
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -0,0 +1,199 @@
+// Copyright (C) 2013 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.acceptance.server.change;
+
+import static com.google.gerrit.acceptance.git.GitUtil.add;
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createCommit;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.git.GitUtil.pushHead;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.git.GitUtil.Commit;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class GetRelatedIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  private TestAccount admin;
+  private RestSession session;
+  private Git git;
+  private ReviewDb db;
+
+  @Before
+  public void setUp() throws Exception {
+    admin = accounts.admin();
+    session = new RestSession(server, admin);
+
+    initSsh(admin);
+    Project.NameKey project = new Project.NameKey("p");
+    SshSession sshSession = new SshSession(server, admin);
+    createProject(sshSession, project.get());
+    git = cloneProject(sshSession.getUrl() + "/" + project.get());
+    sshSession.close();
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  @Test
+  public void getRelatedNoResult() throws GitAPIException,
+      IOException, Exception {
+    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PatchSet.Id ps = push.to(git, "refs/for/master").getPatchSetId();
+    List<ChangeAndCommit> related = getRelated(ps);
+    assertEquals(0, related.size());
+  }
+
+  @Test
+  public void getRelatedLinear() throws GitAPIException,
+      IOException, Exception {
+    add(git, "a.txt", "1");
+    Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
+    add(git, "b.txt", "2");
+    Commit c2 = createCommit(git, admin.getIdent(), "subject: 2");
+    pushHead(git, "refs/for/master", false);
+
+    for (Commit c : ImmutableList.of(c2, c1)) {
+      List<ChangeAndCommit> related = getRelated(getPatchSetId(c));
+      assertEquals(2, related.size());
+      assertEquals("related to " + c.getChangeId(), c2.getChangeId(), related.get(0).changeId);
+      assertEquals("related to " + c.getChangeId(), c1.getChangeId(), related.get(1).changeId);
+    }
+  }
+
+  @Test
+  public void getRelatedReorder() throws GitAPIException,
+      IOException, Exception {
+    // Create two commits and push.
+    add(git, "a.txt", "1");
+    Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
+    add(git, "b.txt", "2");
+    Commit c2 = createCommit(git, admin.getIdent(), "subject: 2");
+    pushHead(git, "refs/for/master", false);
+    PatchSet.Id c1ps1 = getPatchSetId(c1);
+    PatchSet.Id c2ps1 = getPatchSetId(c2);
+
+    // Swap the order of commits and push again.
+    git.reset().setMode(ResetType.HARD).setRef("HEAD^^").call();
+    git.cherryPick().include(c2.getCommit()).include(c1.getCommit()).call();
+    pushHead(git, "refs/for/master", false);
+    PatchSet.Id c1ps2 = getPatchSetId(c1);
+    PatchSet.Id c2ps2 = getPatchSetId(c2);
+
+    for (PatchSet.Id ps : ImmutableList.of(c2ps2, c1ps2)) {
+      List<ChangeAndCommit> related = getRelated(ps);
+      assertEquals(2, related.size());
+      assertEquals("related to " + ps, c1.getChangeId(), related.get(0).changeId);
+      assertEquals("related to " + ps, c2.getChangeId(), related.get(1).changeId);
+    }
+
+    for (PatchSet.Id ps : ImmutableList.of(c2ps1, c1ps1)) {
+      List<ChangeAndCommit> related = getRelated(ps);
+      assertEquals(2, related.size());
+      assertEquals("related to " + ps, c2.getChangeId(), related.get(0).changeId);
+      assertEquals("related to " + ps, c1.getChangeId(), related.get(1).changeId);
+    }
+  }
+
+  @Test
+  public void getRelatedReorderAndExtend() throws GitAPIException,
+      IOException, Exception {
+    // Create two commits and push.
+    add(git, "a.txt", "1");
+    Commit c1 = createCommit(git, admin.getIdent(), "subject: 1");
+    add(git, "b.txt", "2");
+    Commit c2 = createCommit(git, admin.getIdent(), "subject: 2");
+    pushHead(git, "refs/for/master", false);
+    PatchSet.Id c1ps1 = getPatchSetId(c1);
+    PatchSet.Id c2ps1 = getPatchSetId(c2);
+
+    // Swap the order of commits, create a new commit on top, and push again.
+    git.reset().setMode(ResetType.HARD).setRef("HEAD^^").call();
+    git.cherryPick().include(c2.getCommit()).include(c1.getCommit()).call();
+    add(git, "c.txt", "3");
+    Commit c3 = createCommit(git, admin.getIdent(), "subject: 3");
+    pushHead(git, "refs/for/master", false);
+    PatchSet.Id c1ps2 = getPatchSetId(c1);
+    PatchSet.Id c2ps2 = getPatchSetId(c2);
+    PatchSet.Id c3ps1 = getPatchSetId(c3);
+
+
+    for (PatchSet.Id ps : ImmutableList.of(c3ps1, c2ps2, c1ps2)) {
+      List<ChangeAndCommit> related = getRelated(ps);
+      assertEquals(3, related.size());
+      assertEquals("related to " + ps, c3.getChangeId(), related.get(0).changeId);
+      assertEquals("related to " + ps, c1.getChangeId(), related.get(1).changeId);
+      assertEquals("related to " + ps, c2.getChangeId(), related.get(2).changeId);
+    }
+
+    for (PatchSet.Id ps : ImmutableList.of(c2ps1, c1ps1)) {
+      List<ChangeAndCommit> related = getRelated(ps);
+      assertEquals(3, related.size());
+      assertEquals("related to " + ps, c3.getChangeId(), related.get(0).changeId);
+      assertEquals("related to " + ps, c2.getChangeId(), related.get(1).changeId);
+      assertEquals("related to " + ps, c1.getChangeId(), related.get(2).changeId);
+    }
+  }
+
+  private List<ChangeAndCommit> getRelated(PatchSet.Id ps) throws IOException {
+    String url = String.format("/changes/%d/revisions/%d/related",
+        ps.getParentKey().get(), ps.get());
+    RelatedInfo related = OutputFormat.JSON_COMPACT.newGson().fromJson(
+        session.get(url).getReader(),
+        new TypeToken<RelatedInfo>() {}.getType());
+    return related.changes;
+  }
+
+  private PatchSet.Id getPatchSetId(Commit c) throws OrmException {
+    return Iterables.getOnlyElement(
+        db.changes().byKey(new Change.Key(c.getChangeId()))).currentPatchSetId();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/JsUiPlugin.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/RelatedInfo.java
similarity index 72%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/api/JsUiPlugin.java
rename to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/RelatedInfo.java
index 64f832b..4089910 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/JsUiPlugin.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/RelatedInfo.java
@@ -12,13 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.client.api;
+package com.google.gerrit.acceptance.server.change;
 
-import com.google.gwt.core.client.JavaScriptObject;
+import java.util.List;
 
-class JsUiPlugin extends JavaScriptObject {
-  final native String name() /*-{ return this.name }-*/;
-
-  protected JsUiPlugin() {
-  }
+public class RelatedInfo {
+  public List<ChangeAndCommit> changes;
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index 7fd8864..408ad39 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -33,14 +33,18 @@
     return new LabelType(name, values);
   }
 
-  private static String checkName(String name) {
+  public static String checkName(String name) {
+    if (name == null || name.isEmpty()) {
+      throw new IllegalArgumentException("Empty label name");
+    }
     if ("SUBM".equals(name)) {
       throw new IllegalArgumentException(
           "Reserved label name \"" + name + "\"");
     }
     for (int i = 0; i < name.length(); i++) {
       char c = name.charAt(i);
-      if (!((c >= 'a' && c <= 'z') ||
+      if ((i == 0 && c == '-') ||
+          !((c >= 'a' && c <= 'z') ||
             (c >= 'A' && c <= 'Z') ||
             (c >= '0' && c <= '9') ||
             c == '-')) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
index 8015117..19fcbeb 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
@@ -15,29 +15,17 @@
 package com.google.gerrit.common.data;
 
 import com.google.gerrit.common.audit.Audit;
-import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
 import com.google.gwtjsonrpc.common.RpcImpl;
 import com.google.gwtjsonrpc.common.RpcImpl.Version;
-import com.google.gwtjsonrpc.common.VoidResult;
 
 @RpcImpl(version = Version.V2_0)
 public interface PatchDetailService extends RemoteJsonService {
   @Audit
   void patchScript(Patch.Key key, PatchSet.Id a, PatchSet.Id b,
       AccountDiffPreference diffPrefs, AsyncCallback<PatchScript> callback);
-
-  @Audit
-  @SignInRequired
-  void saveDraft(PatchLineComment comment,
-      AsyncCallback<PatchLineComment> callback);
-
-  @Audit
-  @SignInRequired
-  void deleteDraft(PatchLineComment.Key key, AsyncCallback<VoidResult> callback);
 }
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 179adc2..7972e52 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
@@ -22,6 +22,7 @@
 import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.admin.ProjectScreen;
 import com.google.gerrit.client.api.ApiGlue;
+import com.google.gerrit.client.api.PluginLoader;
 import com.google.gerrit.client.changes.ChangeConstants;
 import com.google.gerrit.client.changes.ChangeListScreen;
 import com.google.gerrit.client.config.ConfigServerApi;
@@ -49,12 +50,8 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.aria.client.Roles;
-import com.google.gwt.core.client.Callback;
-import com.google.gwt.core.client.CodeDownloadException;
 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.dom.client.ClickEvent;
@@ -84,12 +81,9 @@
 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.HashMap;
@@ -571,7 +565,7 @@
     }
 
     saveDefaultTheme();
-    loadPlugins(hpd, token);
+    PluginLoader.load(hpd.plugins, token);
   }
 
   private void saveDefaultTheme() {
@@ -580,53 +574,6 @@
         Document.get().getElementById("gerrit_footer"));
   }
 
-  private void loadPlugins(HostPageData hpd, final String token) {
-    if (hpd.plugins != null && !hpd.plugins.isEmpty()) {
-      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;
-                if (reason instanceof CodeDownloadException) {
-                  d = new ErrorDialog(M.cannotDownloadPlugin(url));
-                } else {
-                  d = new ErrorDialog(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() {
     menuLeft.clear();
     menuRight.clear();
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 44fcc8a..c0f91d3 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
@@ -20,6 +20,7 @@
   String menuSignIn();
   String menuRegister();
   String reportBug();
+  String loadingPlugins();
 
   String signInDialogTitle();
   String signInDialogClose();
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 893838a..d940f53 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
@@ -1,6 +1,7 @@
 menuSignIn = Sign In
 menuRegister = Register
 reportBug = Report Bug
+loadingPlugins = Loading plugins ...
 
 signInDialogTitle = Code Review - Sign In
 signInDialogClose = Close
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 660a160..5bb207d 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
@@ -116,6 +116,7 @@
   String errorDialogGlass();
   String errorDialogText();
   String errorDialogTitle();
+  String loadingPluginsDialog();
   String fileColumnHeader();
   String fileCommentBorder();
   String fileLine();
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 8196e98..4a97c81 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
@@ -13,7 +13,7 @@
 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
-cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}.
+pluginFailed = Plugin "{0}" failed to load
+cannotDownloadPlugin = Cannot load plugin from {0}
 
 parentUpdateFailed = Setting parent project failed: {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
index fdf0af3..8ed5f0b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
@@ -34,6 +34,7 @@
     p.syntaxHighlighting(in.isSyntaxHighlighting());
     p.hideTopMenu(in.isHideTopMenu());
     p.expandAllComments(in.isExpandAllComments());
+    p.manualReview(in.isManualReview());
     return p;
   }
 
@@ -48,6 +49,7 @@
     p.setSyntaxHighlighting(syntaxHighlighting());
     p.setHideTopMenu(hideTopMenu());
     p.setExpandAllComments(expandAllComments());
+    p.setManualReview(manualReview());
   }
 
   public final void ignoreWhitespace(Whitespace i) {
@@ -64,6 +66,7 @@
   public final native void syntaxHighlighting(boolean s) /*-{ this.syntax_highlighting = s }-*/;
   public final native void hideTopMenu(boolean s) /*-{ this.hide_top_menu = s }-*/;
   public final native void expandAllComments(boolean e) /*-{ this.expand_all_comments = e }-*/;
+  public final native void manualReview(boolean r) /*-{ this.manual_review = r }-*/;
 
   public final Whitespace ignoreWhitespace() {
     String s = ignoreWhitespaceRaw();
@@ -80,6 +83,8 @@
   public final native boolean syntaxHighlighting() /*-{ return this.syntax_highlighting || false }-*/;
   public final native boolean hideTopMenu() /*-{ return this.hide_top_menu || false }-*/;
   public final native boolean expandAllComments() /*-{ return this.expand_all_comments || false }-*/;
+  public final native boolean manualReview() /*-{ return this.manual_review || false }-*/;
+  public final boolean autoReview() { return !manualReview(); }
 
   private final native int get(String n, int d)
   /*-{ return this.hasOwnProperty(n) ? this[n] : d }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
index 02a19dc8..1d7b960 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Style.Display;
@@ -125,7 +126,7 @@
       history.getStyle().setDisplay(Display.BLOCK);
       gitweb.setText(c.getLinkName());
       gitweb.setHref(c.toFileHistory(new Branch.NameKey(value.getProjectName(),
-          "refs/meta/config"), "project.config"));
+          RefNames.REFS_CONFIG), "project.config"));
     } else {
       history.getStyle().setDisplay(Display.NONE);
     }
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 b7c82fb..d84f931 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
@@ -34,6 +34,7 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -394,7 +395,7 @@
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       String iconCellStyle = Gerrit.RESOURCES.css().iconCell();
       String dataCellStyle = Gerrit.RESOURCES.css().dataCell();
-      if ("refs/meta/config".equals(k.getShortName())
+      if (RefNames.REFS_CONFIG.equals(k.getShortName())
           || "HEAD".equals(k.getShortName())) {
         iconCellStyle = Gerrit.RESOURCES.css().specialBranchIconCell();
         dataCellStyle = Gerrit.RESOURCES.css().specialBranchDataCell();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
index 772b7c6..d23e094 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.client.api;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.History;
@@ -24,29 +26,36 @@
   private static String pluginName;
 
   public static void init() {
-    init0();
+    init0(GWT.getHostPageBaseURL(), NativeString.TYPE);
     ActionContext.init();
+    Plugin.init();
     addHistoryHook();
   }
 
-  private static native void init0() /*-{
-    var serverUrl = @com.google.gwt.core.client.GWT::getHostPageBaseURL()();
-    var Plugin = function (name){this.name = name};
-    var Gerrit = {
+  private static native void init0(String serverUrl, JavaScriptObject JsonString) /*-{
+    $wnd.Gerrit = {
+      JsonString: JsonString,
+      events: {},
+      plugins: {},
+      change_actions: {},
+      revision_actions: {},
+      project_actions: {},
+
       getPluginName: @com.google.gerrit.client.api.ApiGlue::getPluginName(),
       install: function (f) {
-        var p = new Plugin(this.getPluginName());
-        @com.google.gerrit.client.api.ApiGlue::install(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gerrit/client/api/JsUiPlugin;)(f,p);
+        var p = this._getPluginByUrl(@com.google.gerrit.client.api.PluginName::getCallerUrl()());
+        @com.google.gerrit.client.api.ApiGlue::install(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gerrit/client/api/Plugin;)(f,p);
+      },
+      installGwt: function(u){return this._getPluginByUrl(u)},
+      _getPluginByUrl: function(u) {
+        return u.indexOf(serverUrl) == 0
+          ? this.plugins[u.substring(serverUrl.length)]
+          : this.plugins[u]
       },
 
       go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
       refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
 
-      events: {},
-      change_actions: {},
-      revision_actions: {},
-      project_actions: {},
-
       on: function (e,f){(this.events[e] || (this.events[e]=[])).push(f)},
       onAction: function (t,n,c){this._onAction(this.getPluginName(),t,n,c)},
       _onAction: function (p,t,n,c) {
@@ -81,34 +90,7 @@
         }
       },
       'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
-      JsonString: @com.google.gerrit.client.rpc.NativeString::TYPE,
     };
-
-    Plugin.prototype = {
-      getPluginName: function(){return this.name},
-      go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
-      refresh: Gerrit.refresh,
-      onAction: function(t,n,c) {Gerrit._onAction(this.name,t,n,c)},
-
-      url: function (d) {
-        var u = serverUrl + 'plugins/' + this.name + '/';
-        if (d && d.length > 0) u += d.charAt(0)=='/' ? d.substring(1) : d;
-        return u;
-      },
-
-      _api: function(d) {
-        var u = 'plugins/' + this.name + '/';
-        if (d && d.length > 0) u += d.charAt(0)=='/' ? d.substring(1) : d;
-        return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(u);
-      },
-
-      get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
-      post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
-      put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
-      'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
-    };
-
-    $wnd.Gerrit = Gerrit;
   }-*/;
 
   /** Install deprecated {@code gerrit_addHistoryHook()} function. */
@@ -119,17 +101,25 @@
      };
   }-*/;
 
-  private static void install(JavaScriptObject cb, JsUiPlugin p) {
+  private static void install(JavaScriptObject cb, Plugin p) throws Exception {
     try {
       pluginName = p.name();
       invoke(cb, p);
+      p._initialized();
+    } catch (Exception e) {
+      p.failure(e);
+      throw e;
     } finally {
       pluginName = null;
+      PluginLoader.loaded();
     }
   }
 
   private static final String getPluginName() {
-    return pluginName != null ? pluginName : PluginName.get();
+    if (pluginName != null) {
+      return pluginName;
+    }
+    return PluginName.fromUrl(PluginName.getCallerUrl());
   }
 
   private static final void go(String urlOrToken) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
new file mode 100644
index 0000000..632f179
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2013 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.api;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+final class Plugin extends JavaScriptObject {
+  private static final JavaScriptObject TYPE = createType();
+
+  static Plugin create(String url) {
+    int s = "plugins/".length();
+    int e = url.indexOf('/', s);
+    String name = url.substring(s, e);
+    return create(TYPE, url, name);
+  }
+
+  final native String url() /*-{ return this._scriptUrl }-*/;
+  final native String name() /*-{ return this.name }-*/;
+
+  final native boolean loaded() /*-{ return this._success || this._failure != null }-*/;
+  final native Exception failure() /*-{ return this._failure }-*/;
+  final native void failure(Exception e) /*-{ this._failure = e }-*/;
+  final native boolean success() /*-{ return this._success || false }-*/;
+  final native void _initialized() /*-{ this._success = true }-*/;
+
+  private static native Plugin create(JavaScriptObject T, String u, String n)
+  /*-{ return new T(u,n) }-*/;
+
+  private static native JavaScriptObject createType() /*-{
+    function Plugin(u, n) {
+      this._scriptUrl = u;
+      this.name = n;
+    }
+    return Plugin;
+  }-*/;
+
+  static native void init() /*-{
+    var G = $wnd.Gerrit;
+    @com.google.gerrit.client.api.Plugin::TYPE.prototype = {
+      getPluginName: function(){return this.name},
+      go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
+      refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
+      on: G.on,
+      onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
+
+      url: function (u){return G.url(this._url(u))},
+      get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
+      post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
+      put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
+      'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
+
+      _loadedGwt: function(){@com.google.gerrit.client.api.PluginLoader::loaded()()},
+      _api: function(u){return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(this._url(u))},
+      _url: function (d) {
+        var u = 'plugins/' + this.name + '/';
+        if (d && d.length > 0)
+          return u + (d.charAt(0)=='/' ? d.substring(1) : d);
+        return u;
+      },
+    };
+  }-*/;
+
+  protected Plugin() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginLoader.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginLoader.java
new file mode 100644
index 0000000..8b5c93e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginLoader.java
@@ -0,0 +1,185 @@
+// Copyright (C) 2013 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.api;
+
+import com.google.gerrit.client.ErrorDialog;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gwt.core.client.Callback;
+import com.google.gwt.core.client.CodeDownloadException;
+import com.google.gwt.core.client.ScriptInjector;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwtexpui.progress.client.ProgressBar;
+
+import java.util.List;
+
+/** Loads JavaScript plugins with a progress meter visible. */
+public class PluginLoader extends DialogBox {
+  private static final int MAX_LOAD_TIME_MILLIS = 5000;
+  private static PluginLoader self;
+
+  public static void load(List<String> plugins, final String token) {
+    if (plugins == null || plugins.isEmpty()) {
+      Gerrit.display(token);
+    } else {
+      self = new PluginLoader(token);
+      self.load(plugins);
+      self.startTimers();
+      self.center();
+    }
+  }
+
+  static void loaded() {
+    self.loadedOne();
+  }
+
+  private final String token;
+  private ProgressBar progress;
+  private Timer show;
+  private Timer update;
+  private Timer timeout;
+  private boolean visible;
+
+  private PluginLoader(String tokenToDisplay) {
+    super(/* auto hide */false, /* modal */true);
+    token = tokenToDisplay;
+    progress = new ProgressBar(Gerrit.C.loadingPlugins());
+
+    setStyleName(Gerrit.RESOURCES.css().errorDialog());
+    addStyleName(Gerrit.RESOURCES.css().loadingPluginsDialog());
+  }
+
+  private void load(List<String> pluginUrls) {
+    for (String url : pluginUrls) {
+      Plugin plugin = Plugin.create(url);
+      plugins().put(url, plugin);
+      ScriptInjector.fromUrl(url)
+        .setWindow(ScriptInjector.TOP_WINDOW)
+        .setCallback(new LoadCallback(plugin))
+        .inject();
+    }
+  }
+
+  private void startTimers() {
+    show = new Timer() {
+      @Override
+      public void run() {
+        setText(Window.getTitle());
+        setWidget(progress);
+        setGlassEnabled(true);
+        getGlassElement().addClassName(Gerrit.RESOURCES.css().errorDialogGlass());
+        hide(true);
+        center();
+        visible = true;
+      }
+    };
+    show.schedule(500);
+
+    update = new Timer() {
+      private int cycle;
+
+      @Override
+      public void run() {
+        progress.setValue(100 * ++cycle * 250 / MAX_LOAD_TIME_MILLIS);
+      }
+    };
+    update.scheduleRepeating(250);
+
+    timeout = new Timer() {
+      @Override
+      public void run() {
+        finish();
+      }
+    };
+    timeout.schedule(MAX_LOAD_TIME_MILLIS);
+  }
+
+  private void loadedOne() {
+    boolean done = true;
+    for (Plugin plugin : Natives.asList(plugins().values())) {
+      done &= plugin.loaded();
+    }
+    if (done) {
+      finish();
+    }
+  }
+
+  private void finish() {
+    show.cancel();
+    update.cancel();
+    timeout.cancel();
+    self = null;
+
+    if (!hadFailures()) {
+      if (visible) {
+        progress.setValue(100);
+        new Timer() {
+          @Override
+          public void run() {
+            hide(true);
+          }
+        }.schedule(250);
+      } else {
+        hide(true);
+      }
+    }
+
+    Gerrit.display(token);
+  }
+
+  private boolean hadFailures() {
+    boolean failed = false;
+    for (Plugin plugin : Natives.asList(plugins().values())) {
+      if (!plugin.success()) {
+        failed = true;
+
+        Exception e = plugin.failure();
+        String msg;
+        if (e != null && e instanceof CodeDownloadException) {
+          msg = Gerrit.M.cannotDownloadPlugin(plugin.url());
+        } else {
+          msg = Gerrit.M.pluginFailed(plugin.name());
+        }
+        hide(true);
+        new ErrorDialog(msg).center();
+      }
+    }
+    return failed;
+  }
+
+  private static native NativeMap<Plugin> plugins()
+  /*-{ return $wnd.Gerrit.plugins }-*/;
+
+  private class LoadCallback implements Callback<Void, Exception> {
+    private final Plugin plugin;
+
+    LoadCallback(Plugin plugin) {
+      this.plugin = plugin;
+    }
+
+    @Override
+    public void onSuccess(Void result) {
+    }
+
+    @Override
+    public void onFailure(Exception reason) {
+      plugin.failure(reason);
+      loadedOne();
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
index 5fc87d5..2c1f2c7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
@@ -31,14 +31,29 @@
 class PluginName {
   private static final String UNKNOWN = "<unknown>";
 
-  static String get() {
-    return GWT.<PluginName> create(PluginName.class).guessName();
+  private static String baseUrl() {
+    return GWT.getHostPageBaseURL() + "plugins/";
   }
 
-  String guessName() {
+  static String getCallerUrl() {
+    return GWT.<PluginName> create(PluginName.class).findCallerUrl();
+  }
+
+  static String fromUrl(String url) {
+    String baseUrl = baseUrl();
+    if (url != null && url.startsWith(baseUrl)) {
+      int s = url.indexOf('/', baseUrl.length());
+      if (s > 0) {
+        return url.substring(baseUrl.length(), s);
+      }
+    }
+    return UNKNOWN;
+  }
+
+  String findCallerUrl() {
     JavaScriptException err = makeException();
     if (hasStack(err)) {
-      return PluginNameMoz.guessName(err);
+      return PluginNameMoz.getUrl(err);
     }
 
     String baseUrl = baseUrl();
@@ -46,19 +61,12 @@
     for (int i = trace.length - 1; i >= 0; i--) {
       String u = trace[i].getFileName();
       if (u != null && u.startsWith(baseUrl)) {
-        int s = u.indexOf('/', baseUrl.length());
-        if (s > 0) {
-          return u.substring(baseUrl.length(), s);
-        }
+        return u;
       }
     }
     return UNKNOWN;
   }
 
-  private static String baseUrl() {
-    return GWT.getHostPageBaseURL() + "plugins/";
-  }
-
   private static StackTraceElement[] getTrace(JavaScriptException err) {
     StackTraceCreator.fillInStackTrace(err);
     return err.getStackTrace();
@@ -72,21 +80,22 @@
 
   /** Extracts URL from the stack frame. */
   static class PluginNameMoz extends PluginName {
-    String guessName() {
-      return guessName(makeException());
+    String findCallerUrl() {
+      return getUrl(makeException());
     }
 
-    static String guessName(JavaScriptException e) {
+    private static String getUrl(JavaScriptException e) {
       String baseUrl = baseUrl();
       JsArrayString stack = getStack(e);
       for (int i = stack.length() - 1; i >= 0; i--) {
         String frame = stack.get(i);
         int at = frame.indexOf(baseUrl);
         if (at >= 0) {
-          int s = frame.indexOf('/', at + baseUrl.length());
-          if (s > 0) {
-            return frame.substring(at + baseUrl.length(), s);
+          int end = frame.indexOf(':', at + baseUrl.length());
+          if (end < 0) {
+            end = frame.length();
           }
+          return frame.substring(at, end);
         }
       }
       return UNKNOWN;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index c974180..5aa5537 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -64,6 +64,7 @@
 
   interface FileTableCss extends CssResource {
     String table();
+    String nohover();
     String pointer();
     String reviewed();
     String status();
@@ -81,6 +82,7 @@
 
   private static final String REVIEWED;
   private static final String OPEN;
+  private static final int C_PATH = 3;
   private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
 
   static {
@@ -111,6 +113,7 @@
       if (t != null) {
         t.onOpenRow(1 + idx);
         e.preventDefault();
+        e.stopPropagation();
         return false;
       }
     }
@@ -307,6 +310,15 @@
         Gerrit.display(url(list.get(row - 1)));
       }
     }
+
+    @Override
+    protected void onCellSingleClick(Event event, int row, int column) {
+      if (column == C_PATH && link.handleAsClick(event)) {
+        onOpenRow(row);
+      } else {
+        super.onCellSingleClick(event, row, column);
+      }
+    }
   }
 
   private final class DisplayCommand implements RepeatingCommand {
@@ -406,7 +418,7 @@
     }
 
     private void header(SafeHtmlBuilder sb) {
-      sb.openTr();
+      sb.openTr().setStyleName(R.css().nohover());
       sb.openTh().setStyleName(R.css().pointer()).closeTh();
       sb.openTh().setStyleName(R.css().reviewed()).closeTh();
       sb.openTh().setStyleName(R.css().status()).closeTh();
@@ -586,7 +598,7 @@
     }
 
     private void footer(SafeHtmlBuilder sb) {
-      sb.openTr();
+      sb.openTr().setStyleName(R.css().nohover());
       sb.openTh().setStyleName(R.css().pointer()).closeTh();
       sb.openTh().setStyleName(R.css().reviewed()).closeTh();
       sb.openTh().setStyleName(R.css().status()).closeTh();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index 08c7d5d..f98e8b4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -324,7 +324,7 @@
           : null;
     }
 
-    final native boolean has_change_number()
+    public final native boolean has_change_number()
     /*-{ return this.hasOwnProperty('_change_number') }-*/;
 
     final native boolean has_revision_number()
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index 3693ced..92b30a0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -287,54 +287,59 @@
       NativeMap<LabelInfo> all,
       NativeMap<JsArrayString> permitted) {
     TreeSet<Short> values = new TreeSet<Short>();
+    List<LabelAndValues> labels =
+        new ArrayList<LabelAndValues>(permitted.size());
     for (String id : names) {
       JsArrayString p = permitted.get(id);
       if (p != null) {
+        Set<Short> a = new TreeSet<Short>();
         for (int i = 0; i < p.length(); i++) {
-          values.add(LabelInfo.parseValue(p.get(i)));
+          a.add(LabelInfo.parseValue(p.get(i)));
         }
+        labels.add(new LabelAndValues(all.get(id), a));
+        values.addAll(a);
       }
     }
     List<Short> columns = new ArrayList<Short>(values);
 
-    labelsTable.resize(1 + permitted.size(), 1 + values.size());
+    labelsTable.resize(1 + labels.size(), 1 + values.size());
     for (int c = 0; c < columns.size(); c++) {
       labelsTable.setText(0, 1 + c, LabelValue.formatValue(columns.get(c)));
       labelsTable.getCellFormatter().setStyleName(0, 1 + c, style.label_value());
     }
 
-    List<String> checkboxes = new ArrayList<String>(permitted.size());
+    List<LabelAndValues> checkboxes =
+        new ArrayList<LabelAndValues>(labels.size());
     int row = 1;
-    for (String id : names) {
-      Set<Short> vals = all.get(id).value_set();
-      if (isCheckBox(vals)) {
-        checkboxes.add(id);
+    for (LabelAndValues lv : labels) {
+      if (isCheckBox(lv.info.value_set())) {
+        checkboxes.add(lv);
       } else {
-        renderRadio(row++, id, columns, vals, all.get(id));
+        renderRadio(row++, columns, lv);
       }
     }
-    for (String id : checkboxes) {
-      renderCheckBox(row++, id, all.get(id));
+    for (LabelAndValues lv : checkboxes) {
+      renderCheckBox(row++, lv);
     }
   }
 
-  private void renderRadio(int row, final String id,
-      List<Short> columns,
-      Set<Short> values,
-      LabelInfo info) {
+  private void renderRadio(int row, List<Short> columns, LabelAndValues lv) {
+    final String id = lv.info.name();
+
     labelsTable.setText(row, 0, id);
     labelsTable.getCellFormatter().setStyleName(row, 0, style.label_name());
 
     ApprovalInfo self = Gerrit.isSignedIn()
-        ? info.for_user(Gerrit.getUserAccount().getId().get())
+        ? lv.info.for_user(Gerrit.getUserAccount().getId().get())
         : null;
 
-    final List<RadioButton> group = new ArrayList<RadioButton>(values.size());
+    final List<RadioButton> group =
+        new ArrayList<RadioButton>(lv.permitted.size());
     for (int i = 0; i < columns.size(); i++) {
       final Short v = columns.get(i);
-      if (values.contains(v)) {
+      if (lv.permitted.contains(v)) {
         RadioButton b = new RadioButton(id);
-        b.setTitle(info.value_text(LabelValue.formatValue(v)));
+        b.setTitle(lv.info.value_text(LabelValue.formatValue(v)));
         if ((self != null && v == self.value()) || (self == null && v == 0)) {
           b.setValue(true);
         }
@@ -364,14 +369,16 @@
     }
   }
 
-  private void renderCheckBox(int row, final String id, LabelInfo info) {
+  private void renderCheckBox(int row, LabelAndValues lv) {
     ApprovalInfo self = Gerrit.isSignedIn()
-        ? info.for_user(Gerrit.getUserAccount().getId().get())
+        ? lv.info.for_user(Gerrit.getUserAccount().getId().get())
         : null;
 
+    final String id = lv.info.name();
     final CheckBox b = new CheckBox();
     b.setText(id);
-    b.setTitle(info.value_text("+1"));
+    b.setTitle(lv.info.value_text("+1"));
+    b.setEnabled(lv.permitted.contains((short) 1));
     if (self != null && self.value() == 1) {
       b.setValue(true);
     }
@@ -433,4 +440,14 @@
     }
     return Natives.asList(l);
   }
+
+  private static class LabelAndValues {
+    final LabelInfo info;
+    final Set<Short> permitted;
+
+    LabelAndValues(LabelInfo info, Set<Short> permitted) {
+      this.info = info;
+      this.permitted = permitted;
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
index c2947cf..4b695d2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -22,6 +22,12 @@
 .table tr {
   vertical-align: top;
 }
+.table tr:hover {
+  background: rgba(209, 245, 248, 0.32);
+}
+.table tr.nohover:hover {
+  background: transparent;
+}
 
 .status {
   padding-right: 4px;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
index d27f29e..df6638d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
@@ -108,7 +108,7 @@
     ChangeDetail r = new ChangeDetail();
     r.setAllowsAnonymous(rev.has_fetch() && rev.fetch().containsKey("http"));
     r.setCanAbandon(can(info.actions(), "abandon"));
-    r.setCanEditCommitMessage(can(info.actions(), "message"));
+    r.setCanEditCommitMessage(can(rev.actions(), "message"));
     r.setCanCherryPick(can(rev.actions(), "cherrypick"));
     r.setCanPublish(can(rev.actions(), "publish"));
     r.setCanRebase(can(rev.actions(), "rebase"));
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 7e222bb..296e481 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
@@ -345,7 +345,7 @@
                   List<ChangeInfo> d = new ArrayList<ChangeInfo>();
                   for (CommitInfo p : Natives.asList(self.commit().parents())) {
                     ChangeAndCommit pc = m.get(p.commit());
-                    if (pc != null) {
+                    if (pc != null && pc.has_change_number()) {
                       ChangeInfo i = new ChangeInfo();
                       load(pc, i);
                       d.add(i);
@@ -364,7 +364,9 @@
                 List<ChangeInfo> n = new ArrayList<ChangeInfo>();
                 for (int i = 0; i < info.changes().length(); i++) {
                   ChangeAndCommit c = info.changes().get(i);
-                  if (c.commit() != null && c.commit().parents() != null) {
+                  if (c.has_change_number()
+                      && c.commit() != null
+                      && c.commit().parents() != null) {
                     for (int j = 0; j < c.commit().parents().length(); j++) {
                       CommitInfo p = c.commit().parents().get(j);
                       if (mine.contains(p.commit())) {
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 96073cc..e08703e 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
@@ -34,7 +34,6 @@
 import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.changes.ListChangesOption;
-import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Change;
@@ -485,26 +484,12 @@
     for (String path : paths) {
       JsArray<CommentInfo> comments = drafts.get(path);
       for (int i = 0; i < comments.length(); i++) {
-        d.add(toComment(path, comments.get(i)));
+        d.add(CommentEditorPanel.toComment(patchSetId, path, comments.get(i)));
       }
     }
     return d;
   }
 
-  private PatchLineComment toComment(String path, CommentInfo i) {
-    PatchLineComment p = new PatchLineComment(
-        new PatchLineComment.Key(
-            new Patch.Key(patchSetId, path),
-            i.id()),
-        i.line(),
-        Gerrit.getUserAccount().getId(),
-        i.in_reply_to(),
-        i.updated());
-    p.setMessage(i.message());
-    p.setSide((short) (i.side() == Side.PARENT ? 0 : 1));
-    return p;
-  }
-
   private static class ValueRadioButton extends RadioButton {
     final LabelInfo label;
     final String value;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
index 351442b..0aaa6f6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
@@ -25,6 +25,11 @@
   box-shadow: 3px 3px 3px #888888;
   margin-bottom: 5px;
   margin-right: 5px;
+  -webkit-touch-callout: initial;
+  -webkit-user-select: initial;
+  -khtml-user-select: initial;
+  -moz-user-select: initial;
+  -ms-user-select: initial;
 }
 
 .header { cursor: pointer; }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index 407108b..17bacc6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -45,6 +45,7 @@
     String range();
     String rangeHighlight();
     String showTabs();
+    String padding();
   }
 
   @UiField
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index 6c2fde1..05ea366 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -24,10 +24,18 @@
     @external .cm-searching, .cm-trailingspace, .cm-tab;
 
     .fullscreen {
+      background-color: #f7f7f7;
       border-bottom: 1px solid #ddd;
     }
 
-    .difftable { max-width: 1484px; }
+    .difftable {
+      max-width: 1484px;
+      -webkit-touch-callout: none;
+      -webkit-user-select: none;
+      -khtml-user-select: none;
+      -moz-user-select: none;
+      -ms-user-select: none;
+    }
     .difftable .CodeMirror-lines { padding: 0; }
     .difftable .CodeMirror pre {
       padding: 0;
@@ -109,6 +117,10 @@
       content: "\00bb";
       color: #f00;
     }
+    .padding {
+      margin-left: 21px;
+      border-left: 2px solid #d64040;
+    }
   </ui:style>
   <g:HTMLPanel styleName='{style.difftable}'>
     <table class='{style.table}'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
index a4bfcfb..306109c7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
@@ -26,7 +26,7 @@
   .reviewed input {
     margin: 0;
     padding: 0;
-    vertical-align: bottom;
+    vertical-align: middle;
   }
   .path {
     white-space: no-wrap;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
index 68a24f5..069e204 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
@@ -61,7 +61,7 @@
     popup.setPopupPositionAndShow(new PositionCallback() {
       @Override
       public void setPosition(int offsetWidth, int offsetHeight) {
-        popup.setPopupPosition(390, 120);
+        popup.setPopupPosition(300, 120);
       }
     });
     current.setFocus(true);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
index 1c92d56..a8c0872 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -80,6 +80,7 @@
   @UiField ToggleButton whitespaceErrors;
   @UiField ToggleButton showTabs;
   @UiField ToggleButton topMenu;
+  @UiField ToggleButton manualReview;
   @UiField ToggleButton expandAllComments;
   @UiField Button apply;
   @UiField Button save;
@@ -127,6 +128,7 @@
     whitespaceErrors.setValue(prefs.showWhitespaceErrors());
     showTabs.setValue(prefs.showTabs());
     topMenu.setValue(!prefs.hideTopMenu());
+    manualReview.setValue(prefs.manualReview());
     expandAllComments.setValue(prefs.expandAllComments());
 
     switch (view.getIntraLineStatus()) {
@@ -244,6 +246,11 @@
     view.resizeCodeMirror();
   }
 
+  @UiHandler("manualReview")
+  void onManualReview(ValueChangeEvent<Boolean> e) {
+    prefs.manualReview(e.getValue());
+  }
+
   @UiHandler("syntaxHighlighting")
   void onSyntaxHighlighting(ValueChangeEvent<Boolean> e) {
     prefs.syntaxHighlighting(e.getValue());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
index 7a1948c..ed77d09 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
@@ -204,6 +204,13 @@
         </g:ToggleButton></td>
       </tr>
       <tr>
+        <th><ui:msg>Mark Reviewed</ui:msg></th>
+        <td><g:ToggleButton ui:field='manualReview'>
+          <g:upFace><ui:msg>Automatic</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Manual</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
         <th><ui:msg>Expand All Comments</ui:msg></th>
         <td><g:ToggleButton ui:field='expandAllComments'>
           <g:upFace><ui:msg>Collapse</ui:msg></g:upFace>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index 4f2fa10..4a8b8a1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.client.changes.ChangeList;
 import com.google.gerrit.client.changes.CommentApi;
 import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.changes.ReviewInfo;
 import com.google.gerrit.client.diff.DiffInfo.Region;
 import com.google.gerrit.client.diff.DiffInfo.Span;
 import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
@@ -260,18 +261,24 @@
     });
     diffTable.sidePanel.adjustGutters(cmB);
 
-    int line = 0;
-    if (!diffChunks.isEmpty()) {
-      DiffChunkInfo d = diffChunks.get(0);
-      CodeMirror cm = getCmFromSide(d.getSide());
-      line = d.getStart();
-      if (cm.lineAtHeight(height - 20) < line) {
-        cm.scrollToY(cm.heightAtLine(line, "local") - 0.5 * height);
+    if (diff.meta_b() != null) {
+      int line = 0;
+      if (!diffChunks.isEmpty()) {
+        DiffChunkInfo d = diffChunks.get(0);
+        CodeMirror cm = getCmFromSide(d.getSide());
+        line = d.getStart();
+        if (cm.lineAtHeight(height - 20) < line) {
+          cm.scrollToY(cm.heightAtLine(line, "local") - 0.5 * height);
+        }
       }
+      cmB.setCursor(LineCharacter.create(line));
+      cmB.focus();
+    } else {
+      cmA.setCursor(LineCharacter.create(0));
+      cmA.focus();
     }
-    cmB.setCursor(LineCharacter.create(line));
-    cmB.focus();
 
+    autoReview();
     prefetchNextFile();
   }
 
@@ -1146,6 +1153,7 @@
   private PaddingWidgetWrapper addPaddingWidget(CodeMirror cm,
       int line, double height, Unit unit, Integer index) {
     SimplePanel padding = new SimplePanel();
+    padding.setStyleName(DiffTable.style.padding());
     padding.getElement().getStyle().setHeight(height, unit);
     Configuration config = Configuration.create()
         .set("coverGutter", true)
@@ -1277,6 +1285,7 @@
         if (cm.somethingSelected()) {
           lineActiveBoxMap.put(handle,
               addNewDraft(cm, line, fromTo.getTo().getLine() == line ? fromTo : null));
+          cm.setSelection(cm.getCursor());
         } else if (box == null) {
           lineActiveBoxMap.put(handle, addNewDraft(cm, line, null));
         } else if (box instanceof DraftBox) {
@@ -1398,14 +1407,21 @@
         if (res < 0) {
           res = -res - (prev ? 1 : 2);
         }
-
         res = res + (prev ? -1 : 1);
-        DiffChunkInfo lookUp = diffChunks.get(getWrapAroundDiffChunkIndex(res));
+        if (res < 0 || diffChunks.size() <= res) {
+          return;
+        }
+
+        DiffChunkInfo lookUp = diffChunks.get(res);
         // If edit, skip the deletion chunk and set focus on the insertion one.
         if (lookUp.isEdit() && lookUp.getSide() == DisplaySide.A) {
           res = res + (prev ? -1 : 1);
+          if (res < 0 || diffChunks.size() <= res) {
+            return;
+          }
         }
-        DiffChunkInfo target = diffChunks.get(getWrapAroundDiffChunkIndex(res));
+
+        DiffChunkInfo target = diffChunks.get(res);
         CodeMirror targetCm = getCmFromSide(target.getSide());
         targetCm.setCursor(LineCharacter.create(target.getStart()));
         targetCm.focus();
@@ -1461,10 +1477,6 @@
     return null;
   }
 
-  private int getWrapAroundDiffChunkIndex(int index) {
-    return (index + diffChunks.size()) % diffChunks.size();
-  }
-
   void defer(Runnable thunk) {
     if (deferred == null) {
       final ArrayList<Runnable> list = new ArrayList<Runnable>();
@@ -1612,6 +1624,25 @@
     });
   }
 
+  private void autoReview() {
+    if (Gerrit.isSignedIn() && prefs.autoReview()) {
+      ChangeApi.revision(revision)
+        .view("files")
+        .id(path)
+        .view("reviewed")
+        .background()
+        .put(new AsyncCallback<ReviewInfo>() {
+            @Override
+            public void onSuccess(ReviewInfo result) {
+              header.reviewed.setValue(true, false);
+            }
+            @Override
+            public void onFailure(Throwable caught) {
+            }
+          });
+    }
+  }
+
   private void prefetchNextFile() {
     String nextPath = header.getNextPath();
     if (nextPath != null) {
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 1b82573..9c8ec57 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
@@ -431,6 +431,11 @@
   font-size: 15px;
   font-family: verdana;
 }
+.loadingPluginsDialog {
+  background: #fff;
+  color: #000;
+  width: auto;
+}
 
 
 /** Screen **/
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 fd3e35c..deaeed9 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
@@ -18,6 +18,9 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.changes.CommentInput;
 import com.google.gerrit.client.changes.PatchTable;
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.rpc.GerritCallback;
@@ -937,13 +940,17 @@
       CommentEditorPanel p = findOrCreateEditor(newComment, false);
       if (p == null) {
         enableButtons(false);
-        PatchUtil.DETAIL_SVC.saveDraft(newComment,
-            new GerritCallback<PatchLineComment>() {
+        final PatchSet.Id psId = newComment.getKey().getParentKey().getParentKey();
+        CommentInput in = CommentEditorPanel.toInput(newComment);
+        CommentApi.createDraft(psId, in,
+            new GerritCallback<CommentInfo>() {
               @Override
-              public void onSuccess(final PatchLineComment result) {
+              public void onSuccess(CommentInfo result) {
                 enableButtons(true);
                 notifyDraftDelta(1);
-                findOrCreateEditor(result, true).setOpen(false);
+                findOrCreateEditor(CommentEditorPanel.toComment(
+                    psId, newComment.getKey().getParentKey().get(), result),
+                  true).setOpen(false);
               }
 
               @Override
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 9ed3f18..f41b90e 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
@@ -15,10 +15,17 @@
 package com.google.gerrit.client.patches;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.changes.CommentInput;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.CommentPanel;
+import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.DoubleClickEvent;
@@ -236,30 +243,37 @@
     cancel.setEnabled(false);
     discard.setEnabled(false);
 
-    PatchUtil.DETAIL_SVC.saveDraft(comment,
-        new GerritCallback<PatchLineComment>() {
-          public void onSuccess(final PatchLineComment result) {
-            notifyDraftDelta(isNew() ? 1 : 0);
-            comment = result;
-            text.setReadOnly(false);
-            save.setEnabled(true);
-            cancel.setEnabled(true);
-            discard.setEnabled(true);
-            render();
-            onSave.onSuccess(VoidResult.INSTANCE);
-          }
+    final PatchSet.Id psId = comment.getKey().getParentKey().getParentKey();
+    final boolean wasNew = isNew();
+    GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
+      public void onSuccess(CommentInfo result) {
+        notifyDraftDelta(wasNew ? 1 : 0);
+        comment = toComment(psId, comment.getKey().get(), result);
+        text.setReadOnly(false);
+        save.setEnabled(true);
+        cancel.setEnabled(true);
+        discard.setEnabled(true);
+        render();
+        onSave.onSuccess(VoidResult.INSTANCE);
+      }
 
-          @Override
-          public void onFailure(final Throwable caught) {
-            text.setReadOnly(false);
-            text.setFocus(true);
-            save.setEnabled(true);
-            cancel.setEnabled(true);
-            discard.setEnabled(true);
-            super.onFailure(caught);
-            onSave.onFailure(caught);
-          }
-        });
+      @Override
+      public void onFailure(final Throwable caught) {
+        text.setReadOnly(false);
+        text.setFocus(true);
+        save.setEnabled(true);
+        cancel.setEnabled(true);
+        discard.setEnabled(true);
+        super.onFailure(caught);
+        onSave.onFailure(caught);
+      }
+    };
+    CommentInput input = toInput(comment);
+    if (wasNew) {
+      CommentApi.createDraft(psId, input, cb);
+    } else {
+      CommentApi.updateDraft(psId, input.id(), input, cb);
+    }
   }
 
   private void notifyDraftDelta(final int delta) {
@@ -283,9 +297,11 @@
     cancel.setEnabled(false);
     discard.setEnabled(false);
 
-    PatchUtil.DETAIL_SVC.deleteDraft(comment.getKey(),
-        new GerritCallback<VoidResult>() {
-          public void onSuccess(final VoidResult result) {
+    CommentApi.deleteDraft(
+        comment.getKey().getParentKey().getParentKey(),
+        comment.getKey().get(),
+        new GerritCallback<JavaScriptObject>() {
+          public void onSuccess(JavaScriptObject result) {
             notifyDraftDelta(-1);
             removeUI();
           }
@@ -319,4 +335,33 @@
     }
     return null;
   }
+
+  public static CommentInput toInput(PatchLineComment c) {
+    CommentInput i = CommentInput.createObject().cast();
+    i.setId(c.getKey().get());
+    i.setPath(c.getKey().getParentKey().get());
+    i.setSide(c.getSide() == 0 ? Side.PARENT : Side.REVISION);
+    if (c.getLine() > 0) {
+      i.setLine(c.getLine());
+    }
+    i.setInReplyTo(c.getParentUuid());
+    i.setMessage(c.getMessage());
+    return i;
+  }
+
+  public static PatchLineComment toComment(PatchSet.Id ps,
+      String path,
+      CommentInfo i) {
+    PatchLineComment p = new PatchLineComment(
+        new PatchLineComment.Key(
+            new Patch.Key(ps, path),
+            i.id()),
+        i.line(),
+        Gerrit.getUserAccount().getId(),
+        i.in_reply_to(),
+        i.updated());
+    p.setMessage(i.message());
+    p.setSide((short) (i.side() == Side.PARENT ? 0 : 1));
+    return p;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index 683471a..22e96ed 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -32,6 +32,7 @@
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
@@ -40,6 +41,7 @@
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
 import org.eclipse.jgit.diff.Edit;
 
 import java.util.ArrayList;
@@ -76,8 +78,8 @@
   }
 
   @Override
-  protected void onCellSingleClick(int row, int column) {
-    super.onCellSingleClick(row, column);
+  protected void onCellSingleClick(Event event, int row, int column) {
+    super.onCellSingleClick(event, row, column);
     if (column == 1 || column == 4) {
       onCellDoubleClick(row, column);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
index 82e3107..7bc16fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
@@ -98,8 +99,8 @@
   }
 
   @Override
-  protected void onCellSingleClick(int row, int column) {
-    super.onCellSingleClick(row, column);
+  protected void onCellSingleClick(Event event, int row, int column) {
+    super.onCellSingleClick(event, row, column);
     if (column == 1 || column == 2) {
       if (!"".equals(table.getText(row, column))) {
         onCellDoubleClick(row, column);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 38dda06..3780ef8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -53,7 +53,7 @@
           }
           final int row = rowOf(td);
           if (getRowItem(row) != null) {
-            onCellSingleClick(rowOf(td), columnOf(td));
+            onCellSingleClick(event, rowOf(td), columnOf(td));
             return;
           }
           break;
@@ -145,7 +145,7 @@
   }
 
   /** Invoked when the user clicks on a table cell. */
-  protected void onCellSingleClick(int row, int column) {
+  protected void onCellSingleClick(Event event, int row, int column) {
     movePointerTo(row);
   }
 
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
index 6ff6f72..28f5096c 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -209,6 +209,10 @@
     return FromTo.create(getCursor("start"), getCursor("end"));
   }
 
+  public final native void setSelection(LineCharacter lineCh) /*-{
+    this.setSelection(lineCh);
+  }-*/;
+
   public final native void setCursor(LineCharacter lineCh) /*-{
     this.setCursor(lineCh);
   }-*/;
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
index e448fa9..c3128f6 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
@@ -52,4 +52,5 @@
 application/xml
 
 application/x-javascript = application/javascript
+text/x-h = text/x-c++hdr
 text/x-java-source = text/x-java
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index e7524141..64d5838 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -22,36 +22,28 @@
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
 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.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.patch.PatchScriptFactory;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.util.Collections;
-
 class PatchDetailServiceImpl extends BaseServiceImplementation implements
     PatchDetailService {
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
-  private final SaveDraft.Factory saveDraftFactory;
   private final ChangeControl.Factory changeControlFactory;
 
   @Inject
   PatchDetailServiceImpl(final Provider<ReviewDb> schema,
       final Provider<CurrentUser> currentUser,
       final PatchScriptFactory.Factory patchScriptFactoryFactory,
-      final SaveDraft.Factory saveDraftFactory,
       final ChangeControl.Factory changeControlFactory) {
     super(schema, currentUser);
 
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
-    this.saveDraftFactory = saveDraftFactory;
     this.changeControlFactory = changeControlFactory;
   }
 
@@ -73,36 +65,4 @@
       }
     }.to(callback);
   }
-
-  public void saveDraft(final PatchLineComment comment,
-      final AsyncCallback<PatchLineComment> callback) {
-    saveDraftFactory.create(comment).to(callback);
-  }
-
-  public void deleteDraft(final PatchLineComment.Key commentKey,
-      final AsyncCallback<VoidResult> callback) {
-    run(callback, new Action<VoidResult>() {
-      public VoidResult run(ReviewDb db) throws OrmException, Failure {
-        Change.Id id = commentKey.getParentKey().getParentKey().getParentKey();
-        db.changes().beginTransaction(id);
-        try {
-          final PatchLineComment comment = db.patchComments().get(commentKey);
-          if (comment == null) {
-            throw new Failure(new NoSuchEntityException());
-          }
-          if (!getAccountId().equals(comment.getAuthor())) {
-            throw new Failure(new NoSuchEntityException());
-          }
-          if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
-            throw new Failure(new IllegalStateException("Comment published"));
-          }
-          db.patchComments().delete(Collections.singleton(comment));
-          db.commit();
-          return VoidResult.INSTANCE;
-        } finally {
-          db.rollback();
-        }
-      }
-    });
-  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
index 4e69b14..83bfcdc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.httpd.rpc.RpcServletModule;
 import com.google.gerrit.httpd.rpc.UiRpcModule;
-import com.google.gerrit.server.config.FactoryModule;
 
 public class PatchModule extends RpcServletModule {
   public PatchModule() {
@@ -25,12 +24,6 @@
 
   @Override
   protected void configureServlets() {
-    install(new FactoryModule() {
-      @Override
-      protected void configure() {
-        factory(SaveDraft.Factory.class);
-      }
-    });
     rpc(PatchDetailServiceImpl.class);
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
deleted file mode 100644
index d44a4cf..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.patch;
-
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.util.TimeUtil;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.Collections;
-
-class SaveDraft extends Handler<PatchLineComment> {
-  interface Factory {
-    SaveDraft create(PatchLineComment comment);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final IdentifiedUser currentUser;
-
-  private final PatchLineComment comment;
-
-  @Inject
-  SaveDraft(final ChangeControl.Factory changeControlFactory,
-      final ReviewDb db, final IdentifiedUser currentUser,
-      @Assisted final PatchLineComment comment) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.currentUser = currentUser;
-    this.comment = comment;
-  }
-
-  @Override
-  public PatchLineComment call() throws NoSuchChangeException, OrmException {
-    if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
-      throw new IllegalStateException("Comment published");
-    }
-
-    final Patch.Key patchKey = comment.getKey().getParentKey();
-    final PatchSet.Id patchSetId = patchKey.getParentKey();
-    final Change.Id changeId = patchKey.getParentKey().getParentKey();
-
-    db.changes().beginTransaction(changeId);
-    try {
-      changeControlFactory.validateFor(changeId);
-      if (db.patchSets().get(patchSetId) == null) {
-        throw new NoSuchChangeException(changeId);
-      }
-
-      final Account.Id me = currentUser.getAccountId();
-      if (comment.getKey().get() == null) {
-        if (comment.getLine() < 0) {
-          throw new IllegalStateException("Comment line must be >= 0, not "
-              + comment.getLine());
-        }
-
-        if (comment.getParentUuid() != null) {
-          final PatchLineComment parent =
-              db.patchComments().get(
-                  new PatchLineComment.Key(patchKey, comment.getParentUuid()));
-          if (parent == null || parent.getSide() != comment.getSide()) {
-            throw new IllegalStateException("Parent comment must be on same side");
-          }
-        }
-        if (comment.getRange() != null
-            && comment.getLine() != comment.getRange().getEndLine()) {
-            throw new IllegalStateException(
-              "Range endLine must be on the same line as the comment");
-        }
-
-        final PatchLineComment nc =
-            new PatchLineComment(new PatchLineComment.Key(patchKey,
-                ChangeUtil.messageUUID(db)), comment.getLine(), me,
-                comment.getParentUuid(), TimeUtil.nowTs());
-        nc.setSide(comment.getSide());
-        nc.setMessage(comment.getMessage());
-        nc.setRange(comment.getRange());
-        db.patchComments().insert(Collections.singleton(nc));
-        db.commit();
-        return nc;
-
-      } else {
-        if (!me.equals(comment.getAuthor())) {
-          throw new NoSuchChangeException(changeId);
-        }
-        comment.setWrittenOn(TimeUtil.nowTs());
-        db.patchComments().update(Collections.singleton(comment));
-        db.commit();
-        return comment;
-      }
-    } finally {
-      db.rollback();
-    }
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 8973132..ee4439f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -27,10 +27,10 @@
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
@@ -113,7 +113,7 @@
       md.close();
     }
 
-    final RefControl metaConfigControl = pc.controlForRef(GitRepositoryManager.REF_CONFIG);
+    final RefControl metaConfigControl = pc.controlForRef(RefNames.REFS_CONFIG);
     List<AccessSection> local = new ArrayList<AccessSection>();
     Set<String> ownerOf = new HashSet<String>();
     Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index 3b1f7f2..18af5a2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
@@ -35,7 +36,6 @@
 import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.change.PostReviewers;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.SystemGroupBackend;
@@ -134,7 +134,7 @@
         user.getAccountId(),
         new Branch.NameKey(
             config.getProject().getNameKey(),
-            GitRepositoryManager.REF_CONFIG),
+            RefNames.REFS_CONFIG),
         TimeUtil.nowTs());
 
     ps.setCreatedOn(change.getCreatedOn());
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index ffa9cf4..9f397fc 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -18,6 +18,7 @@
   deps = [
     '//gerrit-common:annotations',
     '//gerrit-common:server',
+    '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//lib:guava',
     '//lib/guice:guice',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
index 45ca87b..b5f0768 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.pgm.init;
 
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.git.VersionedMetaData;
 import com.google.inject.Inject;
@@ -59,7 +59,7 @@
 
   @Override
   protected String getRefName() {
-    return GitRepositoryManager.REF_CONFIG;
+    return RefNames.REFS_CONFIG;
   }
 
   public Config load() throws IOException, ConfigInvalidException {
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java
index 24b8136..7a23313 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java
@@ -14,7 +14,7 @@
 
 package ${package}.client;
 
-import com.google.gerrit.plugin.client.Plugin;
+import com.google.gerrit.plugin.client.PluginEntryPoint;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.ui.Button;
@@ -27,10 +27,10 @@
 /**
  * HelloWorld Plugin.
  */
-public class HelloPlugin extends Plugin {
+public class HelloPlugin extends PluginEntryPoint {
 
   @Override
-  public void onModuleLoad() {
+  public void onPluginLoad() {
     // Create the dialog box
     final DialogBox dialogBox = new DialogBox();
 
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
index b1ff403..e02225b 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2013 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.
@@ -14,21 +14,46 @@
 
 package com.google.gerrit.plugin.client;
 
-import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
 
 /**
- * Base class for writing Gerrit Web UI plugins
+ * Wrapper around the plugin instance exposed by Gerrit.
  *
- * Writing a plugin:
- * <ol>
- * <li>Declare subtype of Plugin</li>
- * <li>Bind WebUiPlugin to GwtPlugin implementation in Gerrit-Module</li>
- * </ol>
+ * Listeners for events generated by the main UI must be registered
+ * through this instance.
  */
-public abstract class Plugin implements EntryPoint {
-  public native static void go(String t)
-  /*-{ $wnd.Gerrit.go(t) }-*/;
+public final class Plugin extends JavaScriptObject {
+  private static final Plugin self = install(
+      GWT.getModuleBaseURL() + GWT.getModuleName() + ".nocache.js");
 
-  public native static void refresh()
-  /*-{ $wnd.Gerrit.refresh() }-*/;
+  /** Obtain the plugin instance wrapper. */
+  public static Plugin get() {
+    return self;
+  }
+
+  /** Installed name of the plugin. */
+  public final String getName() {
+    return getPluginName();
+  }
+
+  /** Installed name of the plugin. */
+  public final native String getPluginName()
+  /*-{ return this.getPluginName() }-*/;
+
+  /** Navigate the UI to the screen identified by the token. */
+  public final native void go(String token)
+  /*-{ return this.go(token) }-*/;
+
+  /** Refresh the current UI. */
+  public final native void refresh()
+  /*-{ return this.refresh() }-*/;
+
+  protected Plugin() {
+  }
+
+  native void _initialized() /*-{ this._success = true }-*/;
+  native void _loaded() /*-{ this._loadedGwt() }-*/;
+  private static native final Plugin install(String u)
+  /*-{ return $wnd.Gerrit.installGwt(u) }-*/;
 }
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
new file mode 100644
index 0000000..ca1cdbf
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.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.plugin.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 PluginEntryPoint implements EntryPoint {
+  /**
+   * The plugin entry point method, called automatically by loading
+   * a module that declares an implementing class as an entry point.
+   */
+  public abstract void onPluginLoad();
+
+  public final void onModuleLoad() {
+    Plugin self = Plugin.get();
+    try {
+      onPluginLoad();
+      self._initialized();
+    } finally {
+      self._loaded();
+    }
+  }
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
index 54c556d..613978a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.reviewdb.client;
 
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+
 import com.google.gwtorm.client.Column;
 import com.google.gwtorm.client.IntKey;
 
@@ -21,8 +23,6 @@
 
 /** A single revision of a {@link Change}. */
 public final class PatchSet {
-  private static final String REFS_CHANGES = "refs/changes/";
-
   /** Is the reference name a change reference? */
   public static boolean isRef(final String name) {
     if (name == null || !name.startsWith(REFS_CHANGES)) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
new file mode 100644
index 0000000..ede5c26
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 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.reviewdb.client;
+
+/** Constants and utilities for Gerrit-specific ref names. */
+public class RefNames {
+  public static final String REFS_CHANGES = "refs/changes/";
+
+  /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
+  public static final String REFS_REJECT_COMMITS = "refs/meta/reject-commits";
+
+  /** Configuration settings for a project {@code refs/meta/config} */
+  public static final String REFS_CONFIG = "refs/meta/config";
+
+  /** Configurations of project-specific dashboards (canned search queries). */
+  public static final String REFS_DASHBOARDS = "refs/meta/dashboards/";
+
+  /**
+   * Prefix applied to merge commit base nodes.
+   * <p>
+   * References in this directory should take the form
+   * {@code refs/cache-automerge/xx/yyyy...} where xx is
+   * the first two digits of the merge commit's object
+   * name, and yyyyy... is the remaining 38. The reference
+   * should point to a treeish that is the automatic merge
+   * result of the merge commit's parents.
+   */
+  public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
+
+  private RefNames() {
+  }
+}
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 5e992cd9..091c9b7 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -90,7 +90,6 @@
     '//gerrit-common:server',
     '//gerrit-cache-h2:cache-h2',
     '//gerrit-lucene:lucene',
-    '//gerrit-reviewdb:client',
     '//gerrit-reviewdb:server',
     '//lib:guava',
     '//lib:gwtorm',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
index 4ab3442..ab57059 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.rules;
 
 import com.google.gerrit.common.Version;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -79,7 +79,7 @@
   }
 
   public Status call() throws IOException, CompileException {
-    ObjectId metaConfig = git.resolve(GitRepositoryManager.REF_CONFIG);
+    ObjectId metaConfig = git.resolve(RefNames.REFS_CONFIG);
     if (metaConfig == null) {
       return Status.NO_RULES;
     }
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 dad53b9..4a3cd9a 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
@@ -20,6 +20,7 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -66,7 +67,7 @@
  * <p>
  * Rules are loaded from the {@code site_path/cache/rules/rules-SHA1.jar}, where
  * {@code SHA1} is the SHA1 of the Prolog {@code rules.pl} in a project's
- * {@link GitRepositoryManager#REF_CONFIG} branch.
+ * {@link RefNames#REFS_CONFIG} branch.
  */
 @Singleton
 public class RulesCache {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
index 4002c1a..3a0a27d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -29,11 +29,11 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.GroupJson;
@@ -154,7 +154,7 @@
 
     public ProjectAccessInfo(ProjectControl pc, ProjectConfig config) {
       final RefControl metaConfigControl =
-          pc.controlForRef(GitRepositoryManager.REF_CONFIG);
+          pc.controlForRef(RefNames.REFS_CONFIG);
       local = Maps.newHashMap();
       ownerOf = Sets.newHashSet();
       Map<AccountGroup.UUID, Boolean> visibleGroups =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
index 23e508f..d804f38 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
@@ -92,7 +92,6 @@
       throws OrmException, IOException {
     Map<Change.Id, Change> changes = allOpenChanges(rsrc);
     Map<PatchSet.Id, PatchSet> patchSets = allPatchSets(changes.keySet());
-    List<ChangeAndCommit> list = children(rsrc, rw, changes, patchSets);
 
     Map<String, PatchSet> commits = Maps.newHashMap();
     for (PatchSet p : patchSets.values()) {
@@ -112,11 +111,19 @@
       }
     }
 
+    Set<Change.Id> added = Sets.newHashSet();
+    List<ChangeAndCommit> parents = Lists.newArrayList();
     for (RevCommit c; (c = rw.next()) != null;) {
       PatchSet p = commits.get(c.name());
-      Change g = p != null ? changes.get(p.getId().getParentKey()) : null;
-      list.add(new ChangeAndCommit(g, p, c));
+      Change g = null;
+      if (p != null) {
+        g = changes.get(p.getId().getParentKey());
+        added.add(p.getId().getParentKey());
+      }
+      parents.add(new ChangeAndCommit(g, p, c));
     }
+    List<ChangeAndCommit> list = children(rsrc, rw, changes, patchSets, added);
+    list.addAll(parents);
 
     if (list.size() == 1) {
       ChangeAndCommit r = list.get(0);
@@ -155,7 +162,8 @@
   }
 
   private List<ChangeAndCommit> children(RevisionResource rsrc, RevWalk rw,
-      Map<Change.Id, Change> changes, Map<PatchSet.Id, PatchSet> patchSets)
+      Map<Change.Id, Change> changes, Map<PatchSet.Id, PatchSet> patchSets,
+      Set<Change.Id> added)
       throws OrmException, IOException {
     // children is a map of parent commit name to PatchSet built on it.
     Multimap<String, PatchSet.Id> children = allChildren(changes.keySet());
@@ -192,7 +200,9 @@
         if (!c.has(seenCommit)) {
           c.add(seenCommit);
           q.addFirst(ps.getRevision().get());
-          graph.add(new ChangeAndCommit(change, ps, c));
+          if (added.add(ps.getId().getParentKey())) {
+            graph.add(new ChangeAndCommit(change, ps, c));
+          }
         }
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
index 53c6e79..c1f8598 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
@@ -28,11 +28,11 @@
 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.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.Mergeable.MergeableInfo;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.git.WorkQueue.Executor;
@@ -110,11 +110,11 @@
   @Override
   public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
     String ref = event.getRefName();
-    if (ref.startsWith(Constants.R_HEADS) || ref.equals(GitRepositoryManager.REF_CONFIG)) {
+    if (ref.startsWith(Constants.R_HEADS) || ref.equals(RefNames.REFS_CONFIG)) {
       executor.submit(new BranchUpdateTask(schemaFactory,
           new Project.NameKey(event.getProjectName()), ref));
     }
-    if (ref.equals(GitRepositoryManager.REF_CONFIG)) {
+    if (ref.equals(RefNames.REFS_CONFIG)) {
       Project.NameKey p = new Project.NameKey(event.getProjectName());
       try {
         ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
@@ -124,7 +124,7 @@
             new ProjectUpdateTask(schemaFactory, p).call();
           } catch (Exception e) {
             String msg = "Failed to update mergeability flags for project " + p.get()
-                + " on update of " + GitRepositoryManager.REF_CONFIG;
+                + " on update of " + RefNames.REFS_CONFIG;
             log.error(msg, e);
             Throwables.propagateIfPossible(e);
             throw new RuntimeException(msg, e);
@@ -132,7 +132,7 @@
         }
       } catch (ConfigInvalidException | IOException e) {
         String msg = "Failed to update mergeability flags for project " + p.get()
-            + " on update of " + GitRepositoryManager.REF_CONFIG;
+            + " on update of " + RefNames.REFS_CONFIG;
         log.error(msg, e);
         throw new RuntimeException(msg, e);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index d54d64a..2ee3ad0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -49,6 +49,7 @@
 import com.google.gerrit.server.account.AccountsCollection;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -387,7 +388,7 @@
         // User requested delete of this label.
         if (c != null) {
           if (c.getValue() != 0) {
-            labelDelta.add("-" + normName);
+            addLabelDelta(normName, (short) 0);
           }
           del.add(c);
         }
@@ -395,7 +396,7 @@
         c.setValue(ent.getValue());
         c.setGranted(timestamp);
         upd.add(c);
-        labelDelta.add(format(normName, c.getValue()));
+        addLabelDelta(normName, c.getValue());
         categories.put(normName, c.getValue());
       } else if (c != null && c.getValue() == ent.getValue()) {
         current.put(normName, c);
@@ -407,7 +408,7 @@
             ent.getValue(), TimeUtil.nowTs());
         c.setGranted(timestamp);
         ins.add(c);
-        labelDelta.add(format(normName, c.getValue()));
+        addLabelDelta(normName, c.getValue());
         categories.put(normName, c.getValue());
       }
     }
@@ -467,14 +468,8 @@
     return current;
   }
 
-  private static String format(String name, short value) {
-    StringBuilder sb = new StringBuilder(name.length() + 2);
-    sb.append(name);
-    if (value >= 0) {
-      sb.append('+');
-    }
-    sb.append(value);
-    return sb.toString();
+  private void addLabelDelta(String name, short value) {
+    labelDelta.add(new LabelVote(name, value).format());
   }
 
   private boolean insertMessage(RevisionResource rsrc, String msg)
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 6859262..7a64155 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
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.git;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_REJECT_COMMITS;
 
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.reviewdb.client.Project;
@@ -96,7 +96,7 @@
         NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(project,
             repo, inserter);
         NoteMap newlyCreated =
-            notesBranchUtil.commitNewNotes(banCommitNotes, REF_REJECT_COMMITS,
+            notesBranchUtil.commitNewNotes(banCommitNotes, REFS_REJECT_COMMITS,
                 createPersonIdent(), buildCommitMessage(commitsToBan, reason));
 
         for (Note n : banCommitNotes) {
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
index 32dc303..eb4acfc 100644
--- 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
@@ -19,6 +19,7 @@
 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.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gwtorm.server.SchemaFactory;
@@ -72,7 +73,7 @@
 
   @Override
   public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
-    if (event.getRefName().startsWith("refs/changes/")) {
+    if (event.getRefName().startsWith(RefNames.REFS_CHANGES)) {
       cache.invalidate(new Project.NameKey(event.getProjectName()));
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index 0118404..ffb91ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -31,27 +31,6 @@
  * environment.
  */
 public interface GitRepositoryManager {
-  /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
-  public static final String REF_REJECT_COMMITS = "refs/meta/reject-commits";
-
-  /** Configuration settings for a project {@code refs/meta/config} */
-  public static final String REF_CONFIG = "refs/meta/config";
-
-  /** Configurations of project-specific dashboards (canned search queries). */
-  public static String REFS_DASHBOARDS = "refs/meta/dashboards/";
-
-  /**
-   * Prefix applied to merge commit base nodes.
-   * <p>
-   * References in this directory should take the form
-   * {@code refs/cache-automerge/xx/yyyy...} where xx is
-   * the first two digits of the merge commit's object
-   * name, and yyyyy... is the remaining 38. The reference
-   * should point to a treeish that is the automatic merge
-   * result of the merge commit's parents.
-   */
-  public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
-
   /**
    * Get (or open) a repository by name.
    *
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 ee610fc..16f8ee2 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
@@ -39,6 +39,7 @@
 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.client.RefNames;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -579,7 +580,7 @@
     }
 
     if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
-      if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
+      if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
         try {
           ProjectConfig cfg =
               new ProjectConfig(destProject.getProject().getNameKey());
@@ -606,7 +607,7 @@
                   mergeTip);
             }
 
-            if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
+            if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
               projectCache.evict(destProject.getProject());
               destProject = projectCache.get(destProject.getProject().getNameKey());
               repoManager.setProjectDescription(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 3a29587..97ae746 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.State;
@@ -370,7 +371,7 @@
 
   @Override
   protected String getRefName() {
-    return GitRepositoryManager.REF_CONFIG;
+    return RefNames.REFS_CONFIG;
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
index fc95608..476ccae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.collect.Iterables;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.project.ProjectState;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -38,7 +39,7 @@
 
   @Override
   protected String getRefName() {
-    return GitRepositoryManager.REF_CONFIG;
+    return RefNames.REFS_CONFIG;
   }
 
   @Override
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 611ce52..b7b34b1 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.git;
 
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
@@ -58,6 +59,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -156,8 +158,8 @@
   private static final Logger log =
       LoggerFactory.getLogger(ReceiveCommits.class);
 
-  public static final Pattern NEW_PATCHSET =
-      Pattern.compile("^refs/changes/(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
+  public static final Pattern NEW_PATCHSET = Pattern.compile(
+      "^" + REFS_CHANGES + "(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
 
   private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
 
@@ -168,12 +170,12 @@
   private enum Error {
         CONFIG_UPDATE("You are not allowed to perform this operation.\n"
         + "Configuration changes can only be pushed by project owners\n"
-        + "who also have 'Push' rights on " + GitRepositoryManager.REF_CONFIG),
+        + "who also have 'Push' rights on " + RefNames.REFS_CONFIG),
         UPDATE("You are not allowed to perform this operation.\n"
         + "To push into this reference you need 'Push' rights."),
         DELETE("You need 'Push' rights with the 'Force Push'\n"
             + "flag set to delete references."),
-        DELETE_CHANGES("Cannot delete from 'refs/changes'"),
+        DELETE_CHANGES("Cannot delete from '" + REFS_CHANGES + "'"),
         CODE_REVIEW("You need 'Push' rights to upload code review requests.\n"
             + "Verify that you are pushing to the right branch.");
 
@@ -397,8 +399,8 @@
         Map<String, Ref> filteredRefs = Maps.newHashMapWithExpectedSize(refs.size());
         for (Map.Entry<String, Ref> e : refs.entrySet()) {
           String name = e.getKey();
-          if (!name.startsWith("refs/changes/")
-              && !name.startsWith(GitRepositoryManager.REFS_CACHE_AUTOMERGE)) {
+          if (!name.startsWith(REFS_CHANGES)
+              && !name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
             filteredRefs.put(name, e.getValue());
           }
         }
@@ -914,8 +916,8 @@
       validateNewCommits(ctl, cmd);
       batch.addCommand(cmd);
     } else {
-      if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
-        errors.put(Error.CONFIG_UPDATE, GitRepositoryManager.REF_CONFIG);
+      if (RefNames.REFS_CONFIG.equals(ctl.getRefName())) {
+        errors.put(Error.CONFIG_UPDATE, RefNames.REFS_CONFIG);
       } else {
         errors.put(Error.UPDATE, ctl.getRefName());
       }
@@ -944,13 +946,13 @@
 
   private void parseDelete(final ReceiveCommand cmd) {
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
-    if (ctl.getRefName().startsWith("refs/changes/")) {
+    if (ctl.getRefName().startsWith(REFS_CHANGES)) {
       errors.put(Error.DELETE_CHANGES, ctl.getRefName());
       reject(cmd, "cannot delete changes");
     } else if (ctl.canDelete()) {
       batch.addCommand(cmd);
     } else {
-      if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+      if (RefNames.REFS_CONFIG.equals(ctl.getRefName())) {
         reject(cmd, "cannot delete project configuration");
       } else {
         errors.put(Error.DELETE, ctl.getRefName());
@@ -1222,7 +1224,7 @@
    */
   private NoteMap loadRejectCommitsMap() throws IOException {
     try {
-      Ref ref = repo.getRef(GitRepositoryManager.REF_REJECT_COMMITS);
+      Ref ref = repo.getRef(RefNames.REFS_REJECT_COMMITS);
       if (ref == null) {
         return NoteMap.newEmptyMap();
       }
@@ -1232,7 +1234,7 @@
       return NoteMap.read(rw.getObjectReader(), map);
     } catch (IOException badMap) {
       throw new IOException("Cannot load "
-          + GitRepositoryManager.REF_REJECT_COMMITS, badMap);
+          + RefNames.REFS_REJECT_COMMITS, badMap);
     }
   }
 
@@ -1424,7 +1426,7 @@
     for (Ref ref : allRefs.values()) {
       if (ref.getObjectId() == null) {
         continue;
-      } else if (ref.getName().startsWith("refs/changes/")) {
+      } else if (ref.getName().startsWith(REFS_CHANGES)) {
         existing.add(ref.getObjectId());
       } else if (ref.getName().startsWith(R_HEADS)
           || (forRef != null && forRef.equals(ref.getName()))) {
@@ -1708,6 +1710,7 @@
       if (newCommit == priorCommit) {
         // Ignore requests to make the change its current state.
         skip = true;
+        reject(inputCommand, "commit already exists");
         return false;
       }
 
@@ -2057,7 +2060,7 @@
         && ctl.canUploadMerges()
         && !projectControl.getProjectState().isUseSignedOffBy()
         && Iterables.isEmpty(rejectCommits)
-        && !GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())
+        && !RefNames.REFS_CONFIG.equals(ctl.getRefName())
         && !(MagicBranch.isMagicBranch(cmd.getRefName())
             || NEW_PATCHSET.matcher(cmd.getRefName()).matches())) {
       return;
@@ -2233,7 +2236,7 @@
   private SetMultimap<ObjectId, Ref> changeRefsById() throws IOException {
     if (refsById == null) {
       refsById =  HashMultimap.create();
-      for (Ref r : repo.getRefDatabase().getRefs("refs/changes/").values()) {
+      for (Ref r : repo.getRefDatabase().getRefs(REFS_CHANGES).values()) {
         if (PatchSet.isRef(r.getName())) {
           refsById.put(r.getObjectId(), r);
         }
@@ -2340,6 +2343,6 @@
   }
 
   private static boolean isConfig(final ReceiveCommand cmd) {
-    return cmd.getRefName().equals(GitRepositoryManager.REF_CONFIG);
+    return cmd.getRefName().equals(RefNames.REFS_CONFIG);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index f34ce8b..7ae8271 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -21,6 +21,7 @@
 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.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.gwtorm.server.OrmException;
@@ -164,8 +165,8 @@
   }
 
   private static boolean skip(String name) {
-    return name.startsWith("refs/changes/")
-        || name.startsWith(GitRepositoryManager.REFS_CACHE_AUTOMERGE)
+    return name.startsWith(RefNames.REFS_CHANGES)
+        || name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
         || MagicBranch.isMagicBranch(name);
   }
 }
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 14aae94..343b49c 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
@@ -19,9 +19,11 @@
 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.client.RefNames;
 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.RefDatabase;
@@ -68,9 +70,9 @@
 
   public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeperately) {
     if (projectCtl.allRefsAreVisibleExcept(
-        ImmutableSet.of(GitRepositoryManager.REF_CONFIG))) {
+        ImmutableSet.of(RefNames.REFS_CONFIG))) {
       Map<String, Ref> r = Maps.newHashMap(refs);
-      r.remove(GitRepositoryManager.REF_CONFIG);
+      r.remove(RefNames.REFS_CONFIG);
       return r;
     }
 
@@ -79,7 +81,7 @@
     final List<Ref> deferredTags = new ArrayList<Ref>();
 
     for (Ref ref : refs.values()) {
-      if (ref.getName().startsWith(GitRepositoryManager.REFS_CACHE_AUTOMERGE)) {
+      if (ref.getName().startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
         continue;
       } else if (PatchSet.isRef(ref.getName())) {
         // Reference to a patch set is visible if the change is visible.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 05dbf0e..3c6c9ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -18,12 +18,12 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.events.CommitReceivedEvent;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.git.ReceiveCommits;
 import com.google.gerrit.server.git.ValidationError;
@@ -311,7 +311,7 @@
         CommitReceivedEvent receiveEvent) throws CommitValidationException {
       IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
 
-      if (GitRepositoryManager.REF_CONFIG.equals(refControl.getRefName())) {
+      if (RefNames.REFS_CONFIG.equals(refControl.getRefName())) {
         List<CommitValidationMessage> messages =
             new LinkedList<CommitValidationMessage>();
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 6eed909..0370d36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -22,12 +22,12 @@
 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.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CommitMergeStatus;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
@@ -96,7 +96,7 @@
         final Branch.NameKey destBranch,
         final PatchSet.Id patchSetId)
         throws MergeValidationException {
-      if (GitRepositoryManager.REF_CONFIG.equals(destBranch.get())) {
+      if (RefNames.REFS_CONFIG.equals(destBranch.get())) {
         final Project.NameKey newParent;
         try {
           ProjectConfig cfg =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 7d521f2..2cda334 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -18,6 +18,7 @@
 import com.google.common.cache.CacheLoader;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 
@@ -249,7 +250,7 @@
   public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
       boolean save) throws IOException {
     String hash = b.name();
-    String refName = GitRepositoryManager.REFS_CACHE_AUTOMERGE
+    String refName = RefNames.REFS_CACHE_AUTOMERGE
         + hash.substring(0, 2)
         + "/"
         + hash.substring(2);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index 6e5ef65..3df8e2f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REFS_DASHBOARDS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
index fcd2284..8acc29e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REFS_DASHBOARDS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index 7ed5b5f..92f71e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 
@@ -115,7 +116,7 @@
         if (refControl.isVisible()) {
           if (ref.getName().startsWith(Constants.R_HEADS)) {
             branches.add(createBranchInfo(ref, refControl, targets));
-          } else if (GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
+          } else if (RefNames.REFS_CONFIG.equals(ref.getName())) {
             configBranch = createBranchInfo(ref, refControl, targets);
           }
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
index e3247fc..fbfcc8f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REFS_DASHBOARDS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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 3d3943c..6cfc7b4 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
@@ -26,6 +26,7 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.StringUtil;
@@ -85,7 +86,7 @@
         Ref head = git.getRef(Constants.HEAD);
         return head != null
           && head.isSymbolic()
-          && GitRepositoryManager.REF_CONFIG.equals(head.getLeaf().getName());
+          && RefNames.REFS_CONFIG.equals(head.getLeaf().getName());
       }
     },
     ALL {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index 8a6303f..ca006de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -109,7 +110,7 @@
     final Project.NameKey nameKey = createProjectArgs.getProject();
     try {
       final String head =
-          createProjectArgs.permissionsOnly ? GitRepositoryManager.REF_CONFIG
+          createProjectArgs.permissionsOnly ? RefNames.REFS_CONFIG
               : createProjectArgs.branch.get(0);
       final Repository repo = repoManager.createRepository(nameKey);
       try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index cbb56b3..3b6ce91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -27,6 +27,7 @@
 import com.google.inject.Singleton;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -59,22 +60,23 @@
      *        priority order (project specific definitions must appear before
      *        inherited ones).
      * @param ref reference being accessed.
-     * @param username if the reference is a per-user reference, access sections
-     *        using the parameter variable "${username}" will first have {@code
-     *        username} inserted into them before seeing if they apply to the
-     *        reference named by {@code ref}. If null, per-user references are
-     *        ignored.
+     * @param usernames if the reference is a per-user reference, access sections
+     *        using the parameter variable "${username}" will first have each of
+     *        {@code usernames} inserted into them before seeing if they apply to
+     *        the reference named by {@code ref}. If null or empty, per-user
+     *        references are ignored.
      * @return map of permissions that apply to this reference, keyed by
      *         permission name.
      */
     PermissionCollection filter(Iterable<SectionMatcher> matcherList,
-        String ref, String username) {
+        String ref, Collection<String> usernames) {
       if (isRE(ref)) {
         ref = RefControl.shortestExample(ref);
       } else if (ref.endsWith("/*")) {
         ref = ref.substring(0, ref.length() - 1);
       }
 
+      boolean hasUsernames = usernames != null && !usernames.isEmpty();
       boolean perUser = false;
       Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
       for (SectionMatcher sm : matcherList) {
@@ -90,12 +92,17 @@
         // that will never be shared with non-user references, and the per-user
         // references are usually less frequent than the non-user references.
         //
-        if (username != null && !perUser
-            && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
-          perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
-        }
-
-        if (sm.match(ref, username)) {
+        if (hasUsernames) {
+          if (!perUser && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
+            perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
+          }
+          for (String username : usernames) {
+            if (sm.match(ref, username)) {
+              sectionToProject.put(sm.section, sm.project);
+              break;
+            }
+          }
+        } else if (sm.match(ref, null)) {
           sectionToProject.put(sm.section, sm.project);
         }
       }
@@ -140,21 +147,20 @@
         }
       }
 
-      return new PermissionCollection(permissions, ruleProps,
-          perUser ? username : null);
+      return new PermissionCollection(permissions, ruleProps, perUser);
     }
   }
 
   private final Map<String, List<PermissionRule>> rules;
   private final Map<PermissionRule, ProjectRef> ruleProps;
-  private final String username;
+  private final boolean perUser;
 
   private PermissionCollection(Map<String, List<PermissionRule>> rules,
       Map<PermissionRule, ProjectRef> ruleProps,
-      String username) {
+      boolean perUser) {
     this.rules = rules;
     this.ruleProps = ruleProps;
-    this.username = username;
+    this.perUser = perUser;
   }
 
   /**
@@ -162,7 +168,7 @@
    *         this collection, making the results user specific.
    */
   public boolean isUserSpecific() {
-    return username != null;
+    return perUser;
   }
 
   /**
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 30cfc2b..1156677 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
@@ -191,8 +192,15 @@
     }
     RefControl ctl = refControls.get(refName);
     if (ctl == null) {
+      ImmutableList.Builder<String> usernames = ImmutableList.<String> builder();
+      if (user.getUserName() != null) {
+        usernames.add(user.getUserName());
+      }
+      if (user instanceof IdentifiedUser) {
+        usernames.addAll(((IdentifiedUser) user).getEmailAddresses());
+      }
       PermissionCollection relevant =
-          permissionFilter.filter(access(), refName, user.getUserName());
+          permissionFilter.filter(access(), refName, usernames.build());
       ctl = new RefControl(this, refName, relevant);
       refControls.put(refName, ctl);
     }
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 2e79e2f..eceb509 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
@@ -30,6 +30,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.RulesCache;
@@ -164,7 +165,7 @@
     try {
       Repository git = gitMgr.openRepository(getProject().getNameKey());
       try {
-        Ref ref = git.getRef(GitRepositoryManager.REF_CONFIG);
+        Ref ref = git.getRef(RefNames.REFS_CONFIG);
         if (ref == null || ref.getObjectId() == null) {
           return true;
         }
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 0397748..5b21532 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
@@ -23,10 +23,10 @@
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.InternalUser;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.group.SystemGroupBackend;
 
 import dk.brics.automaton.RegExp;
@@ -161,7 +161,7 @@
 
   /** @return true if this user can submit patch sets to this ref */
   public boolean canSubmit() {
-    if (GitRepositoryManager.REF_CONFIG.equals(refName)) {
+    if (RefNames.REFS_CONFIG.equals(refName)) {
       // Always allow project owners to submit configuration changes.
       // Submitting configuration changes modifies the access control
       // rules. Allowing this to be done by a non-project-owner opens
@@ -175,7 +175,7 @@
 
   /** @return true if the user can update the reference as a fast-forward. */
   public boolean canUpdate() {
-    if (GitRepositoryManager.REF_CONFIG.equals(refName)
+    if (RefNames.REFS_CONFIG.equals(refName)
         && !projectControl.isOwner()) {
       // Pushing requires being at least project owner, in addition to push.
       // Pushing configuration changes modifies the access control
@@ -211,7 +211,7 @@
   }
 
   private boolean canPushWithForce() {
-    if (!canWrite() || (GitRepositoryManager.REF_CONFIG.equals(refName)
+    if (!canWrite() || (RefNames.REFS_CONFIG.equals(refName)
         && !projectControl.isOwner())) {
       // Pushing requires being at least project owner, in addition to push.
       // Pushing configuration changes modifies the access control
@@ -294,7 +294,7 @@
    * @return {@code true} if the user specified can delete a Git ref.
    */
   public boolean canDelete() {
-    if (!canWrite() || (GitRepositoryManager.REF_CONFIG.equals(refName))) {
+    if (!canWrite() || (RefNames.REFS_CONFIG.equals(refName))) {
       // Never allow removal of the refs/meta/config branch.
       // Deleting the branch would destroy all Gerrit specific
       // metadata about the project, including its access rules.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
index 0c8ecb8..dcf6841 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -105,7 +105,7 @@
 
       String u;
       if (isRE(template.getPattern())) {
-        u = username.replace(".", "\\.");
+        u = Pattern.quote(username);
       } else {
         u = username;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index b3ea6b4..4674cf4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.OrPredicate;
 import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.util.LabelVote;
 import com.google.inject.Provider;
 
 import java.util.List;
@@ -75,25 +76,39 @@
       ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
       IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
       String value, Set<Account.Id> accounts, AccountGroup.UUID group) {
-    String label;
-    Test test;
-    int expVal;
-    Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
-    Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
-    if (m1.find()) {
-      label = value.substring(0, m1.start());
-      test = Test.op(m1.group(1));
-      expVal = value(m1.group(2));
+    String label = null;
+    Test test = null;
+    int expVal = 0;
 
-    } else if (m2.find()) {
-      label = value.substring(0, m2.start());
+    try {
+      LabelVote v = LabelVote.parse(value);
       test = Test.EQ;
-      expVal = value(m2.group(1));
+      label = v.getLabel();
+      expVal = v.getValue();
+    } catch (IllegalArgumentException e) {
+      // Try next format.
+    }
 
-    } else {
-      label = value;
+    try {
+      LabelVote v = LabelVote.parseWithEquals(value);
       test = Test.EQ;
-      expVal = 1;
+      label = v.getLabel();
+      expVal = v.getValue();
+    } catch (IllegalArgumentException e) {
+      // Try next format.
+    }
+
+    if (label == null) {
+      Matcher m = Pattern.compile("(>=|<=)([+-]?\\d+)$").matcher(value);
+      if (m.find()) {
+        label = value.substring(0, m.start());
+        test = Test.op(m.group(1));
+        expVal = value(m.group(2));
+      } else {
+        label = value;
+        test = Test.EQ;
+        expVal = 1;
+      }
     }
 
     List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 6c4569b..99ed71f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -97,7 +98,7 @@
         initAllProjects(git);
 
         RefUpdate u = git.updateRef(Constants.HEAD);
-        u.link(GitRepositoryManager.REF_CONFIG);
+        u.link(RefNames.REFS_CONFIG);
       } catch (RepositoryNotFoundException err) {
         String name = allProjectsName.get();
         throw new IOException("Cannot create repository " + name, err);
@@ -131,7 +132,7 @@
     AccessSection all = config.getAccessSection(AccessSection.ALL, true);
     AccessSection heads = config.getAccessSection(AccessSection.HEADS, true);
     AccessSection tags = config.getAccessSection("refs/tags/*", true);
-    AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
+    AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true);
     AccessSection magic = config.getAccessSection("refs/for/" + AccessSection.ALL, true);
 
     grant(config, cap, GlobalCapability.ADMINISTRATE_SERVER, admin);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
index e5f6992..cbcda9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
@@ -186,7 +187,7 @@
         // Grant out read on the config branch by default.
         //
         if (config.getProject().getNameKey().equals(systemConfig.wildProjectName)) {
-          AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
+          AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true);
           Permission read = meta.getPermission(READ, true);
           read.getRule(config.resolve(projectOwners), true);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
index 3ba77ec..bcd5f40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.schema;
 
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
@@ -44,8 +45,8 @@
     keysOne = new HashSet<String>();
     keysTwo = new HashSet<String>();
 
-    keysOne.add(GitRepositoryManager.REF_CONFIG);
-    keysTwo.add(GitRepositoryManager.REF_CONFIG);
+    keysOne.add(RefNames.REFS_CONFIG);
+    keysTwo.add(RefNames.REFS_CONFIG);
     keysTwo.add(Constants.HEAD);
   }
 
@@ -73,10 +74,10 @@
           try {
             RefUpdate update = git.updateRef(Constants.HEAD);
             update.disableRefLog();
-            update.link(GitRepositoryManager.REF_CONFIG);
+            update.link(RefNames.REFS_CONFIG);
           } catch (IOException err) {
             ui.message("warning: " + name.get() + ": Cannot update HEAD to "
-                + GitRepositoryManager.REF_CONFIG + ": " + err.getMessage());
+                + RefNames.REFS_CONFIG + ": " + err.getMessage());
           }
         }
       } finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
new file mode 100644
index 0000000..b8d942d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
@@ -0,0 +1,109 @@
+// Copyright (C) 2013 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.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.LabelType;
+
+/** A single vote on a label, consisting of a label name and a value. */
+public class LabelVote {
+  public static LabelVote parse(String text) {
+    checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
+    if (text.charAt(0) == '-') {
+      return new LabelVote(text.substring(1), (short) 0);
+    }
+    short sign = 0;
+    int i;
+    for (i = text.length() - 1; i >= 0; i--) {
+      int c = text.charAt(i);
+      if (c == '-') {
+        sign = (short) -1;
+        break;
+      } else if (c == '+') {
+        sign = (short) 1;
+        break;
+      } else if (!('0' <= c && c <= '9')) {
+        break;
+      }
+    }
+    if (sign == 0) {
+      return new LabelVote(text, (short) 1);
+    }
+    return new LabelVote(text.substring(0, i),
+        (short)(sign * Short.parseShort(text.substring(i + 1))));
+  }
+
+  public static LabelVote parseWithEquals(String text) {
+    checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
+    int e = text.lastIndexOf('=');
+    checkArgument(e >= 0, "Label vote missing '=': %s", text);
+    return new LabelVote(text.substring(0, e),
+        Short.parseShort(text.substring(e + 1), text.length()));
+  }
+
+  private final String name;
+  private final short value;
+
+  public LabelVote(String name, short value) {
+    this.name = LabelType.checkName(name);
+    this.value = value;
+  }
+
+  public String getLabel() {
+    return name;
+  }
+
+  public short getValue() {
+    return value;
+  }
+
+  public String format() {
+    if (value == (short) 0) {
+      return '-' + name;
+    } else if (value == (short) 1) {
+      return name;
+    } else if (value < 0) {
+      return name + value;
+    } else {
+      return name + '+' + value;
+    }
+  }
+
+  public String formatWithEquals() {
+    if (value <= (short) 0) {
+      return name + '=' + value;
+    } else {
+      return name + "=+" + value;
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof LabelVote) {
+      LabelVote l = (LabelVote) o;
+      return Objects.equal(name, l.name)
+          && value == l.value;
+    }
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return format();
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index 0d5207a..1a9f74b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -208,12 +209,12 @@
     md.setMessage("Edit\n");
     cfg.commit(md);
 
-    Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
+    Ref ref = db.getRef(RefNames.REFS_CONFIG);
     return util.getRevWalk().parseCommit(ref.getObjectId());
   }
 
   private void update(RevCommit rev) throws Exception {
-    RefUpdate u = db.updateRef(GitRepositoryManager.REF_CONFIG);
+    RefUpdate u = db.updateRef(RefNames.REFS_CONFIG);
     u.disableRefLog();
     u.setNewObjectId(rev);
     switch (u.forceUpdate()) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 64ce398..594580f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -247,6 +247,18 @@
   }
 
   @Test
+  public void testUsernameEmailPatternWithRegex() {
+    grant(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
+
+    ProjectControl u = util.user(local, "d.v@ger-rit.org", DEVS);
+    ProjectControl d = util.user(local, "dev@ger-rit.org", DEVS);
+    assertFalse("u can't read",
+        u.controlForRef("refs/sb/dev@ger-rit.org/heads/foobar").isVisible());
+    assertTrue("d can read",
+        d.controlForRef("refs/sb/dev@ger-rit.org/heads/foobar").isVisible());
+  }
+
+  @Test
   public void testSortWithRegex() {
     grant(local, READ, DEVS, "^refs/heads/.*");
     grant(util.getParentConfig(), READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
new file mode 100644
index 0000000..d7ae61a
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2013 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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class LabelVoteTest {
+  @Test
+  public void parse() {
+    LabelVote l;
+    l = LabelVote.parse("Code-Review-2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -2, l.getValue());
+    l = LabelVote.parse("Code-Review-1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -1, l.getValue());
+    l = LabelVote.parse("-Code-Review");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 0, l.getValue());
+    l = LabelVote.parse("Code-Review");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parse("Code-Review+2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 2, l.getValue());
+  }
+
+  @Test
+  public void format() {
+    assertEquals("Code-Review-2", LabelVote.parse("Code-Review-2").format());
+    assertEquals("Code-Review-1", LabelVote.parse("Code-Review-1").format());
+    assertEquals("-Code-Review", LabelVote.parse("-Code-Review").format());
+    assertEquals("Code-Review", LabelVote.parse("Code-Review").format());
+    assertEquals("Code-Review+2", LabelVote.parse("Code-Review+2").format());
+  }
+
+  @Test
+  public void parseWithEquals() {
+    LabelVote l;
+    l = LabelVote.parseWithEquals("Code-Review=-2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -2, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=-1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -1, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=0");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 0, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=+1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 2, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=+2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 2, l.getValue());
+  }
+
+  @Test
+  public void formatWithEquals() {
+    assertEquals("Code-Review=-2",
+        LabelVote.parseWithEquals("Code-Review=-2").formatWithEquals());
+    assertEquals("Code-Review=-1",
+        LabelVote.parseWithEquals("Code-Review=-1").formatWithEquals());
+    assertEquals("Code-Review=0",
+        LabelVote.parseWithEquals("Code-Review=0").formatWithEquals());
+    assertEquals("Code-Review=+1",
+        LabelVote.parseWithEquals("Code-Review=+1").formatWithEquals());
+    assertEquals("Code-Review=+2",
+        LabelVote.parseWithEquals("Code-Review=+2").formatWithEquals());
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index a257a97..8837eec 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -14,9 +14,7 @@
 
 package com.google.gerrit.sshd.commands;
 
-import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelValue;
@@ -41,6 +39,7 @@
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gerrit.util.cli.CmdLineParser;
@@ -117,18 +116,8 @@
 
   @Option(name = "--label", aliases = "-l", usage = "custom label(s) to assign", metaVar = "LABEL=VALUE")
   void addLabel(final String token) {
-    List<String> parts = ImmutableList.copyOf(Splitter.on('=').split(token));
-    if (parts.size() != 2) {
-      throw new IllegalArgumentException("invalid custom label " + token);
-    }
-    short value;
-    try {
-      value = Short.parseShort(parts.get(1));
-    } catch (IllegalArgumentException e) {
-      throw new IllegalArgumentException("invalid custom label value "
-          + parts.get(1));
-    }
-    customLabels.put(parts.get(0), value);
+    LabelVote v = LabelVote.parse(token);
+    customLabels.put(v.getLabel(), v.getValue());
   }
 
   @Inject
diff --git a/lib/BUCK b/lib/BUCK
index 2469692..9902422 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -24,9 +24,9 @@
 
 maven_jar(
   name = 'gwtorm',
-  id = 'gwtorm:gwtorm:1.7',
-  bin_sha1 = 'ee3b316a023f1422dd4b6654a3d51d0e5690809c',
-  src_sha1 = 'a145bde4cc87a4ff4cec283880e2a03be32cc868',
+  id = 'gwtorm:gwtorm:1.8',
+  bin_sha1 = '0ec006f69f7b8aa48e22d4bdecea820fd53c0b4b',
+  src_sha1 = 'b0e347a3053328f029c93ac347a4761a98293073',
   license = 'Apache2.0',
   deps = [':protobuf'],
   repository = GERRIT,
@@ -34,9 +34,9 @@
 
 maven_jar(
   name = 'gwtjsonrpc',
-  id = 'gwtjsonrpc:gwtjsonrpc:1.3',
-  bin_sha1 = '1717ba11ab0c5160798c80085220a63f864691d3',
-  src_sha1 = '9e01c5d7bd54f8e70066450b372a43c16404789e',
+  id = 'gwtjsonrpc:gwtjsonrpc:1.4',
+  bin_sha1 = '7500577cbf3afc3395ce15a2f5bccc4b02b0d497',
+  src_sha1 = '589e8eeb4663ee7ba330275ccfac2d6b2c6f28db',
   license = 'Apache2.0',
   repository = GERRIT,
 )
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index 701846c..9160d60 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit 701846c29e3ebbc32199ce1f24767ccb8a875ce6
+Subproject commit 9160d607f4134cc9ee1165dea56b98a5452fe8de
diff --git a/plugins/download-commands b/plugins/download-commands
index fe2bc6b..6287d6a 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit fe2bc6be5ef964a5df247a85f82c0155dc2f8876
+Subproject commit 6287d6a8941f68ba8a3a8c27f2a979c02ede489a
diff --git a/plugins/replication b/plugins/replication
index 400f47b..330228e 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 400f47b99183a9141a192956bac6863f1c2fc14d
+Subproject commit 330228ed74629db4ed43e48d2d10925c0d7d2ff8