Merge branch 'stable-2.9'

* stable-2.9:
  Add support for JCE (Java Cryptography Extension) ciphers
  Buck: Respect gerrit-gwtui-common dependency for GWT plugins
  More improvements in the change info block UI
  Add a section about the submit type to the project owner guide
  Fix: Wrong exception mapping in ReceiveCommmits
  Fix REST example for removing included groups from a group
  rest-api-groups.txt: Correct input examples to use [] for lists
  Document project options
  Allow service users to access REST API if auth.gitBasicAuth = true
  Improve documentation about project creation
  Make singleusergroup a core plugin
  Rename 'Change Submit Action' in documentation to 'Submit Type'
  ProjectInfoScreen: Display submit type and content merge next to each other
  Add guide for project owners
  Improve description of the auth.gitBasicAuth parameter

Conflicts:
	Documentation/config-gerrit.txt
	Documentation/rest-api-groups.txt
	gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
	gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java

Change-Id: I269f8f1395c352d8d90ba4f770a19296a50967de
diff --git a/.buckconfig b/.buckconfig
index 4eb5e85..df8bfdc 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -1,10 +1,14 @@
 [alias]
+  all = //:all
   api = //:api
   api_deploy = //tools/maven:deploy
   api_install = //tools/maven:install
+  chrome = //:chrome
   docs = //Documentation:html
+  firefox = //:firefox
   gerrit = //:gerrit
   release = //:release
+  safari = //:safari
   withdocs = //:withdocs
 
 [buildfile]
diff --git a/.buckversion b/.buckversion
index ff1c137..ea13b51 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-2b80cf780ae31bee6609ebc1bbab9ce6fd004dbe
+ca48f0fff22f7fbcb856fd2faa6e15e7de900775
diff --git a/BUCK b/BUCK
index 2d07758..6aed3d7 100644
--- a/BUCK
+++ b/BUCK
@@ -3,6 +3,7 @@
 gerrit_war(name = 'gerrit')
 gerrit_war(name = 'chrome',   ui = 'ui_chrome')
 gerrit_war(name = 'firefox',  ui = 'ui_firefox')
+gerrit_war(name = 'safari',   ui = 'ui_safari')
 gerrit_war(name = 'withdocs', docs = True)
 gerrit_war(name = 'release',  docs = True, context = ['//plugins:core.zip'])
 
@@ -27,3 +28,13 @@
   deps = API_DEPS,
   out = 'api.zip',
 )
+
+genrule(
+  name = 'all',
+  cmd = 'echo done >$OUT',
+  deps = [
+    ':api',
+    ':release',
+  ],
+  out = '__fake.all__',
+)
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 0175431..b361d48 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -23,7 +23,6 @@
   EXPN = '.expn'
 
   asciidoc = [
-      'cd $SRCDIR;',
       '$(exe //lib/asciidoctor:asciidoc)',
       '-z', '$OUT',
       '--tmp', '$TMP',
@@ -49,7 +48,7 @@
     genrule(
       name = ex,
       cmd = '$(exe :replace_macros) --suffix=' + EXPN +
-            ' -s $SRCDIR/%s' % fn +
+            ' -s %s' % fn +
             ' -o $OUT',
       srcs = [src],
       deps = tx + [':replace_macros'],
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 27feee2..527e4de 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1329,6 +1329,22 @@
 If `download.scheme` is not specified, SSH, HTTP and Anonymous HTTP
 downloads are allowed.
 
+[[download.archive]]download.archive::
++
+Specifies which archive formats, if any, should be offered on the change
+screen:
++
+----
+[download]
+  archive = tar
+  archive = tbz2
+  archive = tgz
+  archive = txz
+----
+
+If `download.archive` is not specified defaults to all archive
+commands. Set to `off` or empty string to disable.
+
 [[gerrit]]
 === Section gerrit
 
@@ -1350,6 +1366,13 @@
 +
 Defaults to `All-Projects` if not set.
 
+[[gerrit.allUsers]]gerrit.allUsers::
++
+Name of the project in which meta data of all users is stored.
+The name is relative to `gerrit.basePath`.
++
+Defaults to `All-Users` if not set.
+
 [[gerrit.canonicalWebUrl]]gerrit.canonicalWebUrl::
 +
 The default URL for Gerrit to be accessed through.
@@ -2683,6 +2706,14 @@
 [[sshd]]
 === Section sshd
 
+[[sshd.backend]]sshd.backend::
++
+Starting from version 0.9.0 Apache SSHD project added support for NIO2
+IoSession. To use the new NIO2 session the `backend` option must be set
+to `NIO2`.
++
+By default, `MINA`.
+
 [[sshd.listenAddress]]sshd.listenAddress::
 +
 Specifies the local addresses the internal SSHD should listen
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index db36732..33c4e9f 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -259,6 +259,15 @@
   buck-out/gen/release.war
 ----
 
+[[all]]
+=== Combined build target
+
+To build release and api targets, a combined build target is provided:
+
+----
+  buck build all
+----
+
 [[tests]]
 == Running Unit Tests
 
@@ -469,6 +478,45 @@
   EOF
 ----
 
+== Sporadic failures of unit tests
+
+In case unit tests are failing for non obvious reasons, failed tests may need
+to be repeated:
+
+----
+FAIL  32,0s  7 Passed   1 Failed   com.google.gerrit.acceptance.rest.group.AddRemoveGroupMembersIT
+FAILURE includeRemoveGroup: verify: false
+com.jcraft.jsch.JSchException: verify: false
+----
+
+Because the test execution results are cached by Buck, they must be removed
+before retrying:
+
+----
+  $ rm -rf buck-out/bin/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/.AddRemoveGroupMembersIT/
+----
+
+After clearing the cache and repeating of the failed tests, they are successful:
+
+----
+  $ buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
+  TESTING //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
+  PASS  14,9s  8 Passed   0 Failed   com.google.gerrit.acceptance.rest.group.AddRemoveGroupMembersIT
+  TESTS PASSED
+----
+
+An alternative approach is to use a Buck feature:
+--test-selectors (-filters, -f) option:
+
+----
+  buck test --all -f 'com.google.gerrit.acceptance.rest.change.SubmitByMergeAlwaysIT'
+  TESTING SELECTED TESTS
+  PASS  14,5s  6 Passed   0 Failed   com.google.gerrit.acceptance.rest.change.SubmitByMergeAlwaysIT
+  TESTS PASSED
+----
+
+When this option is used, cache is disabled per design and doesn't need to be deleted.
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 575f5fa..a24d411 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -36,7 +36,7 @@
 ----
 mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
     -DarchetypeArtifactId=gerrit-plugin-archetype \
-    -DarchetypeVersion=2.9-SNAPSHOT \
+    -DarchetypeVersion=2.10-SNAPSHOT \
     -DgroupId=com.googlesource.gerrit.plugins.testplugin \
     -DartifactId=testplugin
 ----
@@ -1706,6 +1706,39 @@
 The download schemes and download commands which are used most often
 are provided by the Gerrit core plugin `download-commands`.
 
+[[links-to-external-tools]]
+== Links To External Tools
+
+Gerrit has extension points that enables development of a
+light-weight plugin that links commits to external
+tools (GitBlit, CGit, company specific resources etc).
+
+PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
+
+[source, java]
+----
+import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.webui.PatchSetWebLink;;
+
+@Listen
+public class MyWeblinkPlugin implements PatchSetWebLink {
+
+  private String name = "MyLink";
+  private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
+
+  @Override
+  public String getLinkName() {
+    return name ;
+  }
+
+  @Override
+  public String getPatchSetUrl(String project, String commit) {
+    return String.format(placeHolderUrlProjectCommit, project, commit);
+  }
+
+}
+----
+
 [[documentation]]
 == Documentation
 
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 2b0cda8..83d8f1a 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -52,13 +52,15 @@
 
 == Mac OS X
 
-On Mac OS X ensure "Java For Mac OS X 10.5 Upate 4" (or later) has
-been installed, and that `JAVA_HOME` is set to
-"/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home".
-Check the installed version by running `java -version` and looking
-for 'build 1.6.0_13-b03-211'.  Versions of Java 6 prior to this
-version crash during the build due to a bug in the JIT compiler.
+On Mac OS X ensure "Java For Mac OS X 10.5 Update 4" (or later) has
+been installed, and that `JAVA_HOME` is set to the
+link:install.html#Requirements[required Java version].
 
+Java installations can typically be found in
+"/System/Library/Frameworks/JavaVM.framework/Versions".
+
+You can check the installed Java version by running `java -version` in
+the terminal.
 
 [[init]]
 == Site Initialization
diff --git a/Documentation/gen_licenses.py b/Documentation/gen_licenses.py
index fb03526..d67b2a5 100755
--- a/Documentation/gen_licenses.py
+++ b/Documentation/gen_licenses.py
@@ -18,6 +18,7 @@
 from __future__ import print_function
 
 from collections import defaultdict, deque
+from os import chdir, path
 import re
 from shutil import copyfileobj
 from subprocess import Popen, PIPE
@@ -27,6 +28,8 @@
 
 def parse_graph():
   graph = defaultdict(list)
+  while not path.isfile('.buckconfig'):
+    chdir('..')
   p = Popen(
     ['buck', 'audit', 'classpath', '--dot'] + MAIN,
     stdout = PIPE)
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 396edf6..883198a 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -168,8 +168,9 @@
 self.onAction(type, view_name, callback);
 ----
 
-* type: `'change'`, `'revision'` or `'project'`, indicating which type
-  of resource the `UiAction` was bound to in the server.
+* type: `'change'`, `'revision'`, `'project'`, or `'branch'`
+  indicating which type of resource the `UiAction` was bound to
+  in the server.
 
 * view_name: string appearing in URLs to name the view. This is the
   second argument of the `get()`, `post()`, `put()`, and `delete()`
@@ -837,8 +838,8 @@
 Gerrit.onAction(type, view_name, callback);
 ----
 
-* type: `'change'` or `'revision'`, indicating what sort of resource
-  the `UiAction` was bound to in the server.
+* type: `'change'`, `'revision'`, `'project'` or `'branch'` indicating
+  what sort of resource the `UiAction` was bound to in the server.
 
 * view_name: string appearing in URLs to name the view. This is the
   second argument of the `get()`, `post()`, `put()`, and `delete()`
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index c1bb66d..8f6b8a4 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -242,6 +242,11 @@
   authenticated and has commented on the current revision.
 --
 
+[[patch-set-links]]
+--
+* `PATCHSET_LINKS`: include the `web_links` field.
+--
+
 .Request
 ----
   GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
@@ -3243,6 +3248,9 @@
 Actions the caller might be able to perform on this revision. The
 information is a map of view name to link:#action-info[ActionInfo]
 entities.
+|'web_links'   |optional|
+Links to the patch set in external sites as a list of
+link:#web-link-info[WebLinkInfo] entities.
 |===========================
 
 [[rule-input]]
@@ -3372,6 +3380,17 @@
 Each tag is listed without the 'refs/tags/' prefix.
 |==========================
 
+[[web-link-info]]
+=== WebLinkInfo
+The `WebLinkInfo` entity describes a link to an external site.
+
+[options="header",width="50%",cols="1,6"]
+|======================
+|Field Name|Description
+|`name`    |The link name.
+|`url`     |The link URL.
+|======================
+
 
 GERRIT
 ------
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index b995bc8..2c3d25c 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -43,11 +43,7 @@
 Operators act as restrictions on the search.  As more operators
 are added to the same query string, they further restrict the
 returned results. Search can also be performed by typing only a
-text with no operator. It will try to match a project name by
-substring.
-
-E.g. Searching for just "gerrit is:starred" will try to match a
-project name by "gerrit" as substring.
+text with no operator, which will match against a variety of fields.
 
 [[age]]
 age:'AGE'::
@@ -127,6 +123,11 @@
 link:http://www.brics.dk/automaton/[dk.brics.automaton
 library] is used for evaluation of such patterns.
 
+[[projects]]
+projects:'PREFIX'::
++
+Changes occurring in projects starting with 'PREFIX'.
+
 [[parentproject]]
 parentproject:'PROJECT'::
 +
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 17ca968..b892c4a 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -20,7 +20,8 @@
 
 When link:config-gerrit.html#auth.gitBasicAuth[gitBasicAuth] is enabled,
 the user is authenticated using standard BasicAuth and credentials validated
-using the same authentication method configured for the Gerrit Web UI.
+using the randomly generated HTTP password on the `HTTP Password` tab
+in the user settings page or against LDAP when configured for the Gerrit Web UI.
 
 When gitBasicAuth is not configured, the user's HTTP credentials can be
 accessed within Gerrit by going to `Settings`, and then accessing the `HTTP
diff --git a/VERSION b/VERSION
index f0594c4..5b9bc32 100644
--- a/VERSION
+++ b/VERSION
@@ -2,5 +2,5 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = '2.9-SNAPSHOT'
+GERRIT_VERSION = '2.10-SNAPSHOT'
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 54afd0b..9b24922 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -14,9 +14,13 @@
 
 package com.google.gerrit.acceptance.api.project;
 
+import static org.junit.Assert.assertEquals;
+
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -28,6 +32,38 @@
 public class ProjectIT extends AbstractDaemonTest  {
 
   @Test
+  public void createProjectFoo() throws RestApiException {
+    String name = "foo";
+    assertEquals(name,
+        gApi.projects()
+            .name(name)
+            .create()
+            .get()
+            .name);
+  }
+
+  @Test(expected = RestApiException.class)
+  public void createProjectFooBar() throws RestApiException {
+    ProjectInput in = new ProjectInput();
+    in.name = "foo";
+    gApi.projects()
+        .name("bar")
+        .create(in);
+  }
+
+  @Test(expected = ResourceConflictException.class)
+  public void createProjectDuplicate() throws RestApiException {
+    ProjectInput in = new ProjectInput();
+    in.name = "baz";
+    gApi.projects()
+        .name("baz")
+        .create(in);
+    gApi.projects()
+        .name("baz")
+        .create(in);
+  }
+
+  @Test
   public void createBranch() throws GitAPIException,
       IOException, RestApiException {
     gApi.projects()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 50522cd..78b9a54 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.acceptance.api.revision;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
@@ -21,6 +24,7 @@
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -140,6 +144,42 @@
         .submit();
   }
 
+  @Test
+  public void canRebase()
+      throws GitAPIException, IOException, RestApiException, Exception {
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
+    PushOneCommit.Result r1 = push.to(git, "refs/for/master");
+    merge(r1);
+
+    push = pushFactory.create(db, admin.getIdent());
+    PushOneCommit.Result r2 = push.to(git, "refs/for/master");
+    assertFalse(gApi.changes()
+        .id(r2.getChangeId())
+        .revision(r2.getCommit().name())
+        .canRebase());
+    merge(r2);
+
+    git.checkout().setName(r1.getCommit().name()).call();
+    push = pushFactory.create(db, admin.getIdent());
+    PushOneCommit.Result r3 = push.to(git, "refs/for/master");
+
+    assertTrue(gApi.changes()
+        .id(r3.getChangeId())
+        .revision(r3.getCommit().name())
+        .canRebase());
+  }
+
+  protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
+    return gApi.changes()
+        .id(r.getChangeId())
+        .current();
+  }
+
+  private void merge(PushOneCommit.Result r) throws Exception {
+    revision(r).review(ReviewInput.approve());
+    revision(r).submit();
+  }
+
   private PushOneCommit.Result updateChange(PushOneCommit.Result r,
       String content) throws GitAPIException, IOException {
     PushOneCommit push = pushFactory.create(db, admin.getIdent(),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
new file mode 100644
index 0000000..217af2f
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
@@ -0,0 +1,9 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  deps = [
+    '//gerrit-acceptance-tests:lib',
+  ],
+  source_under_test = ['//gerrit-pgm:pgm'],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
new file mode 100644
index 0000000..5778b19
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2014 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.pgm;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.TempFileUtil;
+import com.google.gerrit.launcher.GerritLauncher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+public class ReindexIT {
+  private File sitePath;
+
+  @Before
+  public void createTempDirectory() throws Exception {
+    sitePath = TempFileUtil.createTempDirectory();
+  }
+
+  @After
+  public void destroySite() throws Exception {
+    if (sitePath != null) {
+      TempFileUtil.recursivelyDelete(sitePath);
+    }
+  }
+
+  @Test
+  public void reindexEmptySite() throws Exception {
+    initSite();
+    runGerrit("reindex", "-d", sitePath.getPath(),
+        "--show-stack-trace");
+  }
+
+  @Test
+  public void reindexEmptySiteWithRecheckMergeable() throws Exception {
+    initSite();
+    runGerrit("reindex", "-d", sitePath.getPath(),
+        "--show-stack-trace",
+        "--recheck-mergeable");
+  }
+
+  private void initSite() throws Exception {
+    runGerrit("init", "-d", sitePath.getPath(),
+        "--batch", "--no-auto-start", "--skip-plugins", "--show-stack-trace");
+  }
+
+  private static void runGerrit(String... args) throws Exception {
+    assertEquals(0, GerritLauncher.mainImpl(args));
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 13901c3..ab8a5fc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -31,12 +31,12 @@
 import com.google.gerrit.acceptance.SshSession;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.change.ChangeJson.LabelInfo;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index d137e62..12d002e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -19,8 +19,8 @@
 import static org.junit.Assert.assertSame;
 
 import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.extensions.common.SubmitType;
 
 import com.jcraft.jsch.JSchException;
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index d6fbca4..2ef4ecc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.common.SubmitType;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.revwalk.RevCommit;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
index 2a01d84..9512c7a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gwtorm.server.OrmException;
 
 import com.jcraft.jsch.JSchException;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index 486c529..fde462b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -4,7 +4,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gwtorm.server.OrmException;
 
 import com.jcraft.jsch.JSchException;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index 8968084..7b4d23d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.common.SubmitType;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.revwalk.RevCommit;
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 b593840..93d31c7 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
@@ -24,17 +24,20 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.common.SubmitType;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.CreateProject;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -65,6 +68,21 @@
   @Inject
   private GitRepositoryManager git;
 
+  @Inject
+  private GerritApi gApi;
+
+  @Test
+  public void testCreateProjectApi() throws RestApiException, IOException {
+    final String newProjectName = "newProject";
+    ProjectApi projectApi = gApi.projects().name(newProjectName).create();
+    ProjectInfo p = projectApi.get();
+    assertEquals(newProjectName, p.name);
+    ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+    assertNotNull(projectState);
+    assertProjectInfo(projectState.getProject(), p);
+    assertHead(newProjectName, "refs/heads/master");
+  }
+
   @Test
   public void testCreateProject() throws IOException {
     final String newProjectName = "newProject";
@@ -80,7 +98,7 @@
 
   @Test
   public void testCreateProjectWithNameMismatch_BadRequest() throws IOException {
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.name = "otherName";
     RestResponse r = adminSession.put("/projects/someName", in);
     assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
@@ -89,7 +107,7 @@
   @Test
   public void testCreateProjectWithProperties() throws IOException {
     final String newProjectName = "newProject";
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.description = "Test description";
     in.submitType = SubmitType.CHERRY_PICK;
     in.useContributorAgreements = InheritableBoolean.TRUE;
@@ -115,7 +133,7 @@
     RestResponse r = adminSession.put("/projects/" + parentName);
     r.consume();
     final String childName = "child";
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.parent = parentName;
     r = adminSession.put("/projects/" + childName, in);
     Project project = projectCache.get(new Project.NameKey(childName)).getProject();
@@ -124,7 +142,7 @@
 
   public void testCreateChildProjectUnderNonExistingParent_UnprocessableEntity()
       throws IOException {
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.parent = "non-existing-project";
     RestResponse r = adminSession.put("/projects/child", in);
     assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
@@ -133,7 +151,7 @@
   @Test
   public void testCreateProjectWithOwner() throws IOException {
     final String newProjectName = "newProject";
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.owners = Lists.newArrayListWithCapacity(3);
     in.owners.add("Anonymous Users"); // by name
     in.owners.add(SystemGroupBackend.REGISTERED_USERS.get()); // by UUID
@@ -150,7 +168,7 @@
 
   public void testCreateProjectWithNonExistingOwner_UnprocessableEntity()
       throws IOException {
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.owners = Collections.singletonList("non-existing-group");
     RestResponse r = adminSession.put("/projects/newProject", in);
     assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
@@ -159,7 +177,7 @@
   @Test
   public void testCreatePermissionOnlyProject() throws IOException {
     final String newProjectName = "newProject";
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.permissionsOnly = true;
     adminSession.put("/projects/" + newProjectName, in);
     assertHead(newProjectName, RefNames.REFS_CONFIG);
@@ -168,7 +186,7 @@
   @Test
   public void testCreateProjectWithEmptyCommit() throws IOException {
     final String newProjectName = "newProject";
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.createEmptyCommit = true;
     adminSession.put("/projects/" + newProjectName, in);
     assertEmptyCommit(newProjectName, "refs/heads/master");
@@ -177,7 +195,7 @@
   @Test
   public void testCreateProjectWithBranches() throws IOException {
     final String newProjectName = "newProject";
-    CreateProject.Input in = new CreateProject.Input();
+    ProjectInput in = new ProjectInput();
     in.createEmptyCommit = true;
     in.branches = Lists.newArrayListWithCapacity(3);
     in.branches.add("refs/heads/test");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
index b037ff9..10d59d2d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -21,10 +21,10 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
index c61764b..466a239 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -21,9 +21,9 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
 
@@ -53,7 +53,7 @@
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     List<ProjectInfo> projectInfoList = toProjectInfoList(r);
     // Project 'p' was already created in the base class
-    assertTrue(projectInfoList.size() == 1);
+    assertTrue(projectInfoList.size() == 2);
   }
 
   @Test
@@ -67,7 +67,11 @@
 
     RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    assertProjects(Arrays.asList(existingProject, child1, child2), toProjectInfoList(r));
+    assertProjects(
+        Arrays.asList(
+            new Project.NameKey("All-Users"),
+            existingProject, child1, child2),
+        toProjectInfoList(r));
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
index 788a54b..3128004 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
@@ -21,10 +21,10 @@
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectState;
 
 import java.util.List;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
index a2dd8ec..f3db988 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
@@ -29,14 +29,23 @@
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.testutil.ConfigSuite;
 import com.google.inject.Inject;
 
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
 import org.junit.Before;
 import org.junit.Test;
 
 @NoHttpd
 public class LabelTypeIT extends AbstractDaemonTest {
+  @ConfigSuite.Config
+  public static Config noteDbEnabled() {
+    Config cfg = new Config();
+    cfg.setBoolean("notedb", null, "write", true);
+    cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
+    return cfg;
+  }
 
   @Inject
   private GitRepositoryManager repoManager;
@@ -78,7 +87,7 @@
     saveLabelConfig();
     PushOneCommit.Result r = createChange();
     revision(r).review(ReviewInput.reject());
-    assertApproval(r, -2);
+    //assertApproval(r, -2);
     r = amendChange(r.getChangeId());
     assertApproval(r, -2);
   }
diff --git a/gerrit-acceptance-tests/tests.defs b/gerrit-acceptance-tests/tests.defs
index 3c8b4d5..6c5a087 100644
--- a/gerrit-acceptance-tests/tests.defs
+++ b/gerrit-acceptance-tests/tests.defs
@@ -1,6 +1,7 @@
 def acceptance_tests(
     srcs,
     deps = [],
+    source_under_test = [],
     vm_args = ['-Xmx256m']):
   for j in srcs:
     java_test(
@@ -11,7 +12,7 @@
         '//gerrit-httpd:httpd',
         '//gerrit-sshd:sshd',
         '//gerrit-server:server',
-      ],
+      ] + source_under_test,
       labels = [
         'acceptance',
         'slow',
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 85a051b..cb209e2 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -14,20 +14,22 @@
 
 package com.google.gerrit.server.cache.h2;
 
-import com.google.common.base.Preconditions;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.server.cache.CacheBinding;
 import com.google.gerrit.server.cache.PersistentCacheFactory;
 import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
 import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugins.Plugin;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 
@@ -37,6 +39,7 @@
 
 import java.io.File;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -48,17 +51,18 @@
 
   private final DefaultCacheFactory defaultFactory;
   private final Config config;
-   private final File cacheDir;
+  private final File cacheDir;
   private final List<H2CacheImpl<?, ?>> caches;
+  private final DynamicMap<Cache<?, ?>> cacheMap;
   private final ExecutorService executor;
   private final ScheduledExecutorService cleanup;
-  private volatile boolean started;
 
   @Inject
   H2CacheFactory(
       DefaultCacheFactory defaultCacheFactory,
       @GerritServerConfig Config cfg,
-      SitePaths site) {
+      SitePaths site,
+      DynamicMap<Cache<?, ?>> cacheMap) {
     defaultFactory = defaultCacheFactory;
     config = cfg;
 
@@ -79,6 +83,7 @@
     }
 
     caches = Lists.newLinkedList();
+    this.cacheMap = cacheMap;
 
     if (cacheDir != null) {
       executor = Executors.newFixedThreadPool(
@@ -100,7 +105,6 @@
 
   @Override
   public void start() {
-    started = true;
     if (executor != null) {
       for (final H2CacheImpl<?, ?> cache : caches) {
         executor.execute(new Runnable() {
@@ -141,15 +145,16 @@
         log.warn("Interrupted waiting for disk cache to shutdown");
       }
     }
-    for (H2CacheImpl<?, ?> cache : caches) {
-      cache.stop();
+    synchronized (caches) {
+      for (H2CacheImpl<?, ?> cache : caches) {
+        cache.stop();
+      }
     }
   }
 
   @SuppressWarnings({"unchecked", "cast"})
   @Override
   public <K, V> Cache<K, V> build(CacheBinding<K, V> def) {
-    Preconditions.checkState(!started, "cache must be built before start");
     long limit = config.getLong("cache", def.name(), "diskLimit", 128 << 20);
 
     if (cacheDir == null || limit <= 0) {
@@ -160,7 +165,9 @@
     H2CacheImpl<K, V> cache = new H2CacheImpl<K, V>(
         executor, store, def.keyType(),
         (Cache<K, ValueHolder<V>>) defaultFactory.create(def, true).build());
-    caches.add(cache);
+    synchronized (caches) {
+      caches.add(cache);
+    }
     return cache;
   }
 
@@ -169,7 +176,6 @@
   public <K, V> LoadingCache<K, V> build(
       CacheBinding<K, V> def,
       CacheLoader<K, V> loader) {
-    Preconditions.checkState(!started, "cache must be built before start");
     long limit = config.getLong("cache", def.name(), "diskLimit", 128 << 20);
 
     if (cacheDir == null || limit <= 0) {
@@ -187,6 +193,19 @@
     return cache;
   }
 
+  @Override
+  public void onStop(Plugin plugin) {
+    synchronized (caches) {
+      for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
+          cacheMap.byPlugin(plugin.getName()).entrySet()) {
+        Cache<?, ?> cache = entry.getValue().get();
+        if (caches.remove(cache)) {
+          ((H2CacheImpl<?, ?>) cache).stop();
+        }
+      }
+    }
+  }
+
   private <V, K> SqlStore<K, V> newSqlStore(
       String name,
       TypeLiteral<K> keyType,
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
index 9ed1624..e2dcb27 100644
--- a/gerrit-common/BUCK
+++ b/gerrit-common/BUCK
@@ -6,6 +6,11 @@
   SRC + 'common/auth/SignInRequired.java',
 ]
 
+EXCLUDES = [
+  SRC + 'common/PluginData.java',
+  SRC + 'common/FileUtil.java',
+]
+
 java_library(
   name = 'annotations',
   srcs = ANNOTATIONS,
@@ -14,7 +19,7 @@
 
 gwt_module(
   name = 'client',
-  srcs = glob([SRC + 'common/**/*.java']),
+  srcs = glob([SRC + 'common/**/*.java'], excludes = EXCLUDES),
   gwtxml = SRC + 'Common.gwt.xml',
   deps = [
     '//gerrit-patch-jgit:client',
@@ -35,11 +40,13 @@
   srcs = glob([SRC + 'common/**/*.java'], excludes = ANNOTATIONS),
   deps = [
     ':annotations',
+    '//gerrit-extension-api:api',
     '//gerrit-patch-jgit:server',
     '//gerrit-prettify:server',
     '//gerrit-reviewdb:server',
     '//lib:gwtjsonrpc',
     '//lib:gwtorm',
+    '//lib:guava',
     '//lib/jgit:jgit',
   ],
   visibility = ['PUBLIC'],
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-common/src/main/java/com/google/gerrit/common/Die.java
similarity index 95%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
rename to gerrit-common/src/main/java/com/google/gerrit/common/Die.java
index 96e2ec8..6a1f304 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/Die.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.common;
 
 public class Die extends RuntimeException {
   private static final long serialVersionUID = 1L;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java
new file mode 100644
index 0000000..bed10d6
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.IO;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class FileUtil {
+  public static boolean modified(FileBasedConfig cfg) throws IOException {
+    byte[] curVers;
+    try {
+      curVers = IO.readFully(cfg.getFile());
+    } catch (FileNotFoundException notFound) {
+      return true;
+    }
+
+    byte[] newVers = Constants.encode(cfg.toText());
+    return !Arrays.equals(curVers, newVers);
+  }
+
+  public static void mkdir(final File path) {
+    if (!path.isDirectory() && !path.mkdir()) {
+      throw new Die("Cannot make directory " + path);
+    }
+  }
+
+  public static void chmod(final int mode, final File path) {
+    path.setReadable(false, false /* all */);
+    path.setWritable(false, false /* all */);
+    path.setExecutable(false, false /* all */);
+
+    path.setReadable((mode & 0400) == 0400, true /* owner only */);
+    path.setWritable((mode & 0200) == 0200, true /* owner only */);
+    if (path.isDirectory() || (mode & 0100) == 0100) {
+      path.setExecutable(true, true /* owner only */);
+    }
+
+    if ((mode & 0044) == 0044) {
+      path.setReadable(true, false /* all */);
+    }
+    if ((mode & 0011) == 0011) {
+      path.setExecutable(true, false /* all */);
+    }
+  }
+
+  private FileUtil() {
+  }
+}
\ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
new file mode 100644
index 0000000..fc5bb56
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
@@ -0,0 +1,46 @@
+// 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.common;
+
+package com.google.gerrit.common;
+
+import com.google.common.base.Objects;
+
+import java.io.File;
+
+public class PluginData {
+  public final String name;
+  public final String version;
+  public final File pluginFile;
+
+  public PluginData(String name, String version, File pluginFile) {
+    this.name = name;
+    this.version = version;
+    this.pluginFile = pluginFile;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof PluginData) {
+      PluginData o = (PluginData) obj;
+      return Objects.equal(name, o.name) && Objects.equal(version, o.version)
+          && Objects.equal(pluginFile, o.pluginFile);
+    }
+    return super.equals(obj);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(name, version, pluginFile);
+  }
+}
\ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
index 3342bc2..e067f06 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.List;
 import java.util.Set;
@@ -41,7 +41,7 @@
   protected List<PatchSet> patchSets;
   protected Set<PatchSet.Id> patchSetsWithDraftComments;
   protected List<SubmitRecord> submitRecords;
-  protected Project.SubmitType submitType;
+  protected SubmitType submitType;
   protected SubmitTypeRecord submitTypeRecord;
   protected boolean canSubmit;
   protected List<ChangeMessage> messages;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index 38afaab..66309ea 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.Project;
 
+import java.util.List;
 import java.util.Set;
 
 public class GerritConfig implements Cloneable {
@@ -33,7 +34,7 @@
   protected String httpPasswordUrl;
   protected String reportBugUrl;
   protected String reportBugText;
-  protected boolean gitBasicAuth;
+  protected boolean httpPasswordSettingsEnabled = true;
 
   protected GitwebConfig gitweb;
   protected boolean useContributorAgreements;
@@ -53,6 +54,7 @@
   protected int suggestFrom;
   protected int changeUpdateDelay;
   protected AccountGeneralPreferences.ChangeScreen changeScreen;
+  protected List<String> archiveFormats;
   protected int largeChangeSize;
   protected boolean newFeatures;
 
@@ -112,12 +114,12 @@
     reportBugText = t;
   }
 
-  public boolean isGitBasicAuth() {
-    return gitBasicAuth;
+  public boolean isHttpPasswordSettingsEnabled() {
+    return httpPasswordSettingsEnabled;
   }
 
-  public void setGitBasicAuth(boolean gba) {
-    gitBasicAuth = gba;
+  public void setHttpPasswordSettingsEnabled(boolean httpPasswordSettingsEnabled) {
+    this.httpPasswordSettingsEnabled = httpPasswordSettingsEnabled;
   }
 
   public String getEditFullNameUrl() {
@@ -291,6 +293,14 @@
     this.largeChangeSize = largeChangeSize;
   }
 
+  public List<String> getArchiveFormats() {
+    return archiveFormats;
+  }
+
+  public void setArchiveFormats(List<String> formats) {
+    archiveFormats = formats;
+  }
+
   public boolean getNewFeatures() {
     return newFeatures;
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
index 4eea798..7b19f4c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitTypeRecord.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.common.data;
 
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.extensions.common.SubmitType;
 
 /**
  * Describes the submit type for a change.
@@ -31,7 +31,7 @@
     RULE_ERROR
   }
 
-  public static SubmitTypeRecord OK(Project.SubmitType type) {
+  public static SubmitTypeRecord OK(SubmitType type) {
     SubmitTypeRecord r = new SubmitTypeRecord();
     r.status = Status.OK;
     r.type = type;
@@ -39,7 +39,7 @@
   }
 
   public Status status;
-  public Project.SubmitType type;
+  public SubmitType type;
   public String errorMessage;
 
   public String toString() {
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 0302afd..6ce24b3 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -4,8 +4,11 @@
 gwt_module(
   name = 'client',
   srcs = glob([
+    SRC + 'api/projects/ProjectState.java',
+    SRC + 'common/InheritableBoolean.java',
+    SRC + 'common/ListChangesOption.java',
+    SRC + 'common/SubmitType.java',
     SRC + 'webui/GerritTopMenu.java',
-    SRC + 'common/ListChangesOption.java'
   ]),
   gwtxml = SRC + 'Extensions.gwt.xml',
   visibility = ['PUBLIC'],
@@ -45,7 +48,7 @@
   name = 'extension-api-javadoc',
   title = 'Gerrit Review Extension API Documentation',
   pkg = 'com.google.gerrit.extensions',
-  paths = ['$SRCDIR/src/main/java'],
+  paths = ['src/main/java'],
   srcs = SRCS,
   deps = [
     '//lib/guice:javax-inject',
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
index df53ef1..757e046 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
@@ -14,6 +14,7 @@
  limitations under the License.
 -->
 <module>
-  <source path='webui' />
+  <source path='api' />
   <source path='common' />
+  <source path='webui' />
 </module>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RootRelative.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RootRelative.java
new file mode 100644
index 0000000..3875b77
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RootRelative.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.annotations;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation applied to HttpServletRequest and HttpServletResponse
+ * when they are inherited from Gerrit instead of being injected by
+ * a plugin's ServletModule.  This means that the path returned by
+ * {@link javax.servlet.http.HttpServletRequest#getPathInfo} is
+ * relative to the Gerrit root instead of a path within the plugin's
+ * URL space.
+ */
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface RootRelative {
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 4f90be2..dc2a9a7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -26,4 +26,5 @@
   void publish() throws RestApiException;
   ChangeApi cherryPick(CherryPickInput in) throws RestApiException;
   ChangeApi rebase() throws RestApiException;
+  boolean canRebase();
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 1c7209d..aee5405 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -14,6 +14,12 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
 public interface ProjectApi {
+  ProjectApi create() throws RestApiException;
+  ProjectApi create(ProjectInput in) throws RestApiException;
+  ProjectInfo get();
   BranchApi branch(String ref);
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
new file mode 100644
index 0000000..74b2be87
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
+
+import java.util.List;
+import java.util.Map;
+
+public class ProjectInput {
+  public String name;
+  public String parent;
+  public String description;
+  public boolean permissionsOnly;
+  public boolean createEmptyCommit;
+  public SubmitType submitType;
+  public List<String> branches;
+  public List<String> owners;
+  public InheritableBoolean useContributorAgreements;
+  public InheritableBoolean useSignedOffBy;
+  public InheritableBoolean useContentMerge;
+  public InheritableBoolean requireChangeId;
+  public String maxObjectSizeLimit;
+  public Map<String, Map<String, ConfigValue>> pluginConfigValues;
+
+  public static class ConfigValue {
+    public String value;
+    public List<String> values;
+  }
+}
\ No newline at end of file
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectState.java
similarity index 62%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectState.java
index 96e2ec8..407b7c7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectState.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,16 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.extensions.api.projects;
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  public Die(final String why) {
-    super(why);
-  }
-
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
-  }
-}
+public enum ProjectState {
+  ACTIVE,
+  READ_ONLY,
+  HIDDEN
+}
\ No newline at end of file
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/InheritableBoolean.java
similarity index 62%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/InheritableBoolean.java
index 96e2ec8..676c4d3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/InheritableBoolean.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,16 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.extensions.common;
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  public Die(final String why) {
-    super(why);
-  }
-
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
-  }
-}
+public enum InheritableBoolean {
+  TRUE,
+  FALSE,
+  INHERIT
+}
\ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
index c23e312..f9f8b62 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
@@ -49,7 +49,10 @@
   DRAFT_COMMENTS(12),
 
   /** Include download commands for the caller. */
-  DOWNLOAD_COMMANDS(13);
+  DOWNLOAD_COMMANDS(13),
+
+  /** Include patch set weblinks. */
+  WEB_LINKS(14);
 
   private final int value;
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
new file mode 100644
index 0000000..811a05f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import com.google.gerrit.extensions.api.projects.ProjectState;
+
+import java.util.Map;
+
+public class ProjectInfo {
+  public String id;
+  public String name;
+  public String parent;
+  public String description;
+  public ProjectState state;
+  public Map<String, String> branches;
+}
\ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
index ea5b068..a89911c 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+import java.util.List;
 import java.util.Map;
 
 public class RevisionInfo {
@@ -25,4 +26,15 @@
   public CommitInfo commit;
   public Map<String, FileInfo> files;
   public Map<String, ActionInfo> actions;
+  public List<WebLinkInfo> webLinks;
+
+  public static class WebLinkInfo {
+    public String name;
+    public String url;
+
+    public WebLinkInfo(String name, String url) {
+      this.name = name;
+      this.url = url;
+    }
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SubmitType.java
similarity index 62%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SubmitType.java
index 96e2ec8..95a9693 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/SubmitType.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,16 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.extensions.common;
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  public Die(final String why) {
-    super(why);
-  }
-
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
-  }
-}
+public enum SubmitType {
+  FAST_FORWARD_ONLY,
+  MERGE_IF_NECESSARY,
+  REBASE_IF_NECESSARY,
+  MERGE_ALWAYS,
+  CHERRY_PICK
+}
\ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java
index aa1dc76..91bf7a6 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java
@@ -149,18 +149,21 @@
    */
   public RegistrationHandle set(Provider<T> impl, String pluginName) {
     final NamedProvider<T> item = new NamedProvider<T>(impl, pluginName);
-    while (!ref.compareAndSet(null, item)) {
-      NamedProvider<T> old = ref.get();
-      if (old != null) {
+    NamedProvider<T> old = null;
+    while (!ref.compareAndSet(old, item)) {
+      old = ref.get();
+      if (old != null && !"gerrit".equals(old.pluginName)) {
         throw new ProvisionException(String.format(
             "%s already provided by %s, ignoring plugin %s",
             key.getTypeLiteral(), old.pluginName, pluginName));
       }
     }
+
+    final NamedProvider<T> defaultItem = old;
     return new RegistrationHandle() {
       @Override
       public void remove() {
-        ref.compareAndSet(item, null);
+        ref.compareAndSet(item, defaultItem);
       }
     };
   }
@@ -178,24 +181,27 @@
   public ReloadableRegistrationHandle<T> set(Key<T> key, Provider<T> impl,
       String pluginName) {
     final NamedProvider<T> item = new NamedProvider<T>(impl, pluginName);
-    while (!ref.compareAndSet(null, item)) {
-      NamedProvider<T> old = ref.get();
-      if (old != null) {
+    NamedProvider<T> old = null;
+    while (!ref.compareAndSet(old, item)) {
+      old = ref.get();
+      if (old != null && !"gerrit".equals(old.pluginName)) {
         throw new ProvisionException(String.format(
             "%s already provided by %s, ignoring plugin %s",
             this.key.getTypeLiteral(), old.pluginName, pluginName));
       }
     }
-    return new ReloadableHandle(key, item);
+    return new ReloadableHandle(key, item, old);
   }
 
   private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
     private final Key<T> key;
     private final NamedProvider<T> item;
+    private final NamedProvider<T> defaultItem;
 
-    ReloadableHandle(Key<T> key, NamedProvider<T> item) {
+    ReloadableHandle(Key<T> key, NamedProvider<T> item, NamedProvider<T> defaultItem) {
       this.key = key;
       this.item = item;
+      this.defaultItem = defaultItem;
     }
 
     @Override
@@ -205,14 +211,14 @@
 
     @Override
     public void remove() {
-      ref.compareAndSet(item, null);
+      ref.compareAndSet(item, defaultItem);
     }
 
     @Override
     public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
       NamedProvider<T> n = new NamedProvider<T>(newItem, item.pluginName);
       if (ref.compareAndSet(item, n)) {
-        return new ReloadableHandle(newKey, n);
+        return new ReloadableHandle(newKey, n, defaultItem);
       }
       return null;
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
index ea4a751..2d6098c 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -206,4 +206,8 @@
       return false;
     }
   }
+
+  public static <T> DynamicMap<T> emptyMap() {
+    return new DynamicMap<T>() {};
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
index 8bc57ab..3742f47 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicTypes.java
@@ -95,9 +95,7 @@
         DynamicItem<Object> item = (DynamicItem<Object>) e.getValue();
 
         for (Binding<Object> b : bindings(src, type)) {
-          if (b.getKey().getAnnotation() != null) {
-            handles.add(item.set(b.getKey(), b.getProvider(), pluginName));
-          }
+          handles.add(item.set(b.getKey(), b.getProvider(), pluginName));
         }
       }
     } catch (RuntimeException e) {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
new file mode 100644
index 0000000..b6086f2
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.extensions.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public interface PatchSetWebLink extends WebLink {
+
+  /**
+   * URL to patch set in external service.
+   *
+   * @param projectName Name of the project
+   * @param commit Commit of the patch set
+   * @return url to patch set in external service.
+   */
+  String getPatchSetUrl(final String projectName, final String commit);
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiResult.java
new file mode 100644
index 0000000..106db04
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiResult.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.webui;
+
+import java.net.URI;
+
+/** Default result for {@link UiAction}s with no JavaScript. */
+public class UiResult {
+  /** Display an alert message to the user. */
+  public static UiResult alert(String message) {
+    UiResult r = new UiResult();
+    r.alert = message;
+    return r;
+  }
+
+  /** Launch URL in a new window. */
+  public static UiResult openUrl(URI uri) {
+    return openUrl(uri.toString());
+  }
+
+  /** Launch URL in a new window. */
+  public static UiResult openUrl(String url) {
+    UiResult r = new UiResult();
+    r.url = url;
+    r.openWindow = true;
+    return r;
+  }
+
+  /** Redirect the browser to a new URL. */
+  public static UiResult redirectUrl(String url) {
+    UiResult r = new UiResult();
+    r.url = url;
+    return r;
+  }
+
+  /** Alert the user with a message. */
+  protected String alert;
+
+  /** If present redirect browser to this URL. */
+  protected String url;
+
+  /** When true open {@link #url} in a new tab/window. */
+  protected Boolean openWindow;
+
+  private UiResult() {
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java
similarity index 62%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java
index 96e2ec8..19d9ab7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/WebLink.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2014 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.
@@ -11,17 +11,14 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+package com.google.gerrit.extensions.webui;
 
-package com.google.gerrit.pgm.util;
+public interface WebLink {
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  public Die(final String why) {
-    super(why);
-  }
-
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
-  }
+  /**
+   * The link-name displayed in UI.
+   *
+   * @return name of link.
+   */
+  String getLinkName();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 1b89a5a..c851524 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -233,7 +233,12 @@
       extension(token);
 
     } else if (matchExact(MINE, token)) {
-      Gerrit.display(token, mine(token));
+      String defaultScreenToken = Gerrit.getDefaultScreenToken();
+      if (defaultScreenToken != null && !MINE.equals(defaultScreenToken)) {
+        select(defaultScreenToken);
+      } else {
+        Gerrit.display(token, mine(token));
+      }
 
     } else if (matchPrefix("/dashboard/", token)) {
       dashboard(token);
@@ -440,6 +445,17 @@
       return;
     }
 
+    if (rest.equals("self")) {
+      if (Gerrit.isSignedIn()) {
+        Gerrit.display(token, new AccountDashboardScreen(Gerrit.getUserAccount().getId()));
+      } else {
+        Screen s = new AccountDashboardScreen(null);
+        s.setRequiresSignIn(true);
+        Gerrit.display(token, s);
+      }
+      return;
+    }
+
     if (rest.startsWith("?")) {
       Gerrit.display(token, new CustomDashboardScreen(rest.substring(1)));
       return;
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 cd2cccd..6db8180 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
@@ -18,8 +18,10 @@
 import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
 import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
 
+import com.google.gerrit.client.account.AccountApi;
 import com.google.gerrit.client.account.AccountCapabilities;
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.account.Preferences;
 import com.google.gerrit.client.admin.ProjectScreen;
 import com.google.gerrit.client.api.ApiGlue;
 import com.google.gerrit.client.api.PluginLoader;
@@ -30,14 +32,15 @@
 import com.google.gerrit.client.extensions.TopMenuItem;
 import com.google.gerrit.client.extensions.TopMenuList;
 import com.google.gerrit.client.patches.PatchScreen;
+import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.LinkMenuBar;
 import com.google.gerrit.client.ui.LinkMenuItem;
 import com.google.gerrit.client.ui.MorphingTabPanel;
 import com.google.gerrit.client.ui.PatchLink;
+import com.google.gerrit.client.ui.ProjectLinkMenuItem;
 import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.client.ui.ScreenLoadEvent;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.common.data.GitwebConfig;
@@ -48,7 +51,6 @@
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 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.EntryPoint;
 import com.google.gwt.core.client.GWT;
@@ -70,6 +72,7 @@
 import com.google.gwt.user.client.History;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.Window.Location;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.FocusPanel;
@@ -104,6 +107,7 @@
   private static GerritConfig myConfig;
   private static HostPageData.Theme myTheme;
   private static Account myAccount;
+  private static String defaultScreenToken;
   private static AccountDiffPreference myAccountDiffPref;
   private static String xGerritAuth;
 
@@ -211,6 +215,10 @@
     }
   }
 
+  public static void selectMenu(LinkMenuBar bar) {
+    menuLeft.selectTab(menuLeft.getWidgetIndex(bar));
+  }
+
   /**
    * Update the current history token after a screen change.
    * <p>
@@ -265,6 +273,10 @@
     return topMenu.isVisible();
   }
 
+  public static String getDefaultScreenToken() {
+    return defaultScreenToken;
+  }
+
   public static RootPanel getBottomMenu() {
     return bottomMenu;
   }
@@ -537,7 +549,7 @@
 
     applyUserPreferences();
     populateBottomMenu(bottomMenu, hpd);
-    refreshMenuBar();
+    refreshMenuBar(false);
 
     History.addValueChangeHandler(new ValueChangeHandler<String>() {
       @Override
@@ -547,18 +559,27 @@
     });
     JumpKeys.register(body);
 
-    String token = History.getToken();
-    if (token.isEmpty()) {
-      token = isSignedIn()
-          ? PageLinks.MINE
-          : PageLinks.toChangeQuery("status:open");
-    }
-
     saveDefaultTheme();
     if (hpd.messages != null) {
       new MessageOfTheDayBar(hpd.messages).show();
     }
-    PluginLoader.load(hpd.plugins, token);
+    CallbackGroup cbg = new CallbackGroup();
+    if (isSignedIn()) {
+      AccountApi.self().view("preferences").get(cbg.add(createMyMenuBarCallback()));
+    }
+    PluginLoader.load(hpd.plugins,
+        cbg.addFinal(new GerritCallback<VoidResult>() {
+          @Override
+          public void onSuccess(VoidResult result) {
+            String token = History.getToken();
+            if (token.isEmpty()) {
+              token = isSignedIn()
+                  ? PageLinks.MINE
+                  : PageLinks.toChangeQuery("status:open");
+            }
+            display(token);
+          }
+        }));
   }
 
   private void saveDefaultTheme() {
@@ -568,6 +589,10 @@
   }
 
   public static void refreshMenuBar() {
+    refreshMenuBar(true);
+  }
+
+  private static void refreshMenuBar(boolean populateMyMenu) {
     menuLeft.clear();
     menuRight.clear();
 
@@ -585,14 +610,12 @@
     menuLeft.add(m, C.menuAll());
 
     if (signedIn) {
-      m = new LinkMenuBar();
-      menuBars.put(GerritTopMenu.MY.menuName, m);
-      addLink(m, C.menuMyChanges(), PageLinks.MINE);
-      addLink(m, C.menuMyDrafts(), PageLinks.toChangeQuery("is:draft"));
-      addLink(m, C.menuMyDraftComments(), PageLinks.toChangeQuery("has:draft"));
-      addLink(m, C.menuMyWatchedChanges(), PageLinks.toChangeQuery("is:watched status:open"));
-      addLink(m, C.menuMyStarredChanges(), PageLinks.toChangeQuery("is:starred"));
-      menuLeft.add(m, C.menuMine());
+      LinkMenuBar myBar = new LinkMenuBar();
+      menuBars.put(GerritTopMenu.MY.menuName, myBar);
+      if (populateMyMenu) {
+        AccountApi.self().view("preferences").get(createMyMenuBarCallback());
+      }
+      menuLeft.add(myBar, C.menuMine());
       menuLeft.selectTab(1);
     } else {
       menuLeft.selectTab(0);
@@ -609,22 +632,21 @@
     addDiffLink(diffBar, C.menuDiffPatchSets(), PatchScreen.TopView.PATCH_SETS);
     addDiffLink(diffBar, C.menuDiffFiles(), PatchScreen.TopView.FILES);
 
-    final LinkMenuBar projectsBar = new LinkMenuBar() {
-      @Override
-      public void onScreenLoad(ScreenLoadEvent event) {
-        if (event.getScreen() instanceof ProjectScreen) {
-          menuLeft.selectTab(menuLeft.getWidgetIndex(this));
-        }
-      }
-    };
+    final LinkMenuBar projectsBar = new LinkMenuBar();
     menuBars.put(GerritTopMenu.PROJECTS.menuName, projectsBar);
     addLink(projectsBar, C.menuProjectsList(), PageLinks.ADMIN_PROJECTS);
-    addProjectLink(projectsBar, C.menuProjectsInfo(), ProjectScreen.INFO);
-    addProjectLink(projectsBar, C.menuProjectsBranches(), ProjectScreen.BRANCH);
-    addProjectLink(projectsBar, C.menuProjectsAccess(), ProjectScreen.ACCESS);
+    projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsInfo(), ProjectScreen.INFO));
+    projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsBranches(), ProjectScreen.BRANCH));
+    projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsAccess(), ProjectScreen.ACCESS));
     final LinkMenuItem dashboardsMenuItem =
-        addProjectLink(projectsBar, C.menuProjectsDashboards(),
-            ProjectScreen.DASHBOARDS);
+        new ProjectLinkMenuItem(C.menuProjectsDashboards(),
+            ProjectScreen.DASHBOARDS) {
+      protected boolean match(String token) {
+        return super.match(token) ||
+            (!getTargetHistoryToken().isEmpty() && ("/admin" + token).startsWith(getTargetHistoryToken()));
+      }
+    };
+    projectsBar.addItem(dashboardsMenuItem);
     menuLeft.add(projectsBar, C.menuProjects());
 
     if (signedIn) {
@@ -747,6 +769,27 @@
     });
   }
 
+  private static AsyncCallback<Preferences> createMyMenuBarCallback() {
+    return new GerritCallback<Preferences>() {
+      @Override
+      public void onSuccess(Preferences prefs) {
+        LinkMenuBar myBar = menuBars.get(GerritTopMenu.MY.menuName);
+        myBar.clear();
+        List<TopMenuItem> myMenuItems = Natives.asList(prefs.my());
+        String url = null;
+        if (!myMenuItems.isEmpty()) {
+          if (myMenuItems.get(0).getUrl().startsWith("#")) {
+            url = myMenuItems.get(0).getUrl().substring(1);
+          }
+          for (TopMenuItem item : myMenuItems) {
+            addExtensionLink(myBar, item);
+          }
+        }
+        defaultScreenToken = url;
+      }
+    };
+  }
+
   public static void applyUserPreferences() {
     if (myAccount != null) {
       final AccountGeneralPreferences p = myAccount.getGeneralPreferences();
@@ -838,32 +881,6 @@
       });
   }
 
-  private static LinkMenuItem addProjectLink(final LinkMenuBar m, final String text,
-      final String panel) {
-    LinkMenuItem i = new LinkMenuItem(text, "") {
-        @Override
-        public void onScreenLoad(ScreenLoadEvent event) {
-          Screen screen = event.getScreen();
-          Project.NameKey projectKey;
-          if (screen instanceof ProjectScreen) {
-            projectKey = ((ProjectScreen)screen).getProjectKey();
-          } else {
-            projectKey = ProjectScreen.getSavedKey();
-          }
-
-          if (projectKey != null) {
-            setVisible(true);
-            setTargetHistoryToken(Dispatcher.toProjectAdmin(projectKey, panel));
-          } else {
-            setVisible(false);
-          }
-          super.onScreenLoad(event);
-        }
-      };
-    m.addItem(i);
-    return i;
-  }
-
   private static void addDiffLink(final LinkMenuBar m, final String text,
       final PatchScreen.Type type) {
     m.addItem(new LinkMenuItem(text, "") {
@@ -889,15 +906,26 @@
   }
 
   private static void addExtensionLink(LinkMenuBar m, TopMenuItem item) {
-    Anchor atag = anchor(item.getName(), isAbsolute(item.getUrl())
-        ? item.getUrl()
-        : selfRedirect(item.getUrl()));
-
-    atag.setTarget(item.getTarget());
-    if (item.getId() != null) {
-      atag.getElement().setAttribute("id", item.getId());
+    if (item.getUrl().startsWith("#")
+        && (item.getTarget() == null || item.getTarget().isEmpty())) {
+      LinkMenuItem a =
+          new LinkMenuItem(item.getName(), item.getUrl().substring(1));
+      if (item.getId() != null) {
+        a.getElement().setAttribute("id", item.getId());
+      }
+      m.addItem(a);
+    } else {
+      Anchor atag = anchor(item.getName(), isAbsolute(item.getUrl())
+          ? item.getUrl()
+          : selfRedirect(item.getUrl()));
+      if (item.getTarget() != null && !item.getTarget().isEmpty()) {
+        atag.setTarget(item.getTarget());
+      }
+      if (item.getId() != null) {
+        atag.getElement().setAttribute("id", item.getId());
+      }
+      m.add(atag);
     }
-    m.add(atag);
   }
 
   private static boolean isAbsolute(String url) {
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 2d20b98..a93dda5 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
@@ -116,4 +116,9 @@
   String projectAccessProposeForReviewHint();
 
   String userCannotVoteToolTip();
+
+  String stringListPanelAdd();
+  String stringListPanelDelete();
+  String stringListPanelUp();
+  String stringListPanelDown();
 }
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 af530f4..38498f3 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
@@ -99,3 +99,8 @@
 projectAccessProposeForReviewHint = You may propose these modifications to the project owners by clicking on 'Save for Review'.
 
 userCannotVoteToolTip = User cannot vote in this category
+
+stringListPanelAdd = Add
+stringListPanelDelete = Delete
+stringListPanelUp = Up
+stringListPanelDown = Down
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 98b9534..12a1fea 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
@@ -232,6 +232,7 @@
   String sshHostKeyPanelKnownHostEntry();
   String sshKeyPanelEncodedKey();
   String sshKeyPanelInvalid();
+  String stringListPanelButtons();
   String topMostCell();
   String topmenu();
   String topmenuMenuLeft();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
index 98b0b45a..3b04467 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
@@ -28,6 +28,12 @@
   @Source("arrowRight.gif")
   public ImageResource arrowRight();
 
+  @Source("arrowUp.png")
+  public ImageResource arrowUp();
+
+  @Source("arrowDown.png")
+  public ImageResource arrowDown();
+
   @Source("editText.png")
   public ImageResource edit();
 
@@ -70,6 +76,9 @@
   @Source("warning.png")
   public ImageResource warning();
 
+  @Source("listAdd.png")
+  public ImageResource listAdd();
+
   @Source("dashboard.png")
   public ImageResource dashboard();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 0715aee..3b6011b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -81,6 +81,7 @@
     suggestions.add("comment:");
     suggestions.add("conflicts:");
     suggestions.add("project:");
+    suggestions.add("projects:");
     suggestions.add("parentproject:");
     suggestions.add("branch:");
     suggestions.add("topic:");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/StringListPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/StringListPanel.java
new file mode 100644
index 0000000..ccbc1b8
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/StringListPanel.java
@@ -0,0 +1,316 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client;
+
+import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.client.ui.OnEditEnabler;
+import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StringListPanel extends FlowPanel {
+  private final StringListTable t;
+  private final HorizontalPanel titlePanel;
+  protected final HorizontalPanel buttonPanel;
+  private final Button deleteButton;
+  private Image info;
+  protected FocusWidget widget;
+
+  public StringListPanel(String title, List<String> fieldNames, FocusWidget w,
+      boolean autoSort) {
+    widget = w;
+    titlePanel = new HorizontalPanel();
+    SmallHeading titleLabel = new SmallHeading(title);
+    titlePanel.add(titleLabel);
+    add(titlePanel);
+
+    t = new StringListTable(fieldNames, autoSort);
+    add(t);
+
+    buttonPanel = new HorizontalPanel();
+    buttonPanel.setStyleName(Gerrit.RESOURCES.css().stringListPanelButtons());
+    deleteButton = new Button(Gerrit.C.stringListPanelDelete());
+    deleteButton.setEnabled(false);
+    deleteButton.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        widget.setEnabled(true);
+        t.deleteChecked();
+      }
+    });
+    buttonPanel.add(deleteButton);
+    add(buttonPanel);
+  }
+
+  public void display(List<List<String>> values) {
+    t.display(values);
+  }
+
+  public void setInfo(String msg) {
+    if (info == null) {
+      info = new Image(Gerrit.RESOURCES.info());
+      titlePanel.add(info);
+    }
+    info.setTitle(msg);
+  }
+
+  public List<List<String>> getValues() {
+    return t.getValues();
+  }
+
+  private class StringListTable extends NavigationTable<List<String>> {
+    private final List<NpTextBox> inputs;
+    private final boolean autoSort;
+
+    StringListTable(List<String> names, boolean autoSort) {
+      this.autoSort = autoSort;
+
+      Button addButton =
+          new Button(new ImageResourceRenderer().render(Gerrit.RESOURCES.listAdd()));
+      addButton.setTitle(Gerrit.C.stringListPanelAdd());
+      OnEditEnabler e = new OnEditEnabler(addButton);
+      inputs = new ArrayList<>();
+
+      FlexCellFormatter fmt = table.getFlexCellFormatter();
+      fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().iconHeader());
+      fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().leftMostCell());
+      for (int i = 0; i < names.size(); i++) {
+        fmt.addStyleName(0, i + 1, Gerrit.RESOURCES.css().dataHeader());
+        table.setText(0, i + 1, names.get(i));
+
+        NpTextBox input = new NpTextBox();
+        input.setVisibleLength(35);
+        input.addKeyPressHandler(new KeyPressHandler() {
+          @Override
+          public void onKeyPress(KeyPressEvent event) {
+            if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+              widget.setEnabled(true);
+              add();
+            }
+          }
+        });
+        inputs.add(input);
+        fmt.addStyleName(1, i + 1, Gerrit.RESOURCES.css().dataHeader());
+        table.setWidget(1, i + 1, input);
+        e.listenTo(input);
+      }
+      addButton.setEnabled(false);
+
+      addButton.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          widget.setEnabled(true);
+          add();
+        }
+      });
+      fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().iconHeader());
+      fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().leftMostCell());
+      table.setWidget(1, 0, addButton);
+
+      if (!autoSort) {
+        fmt.addStyleName(0, names.size() + 1, Gerrit.RESOURCES.css().iconHeader());
+        fmt.addStyleName(0, names.size() + 2, Gerrit.RESOURCES.css().iconHeader());
+        fmt.addStyleName(1, names.size() + 1, Gerrit.RESOURCES.css().iconHeader());
+        fmt.addStyleName(1, names.size() + 2, Gerrit.RESOURCES.css().iconHeader());
+      }
+    }
+
+    void display(List<List<String>> values) {
+      for (int row = 2; row < table.getRowCount(); row++) {
+        table.removeRow(row--);
+      }
+      int row = 2;
+      for (List<String> v : values) {
+        populate(row, v);
+        row++;
+      }
+      updateNavigationLinks();
+    }
+
+    List<List<String>> getValues() {
+      List<List<String>> values = new ArrayList<>();
+      for (int row = 2; row < table.getRowCount(); row++) {
+        values.add(getRowItem(row));
+      }
+      return values;
+    }
+
+    @Override
+    protected List<String> getRowItem(int row) {
+      List<String> v = new ArrayList<>();
+      for (int i = 0; i < inputs.size(); i++) {
+        v.add(table.getText(row, i + 1));
+      }
+      return v;
+    }
+
+    private void populate(final int row, List<String> values) {
+      FlexCellFormatter fmt = table.getFlexCellFormatter();
+      fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().iconCell());
+      fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().leftMostCell());
+      CheckBox checkBox = new CheckBox();
+      checkBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+        @Override
+        public void onValueChange(ValueChangeEvent<Boolean> event) {
+          enableDelete();
+        }
+      });
+      table.setWidget(row, 0, checkBox);
+      for (int i = 0; i < values.size(); i++) {
+        fmt.addStyleName(row, i + 1, Gerrit.RESOURCES.css().dataCell());
+        table.setText(row, i + 1, values.get(i));
+      }
+      if (!autoSort) {
+        fmt.addStyleName(row, values.size() + 1, Gerrit.RESOURCES.css().iconCell());
+        fmt.addStyleName(row, values.size() + 2, Gerrit.RESOURCES.css().dataCell());
+
+        Image down = new Image(Gerrit.RESOURCES.arrowDown());
+        down.setTitle(Gerrit.C.stringListPanelDown());
+        down.addClickHandler(new ClickHandler() {
+          @Override
+          public void onClick(ClickEvent event) {
+            moveDown(row);
+          }
+        });
+        table.setWidget(row, values.size() + 1, down);
+
+        Image up = new Image(Gerrit.RESOURCES.arrowUp());
+        up.setTitle(Gerrit.C.stringListPanelUp());
+        up.addClickHandler(new ClickHandler() {
+          @Override
+          public void onClick(ClickEvent event) {
+            moveUp(row);
+          }
+        });
+        table.setWidget(row, values.size() + 2, up);
+      }
+    }
+
+    @Override
+    protected void onCellSingleClick(Event event, int row, int column) {
+      if (column == inputs.size() + 1 && row >= 2
+          && row < table.getRowCount() - 2) {
+        moveDown(row);
+      } else if (column == inputs.size() + 2 && row > 2) {
+        moveUp(row);
+      }
+    }
+
+    void moveDown(int row) {
+      if (row < table.getRowCount() - 1) {
+        swap(row, row + 1);
+      }
+    }
+
+    void moveUp(int row) {
+      if (row > 2) {
+        swap(row - 1, row);
+      }
+    }
+
+    void swap(int row1, int row2) {
+      List<String> value = getRowItem(row1);
+      List<String> nextValue = getRowItem(row2);
+      populate(row1, nextValue);
+      populate(row2, value);
+      updateNavigationLinks();
+      widget.setEnabled(true);
+    }
+
+    private void updateNavigationLinks() {
+      if (!autoSort) {
+        for (int row = 2; row < table.getRowCount(); row++) {
+          table.getWidget(row, inputs.size() + 1).setVisible(
+              row < table.getRowCount() - 1);
+          table.getWidget(row, inputs.size() + 2).setVisible(row > 2);
+        }
+      }
+    }
+
+    void add() {
+      List<String> values = new ArrayList<>();
+      for (NpTextBox input : inputs) {
+        values.add(input.getValue().trim());
+        input.setValue("");
+      }
+      insert(values);
+    }
+
+    void insert(List<String> v) {
+      int insertPos = table.getRowCount();
+      if (autoSort) {
+        for (int row = 1; row < table.getRowCount(); row++) {
+          int compareResult = v.get(0).compareTo(table.getText(row, 1));
+          if (compareResult < 0)  {
+            insertPos = row;
+            break;
+          } else if (compareResult == 0) {
+            return;
+          }
+        }
+      }
+      table.insertRow(insertPos);
+      populate(insertPos, v);
+      updateNavigationLinks();
+    }
+
+    void enableDelete() {
+      for (int row = 2; row < table.getRowCount(); row++) {
+        if (((CheckBox) table.getWidget(row, 0)).getValue()) {
+          deleteButton.setEnabled(true);
+          return;
+        }
+      }
+      deleteButton.setEnabled(false);
+    }
+
+    void deleteChecked() {
+      deleteButton.setEnabled(false);
+      for (int row = 2; row < table.getRowCount(); row++) {
+        if (((CheckBox) table.getWidget(row, 0)).getValue()) {
+          table.removeRow(row--);
+        }
+      }
+      updateNavigationLinks();
+    }
+
+    @Override
+    protected void onOpenRow(int row) {
+    }
+
+    @Override
+    protected Object getRowItemKey(List<String> item) {
+      return item.get(0);
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index d26ee7d..466a30f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -39,6 +39,11 @@
   String buttonSaveChanges();
   String showRelativeDateInChangeTable();
   String showSizeBarInChangeTable();
+  String myMenu();
+  String myMenuInfo();
+  String myMenuName();
+  String myMenuUrl();
+  String myMenuReset();
 
   String changeScreenOldUi();
   String changeScreenNewUi();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index 90c6f5f..a22eb1f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -19,6 +19,13 @@
 buttonSaveChanges = Save Changes
 showRelativeDateInChangeTable = Show Relative Dates In Changes Table
 showSizeBarInChangeTable = Show Change Sizes As Colored Bars In Changes Table
+myMenu = My Menu
+myMenuInfo = \
+  Menu items for the 'My' top level menu. \
+  The first menu item defines the default screen.
+myMenuName = Name
+myMenuUrl = URL
+myMenuReset = Reset
 
 changeScreenOldUi = Old Screen
 changeScreenNewUi = New Screen
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index d8a0427..eef6ca1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -19,12 +19,16 @@
 
 import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.StringListPanel;
+import com.google.gerrit.client.config.ConfigServerApi;
+import com.google.gerrit.client.extensions.TopMenuItem;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.OnEditEnabler;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.i18n.client.DateTimeFormat;
@@ -34,9 +38,11 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwtjsonrpc.common.VoidResult;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.List;
 
 public class MyPreferencesScreen extends SettingsScreen {
   private CheckBox showSiteHeader;
@@ -52,6 +58,7 @@
   private ListBox commentVisibilityStrategy;
   private ListBox changeScreen;
   private ListBox diffView;
+  private StringListPanel myMenus;
   private Button save;
 
   @Override
@@ -202,6 +209,10 @@
         doSave();
       }
     });
+
+    myMenus = new MyMenuPanel(save);
+    add(myMenus);
+
     add(save);
 
     final OnEditEnabler e = new OnEditEnabler(save);
@@ -223,9 +234,11 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    Util.ACCOUNT_SVC.myAccount(new ScreenLoadCallback<Account>(this) {
-      public void preDisplay(final Account result) {
-        display(result.getGeneralPreferences());
+    AccountApi.self().view("preferences")
+        .get(new ScreenLoadCallback<Preferences>(this) {
+      @Override
+      public void preDisplay(Preferences prefs) {
+        display(prefs);
       }
     });
   }
@@ -246,28 +259,37 @@
     diffView.setEnabled(on);
   }
 
-  private void display(final AccountGeneralPreferences p) {
-    showSiteHeader.setValue(p.isShowSiteHeader());
-    useFlashClipboard.setValue(p.isUseFlashClipboard());
-    copySelfOnEmails.setValue(p.isCopySelfOnEmails());
-    reversePatchSetOrder.setValue(p.isReversePatchSetOrder());
-    showUsernameInReviewCategory.setValue(p.isShowUsernameInReviewCategory());
-    setListBox(maximumPageSize, DEFAULT_PAGESIZE, p.getMaximumPageSize());
+  private void display(Preferences p) {
+    showSiteHeader.setValue(p.showSiteHeader());
+    useFlashClipboard.setValue(p.useFlashClipboard());
+    copySelfOnEmails.setValue(p.copySelfOnEmail());
+    reversePatchSetOrder.setValue(p.reversePatchSetOrder());
+    showUsernameInReviewCategory.setValue(p.showUsernameInReviewCategory());
+    setListBox(maximumPageSize, DEFAULT_PAGESIZE, p.changesPerPage());
     setListBox(dateFormat, AccountGeneralPreferences.DateFormat.STD, //
-        p.getDateFormat());
+        p.dateFormat());
     setListBox(timeFormat, AccountGeneralPreferences.TimeFormat.HHMM_12, //
-        p.getTimeFormat());
-    relativeDateInChangeTable.setValue(p.isRelativeDateInChangeTable());
-    sizeBarInChangeTable.setValue(p.isSizeBarInChangeTable());
+        p.timeFormat());
+    relativeDateInChangeTable.setValue(p.relativeDateInChangeTable());
+    sizeBarInChangeTable.setValue(p.sizeBarInChangeTable());
     setListBox(commentVisibilityStrategy,
         AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_RECENT,
-        p.getCommentVisibilityStrategy());
+        p.commentVisibilityStrategy());
     setListBox(changeScreen,
         null,
-        p.getChangeScreen());
+        p.changeScreen());
     setListBox(diffView,
         AccountGeneralPreferences.DiffView.SIDE_BY_SIDE,
-        p.getDiffView());
+        p.diffView());
+    display(p.my());
+  }
+
+  private void display(JsArray<TopMenuItem> items) {
+    List<List<String>> values = new ArrayList<>();
+    for (TopMenuItem item : Natives.asList(items)) {
+      values.add(Arrays.asList(item.getName(), item.getUrl()));
+    }
+    myMenus.display(values);
   }
 
   private void setListBox(final ListBox f, final short defaultValue,
@@ -350,22 +372,30 @@
     enable(false);
     save.setEnabled(false);
 
-    Util.ACCOUNT_SVC.changePreferences(p, new GerritCallback<VoidResult>() {
-      @Override
-      public void onSuccess(final VoidResult result) {
-        Gerrit.getUserAccount().setGeneralPreferences(p);
-        Gerrit.applyUserPreferences();
-        Dispatcher.changeScreen2 = false;
-        enable(true);
-      }
+    List<TopMenuItem> items = new ArrayList<>();
+    for (List<String> v : myMenus.getValues()) {
+      items.add(TopMenuItem.create(v.get(0), v.get(1)));
+    }
 
-      @Override
-      public void onFailure(final Throwable caught) {
-        enable(true);
-        save.setEnabled(true);
-        super.onFailure(caught);
-      }
-    });
+    AccountApi.self().view("preferences")
+        .post(Preferences.create(p, items), new GerritCallback<Preferences>() {
+          @Override
+          public void onSuccess(Preferences prefs) {
+            Gerrit.getUserAccount().setGeneralPreferences(p);
+            Gerrit.applyUserPreferences();
+            Dispatcher.changeScreen2 = false;
+            enable(true);
+            display(prefs);
+            Gerrit.refreshMenuBar();
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            enable(true);
+            save.setEnabled(true);
+            super.onFailure(caught);
+          }
+        });
   }
 
   private static String getLabel(AccountGeneralPreferences.ChangeScreen ui) {
@@ -381,4 +411,28 @@
         return ui.name();
     }
   }
+
+  private class MyMenuPanel extends StringListPanel {
+    MyMenuPanel(Button save) {
+      super(Util.C.myMenu(), Arrays.asList(Util.C.myMenuName(),
+          Util.C.myMenuUrl()), save, false);
+
+      setInfo(Util.C.myMenuInfo());
+
+      Button resetButton = new Button(Util.C.myMenuReset());
+      resetButton.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          ConfigServerApi.defaultPreferences(new GerritCallback<Preferences>() {
+            @Override
+            public void onSuccess(Preferences p) {
+              MyPreferencesScreen.this.display(p.my());
+              widget.setEnabled(true);
+            }
+          });
+        }
+      });
+      buttonPanel.add(resetButton);
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
new file mode 100644
index 0000000..3e66e22
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
@@ -0,0 +1,212 @@
+// Copyright (C) 2014 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.account;
+
+import com.google.gerrit.client.extensions.TopMenuItem;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+import java.util.List;
+
+public class Preferences extends JavaScriptObject {
+  public static Preferences create(AccountGeneralPreferences in, List<TopMenuItem> myMenus) {
+    Preferences p = createObject().cast();
+    if (in == null) {
+      in = AccountGeneralPreferences.createDefault();
+    }
+    p.changesPerPage(in.getMaximumPageSize());
+    p.showSiteHeader(in.isShowSiteHeader());
+    p.useFlashClipboard(in.isUseFlashClipboard());
+    p.downloadScheme(in.getDownloadUrl());
+    p.downloadCommand(in.getDownloadCommand());
+    p.copySelfOnEmail(in.isCopySelfOnEmails());
+    p.dateFormat(in.getDateFormat());
+    p.timeFormat(in.getTimeFormat());
+    p.reversePatchSetOrder(in.isReversePatchSetOrder());
+    p.showUsernameInReviewCategory(in.isShowUsernameInReviewCategory());
+    p.relativeDateInChangeTable(in.isRelativeDateInChangeTable());
+    p.sizeBarInChangeTable(in.isSizeBarInChangeTable());
+    p.commentVisibilityStrategy(in.getCommentVisibilityStrategy());
+    p.diffView(in.getDiffView());
+    p.changeScreen(in.getChangeScreen());
+    p.setMyMenus(myMenus);
+    return p;
+  }
+
+  public final short changesPerPage() {
+    return get("changes_per_page", AccountGeneralPreferences.DEFAULT_PAGESIZE);
+  }
+  private final native short get(String n, int d)
+  /*-{ return this.hasOwnProperty(n) ? this[n] : d }-*/;
+
+  public final native boolean showSiteHeader()
+  /*-{ return this.show_site_header || false }-*/;
+
+  public final native boolean useFlashClipboard()
+  /*-{ return this.use_flash_clipboard || false }-*/;
+
+  public final DownloadScheme downloadScheme() {
+    String s = downloadSchemeRaw();
+    return s != null ? DownloadScheme.valueOf(s) : null;
+  }
+  private final native String downloadSchemeRaw()
+  /*-{ return this.download_scheme }-*/;
+
+  public final DownloadCommand downloadCommand() {
+    String s = downloadCommandRaw();
+    return s != null ? DownloadCommand.valueOf(s) : null;
+  }
+  private final native String downloadCommandRaw()
+  /*-{ return this.download_command }-*/;
+
+  public final native boolean copySelfOnEmail()
+  /*-{ return this.copy_self_on_email || false }-*/;
+
+  public final DateFormat dateFormat() {
+    String s = dateFormatRaw();
+    return s != null ? DateFormat.valueOf(s) : null;
+  }
+  private final native String dateFormatRaw()
+  /*-{ return this.date_format }-*/;
+
+  public final TimeFormat timeFormat() {
+    String s = timeFormatRaw();
+    return s != null ? TimeFormat.valueOf(s) : null;
+  }
+  private final native String timeFormatRaw()
+  /*-{ return this.time_format }-*/;
+
+  public final native boolean reversePatchSetOrder()
+  /*-{ return this.reverse_patch_set_order || false }-*/;
+
+  public final native boolean showUsernameInReviewCategory()
+  /*-{ return this.show_username_in_review_category || false }-*/;
+
+  public final native boolean relativeDateInChangeTable()
+  /*-{ return this.relative_date_in_change_table || false }-*/;
+
+  public final native boolean sizeBarInChangeTable()
+  /*-{ return this.size_bar_in_change_table || false }-*/;
+
+  public final CommentVisibilityStrategy commentVisibilityStrategy() {
+    String s = commentVisibilityStrategyRaw();
+    return s != null ? CommentVisibilityStrategy.valueOf(s) : null;
+  }
+  private final native String commentVisibilityStrategyRaw()
+  /*-{ return this.comment_visibility_strategy }-*/;
+
+  public final DiffView diffView() {
+    String s = diffViewRaw();
+    return s != null ? DiffView.valueOf(s) : null;
+  }
+  private final native String diffViewRaw()
+  /*-{ return this.diff_view }-*/;
+
+  public final ChangeScreen changeScreen() {
+    String s = changeScreenRaw();
+    return s != null ? ChangeScreen.valueOf(s) : null;
+  }
+  private final native String changeScreenRaw()
+  /*-{ return this.change_screen }-*/;
+
+  public final native JsArray<TopMenuItem> my()
+  /*-{ return this.my; }-*/;
+
+  public final native void changesPerPage(short n)
+  /*-{ this.changes_per_page = n }-*/;
+
+  public final native void showSiteHeader(boolean s)
+  /*-{ this.show_site_header = s }-*/;
+
+  public final native void useFlashClipboard(boolean u)
+  /*-{ this.use_flash_clipboard = u }-*/;
+
+  public final void downloadScheme(DownloadScheme d) {
+    downloadSchemeRaw(d != null ? d.toString() : null);
+  }
+  private final native void downloadSchemeRaw(String d)
+  /*-{ this.download_scheme = d }-*/;
+
+  public final void downloadCommand(DownloadCommand d) {
+    downloadCommandRaw(d != null ? d.toString() : null);
+  }
+  public final native void downloadCommandRaw(String d)
+  /*-{ this.download_command = d }-*/;
+
+  public final native void copySelfOnEmail(boolean c)
+  /*-{ this.copy_self_on_email = c }-*/;
+
+  public final void dateFormat(DateFormat f) {
+    dateFormatRaw(f != null ? f.toString() : null);
+  }
+  private final native void dateFormatRaw(String f)
+  /*-{ this.date_format = f }-*/;
+
+  public final void timeFormat(TimeFormat f) {
+    timeFormatRaw(f != null ? f.toString() : null);
+  }
+  private final native void timeFormatRaw(String f)
+  /*-{ this.time_format = f }-*/;
+
+  public final native void reversePatchSetOrder(boolean r)
+  /*-{ this.reverse_patch_set_order = r }-*/;
+
+  public final native void showUsernameInReviewCategory(boolean s)
+  /*-{ this.show_username_in_review_category = s }-*/;
+
+  public final native void relativeDateInChangeTable(boolean d)
+  /*-{ this.relative_date_in_change_table = d }-*/;
+
+  public final native void sizeBarInChangeTable(boolean s)
+  /*-{ this.size_bar_in_change_table = s }-*/;
+
+  public final void commentVisibilityStrategy(CommentVisibilityStrategy s) {
+    commentVisibilityStrategyRaw(s != null ? s.toString() : null);
+  }
+  private final native void commentVisibilityStrategyRaw(String s)
+  /*-{ this.comment_visibility_strategy = s }-*/;
+
+  public final void diffView(DiffView d) {
+    diffViewRaw(d != null ? d.toString() : null);
+  }
+  private final native void diffViewRaw(String d)
+  /*-{ this.diff_view = d }-*/;
+
+  public final void changeScreen(ChangeScreen s) {
+    changeScreenRaw(s != null ? s.toString() : null);
+  }
+  private final native void changeScreenRaw(String s)
+  /*-{ this.change_screen = s }-*/;
+
+  final void setMyMenus(List<TopMenuItem> myMenus) {
+    initMy();
+    for (TopMenuItem n : myMenus) {
+      addMy(n);
+    }
+  }
+  final native void initMy() /*-{ this.my = []; }-*/;
+  final native void addMy(TopMenuItem m) /*-{ this.my.push(m); }-*/;
+
+  protected Preferences() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
index 2c29ab2..c689b49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -29,7 +29,7 @@
     if (Gerrit.getConfig().getSshdAddress() != null) {
       link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
     }
-    if (!Gerrit.getConfig().isGitBasicAuth()) {
+    if (Gerrit.getConfig().isHttpPasswordSettingsEnabled()) {
       link(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
     }
     link(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
index 7f13075..ab94a75c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.client.api.RevisionGlue;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.projects.BranchInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -28,13 +29,19 @@
 
 public class ActionButton extends Button implements ClickHandler {
   private final Project.NameKey project;
+  private final BranchInfo branch;
   private final ChangeInfo change;
   private final RevisionInfo revision;
   private final ActionInfo action;
   private ActionContext ctx;
 
   public ActionButton(Project.NameKey project, ActionInfo action) {
-    this(project, null, null, action);
+    this(project, null, null, null, action);
+  }
+
+  public ActionButton(Project.NameKey project, BranchInfo branch,
+      ActionInfo action) {
+    this(project, branch, null, null, action);
   }
 
   public ActionButton(ChangeInfo change, ActionInfo action) {
@@ -43,11 +50,11 @@
 
   public ActionButton(ChangeInfo change, RevisionInfo revision,
       ActionInfo action) {
-    this(null, change, revision, action);
+    this(null, null, change, revision, action);
   }
 
-  private ActionButton(Project.NameKey project, ChangeInfo change,
-      RevisionInfo revision, ActionInfo action) {
+  private ActionButton(Project.NameKey project, BranchInfo branch,
+      ChangeInfo change, RevisionInfo revision, ActionInfo action) {
     super(new SafeHtmlBuilder()
       .openDiv()
       .append(action.label())
@@ -58,6 +65,7 @@
     addClickHandler(this);
 
     this.project = project;
+    this.branch = branch;
     this.change = change;
     this.revision = revision;
     this.action = action;
@@ -75,6 +83,8 @@
       RevisionGlue.onAction(change, revision, action, this);
     } else if (change != null) {
       ChangeGlue.onAction(change, action, this);
+    } else if (branch != null) {
+      ProjectGlue.onAction(project, branch, action, this);
     } else if (project != null) {
       ProjectGlue.onAction(project, action, this);
     }
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 a70aa8f..e6f5b68 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
@@ -22,6 +22,8 @@
 import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.access.AccessMap;
 import com.google.gerrit.client.access.ProjectAccessInfo;
+import com.google.gerrit.client.actions.ActionButton;
+import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.projects.BranchInfo;
 import com.google.gerrit.client.projects.ProjectApi;
 import com.google.gerrit.client.rpc.GerritCallback;
@@ -387,10 +389,18 @@
         table.setText(row, 3, "");
       }
 
+      FlowPanel actionsPanel = new FlowPanel();
       if (c != null) {
-        table.setWidget(row, 4, new Anchor(c.getLinkName(), false,
+        actionsPanel.add(new Anchor(c.getLinkName(), false,
             c.toBranch(new Branch.NameKey(getProjectKey(), k.ref()))));
       }
+      if (k.actions() != null) {
+        k.actions().copyKeysIntoChildren("id");
+        for (ActionInfo a : Natives.asList(k.actions().values())) {
+          actionsPanel.add(new ActionButton(getProjectKey(), k, a));
+        }
+      }
+      table.setWidget(row, 4, actionsPanel);
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       String iconCellStyle = Gerrit.RESOURCES.css().iconCell();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 2b50cac..0f95f14 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -34,10 +34,11 @@
 import com.google.gerrit.client.ui.NpIntTextBox;
 import com.google.gerrit.client.ui.OnEditEnabler;
 import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ChangeHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -185,14 +186,14 @@
     grid.addHeader(new SmallHeading(Util.C.headingProjectOptions()));
 
     state = new ListBox();
-    for (final Project.State stateValue : Project.State.values()) {
+    for (ProjectState stateValue : ProjectState.values()) {
       state.addItem(Util.toLongString(stateValue), stateValue.name());
     }
     saveEnabler.listenTo(state);
     grid.add(Util.C.headingProjectState(), state);
 
     submitType = new ListBox();
-    for (final Project.SubmitType type : Project.SubmitType.values()) {
+    for (final SubmitType type : SubmitType.values()) {
       submitType.addItem(Util.toLongString(type), type.name());
     }
     submitType.addChangeHandler(new ChangeHandler() {
@@ -238,7 +239,7 @@
    * content merge the useContentMerge checkbox gets disabled.
    */
   private void setEnabledForUseContentMerge() {
-    if (SubmitType.FAST_FORWARD_ONLY.equals(Project.SubmitType
+    if (SubmitType.FAST_FORWARD_ONLY.equals(SubmitType
         .valueOf(submitType.getValue(submitType.getSelectedIndex())))) {
       contentMerge.setEnabled(false);
       InheritedBooleanInfo b = InheritedBooleanInfo.create();
@@ -263,7 +264,7 @@
     grid.addHtml(Util.C.useSignedOffBy(), signedOffBy);
   }
 
-  private void setSubmitType(final Project.SubmitType newSubmitType) {
+  private void setSubmitType(final SubmitType newSubmitType) {
     int index = -1;
     if (submitType != null) {
       for (int i = 0; i < submitType.getItemCount(); i++) {
@@ -277,7 +278,7 @@
     }
   }
 
-  private void setState(final Project.State newState) {
+  private void setState(final ProjectState newState) {
     if (state != null) {
       for (int i = 0; i < state.getItemCount(); i++) {
         if (newState.name().equals(state.getValue(i))) {
@@ -569,8 +570,8 @@
         getBool(contributorAgreements), getBool(contentMerge),
         getBool(signedOffBy), getBool(requireChangeID),
         maxObjectSizeLimit.getText().trim(),
-        Project.SubmitType.valueOf(submitType.getValue(submitType.getSelectedIndex())),
-        Project.State.valueOf(state.getValue(state.getSelectedIndex())),
+        SubmitType.valueOf(submitType.getValue(submitType.getSelectedIndex())),
+        ProjectState.valueOf(state.getValue(state.getSelectedIndex())),
         getPluginConfigValues(), new GerritCallback<ConfigInfo>() {
           @Override
           public void onSuccess(ConfigInfo result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 1696c43..5b8fe38 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -15,7 +15,8 @@
 package com.google.gerrit.client.admin;
 
 import com.google.gerrit.common.data.ProjectAdminService;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gwt.core.client.GWT;
 import com.google.gwtjsonrpc.client.JsonUtil;
 
@@ -31,7 +32,7 @@
     AdminResources.I.css().ensureInjected();
   }
 
-  public static String toLongString(final Project.SubmitType type) {
+  public static String toLongString(final SubmitType type) {
     if (type == null) {
       return "";
     }
@@ -51,7 +52,7 @@
     }
   }
 
-  public static String toLongString(final Project.State type) {
+  public static String toLongString(final ProjectState type) {
     if (type == null) {
       return "";
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
index 453a3f2..7490d82 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.projects.BranchInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
@@ -137,6 +138,7 @@
   final native void set(ActionInfo a) /*-{ this.action=a; }-*/;
   final native void set(ChangeInfo c) /*-{ this.change=c; }-*/;
   final native void set(Project.NameKey p) /*-{ this.project=p; }-*/;
+  final native void set(BranchInfo b) /*-{ this.branch=b }-*/;
   final native void set(RevisionInfo r) /*-{ this.revision=r; }-*/;
 
   final native void button(ActionButton b) /*-{ this._b=b; }-*/;
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 976dc0c..8da896e 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
@@ -43,6 +43,7 @@
       change_actions: {},
       revision_actions: {},
       project_actions: {},
+      branch_actions: {},
 
       getPluginName: @com.google.gerrit.client.api.ApiGlue::getPluginName(),
       injectCss: @com.google.gwt.dom.client.StyleInjector::inject(Ljava/lang/String;),
@@ -72,6 +73,7 @@
         if ('change' == t) this.change_actions[i]=c;
         else if ('revision' == t) this.revision_actions[i]=c;
         else if ('project' == t) this.project_actions[i]=c;
+        else if ('branch' == t) this.branch_actions[i]=c;
         else if ('screen' == t) _screen(p,t,c);
       },
       screen: function(r,c){this._screen(this.getPluginName(),r,c)},
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
index 53a3784..0e4048d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
@@ -21,50 +21,54 @@
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.Location;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
 class DefaultActions {
   static void invoke(ChangeInfo change, ActionInfo action, RestApi api) {
-    final Change.Id id = change.legacy_id();
-    AsyncCallback<JavaScriptObject> cb = new GerritCallback<JavaScriptObject>() {
-      @Override
-      public void onSuccess(JavaScriptObject msg) {
-        if (NativeString.is(msg)) {
-          NativeString str = (NativeString) msg;
-          if (!str.asString().isEmpty()) {
-            Window.alert(str.asString());
-          }
-        }
-        Gerrit.display(PageLinks.toChange(id));
-      }
-    };
-    invoke(action, api, cb);
+    invoke(action, api, callback(PageLinks.toChange(change.legacy_id())));
   }
 
-  static void invokeProjectAction(final Project.NameKey project,
-      ActionInfo action, RestApi api) {
-    AsyncCallback<JavaScriptObject> cb = new GerritCallback<JavaScriptObject>() {
+  static void invoke(Project.NameKey project, ActionInfo action, RestApi api) {
+    invoke(action, api, callback(PageLinks.toProject(project)));
+  }
+
+  private static AsyncCallback<JavaScriptObject> callback(final String target) {
+    return new GerritCallback<JavaScriptObject>() {
       @Override
-      public void onSuccess(JavaScriptObject msg) {
-        if (NativeString.is(msg)) {
-          NativeString str = (NativeString) msg;
-          if (!str.asString().isEmpty()) {
-            Window.alert(str.asString());
-          }
+      public void onSuccess(JavaScriptObject in) {
+        UiResult result = asUiResult(in);
+        if (result.alert() != null) {
+          Window.alert(result.alert());
         }
-        Gerrit.display(PageLinks.toProject(project));
+
+        if (result.redirectUrl() != null && result.openWindow()) {
+          Window.open(result.redirectUrl(), "_blank", null);
+        } else if (result.redirectUrl() != null) {
+          Location.assign(result.redirectUrl());
+        } else {
+          Gerrit.display(target);
+        }
+      }
+
+      private UiResult asUiResult(JavaScriptObject in) {
+        if (NativeString.is(in)) {
+          String str = ((NativeString) in).asString();
+          return str.isEmpty() ? UiResult.none() : UiResult.alert(str);
+        }
+        return in.cast();
       }
     };
-    invoke(action, api, cb);
   }
 
   private static void invoke(ActionInfo action, RestApi api,
       AsyncCallback<JavaScriptObject> cb) {
-    if ("PUT".equalsIgnoreCase(action.method())) {
+    if ("GET".equalsIgnoreCase(action.method())) {
+      api.get(cb);
+    } else if ("PUT".equalsIgnoreCase(action.method())) {
       api.put(JavaScriptObject.createObject(), cb);
     } else if ("DELETE".equalsIgnoreCase(action.method())) {
       api.delete(cb);
@@ -75,4 +79,16 @@
 
   private DefaultActions() {
   }
+
+  private static class UiResult extends JavaScriptObject {
+    static native UiResult alert(String m) /*-{ return {'alert':m} }-*/;
+    static native UiResult none() /*-{ return {} }-*/;
+
+    final native String alert() /*-{ return this.alert }-*/;
+    final native String redirectUrl() /*-{ return this.url }-*/;
+    final native boolean openWindow() /*-{ return this.open_window || false }-*/;
+
+    protected UiResult() {
+    }
+  }
 }
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
index 8b5c93e..f0fb436 100644
--- 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
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gwt.core.client.Callback;
@@ -23,6 +24,7 @@
 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.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.DialogBox;
 import com.google.gwtexpui.progress.client.ProgressBar;
 
@@ -33,11 +35,12 @@
   private static final int MAX_LOAD_TIME_MILLIS = 5000;
   private static PluginLoader self;
 
-  public static void load(List<String> plugins, final String token) {
+  public static void load(List<String> plugins,
+      AsyncCallback<VoidResult> callback) {
     if (plugins == null || plugins.isEmpty()) {
-      Gerrit.display(token);
+      callback.onSuccess(VoidResult.create());
     } else {
-      self = new PluginLoader(token);
+      self = new PluginLoader(callback);
       self.load(plugins);
       self.startTimers();
       self.center();
@@ -48,16 +51,16 @@
     self.loadedOne();
   }
 
-  private final String token;
+  private final AsyncCallback<VoidResult> callback;
   private ProgressBar progress;
   private Timer show;
   private Timer update;
   private Timer timeout;
   private boolean visible;
 
-  private PluginLoader(String tokenToDisplay) {
+  private PluginLoader(AsyncCallback<VoidResult> cb) {
     super(/* auto hide */false, /* modal */true);
-    token = tokenToDisplay;
+    callback = cb;
     progress = new ProgressBar(Gerrit.C.loadingPlugins());
 
     setStyleName(Gerrit.RESOURCES.css().errorDialog());
@@ -139,7 +142,7 @@
       }
     }
 
-    Gerrit.display(token);
+    callback.onSuccess(VoidResult.create());
   }
 
   private boolean hadFailures() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
index bce691c..3f2e794 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.client.actions.ActionButton;
 import com.google.gerrit.client.actions.ActionInfo;
+import com.google.gerrit.client.projects.BranchInfo;
 import com.google.gerrit.client.projects.ProjectApi;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.reviewdb.client.Project;
@@ -24,10 +25,31 @@
 public class ProjectGlue {
   public static void onAction(
       Project.NameKey project,
+      BranchInfo branch,
+      ActionInfo action,
+      ActionButton button) {
+    RestApi api = ProjectApi.project(project)
+        .view("branches").id(branch.ref())
+        .view(action.id());
+    JavaScriptObject f = branchAction(action.id());
+    if (f != null) {
+      ActionContext c = ActionContext.create(api);
+      c.set(action);
+      c.set(project);
+      c.set(branch);
+      c.button(button);
+      ApiGlue.invoke(f, c);
+    } else {
+      DefaultActions.invoke(project, action, api);
+    }
+  }
+
+  public static void onAction(
+      Project.NameKey project,
       ActionInfo action,
       ActionButton button) {
     RestApi api = ProjectApi.project(project).view(action.id());
-    JavaScriptObject f = get(action.id());
+    JavaScriptObject f = projectAction(action.id());
     if (f != null) {
       ActionContext c = ActionContext.create(api);
       c.set(action);
@@ -35,14 +57,18 @@
       c.button(button);
       ApiGlue.invoke(f, c);
     } else {
-      DefaultActions.invokeProjectAction(project, action, api);
+      DefaultActions.invoke(project, action, api);
     }
   }
 
-  private static final native JavaScriptObject get(String id) /*-{
+  private static final native JavaScriptObject projectAction(String id) /*-{
     return $wnd.Gerrit.project_actions[id];
   }-*/;
 
+  private static final native JavaScriptObject branchAction(String id) /*-{
+    return $wnd.Gerrit.branch_actions[id];
+  }-*/;
+
   private ProjectGlue() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowDown.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowDown.png
new file mode 100644
index 0000000..ba67de7
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowDown.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowUp.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowUp.png
new file mode 100644
index 0000000..5674d6c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/arrowUp.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index e0763e0..fe61b2a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -48,11 +48,10 @@
 import com.google.gerrit.client.ui.UserActivityMonitor;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
@@ -203,7 +202,8 @@
     RestApi call = ChangeApi.detail(changeId.get());
     ChangeList.addOptions(call, EnumSet.of(
       ListChangesOption.CURRENT_ACTIONS,
-      ListChangesOption.ALL_REVISIONS));
+      ListChangesOption.ALL_REVISIONS,
+      ListChangesOption.WEB_LINKS));
     if (!fg) {
       call.background();
     }
@@ -851,7 +851,7 @@
 
   private void renderSubmitType(String action) {
     try {
-      SubmitType type = Project.SubmitType.valueOf(action);
+      SubmitType type = SubmitType.valueOf(action);
       submitActionText.setInnerText(
           com.google.gerrit.client.admin.Util.toLongString(type));
     } catch (IllegalArgumentException e) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 931a94b..6400b2b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -21,24 +21,28 @@
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
 import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.changes.ChangeInfo.WebLinkInfo;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.TableCellElement;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
@@ -53,7 +57,7 @@
 
   @UiField Style style;
   @UiField CopyableLabel commitName;
-  @UiField AnchorElement browserLink;
+  @UiField TableCellElement webLinkCell;
   @UiField InlineHyperlink authorNameEmail;
   @UiField Element authorDate;
   @UiField InlineHyperlink committerNameEmail;
@@ -100,14 +104,29 @@
         committerDate, change.status());
     text.setHTML(commentLinkProcessor.apply(
         new SafeHtmlBuilder().append(commit.message()).linkify()));
+    setWebLinks(change, revision, revInfo);
+  }
 
+  private void setWebLinks(ChangeInfo change, String revision,
+      RevisionInfo revInfo) {
     GitwebLink gw = Gerrit.getGitwebLink();
     if (gw != null && gw.canLink(revInfo)) {
-      browserLink.setInnerText(gw.getLinkName());
-      browserLink.setHref(gw.toRevision(change.project(), revision));
-    } else {
-      UIObject.setVisible(browserLink, false);
+      addWebLink(gw.toRevision(change.project(), revision), gw.getLinkName());
     }
+
+    JsArray<WebLinkInfo> links = revInfo.web_links();
+    if (links != null) {
+      for (WebLinkInfo link : Natives.asList(links)) {
+        addWebLink(link.url(), parenthesize(link.name()));
+      }
+    }
+  }
+
+  private void addWebLink(String href, String name) {
+    AnchorElement a = DOM.createAnchor().cast();
+    a.setHref(href);
+    a.setInnerText(name);
+    webLinkCell.appendChild(a);
   }
 
   private static void formatLink(GitPerson person, InlineHyperlink name,
@@ -118,6 +137,14 @@
     date.setInnerText(FormatUtil.mediumFormat(person.date()));
   }
 
+  private static String parenthesize(String str) {
+    return new StringBuilder()
+        .append("(")
+        .append(str)
+        .append(")")
+        .toString();
+  }
+
   private static String renderName(GitPerson person) {
     return person.name() + " <" + person.email() + ">";
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
index c02e9f8..7ca66f9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
@@ -79,6 +79,10 @@
       top: 0px;
       right: -16px;
      }
+     <!-- To make room for the copyableLabel from the adjacent column -->
+    .webLinkCell a:first-child {
+      margin-left:16px;
+    }
   </ui:style>
   <g:HTMLPanel>
     <g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'>
@@ -114,7 +118,7 @@
       <tr>
         <th><ui:msg>Commit</ui:msg></th>
         <td><clippy:CopyableLabel styleName='{style.clippy}' ui:field='commitName'/></td>
-        <td><a style="margin-left:16px;" ui:field='browserLink' href=""/></td>
+        <td ui:field='webLinkCell' class='{style.webLinkCell}'></td>
       </tr>
       <tr>
         <th><ui:msg>Change-Id</ui:msg></th>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
index 929bf90..09335c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
@@ -46,7 +46,6 @@
 import java.util.List;
 
 class DownloadBox extends VerticalPanel {
-  private final static String ARCHIVE[] = {"tar", "tbz2", "tgz", "txz"};
   private final ChangeInfo change;
   private final String revision;
   private final PatchSet.Id psId;
@@ -149,8 +148,13 @@
   }
 
   private void insertArchive() {
-    List<Anchor> formats = new ArrayList<>(ARCHIVE.length);
-    for (String f : ARCHIVE) {
+    List<String> activated = Gerrit.getConfig().getArchiveFormats();
+    if (activated.isEmpty()) {
+      return;
+    }
+
+    List<Anchor> anchors = new ArrayList<>(activated.size());
+    for (String f : activated) {
       Anchor archive = new Anchor(f);
       archive.setHref(new RestApi("/changes/")
           .id(psId.getParentKey().get())
@@ -159,11 +163,11 @@
           .view("archive")
           .addParameter("format", f)
           .url());
-      formats.add(archive);
+      anchors.add(archive);
     }
 
     HorizontalPanel p = new HorizontalPanel();
-    Iterator<Anchor> it = formats.iterator();
+    Iterator<Anchor> it = anchors.iterator();
     while (it.hasNext()) {
       Anchor a = it.next();
       p.add(a);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
index 57ba4b9..10a140d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -146,6 +146,12 @@
     }
   }
 
+  @UiHandler("addme")
+  void onAddMe(ClickEvent e) {
+    String accountId = String.valueOf(Gerrit.getUserAccountInfo()._account_id());
+    addReviewer(accountId, false);
+  }
+
   @UiHandler("cancel")
   void onCancel(ClickEvent e) {
     openForm.setVisible(true);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
index dd2ef78..4e3b0c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
@@ -72,6 +72,9 @@
         <g:Button ui:field='add' styleName='{res.style.button}'>
           <div>Add</div>
         </g:Button>
+        <g:Button ui:field='addme' styleName='{res.style.button}'>
+          <div>Add Me</div>
+        </g:Button>
         <g:Button ui:field='cancel'
             styleName='{res.style.button}'
             addStyleNames='{style.cancel}'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index bfe70b8..a627725 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -218,6 +218,7 @@
 
     public final native boolean has_fetch() /*-{ return this.hasOwnProperty('fetch') }-*/;
     public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/;
+    public final native JsArray<WebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
 
     public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
       Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {
@@ -296,4 +297,11 @@
     protected IncludedInInfo() {
     }
   }
+
+  public static class WebLinkInfo extends JavaScriptObject {
+    public final native String name() /*-{ return this.name; }-*/;
+    public final native String url() /*-{ return this.url; }-*/;
+    protected WebLinkInfo() {
+    }
+  }
 }
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 1d760f9..9d9078d 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
@@ -42,6 +42,7 @@
 import com.google.gerrit.common.data.ChangeInfo;
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
@@ -50,7 +51,6 @@
 import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gerrit.reviewdb.client.Patch.PatchType;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.dom.client.ChangeEvent;
@@ -311,7 +311,7 @@
             @Override
             public void onSuccess(NativeString result) {
               event.getValue().setSubmitTypeRecord(SubmitTypeRecord.OK(
-                  Project.SubmitType.valueOf(result.asString())));
+                  SubmitType.valueOf(result.asString())));
             }
             public void onFailure(Throwable caught) {}
           }));
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 38a8cf9..10b7ac7 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
@@ -36,11 +36,11 @@
 import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.SubmitType;
 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.client.Project;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -169,7 +169,7 @@
         @Override
         public void onSuccess(NativeString result) {
           submitTypeRecord = SubmitTypeRecord.OK(
-              Project.SubmitType.valueOf(result.asString()));
+              SubmitType.valueOf(result.asString()));
         }
         public void onFailure(Throwable caught) {}
       }));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
index 9cb6c37..5dedaf0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.config;
 
+import com.google.gerrit.client.account.Preferences;
 import com.google.gerrit.client.extensions.TopMenuList;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.RestApi;
@@ -32,4 +33,8 @@
   public static void topMenus(AsyncCallback<TopMenuList> cb) {
     new RestApi("/config/server/top-menus").get(cb);
   }
+
+  public static void defaultPreferences(AsyncCallback<Preferences> cb) {
+    new RestApi("/config/server/preferences").get(cb);
+  }
 }
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 364d94c..3943b69 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
@@ -311,7 +311,7 @@
         .on("N", maybeNextVimSearch(cm))
         .on("P", chunkManager.diffChunkNav(cm, Direction.PREV))
         .on("Shift-M", header.reviewedAndNext())
-        .on("Shift-N", commentManager.commentNav(cm, Direction.NEXT))
+        .on("Shift-N", maybePrevVimSearch(cm))
         .on("Shift-P", commentManager.commentNav(cm, Direction.PREV))
         .on("Shift-O", commentManager.openCloseAll(cm))
         .on("Shift-Left", moveCursorToSide(cm, DisplaySide.A))
@@ -781,6 +781,19 @@
     };
   }
 
+  private Runnable maybePrevVimSearch(final CodeMirror cm) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        if (cm.hasVimSearchHighlight()) {
+          CodeMirror.handleVimKey(cm, "N");
+        } else {
+          commentManager.commentNav(cm, Direction.NEXT).run();
+        }
+      }
+    };
+  }
+
   private Runnable maybeNextVimSearch(final CodeMirror cm) {
     return new Runnable() {
       @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java
index 22bb981..ba43068 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java
@@ -17,11 +17,21 @@
 import com.google.gwt.core.client.JavaScriptObject;
 
 public class TopMenuItem extends JavaScriptObject {
+  public static TopMenuItem create(String name, String url) {
+    TopMenuItem i = createObject().cast();
+    i.name(name);
+    i.url(url);
+    return i;
+  }
+
   public final native String getName() /*-{ return this.name; }-*/;
   public final native String getUrl() /*-{ return this.url; }-*/;
   public final native String getTarget() /*-{ return this.target; }-*/;
   public final native String getId() /*-{ return this.id; }-*/;
 
+  public final native void name(String n) /*-{ this.name = n }-*/;
+  public final native void url(String u) /*-{ this.url = u }-*/;
+
   protected TopMenuItem() {
   }
 }
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 77daccf..3c811a8 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
@@ -1628,3 +1628,13 @@
   padding-top: 5px;
   padding-left: 5px;
 }
+
+/* StringListPanel */
+.stringListPanelButtons {
+  margin-left: 0.5em;
+}
+.stringListPanelButtons .gwt-Button {
+  margin-right: 2em;
+  font-size: 7pt;
+  padding: 1px;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/listAdd.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/listAdd.png
new file mode 100644
index 0000000..1aa7f09
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/listAdd.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
index 3f79afe..849863e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.client.projects;
 
+import com.google.gerrit.client.actions.ActionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gwt.core.client.JavaScriptObject;
 
@@ -27,6 +29,7 @@
   public final native String ref() /*-{ return this.ref; }-*/;
   public final native String revision() /*-{ return this.revision; }-*/;
   public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
+  public final native NativeMap<ActionInfo> actions() /*-{ return this.actions }-*/;
 
   protected BranchInfo() {
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 1d6e6b2..e3c77b8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -17,9 +17,9 @@
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
@@ -63,11 +63,11 @@
   private final native String submit_typeRaw()
   /*-{ return this.submit_type }-*/;
 
-  public final Project.State state() {
+  public final ProjectState state() {
     if (stateRaw() == null) {
-      return Project.State.ACTIVE;
+      return ProjectState.ACTIVE;
     }
-    return Project.State.valueOf(stateRaw());
+    return ProjectState.valueOf(stateRaw());
   }
   private final native String stateRaw()
   /*-{ return this.state }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 63f7e15..4762027 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -19,9 +19,10 @@
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -85,7 +86,7 @@
       InheritableBoolean useContributorAgreements,
       InheritableBoolean useContentMerge, InheritableBoolean useSignedOffBy,
       InheritableBoolean requireChangeId, String maxObjectSizeLimit,
-      SubmitType submitType, Project.State state,
+      SubmitType submitType, ProjectState state,
       Map<String, Map<String, ConfigParameterValue>> pluginConfigValues,
       AsyncCallback<ConfigInfo> cb) {
     ConfigInput in = ConfigInput.create();
@@ -217,7 +218,7 @@
     private final native void setSubmitTypeRaw(String t)
     /*-{ if(t)this.submit_type=t; }-*/;
 
-    final void setState(Project.State s) {
+    final void setState(ProjectState s) {
       setStateRaw(s.name());
     }
     private final native void setStateRaw(String s)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
index cab45b5..bd50e06 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.projects;
 
+import com.google.gerrit.extensions.api.projects.ProjectState;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.ui.SuggestOracle;
@@ -28,8 +29,8 @@
   public final native String name() /*-{ return this.name; }-*/;
   public final native String description() /*-{ return this.description; }-*/;
 
-  public final Project.State state() {
-    return Project.State.valueOf(getStringState());
+  public final ProjectState state() {
+    return ProjectState.valueOf(getStringState());
   }
 
   private final native String getStringState() /*-{ return this.state; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java
index bc88eed..deca808 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuBar.java
@@ -41,10 +41,12 @@
   }
 
   public void addItem(final LinkMenuItem i) {
+    i.setMenuBar(this);
     add(i);
   }
 
   public void insertItem(final LinkMenuItem i, int beforeIndex) {
+    i.setMenuBar(this);
     insert(i, beforeIndex);
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java
index 20569c3..29c053b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/LinkMenuItem.java
@@ -19,6 +19,8 @@
 import com.google.gwt.dom.client.AnchorElement;
 
 public class LinkMenuItem extends InlineHyperlink implements ScreenLoadHandler {
+  private LinkMenuBar bar;
+
   public LinkMenuItem(final String text, final String targetHistoryToken) {
     super(text, targetHistoryToken);
     setStyleName(Gerrit.RESOURCES.css().menuItem());
@@ -32,11 +34,20 @@
     AnchorElement.as(getElement()).blur();
   }
 
+  public void setMenuBar(LinkMenuBar bar) {
+    this.bar = bar;
+  }
+
   public void onScreenLoad(ScreenLoadEvent event) {
-    if (event.getScreen().getToken().equals(getTargetHistoryToken())){
+    if (match(event.getScreen().getToken())) {
+      Gerrit.selectMenu(bar);
       addStyleName(Gerrit.RESOURCES.css().activeRow());
     } else {
       removeStyleName(Gerrit.RESOURCES.css().activeRow());
     }
   }
+
+  protected boolean match(String token) {
+    return token.equals(getTargetHistoryToken());
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
new file mode 100644
index 0000000..2917505
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.admin.ProjectScreen;
+import com.google.gerrit.reviewdb.client.Project;
+
+public class ProjectLinkMenuItem extends LinkMenuItem {
+  private final String panel;
+
+  public ProjectLinkMenuItem(String text, String panel) {
+    super(text, "");
+    this.panel = panel;
+  }
+
+  @Override
+  public void onScreenLoad(ScreenLoadEvent event) {
+    Screen screen = event.getScreen();
+    Project.NameKey projectKey;
+    if (screen instanceof ProjectScreen) {
+      projectKey = ((ProjectScreen)screen).getProjectKey();
+    } else {
+      projectKey = ProjectScreen.getSavedKey();
+    }
+
+    if (projectKey != null) {
+      setVisible(true);
+      setTargetHistoryToken(Dispatcher.toProjectAdmin(projectKey, panel));
+    } else {
+      setVisible(false);
+    }
+    super.onScreenLoad(event);
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index 7f6a2df..28a9190 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -14,11 +14,16 @@
 
 package com.google.gerrit.httpd;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.common.data.GitwebConfig;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.GetArchive;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AuthConfig;
@@ -46,6 +51,7 @@
   private final Config cfg;
   private final AuthConfig authConfig;
   private final DownloadConfig downloadConfig;
+  private final GetArchive.AllowedFormats archiveFormats;
   private final GitWebConfig gitWebConfig;
   private final AllProjectsName wildProject;
   private final SshInfo sshInfo;
@@ -60,11 +66,13 @@
       final AuthConfig ac, final GitWebConfig gwc, final AllProjectsName wp,
       final SshInfo si, final ContactStore cs,
       final ServletContext sc, final DownloadConfig dc,
+      final GetArchive.AllowedFormats af,
       final @AnonymousCowardName String acn) {
     realm = r;
     cfg = gsc;
     authConfig = ac;
     downloadConfig = dc;
+    archiveFormats = af;
     gitWebConfig = gwc;
     sshInfo = si;
     wildProject = wp;
@@ -86,6 +94,7 @@
         config.setRegisterUrl(cfg.getString("auth", null, "registerurl"));
         config.setRegisterText(cfg.getString("auth", null, "registertext"));
         config.setEditFullNameUrl(cfg.getString("auth", null, "editFullNameUrl"));
+        config.setHttpPasswordSettingsEnabled(!authConfig.isGitBasicAuth());
         break;
 
       case CUSTOM_EXTENSION:
@@ -127,6 +136,15 @@
         "gerrit", null, "changeScreen",
         AccountGeneralPreferences.ChangeScreen.CHANGE_SCREEN2));
     config.setLargeChangeSize(cfg.getInt("change", "largeChange", 500));
+    config.setArchiveFormats(Lists.newArrayList(Iterables.transform(
+        archiveFormats.getAllowed(),
+        new Function<ArchiveFormat, String>() {
+          @Override
+          public String apply(ArchiveFormat in) {
+            return in.getShortName();
+          }
+        })));
+
     config.setNewFeatures(cfg.getBoolean("gerrit", "enableNewFeatures", true));
 
     final String reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
@@ -134,8 +152,6 @@
         reportBugUrl : "http://code.google.com/p/gerrit/issues/list");
     config.setReportBugText(cfg.getString("gerrit", null, "reportBugText"));
 
-    config.setGitBasicAuth(authConfig.isGitBasicAuth());
-
     final Set<Account.FieldName> fields = new HashSet<Account.FieldName>();
     for (final Account.FieldName n : Account.FieldName.values()) {
       if (realm.allowsEdit(n)) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index 0e41ef7..3a45089 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -138,6 +138,14 @@
       return false;
     }
 
+    if (!authConfig.isLdapAuthType()
+        && !passwordMatchesTheUserGeneratedOne(who, username, password)) {
+      log.warn("Authentication failed for " + username
+          + ": password does not match the one stored in Gerrit");
+      rsp.sendError(SC_UNAUTHORIZED);
+      return false;
+    }
+
     AuthRequest whoAuth = AuthRequest.forUser(username);
     whoAuth.setPassword(password);
 
@@ -167,6 +175,13 @@
     }
   }
 
+  private boolean passwordMatchesTheUserGeneratedOne(AccountState who,
+      String username, String password) {
+    String accountPassword = who.getPassword(username);
+    return accountPassword != null && password != null
+        && accountPassword.equals(password);
+  }
+
   private String encoding(HttpServletRequest req) {
     return Objects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
   }
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index 33a3534..6c39743 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -50,7 +50,7 @@
     System.exit(mainImpl(argv));
   }
 
-  private static int mainImpl(final String argv[]) throws Exception {
+  public static int mainImpl(final String argv[]) throws Exception {
     if (argv.length == 0) {
       File me;
       try {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 294aa35..c790740 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -125,14 +125,18 @@
     Version lucene43 = Version.LUCENE_43;
     @SuppressWarnings("deprecation")
     Version lucene44 = Version.LUCENE_44;
+    @SuppressWarnings("deprecation")
+    Version lucene46 = Version.LUCENE_46;
     for (Map.Entry<Integer, Schema<ChangeData>> e
         : ChangeSchemas.ALL.entrySet()) {
       if (e.getKey() <= 3) {
         versions.put(e.getValue(), lucene43);
       } else if (e.getKey() <= 5) {
         versions.put(e.getValue(), lucene44);
+      } else if (e.getKey() <= 8) {
+        versions.put(e.getValue(), lucene46);
       } else {
-        versions.put(e.getValue(), Version.LUCENE_46);
+        versions.put(e.getValue(), Version.LUCENE_47);
       }
     }
     LUCENE_VERSIONS = versions.build();
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index b7162ed..d1c468f 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -4,7 +4,6 @@
   'init/AllProjectsConfig.java',
   'init/AllProjectsNameOnInitProvider.java',
   'util/ConsoleUI.java',
-  'util/Die.java',
   'init/InitFlags.java',
   'init/InitStep.java',
   'init/InitStep.java',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
index 76adadc..313408e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
@@ -19,13 +19,13 @@
 
 import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
+import com.google.gerrit.common.Die;
 import com.google.gerrit.pgm.init.InitFlags;
 import com.google.gerrit.pgm.init.InitModule;
 import com.google.gerrit.pgm.init.InstallPlugins;
 import com.google.gerrit.pgm.init.PluginsDistribution;
 import com.google.gerrit.pgm.init.SitePathInitializer;
 import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.pgm.util.Die;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.SitePath;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index e712caa..fb84cfa 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -17,12 +17,14 @@
 import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
 import static com.google.inject.Scopes.SINGLETON;
 
+import com.google.common.cache.Cache;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.DisabledChangeHooks;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -41,7 +43,7 @@
 import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.ChangeKindCache;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
 import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.change.MergeabilityChecksExecutor;
 import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
@@ -198,6 +200,8 @@
         // once, so don't worry about cache removal.
         bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
             .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+        bind(new TypeLiteral<DynamicMap<Cache<?, ?>>>() {})
+            .toInstance(DynamicMap.<Cache<?, ?>> emptyMap());
         bind(new TypeLiteral<List<CommentLinkInfo>>() {})
             .toProvider(CommentLinkProvider.class).in(SINGLETON);
         bind(String.class).annotatedWith(CanonicalWebUrl.class)
@@ -215,6 +219,7 @@
         install(GroupIncludeCacheImpl.module());
         install(ProjectCacheImpl.module());
         install(SectionSortCache.module());
+        install(ChangeKindCacheImpl.module());
         factory(CapabilityControl.Factory.class);
         factory(ChangeData.Factory.class);
         factory(ProjectState.Factory.class);
@@ -293,9 +298,6 @@
       DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
       DynamicSet.setOf(binder(), CommitValidationListener.class);
       factory(CommitValidators.Factory.class);
-
-      install(ChangeKindCache.module());
-
       install(new GitModule());
       install(new NoteDbModule());
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
new file mode 100644
index 0000000..4c65218a
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllUsersNameOnInitProvider.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class AllUsersNameOnInitProvider implements Provider<String> {
+  private final String name;
+
+  @Inject
+  AllUsersNameOnInitProvider(Section.Factory sections) {
+    String n = sections.get("gerrit", null).get("allUsers");
+    name = Objects.firstNonNull(
+        Strings.emptyToNull(n), AllUsersNameProvider.DEFAULT);
+  }
+
+  public String get() {
+    return name;
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
index 699daa8..8929a7b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.pgm.init;
 
-import static com.google.gerrit.pgm.init.InitUtil.chmod;
+import static com.google.gerrit.common.FileUtil.chmod;
 import static com.google.gerrit.pgm.init.InitUtil.die;
 import static com.google.gerrit.pgm.init.InitUtil.domainOf;
 import static com.google.gerrit.pgm.init.InitUtil.isAnyAddress;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index 66c2dd0b..9003b30 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.pgm.init;
 
-import static com.google.gerrit.pgm.init.InitUtil.chmod;
+import static com.google.gerrit.common.FileUtil.chmod;
 import static com.google.gerrit.pgm.init.InitUtil.die;
 import static com.google.gerrit.pgm.init.InitUtil.hostname;
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
index dde0b06..7e06b5a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
@@ -14,7 +14,10 @@
 
 package com.google.gerrit.pgm.init;
 
-import com.google.gerrit.pgm.util.Die;
+import static com.google.gerrit.common.FileUtil.chmod;
+import static com.google.gerrit.common.FileUtil.modified;
+
+import com.google.gerrit.common.Die;
 
 import org.eclipse.jgit.internal.storage.file.LockFile;
 import org.eclipse.jgit.lib.Constants;
@@ -33,7 +36,6 @@
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
-import java.util.Arrays;
 
 /** Utility functions to help initialize a site. */
 class InitUtil {
@@ -71,43 +73,12 @@
     }
   }
 
-  private static boolean modified(FileBasedConfig cfg) throws IOException {
-    byte[] curVers;
-    try {
-      curVers = IO.readFully(cfg.getFile());
-    } catch (FileNotFoundException notFound) {
-      return true;
-    }
-
-    byte[] newVers = Constants.encode(cfg.toText());
-    return !Arrays.equals(curVers, newVers);
-  }
-
   static void mkdir(final File path) {
     if (!path.isDirectory() && !path.mkdir()) {
       throw die("Cannot make directory " + path);
     }
   }
 
-  static void chmod(final int mode, final File path) {
-    path.setReadable(false, false /* all */);
-    path.setWritable(false, false /* all */);
-    path.setExecutable(false, false /* all */);
-
-    path.setReadable((mode & 0400) == 0400, true /* owner only */);
-    path.setWritable((mode & 0200) == 0200, true /* owner only */);
-    if (path.isDirectory() || (mode & 0100) == 0100) {
-      path.setExecutable(true, true /* owner only */);
-    }
-
-    if ((mode & 0044) == 0044) {
-      path.setReadable(true, false /* all */);
-    }
-    if ((mode & 0011) == 0011) {
-      path.setExecutable(true, false /* all */);
-    }
-  }
-
   static String version() {
     return com.google.gerrit.common.Version.getVersion();
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index 9555abd..b943ca3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -16,8 +16,8 @@
 
 import com.google.common.base.Strings;
 import com.google.common.io.Files;
+import com.google.gerrit.common.Die;
 import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.pgm.util.Die;
 import com.google.gerrit.pgm.util.IoUtil;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 82daf81..85e33e6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.pgm.init;
 
-import static com.google.gerrit.pgm.init.InitUtil.chmod;
+import static com.google.gerrit.common.FileUtil.chmod;
 import static com.google.gerrit.pgm.init.InitUtil.die;
 import static com.google.gerrit.pgm.init.InitUtil.extract;
 import static com.google.gerrit.pgm.init.InitUtil.mkdir;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java
index af65170..31fa7dd 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/AbstractProgram.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.pgm.util;
 
 
+import com.google.gerrit.common.Die;
 import com.google.gerrit.util.cli.CmdLineParser;
 import com.google.gerrit.util.cli.OptionHandlers;
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
index 9af95ab..3cbf047 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
@@ -16,6 +16,8 @@
 
 import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
 
+import com.google.gerrit.common.Die;
+
 import java.io.Console;
 import java.lang.reflect.InvocationTargetException;
 import java.util.Set;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
index da7704d..05c8f09 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm.util;
 
+import com.google.gerrit.common.Die;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.util.LogUtil;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
index 746355b..5cdf435 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm.util;
 
+import com.google.gerrit.common.Die;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GarbageCollection;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index 11968db..daef562 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -18,6 +18,7 @@
 import static com.google.inject.Stage.PRODUCTION;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.common.Die;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index d808e0f..06e42fb 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -59,4 +59,5 @@
     '//lib/bouncycastle:bcpg',
   ],
   visibility = ['PUBLIC'],
+  do_it_wrong = True,
 )
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index 401f126..8c7e261 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-archetype</artifactId>
-  <version>2.9-SNAPSHOT</version>
+  <version>2.10-SNAPSHOT</version>
   <name>Gerrit Code Review - Plugin Archetype</name>
 
   <properties>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
index 56e6c95..09f70e8 100644
--- a/gerrit-plugin-gwt-archetype/pom.xml
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-gwt-archetype</artifactId>
-  <version>2.9-SNAPSHOT</version>
+  <version>2.10-SNAPSHOT</version>
   <name>Gerrit Code Review - Web Ui GWT Plugin Archetype</name>
 
   <properties>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index f619f91..3fa83f4 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -25,7 +25,7 @@
       <defaultValue>http://code.google.com/p/gerrit/</defaultValue>
     </requiredProperty>
     <requiredProperty key="Gwt-Version">
-      <defaultValue>2.5.1</defaultValue>
+      <defaultValue>2.6.0</defaultValue>
     </requiredProperty>
 
     <requiredProperty key="gerritApiVersion">
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK
index 8022bd1..0b87bb1 100644
--- a/gerrit-plugin-gwtui/BUCK
+++ b/gerrit-plugin-gwtui/BUCK
@@ -51,8 +51,8 @@
   name = 'gwtui-api-javadoc',
   title = 'Gerrit Review GWT Extension API Documentation',
   pkg = 'com.google.gerrit',
-  paths = ['$SRCDIR/src/main/java'] + COMMON,
+  paths = ['src/main/java'] + COMMON,
   srcs = SRCS + glob(COMMON),
-  deps = DEPS + ['//gerrit-gwtui-common:client-src-lib'],
+  deps = DEPS + ['//gerrit-gwtui-common:client-lib2'],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
index eb32b11..b99130e 100644
--- a/gerrit-plugin-js-archetype/pom.xml
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-js-archetype</artifactId>
-  <version>2.9-SNAPSHOT</version>
+  <version>2.10-SNAPSHOT</version>
   <name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name>
 
   <properties>
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK
index b133f1a..370c869 100644
--- a/gerrit-reviewdb/BUCK
+++ b/gerrit-reviewdb/BUCK
@@ -5,6 +5,7 @@
   srcs = glob([SRC + 'client/**/*.java']),
   gwtxml = SRC + 'ReviewDB.gwt.xml',
   compile_deps = [
+    '//gerrit-extension-api:client',
     '//lib:gwtorm',
     '//lib:gwtorm_src'
   ],
@@ -15,6 +16,9 @@
   name = 'server',
   srcs = glob([SRC + '**/*.java']),
   resources = glob(['src/main/resources/**/*']),
-  deps = ['//lib:gwtorm'],
+  deps = [
+    '//gerrit-extension-api:api',
+    '//lib:gwtorm',
+  ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index 2257ea2..82e4053 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -103,6 +103,12 @@
     }
   }
 
+  public static AccountGeneralPreferences createDefault() {
+    AccountGeneralPreferences p = new AccountGeneralPreferences();
+    p.resetToDefaults();
+    return p;
+  }
+
   /** Number of changes to show in a screen. */
   @Column(id = 2)
   protected short maximumPageSize;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java
index 954b494..b5cef60 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/InheritedBoolean.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.reviewdb.client;
 
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
+import com.google.gerrit.extensions.common.InheritableBoolean;
 
 public class InheritedBoolean {
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index 46e9b22..1114813 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.reviewdb.client;
 
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gwtorm.client.Column;
 import com.google.gwtorm.client.StringKey;
 
@@ -65,32 +68,6 @@
     }
   }
 
-  public static enum SubmitType {
-    FAST_FORWARD_ONLY,
-
-    MERGE_IF_NECESSARY,
-
-    REBASE_IF_NECESSARY,
-
-    MERGE_ALWAYS,
-
-    CHERRY_PICK
-  }
-
-  public static enum State {
-    ACTIVE,
-
-    READ_ONLY,
-
-    HIDDEN
-  }
-
-  public static enum InheritableBoolean {
-    TRUE,
-    FALSE,
-    INHERIT
-  }
-
   protected NameKey name;
 
   protected String description;
@@ -101,7 +78,7 @@
 
   protected SubmitType submitType;
 
-  protected State state;
+  protected ProjectState state;
 
   protected NameKey parent;
 
@@ -123,7 +100,7 @@
   public Project(Project.NameKey nameKey) {
     name = nameKey;
     submitType = SubmitType.MERGE_IF_NECESSARY;
-    state = State.ACTIVE;
+    state = ProjectState.ACTIVE;
     useContributorAgreements = InheritableBoolean.INHERIT;
     useSignedOffBy = InheritableBoolean.INHERIT;
     requireChangeID = InheritableBoolean.INHERIT;
@@ -194,11 +171,11 @@
     submitType = type;
   }
 
-  public State getState() {
+  public ProjectState getState() {
     return state;
   }
 
-  public void setState(final State newState) {
+  public void setState(final ProjectState newState) {
     state = newState;
   }
 
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
index ede5c26..968cfde 100644
--- 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
@@ -14,6 +14,7 @@
 
 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/";
@@ -24,6 +25,9 @@
   /** Configuration settings for a project {@code refs/meta/config} */
   public static final String REFS_CONFIG = "refs/meta/config";
 
+  /** Preference settings for a user {@code refs/users} */
+  public static final String REFS_USER = "refs/users/";
+
   /** Configurations of project-specific dashboards (canned search queries). */
   public static final String REFS_DASHBOARDS = "refs/meta/dashboards/";
 
@@ -39,6 +43,20 @@
    */
   public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
 
+  public static String refsUsers(Account.Id accountId) {
+    StringBuilder r = new StringBuilder();
+    r.append(REFS_USER);
+    int account = accountId.get();
+    int m = account % 100;
+    if (m < 10) {
+      r.append('0');
+    }
+    r.append(m);
+    r.append('/');
+    r.append(account);
+    return r.toString();
+  }
+
   private RefNames() {
   }
 }
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
index 165c8be..0b7f2c1 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
@@ -101,6 +101,11 @@
 
 
 -- *********************************************************************
+-- PatchSetAccess
+CREATE INDEX patch_sets_byRevision
+ON patch_sets (revision);
+
+-- *********************************************************************
 -- PatchSetAncestorAccess
 --    @PrimaryKey covers: ancestorsOf
 --    covers:             descendantsOf
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index ad92293..25e3fae 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -152,6 +152,11 @@
 
 
 -- *********************************************************************
+-- PatchSetAccess
+CREATE INDEX patch_sets_byRevision
+ON patch_sets (revision);
+
+-- *********************************************************************
 -- PatchSetAncestorAccess
 --    @PrimaryKey covers: ancestorsOf
 --    covers:             descendantsOf
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 070764a..a75967a 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -47,6 +47,7 @@
     '//lib:parboiled-core',
     '//lib:pegdown',
     '//lib:protobuf',
+    '//lib:servlet-api-3_1',
     '//lib:velocity',
     '//lib/antlr:java_runtime',
     '//lib/commons:codec',
@@ -101,6 +102,16 @@
     '//lib/guice:guice-servlet',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
+    '//lib/log:impl_log4j',
+    '//lib/log:log4j',
+  ],
+  exported_deps = [
+    '//lib:easymock',
+    '//lib:powermock-api-easymock',
+    '//lib:powermock-api-support',
+    '//lib:powermock-core',
+    '//lib:powermock-module-junit4',
+    '//lib:powermock-module-junit4-common',
   ],
   visibility = ['PUBLIC'],
 )
@@ -188,15 +199,16 @@
     '//gerrit-reviewdb:server',
     '//gerrit-server/src/main/prolog:common',
     '//lib:args4j',
-    '//lib:easymock',
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:junit',
     '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
     '//lib/joda:joda-time',
     '//lib/prolog:prolog-cafe',
   ],
   source_under_test = [':server'],
+  visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index 14aa2e3..e68b29c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -81,6 +81,11 @@
     db.patchSetApprovals().insert(getForPatchSet(db, ctl, ps));
   }
 
+  Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
+      ChangeControl ctl, PatchSet.Id psId) throws OrmException {
+    return getForPatchSet(db, ctl, db.patchSets().get(psId));
+  }
+
   private Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
       ChangeControl ctl, PatchSet ps) throws OrmException {
     ChangeData cd = changeDataFactory.create(db, ctl);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index b648548..64b5169 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.ReviewerState;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -90,11 +91,14 @@
   }
 
   private final NotesMigration migration;
+  private final ApprovalCopier copier;
 
   @VisibleForTesting
   @Inject
-  public ApprovalsUtil(NotesMigration migration) {
+  public ApprovalsUtil(NotesMigration migration,
+      ApprovalCopier copier) {
     this.migration = migration;
+    this.copier = copier;
   }
 
   /**
@@ -225,23 +229,22 @@
     return notes.load().getApprovals();
   }
 
-  public List<PatchSetApproval> byPatchSet(ReviewDb db, ChangeNotes notes,
+  public Iterable<PatchSetApproval> byPatchSet(ReviewDb db, ChangeControl ctl,
       PatchSet.Id psId) throws OrmException {
     if (!migration.readPatchSetApprovals()) {
       return sortApprovals(db.patchSetApprovals().byPatchSet(psId));
     }
-    return notes.load().getApprovals().get(psId);
+    return copier.getForPatchSet(db, ctl, psId);
   }
 
-  public List<PatchSetApproval> byPatchSetUser(ReviewDb db,
-      ChangeNotes notes, PatchSet.Id psId, Account.Id accountId)
+  public Iterable<PatchSetApproval> byPatchSetUser(ReviewDb db,
+      ChangeControl ctl, PatchSet.Id psId, Account.Id accountId)
       throws OrmException {
     if (!migration.readPatchSetApprovals()) {
       return sortApprovals(
           db.patchSetApprovals().byPatchSetUser(psId, accountId));
     }
-    return ImmutableList.copyOf(
-        filterApprovals(byPatchSet(db, notes, psId), accountId));
+    return filterApprovals(byPatchSet(db, ctl, psId), accountId);
   }
 
   public PatchSetApproval getSubmitter(ReviewDb db, ChangeNotes notes,
@@ -250,7 +253,8 @@
       return null;
     }
     try {
-      return getSubmitter(c, byPatchSet(db, notes, c));
+      // Submit approval is never copied, so bypass expensive byPatchSet call.
+      return getSubmitter(c, byChange(db, notes).get(c));
     } catch (OrmException e) {
       return null;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
new file mode 100644
index 0000000..d2bc8b7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2014 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;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.inject.Inject;
+
+import java.util.List;
+
+public class WebLinks {
+
+  private DynamicSet<PatchSetWebLink> patchSetLinks;
+
+  @Inject
+  public WebLinks(final DynamicSet<PatchSetWebLink> patchSetLinks) {
+    this.patchSetLinks = patchSetLinks;
+  }
+
+  public Iterable<Link> getPatchSetLinks(final String project,
+      final String commit) {
+    List<Link> links = Lists.newArrayList();
+    for (PatchSetWebLink webLink : patchSetLinks) {
+      links.add(new Link(webLink.getLinkName(),
+          webLink.getPatchSetUrl(project, commit)));
+    }
+    return links;
+  }
+
+  public class Link {
+    public String name;
+    public String url;
+
+    public Link(String name, String url) {
+      this.name = name;
+      this.url = url;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
new file mode 100644
index 0000000..71c13a3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class WebLinksProvider implements Provider<WebLinks> {
+
+  private DynamicSet<PatchSetWebLink> patchSetLinks;
+
+  @Inject
+  public WebLinksProvider(DynamicSet<PatchSetWebLink> patchSetLinks) {
+    this.patchSetLinks = patchSetLinks;
+  }
+
+  @Override
+  public WebLinks get() {
+    WebLinks webLinks = new WebLinks(patchSetLinks);
+    return webLinks;
+  }
+}
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 3a0a27d..8ff4e03 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
@@ -23,6 +23,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -41,7 +42,6 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.ProjectJson;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.RefControl;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index 8eb6947..401846a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -14,9 +14,11 @@
 
 package com.google.gerrit.server.account;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
@@ -28,23 +30,52 @@
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
 public class GetPreferences implements RestReadView<AccountResource> {
+  private static final Logger log = LoggerFactory.getLogger(GetPreferences.class);
+
+  public static final String MY = "my";
+  public static final String KEY_URL = "url";
+  public static final String KEY_TARGET = "target";
+  public static final String KEY_ID = "id";
+
   private final Provider<CurrentUser> self;
   private final Provider<ReviewDb> db;
+  private final AllUsersName allUsersName;
+  private final GitRepositoryManager gitMgr;
 
   @Inject
-  GetPreferences(Provider<CurrentUser> self, Provider<ReviewDb> db) {
+  GetPreferences(Provider<CurrentUser> self, Provider<ReviewDb> db,
+      AllUsersName allUsersName,
+      GitRepositoryManager gitMgr) {
     this.self = self;
     this.db = db;
+    this.allUsersName = allUsersName;
+    this.gitMgr = gitMgr;
   }
 
   @Override
   public PreferenceInfo apply(AccountResource rsrc)
-      throws AuthException, ResourceNotFoundException, OrmException {
+      throws AuthException,
+      ResourceNotFoundException,
+      OrmException,
+      IOException,
+      ConfigInvalidException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("restricted to administrator");
@@ -53,13 +84,22 @@
     if (a == null) {
       throw new ResourceNotFoundException();
     }
-    return new PreferenceInfo(a.getGeneralPreferences());
+
+    Repository git = gitMgr.openRepository(allUsersName);
+    try {
+      VersionedAccountPreferences p =
+          VersionedAccountPreferences.forUser(rsrc.getUser().getAccountId());
+      p.load(git);
+      return new PreferenceInfo(a.getGeneralPreferences(), p, git);
+    } finally {
+      git.close();
+    }
   }
 
-  static class PreferenceInfo {
+  public static class PreferenceInfo {
     final String kind = "gerritcodereview#preferences";
 
-    short changesPerPage;
+    Short changesPerPage;
     Boolean showSiteHeader;
     Boolean useFlashClipboard;
     DownloadScheme downloadScheme;
@@ -74,23 +114,70 @@
     CommentVisibilityStrategy commentVisibilityStrategy;
     DiffView diffView;
     ChangeScreen changeScreen;
+    List<TopMenu.MenuItem> my;
 
-    PreferenceInfo(AccountGeneralPreferences p) {
-      changesPerPage = p.getMaximumPageSize();
-      showSiteHeader = p.isShowSiteHeader() ? true : null;
-      useFlashClipboard = p.isUseFlashClipboard() ? true : null;
-      downloadScheme = p.getDownloadUrl();
-      downloadCommand = p.getDownloadCommand();
-      copySelfOnEmail = p.isCopySelfOnEmails() ? true : null;
-      dateFormat = p.getDateFormat();
-      timeFormat = p.getTimeFormat();
-      reversePatchSetOrder = p.isReversePatchSetOrder() ? true : null;
-      showUsernameInReviewCategory = p.isShowUsernameInReviewCategory() ? true : null;
-      relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
-      sizeBarInChangeTable = p.isSizeBarInChangeTable() ? true : null;
-      commentVisibilityStrategy = p.getCommentVisibilityStrategy();
-      diffView = p.getDiffView();
-      changeScreen = p.getChangeScreen();
+    public PreferenceInfo(AccountGeneralPreferences p,
+        VersionedAccountPreferences v, Repository allUsers) {
+      if (p != null) {
+        changesPerPage = p.getMaximumPageSize();
+        showSiteHeader = p.isShowSiteHeader() ? true : null;
+        useFlashClipboard = p.isUseFlashClipboard() ? true : null;
+        downloadScheme = p.getDownloadUrl();
+        downloadCommand = p.getDownloadCommand();
+        copySelfOnEmail = p.isCopySelfOnEmails() ? true : null;
+        dateFormat = p.getDateFormat();
+        timeFormat = p.getTimeFormat();
+        reversePatchSetOrder = p.isReversePatchSetOrder() ? true : null;
+        showUsernameInReviewCategory = p.isShowUsernameInReviewCategory() ? true : null;
+        relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
+        sizeBarInChangeTable = p.isSizeBarInChangeTable() ? true : null;
+        commentVisibilityStrategy = p.getCommentVisibilityStrategy();
+        diffView = p.getDiffView();
+        changeScreen = p.getChangeScreen();
+      }
+      my = my(v, allUsers);
+    }
+
+    private List<TopMenu.MenuItem> my(VersionedAccountPreferences v,
+        Repository allUsers) {
+      List<TopMenu.MenuItem> my = my(v);
+      if (my.isEmpty() && !v.isDefaults()) {
+        try {
+          VersionedAccountPreferences d = VersionedAccountPreferences.forDefault();
+          d.load(allUsers);
+          my = my(d);
+        } catch (ConfigInvalidException | IOException e) {
+          log.warn("cannot read default preferences", e);
+        }
+      }
+      if (my.isEmpty()) {
+        my.add(new TopMenu.MenuItem("Changes", "#/dashboard/self", null));
+        my.add(new TopMenu.MenuItem("Drafts", "#/q/is:draft", null));
+        my.add(new TopMenu.MenuItem("Draft Comments", "#/q/has:draft", null));
+        my.add(new TopMenu.MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
+        my.add(new TopMenu.MenuItem("Starred Changes", "#/q/is:starred", null));
+      }
+      return my;
+    }
+
+    private List<TopMenu.MenuItem> my(VersionedAccountPreferences v) {
+      List<TopMenu.MenuItem> my = new ArrayList<>();
+      Config cfg = v.getConfig();
+      for (String subsection : cfg.getSubsections(MY)) {
+        String url = my(cfg, subsection, KEY_URL, "#/");
+        String target = my(cfg, subsection, KEY_TARGET,
+            url.startsWith("#") ? null : "_blank");
+        my.add(new TopMenu.MenuItem(
+            subsection, url, target,
+            my(cfg, subsection, KEY_ID, null)));
+      }
+      return my;
+    }
+
+    private static String my(Config cfg, String subsection, String key,
+        String defaultValue) {
+      String val = cfg.getString(MY, subsection, key);
+      return !Strings.isNullOrEmpty(val) ? val : defaultValue;
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index d0418eb..df985ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -14,60 +14,81 @@
 
 package com.google.gerrit.server.account;
 
+import static com.google.gerrit.server.account.GetPreferences.KEY_ID;
+import static com.google.gerrit.server.account.GetPreferences.KEY_TARGET;
+import static com.google.gerrit.server.account.GetPreferences.KEY_URL;
+import static com.google.gerrit.server.account.GetPreferences.MY;
+
+import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.SetPreferences.Input;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
 import java.util.Collections;
+import java.util.List;
 
 public class SetPreferences implements RestModifyView<AccountResource, Input> {
-  static class Input {
-    Short changesPerPage;
-    Boolean showSiteHeader;
-    Boolean useFlashClipboard;
-    DownloadScheme downloadScheme;
-    DownloadCommand downloadCommand;
-    Boolean copySelfOnEmail;
-    DateFormat dateFormat;
-    TimeFormat timeFormat;
-    Boolean reversePatchSetOrder;
-    Boolean showUsernameInReviewCategory;
-    Boolean relativeDateInChangeTable;
-    Boolean sizeBarInChangeTable;
-    CommentVisibilityStrategy commentVisibilityStrategy;
-    DiffView diffView;
-    ChangeScreen changeScreen;
+  public static class Input {
+    public Short changesPerPage;
+    public Boolean showSiteHeader;
+    public Boolean useFlashClipboard;
+    public DownloadScheme downloadScheme;
+    public DownloadCommand downloadCommand;
+    public Boolean copySelfOnEmail;
+    public DateFormat dateFormat;
+    public TimeFormat timeFormat;
+    public Boolean reversePatchSetOrder;
+    public Boolean showUsernameInReviewCategory;
+    public Boolean relativeDateInChangeTable;
+    public Boolean sizeBarInChangeTable;
+    public CommentVisibilityStrategy commentVisibilityStrategy;
+    public DiffView diffView;
+    public ChangeScreen changeScreen;
+    public List<TopMenu.MenuItem> my;
   }
 
   private final Provider<CurrentUser> self;
   private final AccountCache cache;
   private final ReviewDb db;
+  private final MetaDataUpdate.User metaDataUpdateFactory;
+  private final AllUsersName allUsersName;
 
   @Inject
-  SetPreferences(Provider<CurrentUser> self, AccountCache cache, ReviewDb db) {
+  SetPreferences(Provider<CurrentUser> self, AccountCache cache, ReviewDb db,
+      MetaDataUpdate.User metaDataUpdateFactory, AllUsersName allUsersName) {
     this.self = self;
     this.cache = cache;
     this.db = db;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.allUsersName = allUsersName;
   }
 
   @Override
   public GetPreferences.PreferenceInfo apply(AccountResource rsrc, Input i)
-      throws AuthException, ResourceNotFoundException, OrmException {
+      throws AuthException, ResourceNotFoundException, OrmException,
+      IOException, ConfigInvalidException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("restricted to administrator");
@@ -78,6 +99,8 @@
 
     Account.Id accountId = rsrc.getUser().getAccountId();
     AccountGeneralPreferences p;
+    VersionedAccountPreferences versionedPrefs;
+    MetaDataUpdate md = metaDataUpdateFactory.create(allUsersName);
     db.accounts().beginTransaction(accountId);
     try {
       Account a = db.accounts().get(accountId);
@@ -85,6 +108,9 @@
         throw new ResourceNotFoundException();
       }
 
+      versionedPrefs = VersionedAccountPreferences.forUser(accountId);
+      versionedPrefs.load(md);
+
       p = a.getGeneralPreferences();
       if (p == null) {
         p = new AccountGeneralPreferences();
@@ -139,10 +165,43 @@
 
       db.accounts().update(Collections.singleton(a));
       db.commit();
+      storeMyMenus(versionedPrefs, i.my);
+      versionedPrefs.commit(md);
       cache.evict(accountId);
+      return new GetPreferences.PreferenceInfo(
+          p, versionedPrefs,
+          md.getRepository());
     } finally {
+      md.close();
       db.rollback();
     }
-    return new GetPreferences.PreferenceInfo(p);
+  }
+
+  public static void storeMyMenus(VersionedAccountPreferences prefs,
+      List<TopMenu.MenuItem> my) {
+    Config cfg = prefs.getConfig();
+    if (my != null) {
+      unsetSection(cfg, MY);
+      for (TopMenu.MenuItem item : my) {
+        set(cfg, item.name, KEY_URL, item.url);
+        set(cfg, item.name, KEY_TARGET, item.target);
+        set(cfg, item.name, KEY_ID, item.id);
+      }
+    }
+  }
+
+  private static void set(Config cfg, String section, String key, String val) {
+    if (Strings.isNullOrEmpty(val)) {
+      cfg.unset(MY, section, key);
+    } else {
+      cfg.setString(MY, section, key, val);
+    }
+  }
+
+  private static void unsetSection(Config cfg, String section) {
+    cfg.unsetSection(section, null);
+    for (String subsection: cfg.getSubsections(section)) {
+      cfg.unsetSection(section, subsection);
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountPreferences.java
new file mode 100644
index 0000000..c4d4b06
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountPreferences.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.VersionedMetaData;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+
+/** Preferences for user accounts. */
+public class VersionedAccountPreferences extends VersionedMetaData {
+  private static final String REFS_USER_DEFAULT = RefNames.REFS_USER + "default";
+  private static final String PREFERENCES = "preferences.config";
+
+  public static VersionedAccountPreferences forUser(Account.Id id) {
+    return new VersionedAccountPreferences(RefNames.refsUsers(id));
+  }
+
+  public static VersionedAccountPreferences forDefault() {
+    return new VersionedAccountPreferences(REFS_USER_DEFAULT);
+  }
+
+  private final String ref;
+  private Config cfg;
+
+  private VersionedAccountPreferences(String ref) {
+    this.ref = ref;
+  }
+
+  public boolean isDefaults() {
+    return REFS_USER_DEFAULT.equals(getRefName());
+  }
+
+  @Override
+  protected String getRefName() {
+    return ref;
+  }
+
+  public Config getConfig() {
+    return cfg;
+  }
+
+  @Override
+  protected void onLoad() throws IOException, ConfigInvalidException {
+    cfg = readConfig(PREFERENCES);
+  }
+
+  @Override
+  protected boolean onSave(CommitBuilder commit) throws IOException,
+      ConfigInvalidException {
+    if (Strings.isNullOrEmpty(commit.getMessage())) {
+      commit.setMessage("Updated preferences\n");
+    }
+    saveConfig(PREFERENCES, cfg);
+    return true;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index a9253c3..fb82b7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.change.Rebase;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Submit;
+import com.google.gerrit.server.changedetail.RebaseChange;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -45,6 +46,7 @@
   private final Provider<CherryPick> cherryPick;
   private final Provider<DeleteDraftPatchSet> deleteDraft;
   private final Provider<Rebase> rebase;
+  private final Provider<RebaseChange> rebaseChange;
   private final Provider<PostReview> review;
   private final Provider<Submit> submit;
   private final Provider<Publish> publish;
@@ -55,6 +57,7 @@
       Provider<CherryPick> cherryPick,
       Provider<DeleteDraftPatchSet> deleteDraft,
       Provider<Rebase> rebase,
+      Provider<RebaseChange> rebaseChange,
       Provider<PostReview> review,
       Provider<Submit> submit,
       Provider<Publish> publish,
@@ -63,6 +66,7 @@
     this.cherryPick = cherryPick;
     this.deleteDraft = deleteDraft;
     this.rebase = rebase;
+    this.rebaseChange = rebaseChange;
     this.review = review;
     this.submit = submit;
     this.publish = publish;
@@ -122,6 +126,11 @@
   }
 
   @Override
+  public boolean canRebase() {
+    return rebaseChange.get().canRebase(revision);
+  }
+
+  @Override
   public ChangeApi cherryPick(CherryPickInput in) throws RestApiException {
     try {
       return changes.id(cherryPick.get().apply(revision, in)._number);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 14b05b6..b06f72a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -14,29 +14,111 @@
 
 package com.google.gerrit.server.api.projects;
 
+import com.google.common.base.Preconditions;
+import com.google.gerrit.common.errors.ProjectCreationFailedException;
 import com.google.gerrit.extensions.api.projects.BranchApi;
 import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.project.CreateProject;
+import com.google.gerrit.server.project.ProjectJson;
 import com.google.gerrit.server.project.ProjectResource;
-import com.google.inject.Inject;
+import com.google.gerrit.server.project.ProjectsCollection;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import java.io.IOException;
 
 public class ProjectApiImpl implements ProjectApi {
   interface Factory {
     ProjectApiImpl create(ProjectResource project);
+    ProjectApiImpl create(String name);
   }
 
+  private final Provider<CreateProject.Factory> createProjectFactory;
+  private final ProjectApiImpl.Factory projectApi;
+  private final ProjectsCollection projects;
   private final ProjectResource project;
+  private final ProjectJson projectJson;
+  private final String name;
   private final BranchApiImpl.Factory branchApi;
 
-  @Inject
-  ProjectApiImpl(
+  @AssistedInject
+  ProjectApiImpl(Provider<CreateProject.Factory> createProjectFactory,
+      ProjectApiImpl.Factory projectApi,
+      ProjectsCollection projects,
+      ProjectJson projectJson,
       BranchApiImpl.Factory branchApiFactory,
       @Assisted ProjectResource project) {
+    this(createProjectFactory, projectApi, projects, projectJson,
+        branchApiFactory, project, null);
+  }
+
+  @AssistedInject
+  ProjectApiImpl(Provider<CreateProject.Factory> createProjectFactory,
+      ProjectApiImpl.Factory projectApi,
+      ProjectsCollection projects,
+      ProjectJson projectJson,
+      BranchApiImpl.Factory branchApiFactory,
+      @Assisted String name) {
+    this(createProjectFactory, projectApi, projects, projectJson,
+        branchApiFactory, null, name);
+  }
+
+  private ProjectApiImpl(Provider<CreateProject.Factory> createProjectFactory,
+      ProjectApiImpl.Factory projectApi,
+      ProjectsCollection projects,
+      ProjectJson projectJson,
+      BranchApiImpl.Factory branchApiFactory,
+      ProjectResource project,
+      String name) {
+    this.createProjectFactory = createProjectFactory;
+    this.projectApi = projectApi;
+    this.projects = projects;
+    this.projectJson = projectJson;
     this.project = project;
+    this.name = name;
     this.branchApi = branchApiFactory;
   }
 
   @Override
+  public ProjectApi create() throws RestApiException {
+    return create(new ProjectInput());
+  }
+
+  @Override
+  public ProjectApi create(ProjectInput in) throws RestApiException {
+    try {
+      if (name == null) {
+        throw new ResourceConflictException("Project already exists");
+      }
+      if (in.name != null && !name.equals(in.name)) {
+        throw new BadRequestException("name must match input.name");
+      }
+      createProjectFactory.get().create(name)
+          .apply(TopLevelResource.INSTANCE, in);
+      return projectApi.create(projects.parse(name));
+    } catch (BadRequestException | UnprocessableEntityException
+        | ResourceNotFoundException | ProjectCreationFailedException
+        | IOException e) {
+      throw new RestApiException("Cannot create project: " + e.getMessage(), e);
+    }
+  }
+
+  @Override
+  public ProjectInfo get() {
+    Preconditions.checkNotNull(project);
+    return projectJson.format(project);
+  }
+
+  @Override
   public BranchApi branch(String ref) {
     return branchApi.create(project, ref);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
index bd5e2ac..fc0396d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
@@ -37,7 +37,9 @@
   public ProjectApi name(String name) throws RestApiException {
     try {
       return api.create(projects.parse(name));
-    } catch (IOException | UnprocessableEntityException e) {
+    } catch (UnprocessableEntityException e) {
+      return api.create(name);
+    } catch (IOException e) {
       throw new RestApiException("Cannot retrieve project");
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
index 6e9e71b..0b4baf2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.auth;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AuthConfig;
@@ -42,7 +43,8 @@
   public AuthUser authenticate(AuthRequest req)
       throws MissingCredentialsException, InvalidCredentialsException,
       UnknownUserException, UserNotAllowedException, AuthException {
-    if (req.getUsername() == null || req.getPassword() == null) {
+    if (Strings.isNullOrEmpty(req.getUsername())
+        || Strings.isNullOrEmpty(req.getPassword())) {
       throw new MissingCredentialsException();
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
index 983e956..0769b2a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
@@ -17,6 +17,7 @@
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import com.google.gerrit.server.plugins.Plugin;
 
 public interface PersistentCacheFactory {
   <K, V> Cache<K, V> build(CacheBinding<K, V> def);
@@ -24,4 +25,6 @@
   <K, V> LoadingCache<K, V> build(
       CacheBinding<K, V> def,
       CacheLoader<K, V> loader);
+
+  void onStop(Plugin plugin);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
index e7d05df..a5054f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
@@ -14,20 +14,13 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.collect.Maps;
-
 import org.eclipse.jgit.api.ArchiveCommand;
 import org.eclipse.jgit.archive.TarFormat;
 import org.eclipse.jgit.archive.Tbz2Format;
 import org.eclipse.jgit.archive.TgzFormat;
 import org.eclipse.jgit.archive.TxzFormat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import java.util.Collections;
-import java.util.Map;
-
-enum ArchiveFormat {
+public enum ArchiveFormat {
   TGZ("application/x-gzip", new TgzFormat()),
   TAR("application/x-tar", new TarFormat()),
   TBZ2("application/x-bzip2", new Tbz2Format()),
@@ -35,8 +28,6 @@
   // Zip is not supported because it may be interpreted by a Java plugin as a
   // valid JAR file, whose code would have access to cookies on the domain.
 
-  static final Logger log = LoggerFactory.getLogger(ArchiveFormat.class);
-
   private final ArchiveCommand.Format<?> format;
   private final String mimeType;
 
@@ -46,7 +37,7 @@
     ArchiveCommand.registerFormat(name(), format);
   }
 
-  String getShortName() {
+  public String getShortName() {
     return name().toLowerCase();
   }
 
@@ -61,24 +52,4 @@
   Iterable<String> getSuffixes() {
     return format.suffixes();
   }
-
-  static Map<String, ArchiveFormat> init() {
-    String[] formats = new String[values().length];
-    for (int i = 0; i < values().length; i++) {
-      formats[i] = values()[i].name();
-    }
-
-    Map<String, ArchiveFormat> exts = Maps.newLinkedHashMap();
-    for (String name : formats) {
-      try {
-        ArchiveFormat format = valueOf(name.toUpperCase());
-        for (String ext : format.getSuffixes()) {
-          exts.put(ext, format);
-        }
-      } catch (IllegalArgumentException e) {
-        log.warn("Invalid archive.format {}", name);
-      }
-    }
-    return Collections.unmodifiableMap(exts);
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index ad6b804..5955fde 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -28,6 +28,7 @@
 import static com.google.gerrit.extensions.common.ListChangesOption.LABELS;
 import static com.google.gerrit.extensions.common.ListChangesOption.MESSAGES;
 import static com.google.gerrit.extensions.common.ListChangesOption.REVIEWED;
+import static com.google.gerrit.extensions.common.ListChangesOption.WEB_LINKS;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
@@ -40,7 +41,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
@@ -79,6 +79,7 @@
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.account.AccountInfo;
 import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.LabelNormalizer;
@@ -86,7 +87,6 @@
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -152,6 +152,7 @@
   private ChangeControl lastControl;
   private Set<Change.Id> reviewed;
   private LoadingCache<Project.NameKey, ProjectControl> projectControls;
+  private Provider<WebLinks> webLinks;
 
   @Inject
   ChangeJson(
@@ -169,7 +170,8 @@
       DynamicMap<DownloadScheme> downloadSchemes,
       DynamicMap<DownloadCommand> downloadCommands,
       DynamicMap<RestView<ChangeResource>> changeViews,
-      Revisions revisions) {
+      Revisions revisions,
+      Provider<WebLinks> webLinks) {
     this.db = db;
     this.labelNormalizer = ln;
     this.userProvider = user;
@@ -185,6 +187,7 @@
     this.downloadCommands = downloadCommands;
     this.changeViews = changeViews;
     this.revisions = revisions;
+    this.webLinks = webLinks;
 
     options = EnumSet.noneOf(ListChangesOption.class);
     projectControls = CacheBuilder.newBuilder()
@@ -328,7 +331,7 @@
     if (has(ALL_REVISIONS)
         || has(CURRENT_REVISION)
         || limitToPsId.isPresent()) {
-      out.revisions = revisions(cd, limitToPsId);
+      out.revisions = revisions(cd, limitToPsId, out.project);
       if (out.revisions != null) {
         for (Map.Entry<String, RevisionInfo> entry : out.revisions.entrySet()) {
           if (entry.getValue().isCurrent) {
@@ -365,7 +368,7 @@
         ctrl = projectControls.get(cd.change().getProject())
             .controlFor(cd.change());
       }
-    } catch (NoSuchChangeException | ExecutionException e) {
+    } catch (ExecutionException e) {
       throw new OrmException(e);
     }
     lastControl = ctrl;
@@ -505,22 +508,18 @@
       return;
     }
 
-    // All users ever added, even if they can't vote on one or all labels.
+    // Include a user in the output for this label if either:
+    //  - They are an explicit reviewer.
+    //  - They ever voted on this change.
     Set<Account.Id> allUsers = Sets.newHashSet();
-    ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals =
-        cd.approvals();
-    for (PatchSetApproval psa : allApprovals.values()) {
+    allUsers.addAll(cd.reviewers().values());
+    for (PatchSetApproval psa : cd.approvals().values()) {
       allUsers.add(psa.getAccountId());
     }
 
-    List<PatchSetApproval> currentList =
-        allApprovals.get(baseCtrl.getChange().currentPatchSetId());
-    // Most recent, normalized vote on each label for the current patch set by
-    // each user (may be 0).
     Table<Account.Id, String, PatchSetApproval> current = HashBasedTable.create(
         allUsers.size(), baseCtrl.getLabelTypes().getLabelTypes().size());
-    for (PatchSetApproval psa :
-        labelNormalizer.normalize(baseCtrl, currentList).getNormalized()) {
+    for (PatchSetApproval psa : cd.currentApprovals()) {
       current.put(psa.getAccountId(), psa.getLabel(), psa);
     }
 
@@ -541,9 +540,9 @@
           value = Integer.valueOf(psa.getValue());
           date = psa.getGranted();
         } else {
-          // Either the user cannot vote on this label, or there just wasn't a
-          // dummy approval for this label. Explicitly check whether the user
-          // can vote on this label.
+          // Either the user cannot vote on this label, or they were added as a
+          // reviewer but have not responded yet. Explicitly check whether the
+          // user can vote on this label.
           value = labelNormalizer.canVote(ctl, lt, accountId) ? 0 : null;
         }
         e.getValue().addApproval(approvalInfo(accountId, value, date));
@@ -790,7 +789,7 @@
   }
 
   private Map<String, RevisionInfo> revisions(ChangeData cd,
-      Optional<PatchSet.Id> limitToPsId) throws OrmException {
+      Optional<PatchSet.Id> limitToPsId, String project) throws OrmException {
     ChangeControl ctl = control(cd);
     if (ctl == null) {
       return null;
@@ -819,13 +818,13 @@
     Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
     for (PatchSet in : src) {
       if (ctl.isPatchVisible(in, db.get())) {
-        res.put(in.getRevision().get(), toRevisionInfo(cd, in));
+        res.put(in.getRevision().get(), toRevisionInfo(cd, in, project));
       }
     }
     return res;
   }
 
-  private RevisionInfo toRevisionInfo(ChangeData cd, PatchSet in)
+  private RevisionInfo toRevisionInfo(ChangeData cd, PatchSet in, String project)
       throws OrmException {
     RevisionInfo out = new RevisionInfo();
     out.isCurrent = in.getId().equals(cd.change().currentPatchSetId());
@@ -873,6 +872,13 @@
           : null;
     }
 
+    if (has(WEB_LINKS)) {
+      out.webLinks = Lists.newArrayList();
+      for (WebLinks.Link link : webLinks.get().getPatchSetLinks(
+          project, in.getRevision().get())) {
+        out.webLinks.add(new RevisionInfo.WebLinkInfo(link.name, link.url));
+      }
+    }
     return out;
   }
 
@@ -885,7 +891,6 @@
     commit.committer = toGitPerson(info.getCommitter());
     commit.subject = info.getSubject();
     commit.message = info.getMessage();
-
     for (ParentInfo parent : info.getParents()) {
       CommitInfo i = new CommitInfo();
       i.commit = parent.id.get();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
index f6e5773..0e0984d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 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,32 +14,10 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.common.base.Objects;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.project.ProjectState;
-import com.google.inject.Inject;
-import com.google.inject.Module;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
 
-import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.ThreeWayMerger;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.concurrent.ExecutionException;
 
 /**
  * Cache of {@link ChangeKind} per commit.
@@ -47,149 +25,7 @@
  * This is immutable conditioned on the merge strategy (unless the JGit strategy
  * implementation changes, which might invalidate old entries).
  */
-public class ChangeKindCache {
-  private static final Logger log =
-      LoggerFactory.getLogger(ChangeKindCache.class);
-
-  private static final String ID_CACHE = "change_kind";
-
-  public static Module module() {
-    return new CacheModule() {
-      @Override
-      protected void configure() {
-        cache(ID_CACHE,
-            Key.class,
-            ChangeKind.class)
-          .maximumWeight(0)
-          .loader(Loader.class);
-      }
-    };
-  }
-
-  public static class Key implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    private final ObjectId prior;
-    private final ObjectId next;
-    private final String strategyName;
-    private transient Repository repo;
-
-    private Key(ObjectId prior, ObjectId next, String strategyName,
-        Repository repo) {
-      this.prior = prior.copy();
-      this.next = next.copy();
-      this.strategyName = strategyName;
-      this.repo = repo;
-    }
-
-    public ObjectId getPrior() {
-      return prior;
-    }
-
-    public ObjectId getNext() {
-      return next;
-    }
-
-    public String getStrategyName() {
-      return strategyName;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (o instanceof Key) {
-        Key k = (Key) o;
-        return Objects.equal(prior, k.prior)
-            && Objects.equal(next, k.next)
-            && Objects.equal(strategyName, k.strategyName);
-      }
-      return false;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(prior, next, strategyName);
-    }
-  }
-
-  @Singleton
-  private static class Loader extends CacheLoader<Key, ChangeKind> {
-    @Override
-    public ChangeKind load(Key key) throws IOException {
-      RevWalk walk = new RevWalk(key.repo);
-      try {
-        RevCommit prior = walk.parseCommit(key.prior);
-        walk.parseBody(prior);
-        RevCommit next = walk.parseCommit(key.next);
-        walk.parseBody(next);
-
-        if (!next.getFullMessage().equals(prior.getFullMessage())) {
-          if (next.getTree() == prior.getTree() && isSameParents(prior, next)) {
-            return ChangeKind.NO_CODE_CHANGE;
-          } else {
-            return ChangeKind.REWORK;
-          }
-        }
-
-        if (prior.getParentCount() != 1 || next.getParentCount() != 1) {
-          // Trivial rebases done by machine only work well on 1 parent.
-          return ChangeKind.REWORK;
-        }
-
-        if (next.getTree() == prior.getTree() &&
-           isSameParents(prior, next)) {
-          return ChangeKind.TRIVIAL_REBASE;
-        }
-
-        // A trivial rebase can be detected by looking for the next commit
-        // having the same tree as would exist when the prior commit is
-        // cherry-picked onto the next commit's new first parent.
-        ThreeWayMerger merger = MergeUtil.newThreeWayMerger(
-            key.repo, MergeUtil.createDryRunInserter(), key.strategyName);
-        merger.setBase(prior.getParent(0));
-        if (merger.merge(next.getParent(0), prior)
-            && merger.getResultTreeId().equals(next.getTree())) {
-          return ChangeKind.TRIVIAL_REBASE;
-        } else {
-          return ChangeKind.REWORK;
-        }
-      } finally {
-        key.repo = null;
-        walk.release();
-      }
-    }
-
-    private static boolean isSameParents(RevCommit prior, RevCommit next) {
-      if (prior.getParentCount() != next.getParentCount()) {
-        return false;
-      } else if (prior.getParentCount() == 0) {
-        return true;
-      }
-      return prior.getParent(0).equals(next.getParent(0));
-    }
-  }
-
-  private final LoadingCache<Key, ChangeKind> cache;
-  private final boolean useRecursiveMerge;
-
-  @Inject
-  ChangeKindCache(
-      @GerritServerConfig Config serverConfig,
-      @Named(ID_CACHE) LoadingCache<Key, ChangeKind> cache) {
-    this.cache = cache;
-    this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
-  }
-
+public interface ChangeKindCache {
   public ChangeKind getChangeKind(ProjectState project, Repository repo,
-      ObjectId prior, ObjectId next) {
-    checkNotNull(next, "next");
-    String strategyName = MergeUtil.mergeStrategyName(
-        project.isUseContentMerge(), useRecursiveMerge);
-    try {
-      return cache.get(new Key(prior, next, strategyName, repo));
-    } catch (ExecutionException e) {
-      log.warn("Cannot check trivial rebase of new patch set " + next.name()
-          + " in " + project.getProject().getName(), e);
-      return ChangeKind.REWORK;
-    }
-  }
+      ObjectId prior, ObjectId next);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
new file mode 100644
index 0000000..220dcb6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -0,0 +1,247 @@
+// 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.change;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
+import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.concurrent.ExecutionException;
+
+public class ChangeKindCacheImpl implements ChangeKindCache {
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeKindCacheImpl.class);
+
+  private static final String ID_CACHE = "change_kind";
+
+  public static Module module() {
+    return new CacheModule() {
+      @Override
+      protected void configure() {
+        bind(ChangeKindCache.class).to(ChangeKindCacheImpl.class);
+        persist(ID_CACHE, Key.class, ChangeKind.class)
+            .maximumWeight(2 << 20)
+            .weigher(ChangeKindWeigher.class)
+            .loader(Loader.class);
+      }
+    };
+  }
+
+  @VisibleForTesting
+  public static class NoCache implements ChangeKindCache {
+    private final boolean useRecursiveMerge;
+
+    @Inject
+    NoCache(
+        @GerritServerConfig Config serverConfig) {
+      this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
+    }
+
+    @Override
+    public ChangeKind getChangeKind(ProjectState project, Repository repo,
+        ObjectId prior, ObjectId next) {
+      try {
+        return new Loader().load(
+            new Key(project, repo, prior, next, useRecursiveMerge));
+      } catch (IOException e) {
+        log.warn("Cannot check trivial rebase of new patch set " + next.name()
+            + " in " + project.getProject().getName(), e);
+        return ChangeKind.REWORK;
+      }
+    }
+  }
+
+  public static class Key implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private transient ObjectId prior;
+    private transient ObjectId next;
+    private transient String strategyName;
+
+    private transient Repository repo; // Passed through to loader on miss.
+
+    private Key(ProjectState project, Repository repo, ObjectId prior,
+        ObjectId next, boolean useRecursiveMerge) {
+      checkNotNull(next, "next");
+      String strategyName = MergeUtil.mergeStrategyName(
+          project.isUseContentMerge(), useRecursiveMerge);
+      this.prior = prior.copy();
+      this.next = next.copy();
+      this.strategyName = strategyName;
+      this.repo = repo;
+    }
+
+    public Key(ObjectId prior, ObjectId next, String strategyName) {
+      this.prior = prior;
+      this.next = next;
+      this.strategyName = strategyName;
+    }
+
+    public ObjectId getPrior() {
+      return prior;
+    }
+
+    public ObjectId getNext() {
+      return next;
+    }
+
+    public String getStrategyName() {
+      return strategyName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Key) {
+        Key k = (Key) o;
+        return Objects.equal(prior, k.prior)
+            && Objects.equal(next, k.next)
+            && Objects.equal(strategyName, k.strategyName);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(prior, next, strategyName);
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+      writeNotNull(out, prior);
+      writeNotNull(out, next);
+      out.writeUTF(strategyName);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException {
+      prior = readNotNull(in);
+      next = readNotNull(in);
+      strategyName = in.readUTF();
+    }
+  }
+
+  @Singleton
+  private static class Loader extends CacheLoader<Key, ChangeKind> {
+    @Override
+    public ChangeKind load(Key key) throws IOException {
+      RevWalk walk = new RevWalk(key.repo);
+      try {
+        RevCommit prior = walk.parseCommit(key.prior);
+        walk.parseBody(prior);
+        RevCommit next = walk.parseCommit(key.next);
+        walk.parseBody(next);
+
+        if (!next.getFullMessage().equals(prior.getFullMessage())) {
+          if (next.getTree() == prior.getTree() && isSameParents(prior, next)) {
+            return ChangeKind.NO_CODE_CHANGE;
+          } else {
+            return ChangeKind.REWORK;
+          }
+        }
+
+        if (prior.getParentCount() != 1 || next.getParentCount() != 1) {
+          // Trivial rebases done by machine only work well on 1 parent.
+          return ChangeKind.REWORK;
+        }
+
+        if (next.getTree() == prior.getTree() &&
+           isSameParents(prior, next)) {
+          return ChangeKind.TRIVIAL_REBASE;
+        }
+
+        // A trivial rebase can be detected by looking for the next commit
+        // having the same tree as would exist when the prior commit is
+        // cherry-picked onto the next commit's new first parent.
+        ThreeWayMerger merger = MergeUtil.newThreeWayMerger(
+            key.repo, MergeUtil.createDryRunInserter(), key.strategyName);
+        merger.setBase(prior.getParent(0));
+        if (merger.merge(next.getParent(0), prior)
+            && merger.getResultTreeId().equals(next.getTree())) {
+          return ChangeKind.TRIVIAL_REBASE;
+        } else {
+          return ChangeKind.REWORK;
+        }
+      } finally {
+        key.repo = null;
+        walk.release();
+      }
+    }
+
+    private static boolean isSameParents(RevCommit prior, RevCommit next) {
+      if (prior.getParentCount() != next.getParentCount()) {
+        return false;
+      } else if (prior.getParentCount() == 0) {
+        return true;
+      }
+      return prior.getParent(0).equals(next.getParent(0));
+    }
+  }
+
+  public static class ChangeKindWeigher implements Weigher<Key, ChangeKind> {
+    @Override
+    public int weigh(Key key, ChangeKind changeKind) {
+      return 16 + 2*36 + 2*key.strategyName.length() // Size of Key, 64 bit JVM
+          + 2*changeKind.name().length(); // Size of ChangeKind, 64 bit JVM
+    }
+  }
+
+  private final LoadingCache<Key, ChangeKind> cache;
+  private final boolean useRecursiveMerge;
+
+  @Inject
+  ChangeKindCacheImpl(
+      @GerritServerConfig Config serverConfig,
+      @Named(ID_CACHE) LoadingCache<Key, ChangeKind> cache) {
+    this.cache = cache;
+    this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
+  }
+
+  @Override
+  public ChangeKind getChangeKind(ProjectState project, Repository repo,
+      ObjectId prior, ObjectId next) {
+    try {
+      return cache.get(new Key(project, repo, prior, next, useRecursiveMerge));
+    } catch (ExecutionException e) {
+      log.warn("Cannot check trivial rebase of new patch set " + next.name()
+          + " in " + project.getProject().getName(), e);
+      return ChangeKind.REWORK;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
index 9a4ab21..d602c47 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
@@ -15,14 +15,19 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.api.ArchiveCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -31,18 +36,60 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Set;
 
-class GetArchive implements RestReadView<RevisionResource> {
-  private static final Map<String, ArchiveFormat> formats = ArchiveFormat.init();
+public class GetArchive implements RestReadView<RevisionResource> {
+  @Singleton
+  public static class AllowedFormats {
+    final ImmutableMap<String, ArchiveFormat> extensions;
+    final Set<ArchiveFormat> allowed;
+
+    @Inject
+    AllowedFormats(@GerritServerConfig Config cfg) {
+      Collection<ArchiveFormat> enabled;
+      String v = cfg.getString("download", null, "archive");
+      if (v == null) {
+        enabled = Arrays.asList(ArchiveFormat.values());
+      } else if (v.isEmpty() || "off".equalsIgnoreCase(v)) {
+        enabled = Collections.emptyList();
+      } else {
+        enabled = ConfigUtil.getEnumList(cfg,
+            "download", null, "archive",
+            ArchiveFormat.TGZ);
+      }
+
+      Map<String, ArchiveFormat> exts = new HashMap<>();
+      for (ArchiveFormat format : enabled) {
+        for (String ext : format.getSuffixes()) {
+          exts.put(ext, format);
+        }
+        exts.put(format.name().toLowerCase(), format);
+      }
+      extensions = ImmutableMap.copyOf(exts);
+      allowed = Collections.unmodifiableSet(new LinkedHashSet<>(enabled));
+    }
+
+    public Set<ArchiveFormat> getAllowed() {
+      return allowed;
+    }
+  }
+
   private final GitRepositoryManager repoManager;
+  private final AllowedFormats allowedFormats;
 
   @Option(name = "--format")
   private String format;
 
   @Inject
-  GetArchive(GitRepositoryManager repoManager) {
+  GetArchive(GitRepositoryManager repoManager, AllowedFormats allowedFormats) {
     this.repoManager = repoManager;
+    this.allowedFormats = allowedFormats;
   }
 
   @Override
@@ -51,7 +98,7 @@
     if (Strings.isNullOrEmpty(format)) {
       throw new BadRequestException("format is not specified");
     }
-    final ArchiveFormat f = formats.get("." + format);
+    final ArchiveFormat f = allowedFormats.extensions.get("." + format);
     if (f == null) {
       throw new BadRequestException("unknown archive format");
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
index 96c83d3..e5bcabe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
@@ -36,7 +36,7 @@
       @GerritServerConfig Config config,
       WorkQueue queues) {
     int poolSize = config.getInt("changeMerge", null, "threadPoolSize", 1);
-    return queues.createQueue(poolSize, "MergeabilityChecks");
+    return queues.createQueue(poolSize, "MergeabilityChecks-Background");
   }
 
   @Provides
@@ -52,6 +52,6 @@
     if (poolSize <= 0) {
       return backgroundExecutor;
     }
-    return queues.createQueue(poolSize, "InteractiveMergeabilityChecks");
+    return queues.createQueue(poolSize, "MergeabilityChecks-Interactive");
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index fb8c4e4..c00b96c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -15,13 +15,13 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 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.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.CodeReviewCommit;
@@ -59,7 +59,7 @@
   private static final Logger log = LoggerFactory.getLogger(Mergeable.class);
 
   public static class MergeableInfo {
-    public Project.SubmitType submitType;
+    public SubmitType submitType;
     public boolean mergeable;
   }
 
@@ -132,7 +132,7 @@
 
   private boolean refresh(Change change,
       final PatchSet ps,
-      Project.SubmitType type,
+      SubmitType type,
       Repository git,
       Map<String, Ref> refs,
       final Ref ref) throws IOException, OrmException {
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 25c63b5..0a2ccef 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
@@ -469,7 +469,7 @@
     Map<String, PatchSetApproval> current = Maps.newHashMap();
 
     for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
-        db.get(), rsrc.getNotes(), rsrc.getPatchSet().getId(),
+        db.get(), rsrc.getControl(), rsrc.getPatchSet().getId(),
         rsrc.getAccountId())) {
       if (a.isSubmit()) {
         continue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index 7fde39c..6fd9002 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -239,6 +239,7 @@
         indexer.indexAsync(rsrc.getChange().getId());
     result.reviewers = Lists.newArrayListWithCapacity(added.size());
     for (PatchSetApproval psa : added) {
+      // New reviewers have value 0, don't bother normalizing.
       result.reviewers.add(json.format(
           new ReviewerInfo(psa.getAccountId()),
           reviewers.get(psa.getAccountId()),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
index 5008d02..22ddd2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.git.LabelNormalizer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -46,19 +45,16 @@
   private final Provider<ReviewDb> db;
   private final ChangeData.Factory changeDataFactory;
   private final ApprovalsUtil approvalsUtil;
-  private final LabelNormalizer labelNormalizer;
   private final AccountInfo.Loader.Factory accountLoaderFactory;
 
   @Inject
   ReviewerJson(Provider<ReviewDb> db,
       ChangeData.Factory changeDataFactory,
       ApprovalsUtil approvalsUtil,
-      LabelNormalizer labelNormalizer,
       AccountInfo.Loader.Factory accountLoaderFactory) {
     this.db = db;
     this.changeDataFactory = changeDataFactory;
     this.approvalsUtil = approvalsUtil;
-    this.labelNormalizer = labelNormalizer;
     this.accountLoaderFactory = accountLoaderFactory;
   }
 
@@ -86,17 +82,16 @@
       ChangeNotes changeNotes) throws OrmException {
     PatchSet.Id psId = ctl.getChange().currentPatchSetId();
     return format(out, ctl,
-        approvalsUtil.byPatchSetUser(db.get(), changeNotes, psId, out._id));
+        approvalsUtil.byPatchSetUser(db.get(), ctl, psId, out._id));
   }
 
   public ReviewerInfo format(ReviewerInfo out, ChangeControl ctl,
-      List<PatchSetApproval> approvals) throws OrmException {
+      Iterable<PatchSetApproval> approvals) throws OrmException {
     LabelTypes labelTypes = ctl.getLabelTypes();
 
     // Don't use Maps.newTreeMap(Comparator) due to OpenJDK bug 100167.
     out.approvals = new TreeMap<String,String>(labelTypes.nameComparator());
-    for (PatchSetApproval ca :
-        labelNormalizer.normalize(ctl, approvals).getNormalized()) {
+    for (PatchSetApproval ca : approvals) {
       for (PermissionRange pr : ctl.getLabelRanges()) {
         if (!pr.isEmpty()) {
           LabelType at = labelTypes.byLabel(ca.getLabelId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index c2ac123..2121e7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -258,12 +258,9 @@
       ChangeUpdate update, IdentifiedUser caller, Timestamp timestamp)
       throws OrmException {
     PatchSet.Id psId = rsrc.getPatchSet().getId();
-    List<PatchSetApproval> approvals =
-        approvalsUtil.byPatchSet(dbProvider.get(), rsrc.getNotes(), psId);
-
-    Map<PatchSetApproval.Key, PatchSetApproval> byKey =
-        Maps.newHashMapWithExpectedSize(approvals.size());
-    for (PatchSetApproval psa : approvals) {
+    Map<PatchSetApproval.Key, PatchSetApproval> byKey = Maps.newHashMap();
+    for (PatchSetApproval psa :
+        approvalsUtil.byPatchSet(dbProvider.get(), rsrc.getControl(), psId)) {
       if (!byKey.containsKey(psa.getKey())) {
         byKey.put(psa.getKey(), psa);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
index c5b8b8a..92ff87b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
@@ -17,11 +17,11 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Objects;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.change.TestSubmitRule.Filters;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersName.java
similarity index 60%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersName.java
index 96e2ec8..ff28be4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersName.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,16 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.server.config;
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
+import com.google.gerrit.reviewdb.client.Project;
 
-  public Die(final String why) {
-    super(why);
-  }
-
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
+/** Special name of the project in which meta data for all users is stored. */
+@SuppressWarnings("serial")
+public class AllUsersName extends Project.NameKey {
+  public AllUsersName(String name) {
+    super(name);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java
new file mode 100644
index 0000000..e6ec095
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AllUsersNameProvider.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Config;
+
+public class AllUsersNameProvider implements Provider<AllUsersName> {
+  public static final String DEFAULT = "All-Users";
+
+  private final AllUsersName name;
+
+  @Inject
+  AllUsersNameProvider(@GerritServerConfig Config cfg) {
+    String n = cfg.getString("gerrit", null, "allUsers");
+    if (n == null || n.isEmpty()) {
+      n = DEFAULT;
+    }
+    name = new AllUsersName(n);
+  }
+
+  public AllUsersName get() {
+    return name;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 6d01e7c5..a633acd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -277,4 +277,9 @@
   public String getRegisterPageUrl() {
     return registerPageUrl;
   }
+
+  public boolean isLdapAuthType() {
+    return authType == AuthType.LDAP ||
+        authType == AuthType.LDAP_BIND;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index c6598d4..d0abfcb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
+import com.google.gerrit.extensions.webui.PatchSetWebLink;
 import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.rules.PrologModule;
@@ -42,6 +43,8 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.MimeUtilFileTypeRegistry;
 import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.WebLinks;
+import com.google.gerrit.server.WebLinksProvider;
 import com.google.gerrit.server.account.AccountByEmailCacheImpl;
 import com.google.gerrit.server.account.AccountCacheImpl;
 import com.google.gerrit.server.account.AccountControl;
@@ -65,7 +68,7 @@
 import com.google.gerrit.server.auth.UniversalAuthBackend;
 import com.google.gerrit.server.avatar.AvatarProvider;
 import com.google.gerrit.server.cache.CacheRemovalListener;
-import com.google.gerrit.server.change.ChangeKindCache;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
 import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -152,7 +155,7 @@
     install(AccountByEmailCacheImpl.module());
     install(AccountCacheImpl.module());
     install(ChangeCache.module());
-    install(ChangeKindCache.module());
+    install(ChangeKindCacheImpl.module());
     install(ConflictsCacheImpl.module());
     install(GroupCacheImpl.module());
     install(GroupIncludeCacheImpl.module());
@@ -224,6 +227,7 @@
         .in(SINGLETON);
     bind(FromAddressGenerator.class).toProvider(
         FromAddressGeneratorProvider.class).in(SINGLETON);
+    bind(WebLinks.class).toProvider(WebLinksProvider.class).in(SINGLETON);
 
     bind(PatchSetInfoFactory.class);
     bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
@@ -266,6 +270,7 @@
     DynamicMap.mapOf(binder(), DownloadScheme.class);
     DynamicMap.mapOf(binder(), DownloadCommand.class);
     DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
+    DynamicSet.setOf(binder(), PatchSetWebLink.class);
 
     bind(AnonymousUser.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java
new file mode 100644
index 0000000..f6c1d1d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetPreferences.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.GetPreferences.PreferenceInfo;
+import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+
+public class GetPreferences implements RestReadView<ConfigResource> {
+  private final AllUsersName allUsersName;
+  private final GitRepositoryManager gitMgr;
+
+  @Inject
+  public GetPreferences(AllUsersName allUsersName,
+      GitRepositoryManager gitMgr) {
+    this.allUsersName = allUsersName;
+    this.gitMgr = gitMgr;
+  }
+
+  @Override
+  public PreferenceInfo apply(ConfigResource rsrc)
+      throws IOException, ConfigInvalidException {
+    Repository git = gitMgr.openRepository(allUsersName);
+    try {
+      VersionedAccountPreferences p =
+          VersionedAccountPreferences.forDefault();
+      p.load(git);
+      return new PreferenceInfo(null, p, git);
+    } finally {
+      git.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
index 57bbbf3..761b265 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -30,5 +30,7 @@
     child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
     child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class);
     get(CONFIG_KIND, "version").to(GetVersion.class);
+    get(CONFIG_KIND, "preferences").to(GetPreferences.class);
+    put(CONFIG_KIND, "preferences").to(SetPreferences.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
new file mode 100644
index 0000000..e6f5253
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.account.GetPreferences.PreferenceInfo;
+import com.google.gerrit.server.account.SetPreferences.Input;
+import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+import java.io.IOException;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+public class SetPreferences implements RestModifyView<ConfigResource, Input> {
+  private final MetaDataUpdate.User metaDataUpdateFactory;
+  private final AllUsersName allUsersName;
+
+  @Inject
+  SetPreferences(MetaDataUpdate.User metaDataUpdateFactory,
+      AllUsersName allUsersName) {
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.allUsersName = allUsersName;
+  }
+
+  @Override
+  public Object apply(ConfigResource rsrc, Input i) throws BadRequestException,
+      IOException, ConfigInvalidException {
+    if (i.changesPerPage != null || i.showSiteHeader != null
+        || i.useFlashClipboard != null || i.downloadScheme != null
+        || i.downloadCommand != null || i.copySelfOnEmail != null
+        || i.dateFormat != null || i.timeFormat != null
+        || i.reversePatchSetOrder != null
+        || i.showUsernameInReviewCategory != null
+        || i.relativeDateInChangeTable != null
+        || i.sizeBarInChangeTable != null
+        || i.commentVisibilityStrategy != null || i.diffView != null
+        || i.changeScreen != null) {
+      throw new BadRequestException("unsupported option");
+    }
+
+    VersionedAccountPreferences p;
+    MetaDataUpdate md = metaDataUpdateFactory.create(allUsersName);
+    try {
+      p = VersionedAccountPreferences.forDefault();
+      p.load(md);
+      com.google.gerrit.server.account.SetPreferences.storeMyMenus(p, i.my);
+      p.commit(md);
+      return new PreferenceInfo(null, p, md.getRepository());
+    } finally {
+      md.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index e4fa926..763437f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -46,7 +46,7 @@
   private static final Logger log =
       LoggerFactory.getLogger(QueryDocumentationExecutor.class);
 
-  private static final Version LUCENE_VERSION = Version.LUCENE_46;
+  private static final Version LUCENE_VERSION = Version.LUCENE_47;
 
   private IndexSearcher searcher;
   private QueryParser parser;
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 0919aa0..78a2bc2 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
@@ -30,6 +30,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -37,7 +38,6 @@
 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.Project.SubmitType;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -242,7 +242,7 @@
       while (!toMerge.isEmpty()) {
         toMergeNextTurn.clear();
         final Set<SubmitType> submitTypes =
-            new HashSet<Project.SubmitType>(toMerge.keySet());
+            new HashSet<SubmitType>(toMerge.keySet());
         for (final SubmitType submitType : submitTypes) {
           if (reopen) {
             branchUpdate = openBranch();
@@ -559,7 +559,7 @@
         }
       }
 
-      final SubmitType submitType = getSubmitType(commit.getControl(), ps);
+      SubmitType submitType = getSubmitType(commit.getControl(), ps);
       if (submitType == null) {
         commits.put(changeId,
             CodeReviewCommit.error(CommitMergeStatus.NO_SUBMIT_TYPE));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index b9624fe..9b74ee5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -311,9 +311,9 @@
     return "Verified".equalsIgnoreCase(id.get());
   }
 
-  private List<PatchSetApproval> safeGetApprovals(CodeReviewCommit n) {
+  private Iterable<PatchSetApproval> safeGetApprovals(CodeReviewCommit n) {
     try {
-      return approvalsUtil.byPatchSet(db.get(), n.notes(), n.getPatchsetId());
+      return approvalsUtil.byPatchSet(db.get(), n.getControl(), n.getPatchsetId());
     } catch (OrmException e) {
       log.error("Can't read approval records for " + n.getPatchsetId(), e);
       return Collections.emptyList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index e8b4b6a..b48028e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -137,7 +137,7 @@
     return projectName;
   }
 
-  Repository getRepository() {
+  public Repository getRepository() {
     return db;
   }
 
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 55f4ff7..4971f84 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
@@ -39,12 +39,13 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 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;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.PluginConfig;
@@ -142,8 +143,8 @@
 
   private static final SubmitType defaultSubmitAction =
       SubmitType.MERGE_IF_NECESSARY;
-  private static final State defaultStateValue =
-      State.ACTIVE;
+  private static final ProjectState defaultStateValue =
+      ProjectState.ACTIVE;
 
   private Project.NameKey projectName;
   private Project project;
@@ -398,13 +399,13 @@
     }
     p.setParentName(rc.getString(ACCESS, null, KEY_INHERIT_FROM));
 
-    p.setUseContributorAgreements(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, Project.InheritableBoolean.INHERIT));
-    p.setUseSignedOffBy(getEnum(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, Project.InheritableBoolean.INHERIT));
-    p.setRequireChangeID(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, Project.InheritableBoolean.INHERIT));
+    p.setUseContributorAgreements(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, InheritableBoolean.INHERIT));
+    p.setUseSignedOffBy(getEnum(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, InheritableBoolean.INHERIT));
+    p.setRequireChangeID(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, InheritableBoolean.INHERIT));
     p.setMaxObjectSizeLimit(rc.getString(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT));
 
     p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
-    p.setUseContentMerge(getEnum(rc, SUBMIT, null, KEY_MERGE_CONTENT, Project.InheritableBoolean.INHERIT));
+    p.setUseContentMerge(getEnum(rc, SUBMIT, null, KEY_MERGE_CONTENT, InheritableBoolean.INHERIT));
     p.setState(getEnum(rc, PROJECT, null, KEY_STATE, defaultStateValue));
 
     p.setDefaultDashboard(rc.getString(DASHBOARD, null, KEY_DEFAULT));
@@ -775,13 +776,13 @@
     }
     set(rc, ACCESS, null, KEY_INHERIT_FROM, p.getParentName());
 
-    set(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, p.getUseContributorAgreements(), Project.InheritableBoolean.INHERIT);
-    set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, p.getUseSignedOffBy(), Project.InheritableBoolean.INHERIT);
-    set(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, p.getRequireChangeID(), Project.InheritableBoolean.INHERIT);
+    set(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, p.getUseContributorAgreements(), InheritableBoolean.INHERIT);
+    set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, p.getUseSignedOffBy(), InheritableBoolean.INHERIT);
+    set(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, p.getRequireChangeID(), InheritableBoolean.INHERIT);
     set(rc, RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
 
     set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
-    set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.getUseContentMerge(), Project.InheritableBoolean.INHERIT);
+    set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.getUseContentMerge(), InheritableBoolean.INHERIT);
 
     set(rc, PROJECT, null, KEY_STATE, p.getState(), defaultStateValue);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
index 6e274a1..60eacb1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -90,10 +90,14 @@
   private final CopyOnWriteArrayList<Executor> queues;
 
   @Inject
-  WorkQueue(final IdGenerator idGenerator, @GerritServerConfig final Config cfg) {
+  WorkQueue(IdGenerator idGenerator, @GerritServerConfig Config cfg) {
+    this(idGenerator, cfg.getInt("execution", "defaultThreadPoolSize", 1));
+  }
+
+  public WorkQueue(IdGenerator idGenerator, int defaultThreadPoolSize) {
     this.idGenerator = idGenerator;
     this.queues = new CopyOnWriteArrayList<Executor>();
-    defaultQueueSize = cfg.getInt("execution", "defaultThreadPoolSize", 1);
+    this.defaultQueueSize = defaultThreadPoolSize;
   }
 
   /** Get the default work queue, for miscellaneous tasks. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 18df4c1..6d15ed3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -179,7 +179,7 @@
 
       final List<PatchSetApproval> approvals = Lists.newArrayList();
       for (PatchSetApproval a
-          : args.approvalsUtil.byPatchSet(args.db, n.notes(), n.getPatchsetId())) {
+          : args.approvalsUtil.byPatchSet(args.db, n.getControl(), n.getPatchsetId())) {
         approvals.add(new PatchSetApproval(ps.getId(), a));
       }
       args.db.patchSetApprovals().insert(approvals);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index a97b3fd..5fabb06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -96,7 +96,7 @@
 
             List<PatchSetApproval> approvals = Lists.newArrayList();
             for (PatchSetApproval a : args.approvalsUtil.byPatchSet(
-                args.db, n.notes(), n.getPatchsetId())) {
+                args.db, n.getControl(), n.getPatchsetId())) {
               approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
             }
             // rebaseChange.rebase() may already have copied some approvals,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index a0cd5fd..aa08085 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.git.strategy;
 
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.IdentifiedUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index d23cc82..a1021cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.git.strategy;
 
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.GerritPersonIdent;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index afa516f..a5777da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.index;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -93,6 +94,17 @@
         }
       };
 
+  /** Project containing the change, as a prefix field. */
+  public static final FieldDef<ChangeData, String> PROJECTS =
+      new FieldDef.Single<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_PROJECTS, FieldType.PREFIX, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change().getProject().get();
+        }
+      };
+
   /** Reference (aka branch) the change will submit onto. */
   public static final FieldDef<ChangeData, String> REF =
       new FieldDef.Single<ChangeData, String>(
@@ -104,8 +116,9 @@
         }
       };
 
+  @Deprecated
   /** Topic, a short annotation on the branch. */
-  public static final FieldDef<ChangeData, String> TOPIC =
+  public static final FieldDef<ChangeData, String> LEGACY_TOPIC =
       new FieldDef.Single<ChangeData, String>(
           ChangeQueryBuilder.FIELD_TOPIC, FieldType.EXACT, false) {
         @Override
@@ -115,6 +128,17 @@
         }
       };
 
+  /** Topic, a short annotation on the branch. */
+  public static final FieldDef<ChangeData, String> TOPIC =
+      new FieldDef.Single<ChangeData, String>(
+          "topic2", FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return Objects.firstNonNull(input.change().getTopic(), "");
+        }
+      };
+
   // Same value as UPDATED, but implementations truncated to minutes.
   @Deprecated
   /** Last update time since January 1, 1970. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index a46b22d..bd37408 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -37,7 +37,7 @@
         ChangeField.STATUS,
         ChangeField.PROJECT,
         ChangeField.REF,
-        ChangeField.TOPIC,
+        ChangeField.LEGACY_TOPIC,
         ChangeField.LEGACY_UPDATED,
         ChangeField.LEGACY_SORTKEY,
         ChangeField.PATH,
@@ -57,7 +57,7 @@
         ChangeField.STATUS,
         ChangeField.PROJECT,
         ChangeField.REF,
-        ChangeField.TOPIC,
+        ChangeField.LEGACY_TOPIC,
         ChangeField.LEGACY_UPDATED,
         ChangeField.LEGACY_SORTKEY,
         ChangeField.PATH,
@@ -79,7 +79,7 @@
         ChangeField.STATUS,
         ChangeField.PROJECT,
         ChangeField.REF,
-        ChangeField.TOPIC,
+        ChangeField.LEGACY_TOPIC,
         ChangeField.LEGACY_UPDATED,
         ChangeField.SORTKEY,
         ChangeField.PATH,
@@ -104,7 +104,7 @@
         ChangeField.STATUS,
         ChangeField.PROJECT,
         ChangeField.REF,
-        ChangeField.TOPIC,
+        ChangeField.LEGACY_TOPIC,
         ChangeField.LEGACY_UPDATED,
         ChangeField.SORTKEY,
         ChangeField.PATH,
@@ -130,7 +130,7 @@
         ChangeField.STATUS,
         ChangeField.PROJECT,
         ChangeField.REF,
-        ChangeField.TOPIC,
+        ChangeField.LEGACY_TOPIC,
         ChangeField.LEGACY_UPDATED,
         ChangeField.SORTKEY,
         ChangeField.FILE_PART,
@@ -147,12 +147,60 @@
         ChangeField.APPROVAL,
         ChangeField.MERGEABLE);
 
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V8 = release(
         ChangeField.LEGACY_ID,
         ChangeField.ID,
         ChangeField.STATUS,
         ChangeField.PROJECT,
         ChangeField.REF,
+        ChangeField.LEGACY_TOPIC,
+        ChangeField.UPDATED,
+        ChangeField.FILE_PART,
+        ChangeField.PATH,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL,
+        ChangeField.MERGEABLE);
+
+  @SuppressWarnings("deprecation")
+  static final Schema<ChangeData> V9 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.PROJECTS,
+        ChangeField.REF,
+        ChangeField.LEGACY_TOPIC,
+        ChangeField.UPDATED,
+        ChangeField.FILE_PART,
+        ChangeField.PATH,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL,
+        ChangeField.MERGEABLE);
+
+  static final Schema<ChangeData> V10 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.PROJECTS,
+        ChangeField.REF,
         ChangeField.TOPIC,
         ChangeField.UPDATED,
         ChangeField.FILE_PART,
@@ -170,6 +218,7 @@
         ChangeField.MERGEABLE);
 
 
+
   private static Schema<ChangeData> release(Collection<FieldDef<ChangeData, ?>> fields) {
     return new Schema<ChangeData>(true, fields);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
index bda2ace..c3a60d0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
@@ -82,6 +82,10 @@
     return fields;
   }
 
+  public final boolean hasField(FieldDef<T, ?> field) {
+    return fields.get(field.getName()) == field;
+  }
+
   /**
    * Build all fields in the schema from an input object.
    * <p>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index 07a5f9a..5cb1ba1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -62,7 +62,7 @@
       Table<Account.Id, String, PatchSetApproval> pos = HashBasedTable.create();
       Table<Account.Id, String, PatchSetApproval> neg = HashBasedTable.create();
       for (PatchSetApproval ca : args.approvalsUtil.byPatchSet(
-            args.db.get(), changeData.notes(), patchSet.getId())) {
+            args.db.get(), changeData.changeControl(), patchSet.getId())) {
         LabelType lt = labelTypes.byLabel(ca.getLabelId());
         if (lt == null) {
           continue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 286bf4ad..37be365 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -414,6 +414,7 @@
   protected void onLoad() throws IOException, ConfigInvalidException {
     ObjectId rev = getRevision();
     if (rev == null) {
+      loadDefaults();
       return;
     }
     RevWalk walk = new RevWalk(reader);
@@ -439,6 +440,12 @@
     }
   }
 
+  private void loadDefaults() {
+    approvals = ImmutableListMultimap.of();
+    reviewers = ImmutableSetMultimap.of();
+    submitRecords = ImmutableList.of();
+  }
+
   @Override
   protected boolean onSave(CommitBuilder commit) {
     throw new UnsupportedOperationException(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
index c9ac8de..5321046 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.notedb;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
@@ -36,7 +34,7 @@
   static NotesMigration allEnabled() {
     Config cfg = new Config();
     cfg.setBoolean("notedb", null, "write", true);
-    //cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
+    cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
     return new NotesMigration(cfg);
   }
 
@@ -48,8 +46,6 @@
     write = cfg.getBoolean("notedb", null, "write", false);
     readPatchSetApprovals =
         cfg.getBoolean("notedb", "patchSetApprovals", "read", false);
-    checkArgument(!readPatchSetApprovals,
-        "notedb.readPatchSetApprovals not yet supported");
   }
 
   public boolean write() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index 2f25ed0..7e2a2b5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -38,10 +38,12 @@
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
 import java.util.Collection;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Map;
@@ -92,6 +94,15 @@
     }
   }
 
+  public static Iterable<ExtensionMetaData> scan(File file, String pluginName,
+      Class<? extends Annotation> annotation) throws InvalidPluginException,
+      IOException {
+    Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> result =
+        scan(new JarFile(file), pluginName,
+            Arrays.<Class<? extends Annotation>> asList(annotation));
+    return result.get(annotation);
+  }
+
   public static Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
       JarFile jarFile, String pluginName,
       Iterable<Class<? extends Annotation>> annotations)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index ae90d5b..bc622ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.annotations.RootRelative;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -56,6 +57,8 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 /**
  * Tracks Guice bindings that should be exposed to loaded plugins.
@@ -82,6 +85,8 @@
   private Provider<ModuleGenerator> httpGen;
 
   private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
+  private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
+  private Map<TypeLiteral<?>, DynamicItem<?>> httpItems;
 
   private Map<TypeLiteral<?>, DynamicSet<?>> sysSets;
   private Map<TypeLiteral<?>, DynamicSet<?>> sshSets;
@@ -119,7 +124,9 @@
   }
 
   boolean hasDynamicItem(TypeLiteral<?> type) {
-    return sysItems.containsKey(type);
+    return sysItems.containsKey(type)
+        || (sshItems != null && sshItems.containsKey(type))
+        || (httpItems != null && httpItems.containsKey(type));
   }
 
   boolean hasDynamicSet(TypeLiteral<?> type) {
@@ -154,6 +161,7 @@
   public void setSshInjector(Injector injector) {
     sshModule = copy(injector);
     sshGen = injector.getProvider(ModuleGenerator.class);
+    sshItems = dynamicItemsOf(injector);
     sshSets = dynamicSetsOf(injector);
     sshMaps = dynamicMapsOf(injector);
     onStart.addAll(listeners(injector, StartPluginListener.class));
@@ -175,6 +183,7 @@
   public void setHttpInjector(Injector injector) {
     httpModule = copy(injector);
     httpGen = injector.getProvider(ModuleGenerator.class);
+    httpItems = dynamicItemsOf(injector);
     httpSets = dynamicSetsOf(injector);
     httpMaps = dynamicMapsOf(injector);
     onStart.addAll(listeners(injector, StartPluginListener.class));
@@ -209,6 +218,8 @@
     RequestContext oldContext = enter(plugin);
     try {
       attachItem(sysItems, plugin.getSysInjector(), plugin);
+      attachItem(sshItems, plugin.getSshInjector(), plugin);
+      attachItem(httpItems, plugin.getHttpInjector(), plugin);
 
       attachSet(sysSets, plugin.getSysInjector(), plugin);
       attachSet(sshSets, plugin.getSshInjector(), plugin);
@@ -274,6 +285,8 @@
       reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin);
 
       reattachItem(old, sysItems, newPlugin.getSysInjector(), newPlugin);
+      reattachItem(old, sshItems, newPlugin.getSshInjector(), newPlugin);
+      reattachItem(old, httpItems, newPlugin.getHttpInjector(), newPlugin);
     } finally {
       exit(oldContext);
     }
@@ -459,9 +472,13 @@
 
   private Module copy(Injector src) {
     Set<TypeLiteral<?>> dynamicTypes = Sets.newHashSet();
+    Set<TypeLiteral<?>> dynamicItemTypes = Sets.newHashSet();
     for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
       TypeLiteral<?> type = e.getKey().getTypeLiteral();
-      if (type.getRawType() == DynamicSet.class
+      if (type.getRawType() == DynamicItem.class) {
+        ParameterizedType t = (ParameterizedType) type.getType();
+        dynamicItemTypes.add(TypeLiteral.get(t.getActualTypeArguments()[0]));
+      } else if (type.getRawType() == DynamicSet.class
           || type.getRawType() == DynamicMap.class) {
         ParameterizedType t = (ParameterizedType) type.getType();
         dynamicTypes.add(TypeLiteral.get(t.getActualTypeArguments()[0]));
@@ -478,6 +495,8 @@
         // using DynamicSet<F> or DynamicMap<F> internally. That should be
         // exported to plugins.
         continue;
+      } else if (dynamicItemTypes.contains(e.getKey().getTypeLiteral())) {
+        continue;
       } else if (shouldCopy(e.getKey())) {
         bindings.put(e.getKey(), e.getValue());
       }
@@ -485,6 +504,12 @@
     bindings.remove(Key.get(Injector.class));
     bindings.remove(Key.get(java.util.logging.Logger.class));
 
+    final @Nullable Binding<HttpServletRequest> requestBinding =
+        src.getExistingBinding(Key.get(HttpServletRequest.class));
+
+    final @Nullable Binding<HttpServletResponse> responseBinding =
+        src.getExistingBinding(Key.get(HttpServletResponse.class));
+
     return new AbstractModule() {
       @SuppressWarnings("unchecked")
       @Override
@@ -494,6 +519,17 @@
           Binding<Object> b = (Binding<Object>) e.getValue();
           bind(k).toProvider(b.getProvider());
         }
+
+        if (requestBinding != null) {
+          bind(HttpServletRequest.class)
+              .annotatedWith(RootRelative.class)
+              .toProvider(requestBinding.getProvider());
+        }
+        if (responseBinding != null) {
+          bind(HttpServletResponse.class)
+              .annotatedWith(RootRelative.class)
+              .toProvider(responseBinding.getProvider());
+        }
       }
     };
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 732da4b..b4d8df1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.extensions.webui.JavaScriptPlugin;
 import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.cache.PersistentCacheFactory;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -76,6 +77,10 @@
   static final String PLUGIN_TMP_PREFIX = "plugin_";
   static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
 
+  public static String getPluginName(File srcFile) throws IOException {
+    return Objects.firstNonNull(getGerritPluginName(srcFile), nameOf(srcFile)).toLowerCase();
+  }
+
   private final File pluginsDir;
   private final File dataDir;
   private final File tmpDir;
@@ -90,6 +95,7 @@
   private final Provider<PluginCleanerTask> cleaner;
   private final PluginScannerThread scanner;
   private final Provider<String> urlProvider;
+  private final PersistentCacheFactory persistentCacheFactory;
   private final boolean remoteAdmin;
 
   @Inject
@@ -99,7 +105,8 @@
       PluginUser.Factory puf,
       Provider<PluginCleanerTask> pct,
       @GerritServerConfig Config cfg,
-      @CanonicalWebUrl Provider<String> provider) {
+      @CanonicalWebUrl Provider<String> provider,
+      PersistentCacheFactory cacheFactory) {
     pluginsDir = sitePaths.plugins_dir;
     dataDir = sitePaths.data_dir;
     tmpDir = sitePaths.tmp_dir;
@@ -113,6 +120,7 @@
     cleanupHandles = Maps.newConcurrentMap();
     cleaner = pct;
     urlProvider = provider;
+    persistentCacheFactory = cacheFactory;
 
     remoteAdmin =
         cfg.getBoolean("plugins", null, "allowRemoteAdmin", false);
@@ -228,6 +236,7 @@
   }
 
   synchronized private void unloadPlugin(Plugin plugin) {
+    persistentCacheFactory.onStop(plugin);
     String name = plugin.getName();
     log.info(String.format("Unloading plugin %s", name));
     plugin.stop(env);
@@ -656,7 +665,7 @@
   // If multiple plugin files provide the same plugin name, then only
   // the first plugin remains active and all other plugins with the same
   // name are disabled.
-  private static Multimap<String, File> prunePlugins(File pluginsDir) {
+  public static Multimap<String, File> prunePlugins(File pluginsDir) {
     List<File> jars = scanJarsInPluginsDirectory(pluginsDir);
     Multimap<String, File> map;
     try {
@@ -752,8 +761,7 @@
       throws IOException {
     Multimap<String, File> map = LinkedHashMultimap.create();
     for (File srcFile : plugins) {
-      map.put(Objects.firstNonNull(getGerritPluginName(srcFile),
-          nameOf(srcFile)), srcFile);
+      map.put(getPluginName(srcFile), srcFile);
     }
     return map;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 8e3c1e9..377a768 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -22,13 +22,13 @@
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.notedb.ChangeNotes;
@@ -188,29 +188,25 @@
     }
   }
 
-  private final ApprovalsUtil approvalsUtil;
   private final ChangeData.Factory changeDataFactory;
   private final RefControl refControl;
   private final ChangeNotes notes;
 
   @AssistedInject
   ChangeControl(
-      ApprovalsUtil approvalsUtil,
       ChangeData.Factory changeDataFactory,
       ChangeNotes.Factory notesFactory,
       @Assisted RefControl refControl,
       @Assisted Change change) {
-    this(approvalsUtil, changeDataFactory, refControl,
+    this(changeDataFactory, refControl,
         notesFactory.create(change));
   }
 
   @AssistedInject
   ChangeControl(
-      ApprovalsUtil approvalsUtil,
       ChangeData.Factory changeDataFactory,
       @Assisted RefControl refControl,
       @Assisted ChangeNotes notes) {
-    this.approvalsUtil = approvalsUtil;
     this.changeDataFactory = changeDataFactory;
     this.refControl = refControl;
     this.notes = notes;
@@ -220,7 +216,7 @@
     if (getCurrentUser().equals(who)) {
       return this;
     }
-    return new ChangeControl(approvalsUtil, changeDataFactory,
+    return new ChangeControl(changeDataFactory,
         getRefControl().forUser(who), notes);
   }
 
@@ -640,7 +636,7 @@
     String typeName = ((SymbolTerm)typeTerm).name();
     try {
       return SubmitTypeRecord.OK(
-          Project.SubmitType.valueOf(typeName.toUpperCase()));
+          SubmitType.valueOf(typeName.toUpperCase()));
     } catch (IllegalArgumentException e) {
       return logInvalidType(evaluator.getSubmitRule(), typeName);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index bb620a0..6ca05f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -18,13 +18,13 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicMap.Entry;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -48,7 +48,7 @@
   public InheritedBooleanInfo requireChangeId;
   public MaxObjectSizeLimitInfo maxObjectSizeLimit;
   public SubmitType submitType;
-  public Project.State state;
+  public com.google.gerrit.extensions.api.projects.ProjectState state;
   public Map<String, Map<String, ConfigParameterInfo>> pluginConfig;
   public Map<String, ActionInfo> actions;
 
@@ -108,7 +108,7 @@
     this.maxObjectSizeLimit = maxObjectSizeLimit;
 
     this.submitType = p.getSubmitType();
-    this.state = p.getState() != Project.State.ACTIVE ? p.getState() : null;
+    this.state = p.getState() != com.google.gerrit.extensions.api.projects.ProjectState.ACTIVE ? p.getState() : null;
 
     this.commentlinks = Maps.newLinkedHashMap();
     for (CommentLinkInfo cl : projectState.getCommentLinks()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 5f3d2da..37ab506 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -20,6 +20,10 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.errors.ProjectCreationFailedException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -30,14 +34,9 @@
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.GroupsCollection;
-import com.google.gerrit.server.project.CreateProject.Input;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
-import com.google.gerrit.server.project.PutConfig.ConfigValue;
 import com.google.gerrit.server.validators.ProjectCreationValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.inject.Inject;
@@ -48,27 +47,9 @@
 
 import java.io.IOException;
 import java.util.List;
-import java.util.Map;
 
 @RequiresCapability(GlobalCapability.CREATE_PROJECT)
-public class CreateProject implements RestModifyView<TopLevelResource, Input> {
-  public static class Input {
-    public String name;
-    public String parent;
-    public String description;
-    public boolean permissionsOnly;
-    public boolean createEmptyCommit;
-    public SubmitType submitType;
-    public List<String> branches;
-    public List<String> owners;
-    public InheritableBoolean useContributorAgreements;
-    public InheritableBoolean useSignedOffBy;
-    public InheritableBoolean useContentMerge;
-    public InheritableBoolean requireChangeId;
-    public String maxObjectSizeLimit;
-    public Map<String, Map<String, ConfigValue>> pluginConfigValues;
-  }
-
+public class CreateProject implements RestModifyView<TopLevelResource, ProjectInput> {
   public static interface Factory {
     CreateProject create(String name);
   }
@@ -103,12 +84,12 @@
   }
 
   @Override
-  public Response<ProjectInfo> apply(TopLevelResource resource, Input input)
+  public Response<ProjectInfo> apply(TopLevelResource resource, ProjectInput input)
       throws BadRequestException, UnprocessableEntityException,
       ResourceConflictException, ProjectCreationFailedException,
       ResourceNotFoundException, IOException {
     if (input == null) {
-      input = new Input();
+      input = new ProjectInput();
     }
     if (input.name != null && !name.equals(input.name)) {
       throw new BadRequestException("name must match URL");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
index ea20cea..e937e0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 
 import java.util.List;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java
index 1659cb7..815653f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.inject.Inject;
 
 import org.kohsuke.args4j.Option;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
index 961f7b2..af55c15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.inject.Inject;
 
 class GetProject implements RestReadView<ProjectResource> {
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 b39e362..6a4f013 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
@@ -16,11 +16,17 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
+import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Constants;
@@ -34,14 +40,17 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 public class ListBranches implements RestReadView<ProjectResource> {
-
   private final GitRepositoryManager repoManager;
+  private final DynamicMap<RestView<BranchResource>> branchViews;
 
   @Inject
-  public ListBranches(GitRepositoryManager repoManager) {
+  public ListBranches(GitRepositoryManager repoManager,
+      DynamicMap<RestView<BranchResource>> branchViews) {
     this.repoManager = repoManager;
+    this.branchViews = branchViews;
   }
 
   @Override
@@ -136,17 +145,28 @@
     return branches;
   }
 
-  private static BranchInfo createBranchInfo(Ref ref, RefControl refControl,
+  private BranchInfo createBranchInfo(Ref ref, RefControl refControl,
       Set<String> targets) {
-    return new BranchInfo(ref.getName(),
+    BranchInfo info = new BranchInfo(ref.getName(),
         ref.getObjectId() != null ? ref.getObjectId().name() : null,
         !targets.contains(ref.getName()) && refControl.canDelete());
+    for (UiAction.Description d : UiActions.from(
+        branchViews,
+        new BranchResource(refControl.getProjectControl(), info),
+        Providers.of(refControl.getCurrentUser()))) {
+      if (info.actions == null) {
+        info.actions = new TreeMap<>();
+      }
+      info.actions.put(d.getId(), new ActionInfo(d));
+    }
+    return info;
   }
 
   public static class BranchInfo {
     public String ref;
     public String revision;
     public Boolean canDelete;
+    public Map<String, ActionInfo> actions;
 
     public BranchInfo(String ref, String revision, boolean canDelete) {
       this.ref = ref;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
index 58abe40..486915a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -16,11 +16,11 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.inject.Inject;
 
 import org.kohsuke.args4j.Option;
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 d0b10dc..d249b1d 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
@@ -21,9 +21,11 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -33,7 +35,6 @@
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.util.TreeFormatter;
 import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
@@ -403,7 +404,7 @@
       }
 
       for (ProjectInfo info : output.values()) {
-        info.finish();
+        info.id = Url.encode(info.name);
         info.name = null;
       }
       if (stdout == null) {
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 ca006de..22a761b 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
@@ -22,12 +22,12 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.errors.ProjectCreationFailedException;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 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;
 import com.google.gerrit.server.account.GroupBackend;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
index 0e5cecb..fdff1e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
@@ -25,6 +25,9 @@
   /** @return the parent state for all projects on this server. */
   public ProjectState getAllProjects();
 
+  /** @return the project state of the project storing meta data for all users. */
+  public ProjectState getAllUsers();
+
   /**
    * Get the cached data for a project by its unique name.
    *
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 8ccbca3..1e7a221 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.inject.Inject;
@@ -74,6 +75,7 @@
   }
 
   private final AllProjectsName allProjectsName;
+  private final AllUsersName allUsersName;
   private final LoadingCache<String, ProjectState> byName;
   private final LoadingCache<ListKey, SortedSet<Project.NameKey>> list;
   private final Lock listLock;
@@ -82,10 +84,12 @@
   @Inject
   ProjectCacheImpl(
       final AllProjectsName allProjectsName,
+      final AllUsersName allUsersName,
       @Named(CACHE_NAME) LoadingCache<String, ProjectState> byName,
       @Named(CACHE_LIST) LoadingCache<ListKey, SortedSet<Project.NameKey>> list,
       ProjectCacheClock clock) {
     this.allProjectsName = allProjectsName;
+    this.allUsersName = allUsersName;
     this.byName = byName;
     this.list = list;
     this.listLock = new ReentrantLock(true /* fair */);
@@ -104,6 +108,16 @@
   }
 
   @Override
+  public ProjectState getAllUsers() {
+    ProjectState state = get(allUsersName);
+    if (state == null) {
+      // This should never occur.
+      throw new IllegalStateException("Missing project " + allUsersName);
+    }
+    return state;
+  }
+
+  @Override
   public ProjectState get(final Project.NameKey projectName) {
      try {
       return checkedGet(projectName);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index f71c4c9..c7cc9c3 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
@@ -232,7 +232,8 @@
   }
 
   private boolean isHidden() {
-    return getProject().getState().equals(Project.State.HIDDEN);
+    return getProject().getState().equals(
+        com.google.gerrit.extensions.api.projects.ProjectState.HIDDEN);
   }
 
   /** Can this user see this project exists? */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
index 72910a3..57d1406 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
@@ -15,13 +15,12 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.inject.Inject;
 
-import java.util.Map;
-
 public class ProjectJson {
 
   private final AllProjectsName allProjects;
@@ -42,21 +41,7 @@
     info.parent = parentName != null ? parentName.get() : null;
     info.description = Strings.emptyToNull(p.getDescription());
     info.state = p.getState();
-    info.finish();
+    info.id = Url.encode(info.name);
     return info;
   }
-
-  public static class ProjectInfo {
-    public final String kind = "gerritcodereview#project";
-    public String id;
-    public String name;
-    public String parent;
-    public String description;
-    public Project.State state;
-    public Map<String, String> branches;
-
-    void finish() {
-      id = Url.encode(name);
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
index 459e392..d6a2e09 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.gerrit.extensions.api.projects.ProjectState;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.reviewdb.client.Project;
@@ -41,7 +42,7 @@
     return control.getProject().getNameKey();
   }
 
-  public Project.State getState() {
+  public ProjectState getState() {
     return control.getProject().getState();
   }
 
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 50f232a..69f76e3 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
@@ -29,9 +29,9 @@
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.common.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.RulesCache;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index c481eb4..39d2367 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -19,6 +19,9 @@
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.api.projects.ProjectInput.ConfigValue;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -27,8 +30,6 @@
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
@@ -58,10 +59,6 @@
 
 public class PutConfig implements RestModifyView<ProjectResource, Input> {
   private static final Logger log = LoggerFactory.getLogger(PutConfig.class);
-  public static class ConfigValue {
-    public String value;
-    public List<String> values;
-  }
   public static class Input {
     public String description;
     public InheritableBoolean useContributorAgreements;
@@ -70,7 +67,7 @@
     public InheritableBoolean requireChangeId;
     public String maxObjectSizeLimit;
     public SubmitType submitType;
-    public Project.State state;
+    public com.google.gerrit.extensions.api.projects.ProjectState state;
     public Map<String, Map<String, ConfigValue>> pluginConfigValues;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
index 836899a2..19ba794 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
@@ -14,14 +14,14 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.project.CreateProject.Input;
 
-public class PutProject implements RestModifyView<ProjectResource, Input> {
+public class PutProject implements RestModifyView<ProjectResource, ProjectInput> {
   @Override
-  public Response<?> apply(ProjectResource resource, Input input)
+  public Response<?> apply(ProjectResource resource, ProjectInput input)
       throws ResourceConflictException {
     throw new ResourceConflictException("Project \"" + resource.getName()
         + "\" already exists");
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 7c956da..da093b6 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
@@ -22,7 +22,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 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.extensions.api.projects.ProjectState;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -207,12 +207,12 @@
 
   public boolean canWrite() {
     return getProjectControl().getProject().getState().equals(
-        Project.State.ACTIVE);
+        ProjectState.ACTIVE);
   }
 
   public boolean canRead() {
     return getProjectControl().getProject().getState().equals(
-        Project.State.READ_ONLY) || canWrite();
+        ProjectState.READ_ONLY) || canWrite();
   }
 
   private boolean canPushWithForce() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 333c343..2f0bf33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -343,12 +343,15 @@
     return changeControl != null;
   }
 
-  public ChangeControl changeControl() throws NoSuchChangeException,
-      OrmException {
+  public ChangeControl changeControl() throws OrmException {
     if (changeControl == null) {
       Change c = change();
-      changeControl =
-          changeControlFactory.controlFor(c, userFactory.create(c.getOwner()));
+      try {
+        changeControl =
+            changeControlFactory.controlFor(c, userFactory.create(c.getOwner()));
+      } catch (NoSuchChangeException e) {
+        throw new OrmException(e);
+      }
     }
     return changeControl;
   }
@@ -394,11 +397,9 @@
       Change c = change();
       if (c == null) {
         currentApprovals = Collections.emptyList();
-      } else if (allApprovals != null) {
-        return allApprovals.get(c.currentPatchSetId());
       } else {
-        currentApprovals = approvalsUtil.byPatchSet(
-            db, notes(), c.currentPatchSetId());
+        currentApprovals = ImmutableList.copyOf(approvalsUtil.byPatchSet(
+            db, changeControl(), c.currentPatchSetId()));
       }
     }
     return currentApprovals;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 4a6381e..8ffd88a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -22,8 +22,6 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -36,6 +34,7 @@
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
+import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.Schema;
@@ -55,7 +54,6 @@
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.Config;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -74,14 +72,6 @@
   private static final Pattern DEF_CHANGE =
       Pattern.compile("^([1-9][0-9]*|[iI][0-9a-f]{4,}.*)$");
 
-  private static final Pattern PAT_COMMIT =
-      Pattern.compile("^([0-9a-fA-F]{4," + RevId.LEN + "})$");
-  private static final Pattern PAT_EMAIL =
-      Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+");
-
-  private static final Pattern PAT_LABEL =
-      Pattern.compile("^[a-zA-Z][a-zA-Z0-9]*((=|>=|<=)[+-]?|[+-])\\d+$");
-
   // NOTE: As new search operations are added, please keep the
   // SearchSuggestOracle up to date.
 
@@ -106,6 +96,7 @@
   public static final String FIELD_PARENTPROJECT = "parentproject";
   public static final String FIELD_PATH = "path";
   public static final String FIELD_PROJECT = "project";
+  public static final String FIELD_PROJECTS = "projects";
   public static final String FIELD_REF = "ref";
   public static final String FIELD_REVIEWER = "reviewer";
   public static final String FIELD_REVIEWERIN = "reviewerin";
@@ -372,6 +363,14 @@
   }
 
   @Operator
+  public Predicate<ChangeData> projects(String name) throws QueryParseException {
+    if (!schema(args.indexes).hasField(ChangeField.PROJECTS)) {
+      throw new QueryParseException("Unsupported operator: " + FIELD_PROJECTS);
+    }
+    return new ProjectPrefixPredicate(name);
+  }
+
+  @Operator
   public Predicate<ChangeData> parentproject(String name) {
     return new ParentProjectPredicate(args.db, args.projectCache,
         args.listChildProjects, args.self, name);
@@ -393,8 +392,8 @@
   @Operator
   public Predicate<ChangeData> topic(String name) {
     if (name.startsWith("^"))
-      return new RegexTopicPredicate(name);
-    return new TopicPredicate(name);
+      return new RegexTopicPredicate(schema(args.indexes), name);
+    return new TopicPredicate(schema(args.indexes), name);
   }
 
   @Operator
@@ -678,52 +677,58 @@
   }
 
   @Override
-  protected Predicate<ChangeData> defaultField(String query)
-      throws QueryParseException {
+  protected Predicate<ChangeData> defaultField(String query) {
     if (query.startsWith("refs/")) {
       return ref(query);
-
     } else if (DEF_CHANGE.matcher(query).matches()) {
       return change(query);
-
-    } else if (PAT_COMMIT.matcher(query).matches()) {
-      return commit(query);
-
-    } else if (PAT_EMAIL.matcher(query).find()) {
-      try {
-        return Predicate.or(owner(query), reviewer(query));
-      } catch (OrmException err) {
-        throw error("Cannot lookup user", err);
-      }
-
-    } else if (PAT_LABEL.matcher(query).find()) {
-      try {
-        return label(query);
-      } catch (OrmException err) {
-        throw error("Cannot lookup user", err);
-      }
-
-    } else {
-      // Try to match a project name by substring query.
-      final List<ProjectPredicate> predicate =
-          new ArrayList<ProjectPredicate>();
-      for (Project.NameKey name : args.projectCache.all()) {
-        if (name.get().toLowerCase().contains(query.toLowerCase())) {
-          predicate.add(new ProjectPredicate(name.get()));
-        }
-      }
-
-      // If two or more projects contains "query" as substring create an
-      // OrPredicate holding predicates for all these projects, otherwise if
-      // only one contains that, return only that one predicate by itself.
-      if (predicate.size() == 1) {
-        return predicate.get(0);
-      } else if (predicate.size() > 1) {
-        return Predicate.or(predicate);
-      }
-
-      throw error("Unsupported query:" + query);
     }
+
+    List<Predicate<ChangeData>> predicates = Lists.newArrayListWithCapacity(9);
+    try {
+      predicates.add(commit(query));
+    } catch (IllegalArgumentException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(owner(query));
+    } catch (OrmException | QueryParseException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(reviewer(query));
+    } catch (OrmException | QueryParseException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(file(query));
+    } catch (QueryParseException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(label(query));
+    } catch (OrmException | QueryParseException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(message(query));
+    } catch (QueryParseException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(comment(query));
+    } catch (QueryParseException e) {
+      // Skip.
+    }
+    try {
+      predicates.add(projects(query));
+    } catch (QueryParseException e) {
+      // Skip.
+    }
+    predicates.add(ref(query));
+    predicates.add(branch(query));
+    predicates.add(topic(query));
+    return Predicate.or(predicates);
   }
 
   private Set<Account.Id> parseAccount(String who)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
index e177e37..e64ff13 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
@@ -15,14 +15,14 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.base.Objects;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.common.SubmitType;
 
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.io.Serializable;
 
 public class ConflictKey implements Serializable {
-  private static final long serialVersionUID = 1L;
+  private static final long serialVersionUID = 2L;
 
   private final ObjectId commit;
   private final ObjectId otherCommit;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index ee6587d..61454a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -17,8 +17,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.MergeException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 822ffc2..253f719 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.OrPredicate;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
new file mode 100644
index 0000000..d0faf0f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 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.query.change;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+class ProjectPrefixPredicate extends IndexPredicate<ChangeData> {
+  ProjectPrefixPredicate(String prefix) {
+    super(ChangeField.PROJECTS, prefix);
+  }
+
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    Change c = object.change();
+    return c != null && c.getDest().getParentKey().get().startsWith(getValue());
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index 3a9604f..7d5f1dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.index.Schema;
 import com.google.gwtorm.server.OrmException;
 
 import dk.brics.automaton.RegExp;
@@ -25,8 +25,8 @@
 class RegexTopicPredicate extends RegexPredicate<ChangeData> {
   private final RunAutomaton pattern;
 
-  RegexTopicPredicate(String re) {
-    super(ChangeField.TOPIC, re);
+  RegexTopicPredicate(Schema<ChangeData> schema, String re) {
+    super(TopicPredicate.topicField(schema), re);
 
     if (re.startsWith("^")) {
       re = re.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
index ee4e2ef..7196c9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -14,14 +14,30 @@
 
 package com.google.gerrit.server.query.change;
 
+import static com.google.gerrit.server.index.ChangeField.TOPIC;
+
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.Schema;
 import com.google.gwtorm.server.OrmException;
 
 class TopicPredicate extends IndexPredicate<ChangeData> {
-  TopicPredicate(String topic) {
-    super(ChangeField.TOPIC, topic);
+  @SuppressWarnings("deprecation")
+  static FieldDef<ChangeData, ?> topicField(Schema<ChangeData> schema) {
+    if (schema == null) {
+      return ChangeField.LEGACY_TOPIC;
+    }
+    FieldDef<ChangeData, ?> f = schema.getFields().get(TOPIC.getName());
+    if (f != null) {
+      return f;
+    }
+    return schema.getFields().get(ChangeField.LEGACY_TOPIC.getName());
+  }
+
+  TopicPredicate(Schema<ChangeData> schema, String topic) {
+    super(topicField(schema), topic);
   }
 
   @Override
@@ -30,7 +46,11 @@
     if (change == null) {
       return false;
     }
-    return getValue().equals(change.getTopic());
+    String t = change.getTopic();
+    if (t == null && getField() == TOPIC) {
+      t = "";
+    }
+    return getValue().equals(t);
   }
 
   @Override
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 f592530..7712650 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
@@ -28,9 +28,9 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.extensions.common.InheritableBoolean;
 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;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllUsersCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllUsersCreator.java
new file mode 100644
index 0000000..fda5306
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllUsersCreator.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.common.Version;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+
+/** Creates the {@code All-Users} repository. */
+public class AllUsersCreator {
+  private final GitRepositoryManager mgr;
+  private final AllUsersName allUsersName;
+  private final PersonIdent serverUser;
+
+  @Inject
+  AllUsersCreator(
+      GitRepositoryManager mgr,
+      AllUsersName allUsersName,
+      @GerritPersonIdent PersonIdent serverUser) {
+    this.mgr = mgr;
+    this.allUsersName = allUsersName;
+    this.serverUser = serverUser;
+  }
+
+  public void create() throws IOException, ConfigInvalidException {
+    Repository git = null;
+    try {
+      git = mgr.openRepository(allUsersName);
+      initAllUsers(git);
+    } catch (RepositoryNotFoundException notFound) {
+      try {
+        git = mgr.createRepository(allUsersName);
+        initAllUsers(git);
+      } catch (RepositoryNotFoundException err) {
+        String name = allUsersName.get();
+        throw new IOException("Cannot create repository " + name, err);
+      }
+    } finally {
+      if (git != null) {
+        git.close();
+      }
+    }
+  }
+
+  private void initAllUsers(Repository git)
+      throws IOException, ConfigInvalidException {
+    MetaDataUpdate md = new MetaDataUpdate(
+        GitReferenceUpdated.DISABLED,
+        allUsersName,
+        git);
+    md.getCommitBuilder().setAuthor(serverUser);
+    md.getCommitBuilder().setCommitter(serverUser);
+    md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
+
+    ProjectConfig config = ProjectConfig.read(md);
+    Project project = config.getProject();
+    project.setDescription("Individual user settings and preferences.");
+
+    AccessSection all = config.getAccessSection(RefNames.REFS_USER + "*", true);
+    all.getPermission(Permission.READ, true).setExclusiveGroup(true);
+    config.commit(md);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 589d177..298c0d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -42,6 +42,7 @@
   File site_path;
 
   private final AllProjectsCreator allProjectsCreator;
+  private final AllUsersCreator allUsersCreator;
   private final PersonIdent serverUser;
   private final DataSourceType dataSourceType;
 
@@ -54,18 +55,21 @@
   public SchemaCreator(SitePaths site,
       @Current SchemaVersion version,
       AllProjectsCreator ap,
+      AllUsersCreator auc,
       @GerritPersonIdent PersonIdent au,
       DataSourceType dst) {
-    this(site.site_path, version, ap, au, dst);
+    this(site.site_path, version, ap, auc, au, dst);
   }
 
   public SchemaCreator(@SitePath File site,
       @Current SchemaVersion version,
       AllProjectsCreator ap,
+      AllUsersCreator auc,
       @GerritPersonIdent PersonIdent au,
       DataSourceType dst) {
     site_path = site;
     allProjectsCreator = ap;
+    allUsersCreator = auc;
     serverUser = au;
     dataSourceType = dst;
     versionNbr = version.getVersionNbr();
@@ -90,6 +94,7 @@
       .setAdministrators(GroupReference.forGroup(admin))
       .setBatchUsers(GroupReference.forGroup(batch))
       .create();
+    allUsersCreator.create();
     dataSourceType.getIndexScript().run(db);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
index 5ba7d4c..aaf4607 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaModule.java
@@ -20,6 +20,8 @@
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.FactoryModule;
@@ -39,6 +41,10 @@
       .toProvider(AllProjectsNameProvider.class)
       .in(SINGLETON);
 
+    bind(AllUsersName.class)
+      .toProvider(AllUsersNameProvider.class)
+      .in(SINGLETON);
+
     bind(String.class).annotatedWith(AnonymousCowardName.class).toProvider(
         AnonymousCowardNameProvider.class);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 8800929..90d23ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
 /** A version of the database schema. */
 public abstract class SchemaVersion {
   /** The current schema version. */
-  public static final Class<Schema_93> C = Schema_93.class;
+  public static final Class<Schema_95> C = Schema_95.class;
 
   public static class Module extends AbstractModule {
     @Override
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 cbcda9f..16ca664 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
@@ -31,11 +31,12 @@
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 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;
 import com.google.gerrit.reviewdb.client.SystemConfig;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -213,16 +214,16 @@
 
     switch (rs.getString("submit_type").charAt(0)) {
       case 'F':
-        project.setSubmitType(Project.SubmitType.FAST_FORWARD_ONLY);
+        project.setSubmitType(SubmitType.FAST_FORWARD_ONLY);
         break;
       case 'M':
-        project.setSubmitType(Project.SubmitType.MERGE_IF_NECESSARY);
+        project.setSubmitType(SubmitType.MERGE_IF_NECESSARY);
         break;
       case 'A':
-        project.setSubmitType(Project.SubmitType.MERGE_ALWAYS);
+        project.setSubmitType(SubmitType.MERGE_ALWAYS);
         break;
       case 'C':
-        project.setSubmitType(Project.SubmitType.CHERRY_PICK);
+        project.setSubmitType(SubmitType.CHERRY_PICK);
         break;
       default:
         throw new OrmException("Unsupported submit_type="
@@ -238,8 +239,8 @@
   private static InheritableBoolean asInheritableBoolean(ResultSet rs, String col)
       throws SQLException {
     return "Y".equals(rs.getString(col))
-        ? Project.InheritableBoolean.TRUE
-        : Project.InheritableBoolean.INHERIT;
+        ? InheritableBoolean.TRUE
+        : InheritableBoolean.INHERIT;
   }
 
   private void readOldRefRights(ReviewDb db) throws SQLException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java
new file mode 100644
index 0000000..3d45274
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_94.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_94 extends SchemaVersion {
+  @Inject
+  Schema_94(Provider<Schema_93> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement()) {
+      stmt.execute("CREATE INDEX patch_sets_byRevision"
+          + " ON patch_sets (revision)");
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_95.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_95.java
new file mode 100644
index 0000000..1c839f7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_95.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+public class Schema_95 extends SchemaVersion {
+  private final AllUsersCreator allUsersCreator;
+
+  @Inject
+  Schema_95(Provider<Schema_94> prior, AllUsersCreator allUsersCreator) {
+    super(prior);
+    this.allUsersCreator = allUsersCreator;
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
+      SQLException {
+    try {
+      allUsersCreator.create();
+    } catch (IOException | ConfigInvalidException e) {
+      throw new OrmException(e);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
new file mode 100644
index 0000000..e0e8237
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
@@ -0,0 +1,98 @@
+// 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.securestore;
+
+import com.google.gerrit.common.FileUtil;
+import com.google.gerrit.extensions.annotations.Export;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
+
+@Singleton
+@Export(DefaultSecureStore.NAME)
+public class DefaultSecureStore implements SecureStore {
+  public static final String NAME = "default";
+
+  private final FileBasedConfig sec;
+
+  @Inject
+  DefaultSecureStore(SitePaths site) {
+    File secureConfig = new File(site.etc_dir, "secure.config");
+    sec = new FileBasedConfig(secureConfig, FS.DETECTED);
+    try {
+      sec.load();
+    } catch (Exception e) {
+      throw new RuntimeException("Cannot load secure.config", e);
+    }
+  }
+
+  @Override
+  public String get(String section, String subsection, String name) {
+    return sec.getString(section, subsection, name);
+  }
+
+  @Override
+  public void set(String section, String subsection, String name, String value) {
+    if (value != null) {
+      sec.setString(section, subsection, name, value);
+    } else {
+      sec.unset(section, subsection, name);
+    }
+    save();
+  }
+
+  @Override
+  public void unset(String section, String subsection, String name) {
+    sec.unset(section, subsection, name);
+    save();
+  }
+
+  private void save() {
+    try {
+      saveSecure(sec);
+    } catch (IOException e) {
+      throw new RuntimeException("Cannot save secure.config", e);
+    }
+  }
+
+  private static void saveSecure(final FileBasedConfig sec) throws IOException {
+    if (FileUtil.modified(sec)) {
+      final byte[] out = Constants.encode(sec.toText());
+      final File path = sec.getFile();
+      final LockFile lf = new LockFile(path, FS.DETECTED);
+      if (!lf.lock()) {
+        throw new IOException("Cannot lock " + path);
+      }
+      try {
+        FileUtil.chmod(0600, new File(path.getParentFile(), path.getName()
+            + ".lock"));
+        lf.write(out);
+        if (!lf.commit()) {
+          throw new IOException("Cannot commit write to " + path);
+        }
+      } finally {
+        lf.unlock();
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java
new file mode 100644
index 0000000..3fe00f4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStore.java
@@ -0,0 +1,27 @@
+// 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.securestore;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public interface SecureStore {
+
+  String get(String section, String subsection, String name);
+
+  void set(String section, String subsection, String name, String value);
+
+  void unset(String section, String subsection, String name);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
new file mode 100644
index 0000000..b925105
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2014 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.securestore;
+
+import com.google.common.base.Objects;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class SecureStoreData {
+  public final File pluginFile;
+  public final String storeName;
+  public final String className;
+
+  public SecureStoreData(String pluginName, String className, File jarFile,
+      String storeName) {
+    this.className = className;
+    this.pluginFile = jarFile;
+    this.storeName = String.format("%s/%s", pluginName, storeName);
+  }
+
+  public String getStoreName() {
+    return storeName;
+  }
+
+  public Class<? extends SecureStore> load() {
+    return load(pluginFile);
+  }
+
+  @SuppressWarnings("unchecked")
+  public Class<? extends SecureStore> load(File pluginFile) {
+    try {
+      URL[] pluginJarUrls = new URL[] {pluginFile.toURI().toURL()};
+      ClassLoader currentCL = Thread.currentThread().getContextClassLoader();
+      final URLClassLoader newClassLoader =
+          new URLClassLoader(pluginJarUrls, currentCL);
+      Thread.currentThread().setContextClassLoader(newClassLoader);
+      return (Class<? extends SecureStore>) newClassLoader.loadClass(className);
+    } catch (Exception e) {
+      throw new SecureStoreException(String.format(
+          "Cannot load secure store implementation for %s", storeName), e);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this).add("storeName", storeName)
+        .add("className", className).add("file", pluginFile).toString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof SecureStoreData) {
+      SecureStoreData o = (SecureStoreData) obj;
+      return storeName.equals(o.storeName);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(storeName);
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreException.java
similarity index 61%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreException.java
index 96e2ec8..01450f8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 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.
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.server.securestore;
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
+public class SecureStoreException extends RuntimeException {
+  private static final long serialVersionUID = 5581700510568485065L;
 
-  public Die(final String why) {
-    super(why);
+  SecureStoreException(String msg) {
+    super(msg);
   }
 
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
+  SecureStoreException(String msg, Exception e) {
+    super(msg, e);
   }
 }
diff --git a/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java b/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
index 0f173c7..a471450 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_project_default_submit_type_1.java
@@ -14,7 +14,7 @@
 
 package gerrit;
 
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.project.ChangeControl;
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
index e4de9ed..ba33b97 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -38,6 +38,8 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.change.ChangeKindCache;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
@@ -156,6 +158,11 @@
       }
 
       @Override
+      public ProjectState getAllUsers() {
+        return null;
+      }
+
+      @Override
       public ProjectState get(Project.NameKey projectName) {
         return all.get(projectName);
       }
@@ -217,6 +224,7 @@
             .toProvider(CanonicalWebUrlProvider.class);
         bind(String.class).annotatedWith(AnonymousCowardName.class)
             .toProvider(AnonymousCowardNameProvider.class);
+        bind(ChangeKindCache.class).to(ChangeKindCacheImpl.NoCache.class);
       }
     });
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 4342684..ef92cbf 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -28,6 +28,7 @@
 import com.google.common.hash.Hashing;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
@@ -110,6 +111,9 @@
     schemaCreator.create(db);
     userId = accountManager.authenticate(AuthRequest.forUser("user"))
         .getAccountId();
+    Account userAccount = db.accounts().get(userId);
+    userAccount.setPreferredEmail("user@example.com");
+    db.accounts().update(ImmutableList.of(userAccount));
     user = userFactory.create(userId);
 
     requestContext.setContext(new RequestContext() {
@@ -303,11 +307,30 @@
     Change change2 = newChange(repo2, null, null, null, null).insert();
 
     assertTrue(query("project:foo").isEmpty());
+    assertTrue(query("project:repo").isEmpty());
     assertResultEquals(change1, queryOne("project:repo1"));
     assertResultEquals(change2, queryOne("project:repo2"));
   }
 
   @Test
+  public void byProjectPrefix() throws Exception {
+    TestRepository<InMemoryRepository> repo1 = createProject("repo1");
+    TestRepository<InMemoryRepository> repo2 = createProject("repo2");
+    Change change1 = newChange(repo1, null, null, null, null).insert();
+    Change change2 = newChange(repo2, null, null, null, null).insert();
+
+    assertTrue(query("projects:foo").isEmpty());
+    assertResultEquals(change1, queryOne("projects:repo1"));
+    assertResultEquals(change2, queryOne("projects:repo2"));
+
+    List<ChangeInfo> results;
+    results = query("projects:repo");
+    assertEquals(results.toString(), 2, results.size());
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+  }
+
+  @Test
   public void byBranchAndRef() throws Exception {
     TestRepository<InMemoryRepository> repo = createProject("repo");
     Change change1 = newChange(repo, null, null, null, "master").insert();
@@ -332,14 +355,18 @@
     Change change1 = ins1.getChange();
     change1.setTopic("feature1");
     ins1.insert();
+
     ChangeInserter ins2 = newChange(repo, null, null, null, null);
     Change change2 = ins2.getChange();
     change2.setTopic("feature2");
     ins2.insert();
 
+    Change change3 = newChange(repo, null, null, null, null).insert();
+
     assertTrue(query("topic:foo").isEmpty());
     assertResultEquals(change1, queryOne("topic:feature1"));
     assertResultEquals(change2, queryOne("topic:feature2"));
+    assertResultEquals(change3, queryOne("topic:\"\""));
   }
 
   @Test
@@ -785,6 +812,51 @@
     assertResultEquals(change1, results.get(1));
   }
 
+  @Test
+  public void byDefault() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+
+    Change change1 = newChange(repo, null, null, null, null).insert();
+
+    RevCommit commit2 = repo.parseBody(
+        repo.commit().message("foosubject").create());
+    Change change2 = newChange(repo, commit2, null, null, null).insert();
+
+    RevCommit commit3 = repo.parseBody(
+        repo.commit()
+        .add("Foo.java", "foo contents")
+        .create());
+    Change change3 = newChange(repo, commit3, null, null, null).insert();
+
+    ChangeInserter ins4 = newChange(repo, null, null, null, null);
+    Change change4 = ins4.insert();
+    ReviewInput ri4 = new ReviewInput();
+    ri4.message = "toplevel";
+    ri4.labels = ImmutableMap.<String, Short> of("Code-Review", (short) 1);
+    postReview.apply(new RevisionResource(
+        changes.parse(change4.getId()), ins4.getPatchSet()), ri4);
+
+    ChangeInserter ins5 = newChange(repo, null, null, null, null);
+    Change change5 = ins5.getChange();
+    change5.setTopic("feature5");
+    ins5.insert();
+
+    Change change6 = newChange(repo, null, null, null, "branch6").insert();
+
+    assertResultEquals(change1,
+        queryOne(Integer.toString(change1.getId().get())));
+    assertResultEquals(change2, queryOne("foosubject"));
+    assertResultEquals(change3, queryOne("Foo.java"));
+    assertResultEquals(change4, queryOne("Code-Review+1"));
+    assertResultEquals(change4, queryOne("toplevel"));
+    assertResultEquals(change5, queryOne("feature5"));
+    assertResultEquals(change6, queryOne("branch6"));
+    assertResultEquals(change6, queryOne("refs/heads/branch6"));
+
+    assertEquals(6, query("user@example.com").size());
+    assertEquals(6, query("repo").size());
+  }
+
   protected ChangeInserter newChange(
       TestRepository<InMemoryRepository> repo,
       @Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
@@ -833,7 +905,7 @@
   protected TestRepository<InMemoryRepository> createProject(String name)
       throws Exception {
     CreateProject create = projectFactory.create(name);
-    create.apply(TLR, new CreateProject.Input());
+    create.apply(TLR, new ProjectInput());
     return new TestRepository<InMemoryRepository>(
         repoManager.openRepository(new Project.NameKey(name)));
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
index 948626e..cf60297 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
@@ -32,6 +32,7 @@
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.List;
@@ -43,6 +44,18 @@
     return Guice.createInjector(new InMemoryModule(cfg));
   }
 
+  // Tests for features not supported in V7.
+  @Ignore
+  @Override
+  @Test
+  public void byProjectPrefix() {}
+
+  @Ignore
+  @Override
+  @Test
+  public void byDefault() {}
+  // End tests for features not supported in V7.
+
   @Test
   public void pagination() throws Exception {
     TestRepository<InMemoryRepository> repo = createProject("repo");
@@ -142,4 +155,26 @@
     assertResultEquals(change2, results.get(0));
     assertResultEquals(change1, results.get(1));
   }
+
+  @Override
+  @Test
+  public void byTopic() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    ChangeInserter ins1 = newChange(repo, null, null, null, null);
+    Change change1 = ins1.getChange();
+    change1.setTopic("feature1");
+    ins1.insert();
+
+    ChangeInserter ins2 = newChange(repo, null, null, null, null);
+    Change change2 = ins2.getChange();
+    change2.setTopic("feature2");
+    ins2.insert();
+
+    newChange(repo, null, null, null, null).insert();
+
+    assertTrue(query("topic:\"\"").isEmpty());
+    assertTrue(query("topic:foo").isEmpty());
+    assertResultEquals(change1, queryOne("topic:feature1"));
+    assertResultEquals(change2, queryOne("topic:feature2"));
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index e6b7a3f..d8b6048 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.FactoryModule;
@@ -89,6 +90,8 @@
 
         bind(AllProjectsName.class)
             .toInstance(new AllProjectsName("All-Projects"));
+        bind(AllUsersName.class)
+            .toInstance(new AllUsersName("All-Users"));
 
         bind(GitRepositoryManager.class)
             .toInstance(new InMemoryRepositoryManager());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
new file mode 100644
index 0000000..0ccdae7
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
@@ -0,0 +1,177 @@
+// 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.testutil;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.util.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+public abstract class FilesystemLoggingMockingTestCase extends LoggingMockingTestCase {
+
+  private Collection<File> toCleanup = Lists.newArrayList();
+
+  /**
+   * Asserts that a given file exists.
+   *
+   * @param file The file to test.
+   */
+  protected void assertExists(File file) {
+    assertTrue("File '" + file.getAbsolutePath() + "' does not exist",
+        file.exists());
+  }
+
+  /**
+   * Asserts that a given file does not exist.
+   *
+   * @param file The file to test.
+   */
+  protected void assertDoesNotExist(File file) {
+    assertFalse("File '" + file.getAbsolutePath() + "' exists", file.exists());
+  }
+
+  /**
+   * Asserts that a given file exists and is a directory.
+   *
+   * @param file The file to test.
+   */
+  protected void assertDirectory(File file) {
+    // Although isDirectory includes checking for existence, we nevertheless
+    // explicitly check for existence, to get more appropriate error messages
+    assertExists(file);
+    assertTrue("File '" + file.getAbsolutePath() + "' is not a directory",
+        file.isDirectory());
+  }
+
+  /**
+   * Asserts that creating a directory from the given file worked
+   *
+   * @param file The directory to create
+   */
+  protected void assertMkdirs(File file) {
+    assertTrue("Could not create directory '" + file.getAbsolutePath() + "'",
+        file.mkdirs());
+  }
+
+  /**
+   * Asserts that creating a directory from the specified file worked
+   *
+   * @param parent The parent of the directory to create
+   * @param name The name of the directoryto create (relative to {@code parent}
+   * @return The created directory
+   */
+  protected File assertMkdirs(File parent, String name) {
+    File file = new File(parent, name);
+    assertMkdirs(file);
+    return file;
+  }
+
+  /**
+   * Asserts that creating a file worked
+   *
+   * @param file The file to create
+   */
+  protected void assertCreateFile(File file) throws IOException {
+    assertTrue("Could not create file '" + file.getAbsolutePath() + "'",
+        file.createNewFile());
+  }
+
+  /**
+   * Asserts that creating a file worked
+   *
+   * @param parent The parent of the file to create
+   * @param name The name of the file to create (relative to {@code parent}
+   * @return The created file
+   */
+  protected File assertCreateFile(File parent, String name) throws IOException {
+    File file = new File(parent, name);
+    assertCreateFile(file);
+    return file;
+  }
+
+  /**
+   * Creates a file in the system's default folder for temporary files.
+   *
+   * The file/directory automatically gets removed during tearDown.
+   *
+   * The name of the created file begins with 'gerrit_test_', and is located
+   * in the system's default folder for temporary files.
+   *
+   * @param suffix Trailing part of the file name.
+   * @return The temporary file.
+   * @throws IOException If a file could not be created.
+   */
+  private File createTempFile(String suffix) throws IOException {
+    String prefix ="gerrit_test_";
+    if (!Strings.isNullOrEmpty(getName())) {
+      prefix += getName() + "_";
+    }
+    File tmp = File.createTempFile(prefix, suffix);
+    toCleanup.add(tmp);
+    return tmp;
+  }
+
+  /**
+   * Creates a file in the system's default folder for temporary files.
+   *
+   * The file/directory automatically gets removed during tearDown.
+   *
+   * The name of the created file begins with 'gerrit_test_', and is located
+   * in the system's default folder for temporary files.
+   *
+   * @return The temporary file.
+   * @throws IOException If a file could not be created.
+   */
+  protected File createTempFile() throws IOException {
+    return createTempFile("");
+  }
+
+  /**
+   * Creates a directory in the system's default folder for temporary files.
+   *
+   * The directory (and all it's contained files/directory) automatically get
+   * removed during tearDown.
+   *
+   * The name of the created directory begins with 'gerrit_test_', and is be
+   * located in the system's default folder for temporary files.
+   *
+   * @return The temporary directory.
+   * @throws IOException If a file could not be created.
+   */
+  protected File createTempDir() throws IOException {
+    File tmp = createTempFile(".dir");
+    if (!tmp.delete()) {
+      throw new IOException("Cannot delete temporary file '" + tmp.getPath()
+          + "'");
+    }
+    tmp.mkdir();
+    return tmp;
+  }
+
+  private void cleanupCreatedFiles() throws IOException {
+    for (File file : toCleanup) {
+      FileUtils.delete(file,  FileUtils.RECURSIVE);
+    }
+  }
+
+  public void tearDown() throws Exception {
+    cleanupCreatedFiles();
+    super.tearDown();
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 9e698f7..6b2f7c7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -29,6 +29,8 @@
 import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
@@ -134,6 +136,8 @@
       .toProvider(AnonymousCowardNameProvider.class);
     bind(AllProjectsName.class)
         .toProvider(AllProjectsNameProvider.class);
+    bind(AllUsersName.class)
+        .toProvider(AllUsersNameProvider.class);
     bind(GitRepositoryManager.class)
         .to(InMemoryRepositoryManager.class);
     bind(InMemoryRepositoryManager.class).in(SINGLETON);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java
new file mode 100644
index 0000000..218fa0a
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java
@@ -0,0 +1,134 @@
+// 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.testutil;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.testutil.log.LogUtil;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+import org.junit.After;
+
+import java.util.Iterator;
+
+/**
+ * Testcase capturing associated logs and allowing to assert on them.
+ *
+ * For a test case SomeNameTest, the log for SomeName gets captured. Assertions
+ * on logs run against the coptured log events from this logger. After the
+ * tests, the logger are set back to their original settings.
+ */
+public abstract class LoggingMockingTestCase extends MockingTestCase {
+  private String loggerName;
+  private LogUtil.LoggerSettings loggerSettings;
+  private java.util.Collection<LoggingEvent> loggedEvents;
+
+  /**
+   * Assert a logged event with a given string.
+   * <p>
+   * If such a event is found, it is removed from the captured logs.
+   *
+   * @param needle The string to look for.
+   */
+  protected final void assertLogMessageContains(String needle) {
+    LoggingEvent hit = null;
+    Iterator<LoggingEvent> iter = loggedEvents.iterator();
+    while (hit == null && iter.hasNext()) {
+      LoggingEvent event = iter.next();
+      if (event.getRenderedMessage().contains(needle)) {
+        hit = event;
+      }
+    }
+    assertNotNull("Could not find log message containing '" + needle + "'",
+        hit);
+    assertTrue("Could not remove log message containing '" + needle + "'",
+        loggedEvents.remove(hit));
+  }
+
+  /**
+   * Assert a logged event whose throwable contains a given string
+   * <p>
+   * If such a event is found, it is removed from the captured logs.
+   *
+   * @param needle The string to look for.
+   */
+  protected final void assertLogThrowableMessageContains(String needle) {
+    LoggingEvent hit = null;
+    Iterator<LoggingEvent> iter = loggedEvents.iterator();
+    while (hit == null && iter.hasNext()) {
+      LoggingEvent event = iter.next();
+      if (event.getThrowableInformation().getThrowable().toString()
+          .contains(needle)) {
+        hit = event;
+      }
+    }
+    assertNotNull("Could not find log message with a Throwable containing '"
+        + needle + "'", hit);
+    assertTrue("Could not remove log message with a Throwable containing '"
+        + needle + "'", loggedEvents.remove(hit));
+  }
+
+  /**
+   * Assert that all logged events have been asserted
+   */
+  // As the PowerMock runner does not pass through runTest, we inject log
+  // verification through @After
+  @After
+  public final void assertNoUnassertedLogEvents() {
+    if (loggedEvents.size() > 0) {
+      LoggingEvent event = loggedEvents.iterator().next();
+      String msg = "Found untreated logged events. First one is:\n";
+      msg += event.getRenderedMessage();
+      if (event.getThrowableInformation() != null) {
+        msg += "\n" + event.getThrowableInformation().getThrowable();
+      }
+      fail(msg);
+    }
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    loggedEvents = Lists.newArrayList();
+
+    // The logger we're interested is class name without the trailing "Test".
+    // While this is not the most general approach it is sufficient for now,
+    // and we can improve later to allow tests to specify which loggers are
+    // to check.
+    loggerName = this.getClass().getCanonicalName();
+    loggerName = loggerName.substring(0, loggerName.length()-4);
+    loggerSettings = LogUtil.logToCollection(loggerName, loggedEvents);
+  }
+
+  @Override
+  protected void runTest() throws Throwable {
+    super.runTest();
+    // Plain JUnit runner does not pick up @After, so we add it here
+    // explicitly. Note, that we cannot put this into tearDown, as failure
+    // to verify mocks would bail out and might leave open resources from
+    // subclasses open.
+    assertNoUnassertedLogEvents();
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    if (loggerName != null && loggerSettings != null) {
+      Logger logger = LogManager.getLogger(loggerName);
+      loggerSettings.pushOntoLogger(logger);
+    }
+    super.tearDown();
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/MockingTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/MockingTestCase.java
new file mode 100644
index 0000000..f0c7ce1
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/MockingTestCase.java
@@ -0,0 +1,155 @@
+// 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.testutil;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Test case with some support for automatically verifying mocks.
+ *
+ * This test case works transparently with EasyMock and PowerMock.
+ */
+public abstract class MockingTestCase extends TestCase {
+  private Collection<Object> mocks;
+  private Collection<IMocksControl> mockControls;
+  private boolean mocksReplayed;
+  private boolean usePowerMock;
+
+  /**
+   * Create and register a mock control.
+   *
+   * @return The mock control instance.
+   */
+  protected final IMocksControl createMockControl() {
+    IMocksControl mockControl = EasyMock.createControl();
+    assertTrue("Adding mock control failed", mockControls.add(mockControl));
+    return mockControl;
+  }
+
+  /**
+   * Create and register a mock.
+   *
+   * Creates a mock and registers it in the list of created mocks, so it gets
+   * treated automatically upon {@code replay} and {@code verify};
+   * @param toMock The class to create a mock for.
+   * @return The mock instance.
+   */
+  protected final <T> T createMock(Class<T> toMock) {
+    return createMock(toMock, null);
+  }
+
+  /**
+   * Create a mock for a mock control and register a mock.
+   *
+   * Creates a mock and registers it in the list of created mocks, so it gets
+   * treated automatically upon {@code replay} and {@code verify};
+   * @param toMock The class to create a mock for.
+   * @param control The mock control to create the mock on. If null, do not use
+   *    a specific control.
+   * @return The mock instance.
+   */
+  protected final <T> T createMock(Class<T> toMock, IMocksControl control) {
+    assertFalse("Mocks have already been set to replay", mocksReplayed);
+    final T mock;
+    if (control == null) {
+      if (usePowerMock) {
+        mock = PowerMock.createMock(toMock);
+      } else {
+        mock = EasyMock.createMock(toMock);
+      }
+      assertTrue("Adding " + toMock.getName() + " mock failed",
+          mocks.add(mock));
+    } else {
+      mock = control.createMock(toMock);
+    }
+    return mock;
+  }
+
+  /**
+   * Set all registered mocks to replay
+   */
+  protected final void replayMocks() {
+    assertFalse("Mocks have already been set to replay", mocksReplayed);
+    if (usePowerMock) {
+      PowerMock.replayAll();
+    } else {
+      EasyMock.replay(mocks.toArray());
+    }
+    for (IMocksControl mockControl : mockControls) {
+      mockControl.replay();
+    }
+    mocksReplayed = true;
+  }
+
+  /**
+   * Verify all registered mocks
+   *
+   * This method is called automatically at the end of a test. Nevertheless,
+   * it is safe to also call it beforehand, if this better meets the
+   * verification part of a test.
+   */
+  // As the PowerMock runner does not pass through runTest, we inject mock
+  // verification through @After
+  @After
+  public final void verifyMocks() {
+    if (!mocks.isEmpty() || !mockControls.isEmpty()) {
+      assertTrue("Created mocks have not been set to replay. Call replayMocks "
+          + "within the test", mocksReplayed);
+      if (usePowerMock) {
+        PowerMock.verifyAll();
+      } else {
+        EasyMock.verify(mocks.toArray());
+      }
+      for (IMocksControl mockControl : mockControls) {
+        mockControl.verify();
+      }
+    }
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+
+    usePowerMock = false;
+    RunWith runWith = this.getClass().getAnnotation(RunWith.class);
+    if (runWith != null) {
+      usePowerMock = PowerMockRunner.class.isAssignableFrom(runWith.value());
+    }
+
+    mocks = new ArrayList<Object>();
+    mockControls = new ArrayList<IMocksControl>();
+    mocksReplayed = false;
+  }
+
+  @Override
+  protected void runTest() throws Throwable {
+    super.runTest();
+    // Plain JUnit runner does not pick up @After, so we add it here
+    // explicitly. Note, that we cannot put this into tearDown, as failure
+    // to verify mocks would bail out and might leave open resources from
+    // subclasses open.
+    verifyMocks();
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/PassThroughKeyUtilEncoder.java
similarity index 63%
copy from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
copy to gerrit-server/src/test/java/com/google/gerrit/testutil/PassThroughKeyUtilEncoder.java
index 96e2ec8..e008b78 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/PassThroughKeyUtilEncoder.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 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.
@@ -12,16 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.testutil;
 
-public class Die extends RuntimeException {
-  private static final long serialVersionUID = 1L;
+import com.google.gwtorm.client.KeyUtil.Encoder;
 
-  public Die(final String why) {
-    super(why);
+public class PassThroughKeyUtilEncoder extends Encoder {
+  @Override
+  public String encode(String e) {
+    return e;
   }
 
-  public Die(final String why, final Throwable cause) {
-    super(why, cause);
+  @Override
+  public String decode(String e) {
+    return e;
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/SetMatcher.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/SetMatcher.java
new file mode 100644
index 0000000..2e6b68e
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/SetMatcher.java
@@ -0,0 +1,55 @@
+// 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.testutil;
+
+import com.google.common.collect.Sets;
+
+import org.easymock.EasyMock;
+import org.easymock.IArgumentMatcher;
+
+import java.util.Set;
+
+/**
+ * Match for Iterables via set equals
+ *
+ * Converts both expected and actual parameter to a set and compares those two
+ * sets via equals to determine whether or not they match.
+ */
+public class SetMatcher<T> implements IArgumentMatcher {
+  public static <S extends Iterable<T>,T> S setEq(S expected) {
+    EasyMock.reportMatcher(new SetMatcher<T>(expected));
+    return null;
+  }
+
+  Set<T> expected;
+
+  public SetMatcher(Iterable<T> expected) {
+    this.expected = Sets.newHashSet(expected);
+  }
+
+  @Override
+  public boolean matches(Object actual) {
+    if (actual instanceof Iterable<?>) {
+      Set<?> actualSet = Sets.newHashSet((Iterable<?>)actual);
+      return expected.equals(actualSet);
+    }
+    return false;
+  }
+
+  @Override
+  public void appendTo(StringBuffer buffer) {
+    buffer.append(expected);
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/log/CollectionAppender.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/log/CollectionAppender.java
new file mode 100644
index 0000000..43179e3
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/log/CollectionAppender.java
@@ -0,0 +1,58 @@
+// 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.testutil.log;
+
+import com.google.common.collect.Lists;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * Log4j appender that logs into a list
+ */
+public class CollectionAppender extends AppenderSkeleton {
+  private Collection<LoggingEvent> events;
+
+  public CollectionAppender() {
+    events = new LinkedList<LoggingEvent>();
+  }
+
+  public CollectionAppender(Collection<LoggingEvent> events) {
+    this.events = events;
+  }
+
+  @Override
+  public boolean requiresLayout() {
+    return false;
+  }
+
+  @Override
+  protected void append(LoggingEvent event) {
+    if (! events.add(event)) {
+      throw new RuntimeException("Could not append event " + event);
+    }
+  }
+
+  @Override
+  public void close() {
+  }
+
+  public Collection<LoggingEvent> getLoggedEvents() {
+    return Lists.newLinkedList(events);
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/log/LogUtil.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/log/LogUtil.java
new file mode 100644
index 0000000..f8b73fb
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/log/LogUtil.java
@@ -0,0 +1,88 @@
+// 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.testutil.log;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+
+public class LogUtil {
+  /**
+   * Change logger's setting so it only logs to a collection.
+   *
+   * @param logName Name of the logger to modify.
+   * @param collection The collection to log into.
+   * @return The logger's original settings.
+   */
+  public static LoggerSettings logToCollection(String logName,
+      Collection<LoggingEvent> collection) {
+    Logger logger = LogManager.getLogger(logName);
+    LoggerSettings loggerSettings = new LoggerSettings(logger);
+    logger.removeAllAppenders();
+    logger.setAdditivity(false);
+    CollectionAppender listAppender = new CollectionAppender(collection);
+    logger.addAppender(listAppender);
+    return loggerSettings;
+  }
+
+  /**
+   * Capsule for a logger's settings that get mangled by rerouting logging to a collection
+   */
+  public static class LoggerSettings {
+    private final boolean additive;
+    private final List<Appender> appenders;
+
+    /**
+     * Read off logger settings from an instance.
+     *
+     * @param logger The logger to read the settings off from.
+     */
+    private LoggerSettings(Logger logger) {
+      this.additive = logger.getAdditivity();
+
+      Enumeration<?> appenders = logger.getAllAppenders();
+      this.appenders = new ArrayList<Appender>();
+      while (appenders.hasMoreElements()) {
+        Object appender = appenders.nextElement();
+        if (appender instanceof Appender) {
+          this.appenders.add((Appender)appender);
+        } else {
+          throw new RuntimeException("getAllAppenders of " + logger
+              + " contained an object that is not an Appender");
+        }
+      }
+    }
+
+    /**
+     * Pushes this settings back onto a logger.
+     *
+     * @param logger the logger on which to push the settings.
+     */
+    public void pushOntoLogger(Logger logger) {
+      logger.setAdditivity(additive);
+
+      logger.removeAllAppenders();
+      for (Appender appender : appenders) {
+        logger.addAppender(appender);
+      }
+    }
+  }
+}
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 4728c31..3028968 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -46,6 +46,7 @@
   ),
   deps = [
     ':sshd',
+    '//gerrit-extension-api:api',
     '//gerrit-server:server',
     '//lib:guava',
     '//lib:junit',
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CachingPublicKeyAuthenticator.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CachingPublicKeyAuthenticator.java
new file mode 100644
index 0000000..f315cff
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CachingPublicKeyAuthenticator.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.apache.sshd.common.Session;
+import org.apache.sshd.common.SessionListener;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Singleton
+public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator,
+    SessionListener {
+
+  private final PublickeyAuthenticator authenticator;
+  private final Map<ServerSession, Map<PublicKey, Boolean>> sessionCache;
+
+  @Inject
+  public CachingPublicKeyAuthenticator(DatabasePubKeyAuth authenticator) {
+    this.authenticator = authenticator;
+    this.sessionCache = new ConcurrentHashMap<>();
+  }
+
+  @Override
+  public boolean authenticate(String username, PublicKey key,
+      ServerSession session) {
+    Map<PublicKey, Boolean> m = sessionCache.get(session);
+    if (m == null) {
+      m = new HashMap<>();
+      sessionCache.put(session, m);
+      session.addListener(this);
+    }
+    if (m.containsKey(key)) {
+      return m.get(key);
+    }
+    boolean r = authenticator.authenticate(username, key, session);
+    m.put(key, r);
+    return r;
+  }
+
+  @Override
+  public void sessionCreated(Session session) {
+  }
+
+  @Override
+  public void sessionEvent(Session sesssion, Event event) {
+  }
+
+  @Override
+  public void sessionClosed(Session session) {
+    sessionCache.remove(session);
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 40e58f2..9354da3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.sshd;
 
+import com.google.common.base.Preconditions;
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.sshd.common.KeyPairProvider;
@@ -48,7 +48,6 @@
 /**
  * Authenticates by public key through {@link AccountSshKey} entities.
  */
-@Singleton
 class DatabasePubKeyAuth implements PublickeyAuthenticator {
   private static final Logger log =
       LoggerFactory.getLogger(DatabasePubKeyAuth.class);
@@ -92,10 +91,11 @@
     }
   }
 
-  public boolean authenticate(String username,
-      final PublicKey suppliedKey, final ServerSession session) {
-    final SshSession sd = session.getAttribute(SshSession.KEY);
-
+  @Override
+  public boolean authenticate(String username, PublicKey suppliedKey,
+      ServerSession session) {
+    SshSession sd = session.getAttribute(SshSession.KEY);
+    Preconditions.checkState(sd.getCurrentUser() == null);
     if (PeerDaemonUser.USER_NAME.equals(username)) {
       if (myHostKeys.contains(suppliedKey)
           || getPeerKeys().contains(suppliedKey)) {
@@ -112,10 +112,10 @@
       username = username.toLowerCase(Locale.US);
     }
 
-    final Iterable<SshKeyCacheEntry> keyList = sshKeyCache.get(username);
-    final SshKeyCacheEntry key = find(keyList, suppliedKey);
+    Iterable<SshKeyCacheEntry> keyList = sshKeyCache.get(username);
+    SshKeyCacheEntry key = find(keyList, suppliedKey);
     if (key == null) {
-      final String err;
+      String err;
       if (keyList == SshKeyCacheImpl.NO_SUCH_USER) {
         err = "user-not-found";
       } else if (keyList == SshKeyCacheImpl.NO_KEYS) {
@@ -133,7 +133,7 @@
     // security check to ensure there aren't two users sharing the same
     // user name on the server.
     //
-    for (final SshKeyCacheEntry otherKey : keyList) {
+    for (SshKeyCacheEntry otherKey : keyList) {
       if (!key.getAccount().equals(otherKey.getAccount())) {
         sd.authenticationError(username, "keys-cross-accounts");
         return false;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 767a3fc..7a02fb9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -65,10 +65,11 @@
 import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoAcceptor;
-import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.io.IoServiceFactoryFactory;
 import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.io.mina.MinaServiceFactory;
+import org.apache.sshd.common.io.mina.MinaServiceFactoryFactory;
 import org.apache.sshd.common.io.mina.MinaSession;
+import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
 import org.apache.sshd.common.mac.HMACMD5;
 import org.apache.sshd.common.mac.HMACMD596;
 import org.apache.sshd.common.mac.HMACSHA1;
@@ -188,8 +189,13 @@
     final String kerberosPrincipal = cfg.getString(
         "sshd", null, "kerberosPrincipal");
 
-    System.setProperty(IoServiceFactory.class.getName(),
-        MinaServiceFactory.class.getName());
+    SshSessionBackend backend = cfg.getEnum(
+        "sshd", null, "backend", SshSessionBackend.MINA);
+
+    System.setProperty(IoServiceFactoryFactory.class.getName(),
+        backend == SshSessionBackend.MINA
+            ? MinaServiceFactoryFactory.class.getName()
+            : Nio2ServiceFactoryFactory.class.getName());
 
     if (SecurityUtils.isBouncyCastleRegistered()) {
       initProviderBouncyCastle();
@@ -284,8 +290,10 @@
   public synchronized void stop() {
     if (acceptor != null) {
       try {
-        acceptor.dispose();
+        acceptor.close(true).await();
         log.info("Stopped Gerrit SSHD");
+      } catch (InterruptedException e) {
+        log.warn("Exception caught while closing", e);
       } finally {
         acceptor = null;
       }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 50ab639..7dd12b0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -81,7 +81,7 @@
     bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
 
     bind(GSSAuthenticator.class).to(GerritGSSAuthenticator.class);
-    bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
+    bind(PublickeyAuthenticator.class).to(CachingPublicKeyAuthenticator.class);
 
     bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
     bind(SshPluginStarterCallback.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index fa48084..8db6431 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -27,7 +28,6 @@
 import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.sshd.CommandMetaData;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 67d2738..14aa3ad 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -19,29 +19,26 @@
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.errors.ProjectCreationFailedException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.api.projects.ProjectInput.ConfigValue;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.extensions.restapi.RestApiException;
-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.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.server.project.CreateProject;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.PutConfig.ConfigValue;
 import com.google.gerrit.server.project.SuggestParentCandidates;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -135,7 +132,7 @@
   }
 
   @Inject
-  private Provider<CreateProject.Factory> createProjectFactory;
+  private GerritApi gApi;
 
   @Inject
   private SuggestParentCandidates.Factory suggestParentCandidatesFactory;
@@ -148,7 +145,7 @@
           throw new UnloggedFailure(1, "fatal: Project name is required.");
         }
 
-        CreateProject.Input input = new CreateProject.Input();
+        ProjectInput input = new ProjectInput();
         input.name = projectName;
         if (ownerIds != null) {
           input.owners = Lists.transform(ownerIds,
@@ -176,8 +173,7 @@
           input.pluginConfigValues = parsePluginConfigValues(pluginConfigValues);
         }
 
-        createProjectFactory.get().create(projectName)
-            .apply(TopLevelResource.INSTANCE, input);
+        gApi.projects().name(projectName).create(input);
       } else {
         List<Project.NameKey> parentCandidates =
             suggestParentCandidatesFactory.create().getNameKeys();
@@ -186,8 +182,7 @@
           stdout.print(parent + "\n");
         }
       }
-    } catch (RestApiException | ProjectCreationFailedException | IOException
-        | NoSuchProjectException | OrmException err) {
+    } catch (RestApiException | OrmException | NoSuchProjectException err) {
       throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
     }
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index e0c2a97..cc72e96 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -16,10 +16,10 @@
 
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.api.projects.ProjectState;
+import com.google.gerrit.extensions.common.InheritableBoolean;
+import com.google.gerrit.extensions.common.SubmitType;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.State;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectCache;
@@ -106,7 +106,7 @@
   }
 
   @Option(name = "--project-state", aliases = {"--ps"}, usage = "project's visibility state")
-  private State state;
+  private ProjectState state;
 
   @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
   private String maxObjectSizeLimit;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 17bea45..536ad88 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -30,7 +30,9 @@
 
 import org.apache.sshd.common.io.IoAcceptor;
 import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.mina.MinaAcceptor;
 import org.apache.sshd.common.io.mina.MinaSession;
+import org.apache.sshd.common.io.nio2.Nio2Acceptor;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.session.ServerSession;
 import org.kohsuke.args4j.Option;
@@ -132,6 +134,20 @@
           hostname(remoteAddress)));
     }
     stdout.print("--\n");
+    stdout.print("SSHD Backend: " + getBackend() + "\n");
+  }
+
+  private String getBackend() {
+    IoAcceptor acceptor = daemon.getIoAcceptor();
+    if (acceptor == null) {
+      return "";
+    } else if (acceptor instanceof MinaAcceptor) {
+      return "mina";
+    } else if (acceptor instanceof Nio2Acceptor) {
+      return "nio2";
+    } else {
+      return "unknown";
+    }
   }
 
   private static String id(final SshSession sd) {
diff --git a/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java b/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
index a22bae2..4f48eea 100644
--- a/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
+++ b/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.sshd.commands;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
-import com.google.gerrit.server.project.PutConfig.ConfigValue;
+import com.google.gerrit.extensions.api.projects.ProjectInput.ConfigValue;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index cde128e..a96d095 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -33,7 +33,7 @@
 
 genrule(
   name = 'webapp_assets',
-  cmd = 'cd $SRCDIR/src/main/webapp; zip -qr $OUT .',
+  cmd = 'cd src/main/webapp; zip -qr $OUT .',
   srcs = glob(['src/main/webapp/**/*']),
   deps = [],
   out = 'webapp_assets.zip',
@@ -42,7 +42,7 @@
 
 genrule(
   name = 'log4j-config__jar',
-  cmd = 'jar cf $OUT -C $SRCDIR/src/main/resources .',
+  cmd = 'jar cf $OUT -C src/main/resources .',
   srcs = ['src/main/resources/log4j.properties'],
   out = 'log4j-config.jar',
 )
diff --git a/lib/BUCK b/lib/BUCK
index 290ed92..028d23e 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -25,9 +25,9 @@
 
 maven_jar(
   name = 'gwtorm',
-  id = 'gwtorm:gwtorm:1.8',
-  bin_sha1 = '0ec006f69f7b8aa48e22d4bdecea820fd53c0b4b',
-  src_sha1 = 'b0e347a3053328f029c93ac347a4761a98293073',
+  id = 'gwtorm:gwtorm:1.9',
+  bin_sha1 = 'b03fbe78546788504a326dc6fbf94b93b6d8685e',
+  src_sha1 = '1657bfdb8e9d3d5fa0ae63f38274c3469189ab53',
   license = 'Apache2.0',
   deps = [':protobuf'],
   repository = GERRIT,
@@ -107,8 +107,8 @@
 
 maven_jar(
   name = 'jsch',
-  id = 'com.jcraft:jsch:0.1.50',
-  sha1 = 'fae4a0b1f2a96cb8f58f38da2650814c991cea01',
+  id = 'com.jcraft:jsch:0.1.51',
+  sha1 = '6ceee2696b07cc320d0e1aaea82c7b40768aca0f',
   license = 'jsch',
 )
 
@@ -258,3 +258,80 @@
   attach_source = False,
   visibility = ['//lib/jgit:jgit-archive'],
 )
+
+maven_jar(
+  name = 'powermock-module-junit4',
+  id = 'org.powermock:powermock-module-junit4:1.5',
+  sha1 = '9f6f8d0485249171f9d870e2b269048fa8cad43b',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':junit',
+    ':powermock-module-junit4-common',
+  ],
+)
+
+maven_jar(
+  name = 'powermock-module-junit4-common',
+  id = 'org.powermock:powermock-module-junit4-common:1.5',
+  sha1 = '43db4720ff57af42a1bd5c73fb5cdfebeebd564c',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':junit',
+    ':powermock-reflect',
+  ],
+)
+
+maven_jar(
+  name = 'powermock-reflect',
+  id = 'org.powermock:powermock-reflect:1.5',
+  sha1 = '8df1548eeabb8492ba97d4f3eb84ae4d5f69215e',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':junit',
+    ':objenesis',
+  ],
+)
+
+maven_jar(
+  name = 'powermock-api-easymock',
+  id = 'org.powermock:powermock-api-easymock:1.5',
+  sha1 = 'a485b570b9debb46b53459a8e866a40343b2cfe2',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':easymock',
+    ':powermock-api-support',
+  ],
+)
+
+maven_jar(
+  name = 'powermock-api-support',
+  id = 'org.powermock:powermock-api-support:1.5',
+  sha1 = '7c1b2e4555cfa333aec201c4612345c092820a38',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':junit',
+    ':powermock-core',
+    ':powermock-reflect',
+  ],
+)
+
+maven_jar(
+  name = 'powermock-core',
+  id = 'org.powermock:powermock-core:1.5',
+  sha1 = '4415337ff3fdb7ceb484f11fd08e39711e408976',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':junit',
+    ':powermock-reflect',
+    ':javassist-3.17.1-GA',
+  ],
+)
+
+maven_jar(
+  name = 'javassist-3.17.1-GA',
+  # The GWT version is still at 3.16.1-GA, so those do not match
+  id = 'org.javassist:javassist:3.17.1-GA',
+  sha1 = '30c30512115866b6e0123f1913bc7735b9f76d08',
+  license = 'DO_NOT_DISTRIBUTE',
+)
+
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 0cb785c..cf44662 100644
--- a/lib/asciidoctor/java/DocIndexer.java
+++ b/lib/asciidoctor/java/DocIndexer.java
@@ -51,7 +51,7 @@
 import java.util.zip.ZipOutputStream;
 
 public class DocIndexer {
-  private static final Version LUCENE_VERSION = Version.LUCENE_46;
+  private static final Version LUCENE_VERSION = Version.LUCENE_47;
   private static final Pattern SECTION_HEADER = Pattern.compile("^=+ (.*)");
 
   @Option(name = "-o", usage = "output JAR file")
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index 1ba2d92..ea6e2a1 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -13,10 +13,10 @@
   cmd = ';'.join([
       ':>$OUT',
       "echo '/** @license' >>$OUT",
-      'unzip -p $SRCDIR/%s %s/LICENSE >>$OUT' % (ZIP, TOP),
+      'unzip -p %s %s/LICENSE >>$OUT' % (ZIP, TOP),
       "echo '*/' >>$OUT",
     ] +
-    ['unzip -p $SRCDIR/%s %s/%s >>$OUT' % (ZIP, TOP, n)
+    ['unzip -p %s %s/%s >>$OUT' % (ZIP, TOP, n)
      for n in CM3_CSS + CM3_THEMES]
   ),
   srcs = [genfile(ZIP)],
@@ -30,10 +30,10 @@
   cmd = ';'.join([
       ':>$OUT',
       "echo '/** @license' >>$OUT",
-      'unzip -p $SRCDIR/%s %s/LICENSE >>$OUT' % (ZIP, TOP),
+      'unzip -p %s %s/LICENSE >>$OUT' % (ZIP, TOP),
       "echo '*/' >>$OUT",
     ] +
-    ['unzip -p $SRCDIR/%s %s/%s >>$OUT' % (ZIP, TOP, n)
+    ['unzip -p %s %s/%s >>$OUT' % (ZIP, TOP, n)
      for n in CM3_JS]
   ),
   srcs = [genfile(ZIP)],
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index 80924d9..85d6d77 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -1,13 +1,13 @@
 include_defs('//lib/maven.defs')
 
-REPO = MAVEN_CENTRAL
-VERS = '3.3.0.201403021825-r'
+REPO = GERRIT
+VERS = '3.3.1.201403241930-r.80-gd5110c3'
 
 maven_jar(
   name = 'jgit',
   id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
-  bin_sha1 = '01aa346a5040bd541502dfb40e83edb1d1981c67',
-  src_sha1 = 'c27cc089751cc90dbe085ef09dd0c4a2acdb69cf',
+  bin_sha1 = 'fe38387f8a5079d660aad075cc80cfa39d269c38',
+  src_sha1 = '37196e15fa8e348c5073f7469da294e5285f05d9',
   license = 'jgit',
   repository = REPO,
   unsign = True,
@@ -22,7 +22,7 @@
 maven_jar(
   name = 'jgit-servlet',
   id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
-  sha1 = 'e141488647b80ef25d8d3febffd434a5e2a0a817',
+  sha1 = 'f0d8a1e8abca55a5723fbd595dd63992f16dea5a',
   license = 'jgit',
   repository = REPO,
   deps = [':jgit'],
@@ -36,7 +36,7 @@
 maven_jar(
   name = 'jgit-archive',
   id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
-  sha1 = '87b2b50eb6e7a18a70fd684cc173f3bd2d8e24e8',
+  sha1 = 'e2938053672294e05ee540dcdb7fe57b4b7d6303',
   license = 'jgit',
   repository = REPO,
   deps = [':jgit',
@@ -53,7 +53,7 @@
 maven_jar(
   name = 'junit',
   id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
-  sha1 = '13d0303a669bc2c44db69f8581e3634412b70eed',
+  sha1 = 'd9806c9bc9dac5c8f12f5c5b2b48f390c992ce32',
   license = 'DO_NOT_DISTRIBUTE',
   repository = REPO,
   unsign = True,
diff --git a/lib/log/BUCK b/lib/log/BUCK
index 2659fcd..e02d909 100644
--- a/lib/log/BUCK
+++ b/lib/log/BUCK
@@ -1,31 +1,33 @@
 include_defs('//lib/maven.defs')
 
+VER = '1.7.6'
+
 maven_jar(
   name = 'api',
-  id = 'org.slf4j:slf4j-api:1.6.1',
-  sha1 = '6f3b8a24bf970f17289b234284c94f43eb42f0e4',
+  id = 'org.slf4j:slf4j-api:' + VER,
+  sha1 = '562424e36df3d2327e8e9301a76027fca17d54ea',
   license = 'slf4j',
 )
 
 maven_jar(
   name = 'impl_log4j',
-  id = 'org.slf4j:slf4j-log4j12:1.6.1',
-  sha1 = 'bd245d6746cdd4e6203e976e21d597a46f115802',
+  id = 'org.slf4j:slf4j-log4j12:' + VER,
+  sha1 = '6953717b9850aeb26d1b8375ca07dbd9c50eca4e',
   license = 'slf4j',
   deps = [':log4j'],
 )
 
 maven_jar(
-  name = 'log4j',
-  id = 'log4j:log4j:1.2.16',
-  sha1 = '7999a63bfccbc7c247a9aea10d83d4272bd492c6',
-  license = 'Apache2.0',
-  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
+  name = 'jcl-over-slf4j',
+  id = 'org.slf4j:jcl-over-slf4j:' + VER,
+  sha1 = 'ab1648fe1dd6f1e5c2ec6d12f394672bb8c1036a',
+  license = 'slf4j',
 )
 
 maven_jar(
-  name = 'jcl-over-slf4j',
-  id = 'org.slf4j:jcl-over-slf4j:1.6.1',
-  sha1 = '99c61095a14dfc9e47a086068033c286bf236475',
-  license = 'slf4j',
+  name = 'log4j',
+  id = 'log4j:log4j:1.2.17',
+  sha1 = '5af35056b4d257e4b64b9e8069c0746e8b08629f',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
 )
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index 450a88a..a56b5ab 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -1,12 +1,11 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '4.6.0'
+VERSION = '4.7.0'
 
 maven_jar(
   name = 'core',
   id = 'org.apache.lucene:lucene-core:' + VERSION,
-  bin_sha1 = 'f1d974facaea30a3a0c1752a24097af5a7d40e60',
-  src_sha1 = '19d4eb5def4bc2517a00b50f7a875b7ce33988a7',
+  sha1 = '12d2b92d15158ac0d7b2864f537403acb4d7f69e',
   license = 'Apache2.0',
   exclude = [
     'META-INF/LICENSE.txt',
@@ -17,8 +16,7 @@
 maven_jar(
   name = 'analyzers-common',
   id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION,
-  bin_sha1 = '25dda6706bcb7a741f25f57cdbec6c2f36adc557',
-  src_sha1 = '04866d0e36e3ef708d099014752ad4fef61d4243',
+  sha1 = '399fa6b0d750c8e5c9e4ae73e6407c8b3ed4e8c1',
   license = 'Apache2.0',
   exclude = [
     'META-INF/LICENSE.txt',
@@ -29,7 +27,6 @@
 maven_jar(
   name = 'query-parser',
   id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
-  bin_sha1 = 'ef35f1eb55e50725777162e376e7b5222f45f7fa',
-  src_sha1 = 'e99e3b298e83461c03ef6eb66ab9798a4b712dc6',
+  sha1 = 'f78a804de1582c511224d214c2d9c82ce48379e7',
   license = 'Apache2.0',
 )
diff --git a/lib/maven.defs b/lib/maven.defs
index b874d8e..9eed538 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -48,11 +48,17 @@
     raise NameError('expected id="groupId:artifactId:version"')
   group, artifact, version = parts
 
-  jar = path.join(name, artifact.lower() + '-' + version)
+  if 'SNAPSHOT' in version:
+    file_version = version.replace('-SNAPSHOT', '')
+    version = version.split('-SNAPSHOT')[0] + '-SNAPSHOT'
+  else:
+    file_version = version
+
+  jar = path.join(name, artifact.lower() + '-' + file_version)
   url = '/'.join([
     repository,
     group.replace('.', '/'), artifact, version,
-    artifact + '-' + version])
+    artifact + '-' + file_version])
 
   binjar = jar + '.jar'
   binurl = url + '.jar'
@@ -104,10 +110,11 @@
       out = '__' + name + '__no_src',
     )
 
+  srcdep = [':' + name + '__download_src'] if srcjar else []
   if exported_deps:
     prebuilt_jar(
       name = name + '__jar',
-      deps = deps + license + [':' + name + '__download_bin'],
+      deps = deps + srcdep + license + [':' + name + '__download_bin'],
       binary_jar = genfile(binjar),
       source_jar = genfile(srcjar) if srcjar else None,
     )
@@ -119,7 +126,7 @@
   else:
     prebuilt_jar(
       name = name,
-      deps = deps + license + [':' + name + '__download_bin'],
+      deps = deps + srcdep + license + [':' + name + '__download_bin'],
       binary_jar = genfile(binjar),
       source_jar = genfile(srcjar) if srcjar else None,
       visibility = visibility,
diff --git a/lib/mina/BUCK b/lib/mina/BUCK
index 9467cc4..a3577c2 100644
--- a/lib/mina/BUCK
+++ b/lib/mina/BUCK
@@ -7,19 +7,18 @@
 ]
 
 maven_jar(
+  name = 'sshd',
+  id = 'org.apache.sshd:sshd-core:0.10.1',
+  sha1 = '0081c09917f35565d762c886758dfbdfa1069679',
+  license = 'Apache2.0',
+  deps = [':core'],
+  exclude = EXCLUDE,
+)
+
+maven_jar(
   name = 'core',
   id = 'org.apache.mina:mina-core:2.0.7',
   sha1 = 'c878e2aa82de748474a624ec3933e4604e446dec',
   license = 'Apache2.0',
   exclude = EXCLUDE,
 )
-
-maven_jar(
-  name = 'sshd',
-  id = 'org.apache.sshd:sshd-core:0.9.0.201311081',
-  sha1 = '38f7ac8602e70fa05fdc6147d204198e9cefe5bc',
-  license = 'Apache2.0',
-  deps = [':core'],
-  exclude = EXCLUDE,
-  repository = GERRIT,
-)
diff --git a/lib/prolog/prolog.defs b/lib/prolog/prolog.defs
index b91e2de..62e4c6d 100644
--- a/lib/prolog/prolog.defs
+++ b/lib/prolog/prolog.defs
@@ -19,7 +19,7 @@
     visibility = []):
   genrule(
     name = name + '__pl2j',
-    cmd = 'cd $SRCDIR;$(exe //lib/prolog:compiler)' +
+    cmd = '$(exe //lib/prolog:compiler)' +
       ' $TMP $OUT ' +
       ' '.join(srcs),
     srcs = srcs,
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index dda1b78..78851b2 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit dda1b787dba031597b83db1eb3c1b57565059b68
+Subproject commit 78851b272539483e2f987a20fd6ed64e54ca0b1a
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index b544447..6170241 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit b544447649d9ee3b3f78a6a1a7f839cb6a361292
+Subproject commit 61702414c046dd6b811c9137b765f9db422f83db
diff --git a/tools/default.defs b/tools/default.defs
index be31b85..fa29908 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -225,11 +225,17 @@
     paths,
     srcs = [],
     deps = [],
-    visibility = []
+    visibility = [],
+    do_it_wrong = False,
   ):
+  if do_it_wrong:
+    sourcepath = paths
+  else:
+    sourcepath = ['$SRCDIR/' + n for n in paths]
   genrule(
     name = name,
     cmd = ' '.join([
+      'while ! test -f .buckconfig; do cd ..; done;',
       'javadoc',
       '-quiet',
       '-protected',
@@ -240,7 +246,7 @@
       '-link http://docs.oracle.com/javase/7/docs/api',
       '-subpackages ' + pkg,
       '-sourcepath ',
-      ':'.join([n for n in paths]),
+      ':'.join(sourcepath),
       ' -classpath ',
       ':'.join(['$(location %s)' % n for n in deps]),
       '-d $TMP',
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index 81889f2..ca92f50 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -12,6 +12,7 @@
     '//gerrit-patch-jgit:jgit_patch_tests',
     '//gerrit-plugin-gwtui:gwtui-api',
     '//gerrit-server:server__compile',
+    '//gerrit-server:server_tests',
     '//lib/asciidoctor:asciidoc_lib',
     '//lib/asciidoctor:doc_indexer_lib',
     '//lib/jetty:webapp',
diff --git a/tools/pack_war.py b/tools/pack_war.py
index 6c71d81..09ff054 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -15,7 +15,7 @@
 
 from __future__ import print_function
 from optparse import OptionParser
-from os import makedirs, path, symlink
+from os import getcwd, chdir, makedirs, path, symlink
 from subprocess import check_call
 import sys
 from util import check_output
@@ -33,6 +33,8 @@
 
 def link_jars(libs, directory):
   makedirs(directory)
+  while not path.isfile('.buckconfig'):
+    chdir('..')
   cp = check_output(['buck', 'audit', 'classpath'] + libs)
   for j in cp.strip().splitlines():
     if j not in jars: