Merge "Fix updating of username from AccountManager"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 610b65f..f07ac18 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -871,6 +871,17 @@
 +
 Default is "Submit patch set ${patchSet} into ${branch}".
 
+[[change.submitTooltipAncestors]]change.submitTooltipAncestors::
++
+Tooltip for the submit button if there are ancestors which would
+also be submitted by submitting the change. Additionally to the variables
+as in link:#change.submitTooltip[change.submitTooltip], there is the
+variable `${submitSize}` indicating the number of changes which are
+submitted.
++
+Default is "Submit all ${topicSize} changes of the same topic (${submitSize}
+changes including ancestors and other changes related by topic)".
+
 [[change.submitWholeTopic]]change.submitWholeTopic::
 +
 Determines if the submit button submits the whole topic instead of
@@ -2289,10 +2300,6 @@
 +
 A link:http://lucene.apache.org/[Lucene] index is used.
 +
-* `SOLR`
-+
-A link:https://cwiki.apache.org/confluence/display/solr/SolrCloud[
-SolrCloud] index is used.
 
 +
 By default, `LUCENE`.
@@ -2413,17 +2420,6 @@
   maxBufferedDocs = 500
 ----
 
-==== Solr configuration
-
-Open and closed changes are indexed in separate indexes named
-'changes_open' and 'changes_closed' respectively.
-
-The following settings are only used when the index type is `SOLR`.
-
-[[index.url]]index.url::
-+
-URL of the index server.
-
 [[ldap]]
 === Section ldap
 
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index 4f854fb..b3b72c4 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -146,6 +146,51 @@
 Visit SAP MaxDB's link:http://maxdb.sap.com/documentation/[documentation] for further
 information regarding using SAP MaxDB.
 
+[[createdb_db2]]
+=== DB2
+
+IBM DB2 is a supported database for running Gerrit Code Review. However it is
+recommended only for environments where you intend to run Gerrit on an existing
+DB2 installation to reduce administrative overhead.
+
+Create a system wide user for the Gerrit application, and grant the user
+full rights on the newly created database:
+
+----
+  db2 => create database gerrit
+  db2 => connect to gerrit
+  db2 => grant connect,accessctrl,dataaccess,dbadm,secadm on database to gerrit2;
+----
+
+JDBC driver db2jcc4.jar and db2jcc_license_cu.jar must be obtained
+from your DB2 distribution. Gerrit initialization process tries to copy
+it from a known location:
+
+----
+/opt/ibm/db2/V10.5/java/db2jcc4.jar
+/opt/ibm/db2/V10.5/java/db2jcc_license_cu.jar
+----
+
+If these files cannot be located at this place, then an alternative location
+can be provided during init step execution.
+
+Sample database section in $site_path/etc/gerrit.config:
+
+----
+[database]
+        type = db2
+        database = gerrit
+        hostname = localhost
+        username = gerrit2
+        port = 50001
+----
+
+Sample database section in $site_path/etc/secure.config:
+
+----
+[database]
+        password = secret_pasword
+----
 
 GERRIT
 ------
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index b3525a1..7e1ca10 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -79,6 +79,26 @@
 recompiling.
 * To reflect your changes in the debug session, click `Dev Mode On` then `Compile`.
 
+
+=== Running GWT Debug Mode for Gerrit plugins
+
+A Gerrit plugin can expose GWT module and its implementation can be inspected
+in the SDM debug session.
+
+`codeserver` needs two additional inputs to expose the plugin module in the SDM
+debug session: the module name and the source folder location. For example the
+module name and source folder of `cookbook-plugin` should be added in the local
+copy of the `gerrit_gwt_debug` configuration:
+
+----
+  com.googlesource.gerrit.plugins.cookbook.HelloForm \
+  -src ${resource_loc:/gerrit}/plugins/cookbook-plugin/src/main/java \
+  -- --console-log [...]
+----
+
+After doing that, both the Gerrit core and plugin GWT modules can be activated
+during SDM (debug session)[http://imgur.com/HFXZ5No].
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 6706ba7..0efc395 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -971,6 +971,20 @@
 panels with custom controls:
 
 * Change Screen:
+** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
++
+Panel will be shown in the header bar to the right of the change
+status.
+
+** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
++
+Panel will be shown in the header bar on the right side of the buttons.
+
+** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
++
+Panel will be shown in the header bar on the right side of the pop down
+buttons.
+
 ** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
 +
 Panel will be shown below the change info block.
@@ -980,12 +994,54 @@
 +
 The numeric change ID
 
+* Project Info Screen:
+** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
++
+Panel will be shown at the top of the screen.
+
+** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
++
+Panel will be shown at the bottom of the screen.
+
+** The following parameters are provided:
+*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
++
+The name of the project.
+
+* User Password Screen:
+** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
++
+Panel will be shown at the bottom of the screen.
+
+** The following parameters are provided:
+*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
++
+The link:rest-api-accounts.html#account-info[AccountInfo] entity for
+the current user.
+
+* User Preferences Screen:
+** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
++
+Panel will be shown at the bottom of the screen.
+
+** The following parameters are provided:
+*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
++
+The link:rest-api-accounts.html#account-info[AccountInfo] entity for
+the current user.
+
 * User Profile Screen:
 ** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
 +
 Panel will be shown at the bottom of the screen below the grid with the
 profile data.
 
+** The following parameters are provided:
+*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
++
+The link:rest-api-accounts.html#account-info[AccountInfo] entity for
+the current user.
+
 Example panel:
 [source,java]
 ----
@@ -1706,6 +1762,31 @@
 }
 ----
 
+[[user-settings-screen]]
+== Add User Settings Screen
+
+A link:#gwt_ui_extension[GWT plugin] can implement a user settings
+screen that is integrated into the Gerrit user settings menu.
+
+Example settings screen:
+[source,java]
+----
+public class MyPlugin extends PluginEntryPoint {
+  @Override
+  public void onPluginLoad() {
+    Plugin.get().settingsScreen("my-preferences", "My Preferences",
+        new Screen.EntryPoint() {
+          @Override
+          public void onLoad(Screen screen) {
+            screen.setPageTitle("Settings");
+            screen.add(new InlineLabel("My Preferences"));
+            screen.show();
+          }
+    });
+  }
+}
+----
+
 [[settings-screen]]
 == Plugin Settings Screen
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index d752c85..94f8ac9 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2239,6 +2239,7 @@
 
   )]}'
   {
+    "commit": "674ac754f91e64a0efb8087e59a176484bd534d1",
     "parents": [
       {
         "commit": "1eee2c9d8f352483781e772f35dc586a69ff5646",
@@ -3951,7 +3952,10 @@
 [options="header",cols="1,^1,5"]
 |===========================
 |Field Name    ||Description
-|`commit`      ||The commit ID.
+|`commit`      |Optional|
+The commit ID. Not set if included in a link:#revision-info[
+RevisionInfo] entity that is contained in a map which has the commit ID
+as key.
 |`parents`     ||
 The parent commits of this commit as a list of
 link:#commit-info[CommitInfo] entities. In each parent
@@ -4147,10 +4151,10 @@
 
 [options="header",cols="1,6"]
 |==========================
-|Field Name    |Description
-|`deletePatchSetIfCommitMissing`|If true, delete patch sets from the
+|Field Name                          |Description
+|`delete_patch_set_if_commit_missing`|If true, delete patch sets from the
 database if they refer to missing commit options.
-|`expectMergedAs`               |If set, check that the change is
+|`expect_merged_as`                  |If set, check that the change is
 merged into the destination branch as this exact SHA-1. If not, insert
 a new patch set referring to this commit.
 |==========================
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index a4cbe72..0f9ffc7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -490,6 +491,15 @@
     saveProjectConfig(allProjects, cfg);
   }
 
+  protected void setUseContributorAgreements(InheritableBoolean value)
+      throws Exception {
+    MetaDataUpdate md = metaDataUpdateFactory.create(project);
+    ProjectConfig config = ProjectConfig.read(md);
+    config.getProject().setUseContributorAgreements(value);
+    config.commit(md);
+    projectCache.evict(config.getProject());
+  }
+
   protected void deny(String permission, AccountGroup.UUID id, String ref)
       throws Exception {
     ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index 85ba6de..77c38c8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -47,6 +47,23 @@
   public static final String SUBJECT = "test commit";
   public static final String FILE_NAME = "a.txt";
   public static final String FILE_CONTENT = "some content";
+  public static final String PATCH =
+      "From %s Mon Sep 17 00:00:00 2001\n" +
+      "From: Administrator <admin@example.com>\n" +
+      "Date: %s\n" +
+      "Subject: [PATCH] test commit\n" +
+      "\n" +
+      "Change-Id: %s\n" +
+      "---\n" +
+      "\n" +
+      "diff --git a/a.txt b/a.txt\n" +
+      "new file mode 100644\n" +
+      "index 0000000..f0eec86\n" +
+      "--- /dev/null\n" +
+      "+++ b/a.txt\n" +
+      "@@ -0,0 +1 @@\n" +
+      "+some content\n" +
+      "\\ No newline at end of file\n";
 
   public interface Factory {
     PushOneCommit create(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 980b313..967575a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -18,6 +18,8 @@
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
 import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.Util.blockLabel;
 import static com.google.gerrit.server.project.Util.category;
 import static com.google.gerrit.server.project.Util.value;
 
@@ -242,6 +244,8 @@
     assertThat(info.project).isEqualTo(in.project);
     assertThat(info.branch).isEqualTo(in.branch);
     assertThat(info.subject).isEqualTo(in.subject);
+    assertThat(Iterables.getOnlyElement(info.messages).message)
+        .isEqualTo("Uploaded patch set 1.");
   }
 
   @Test
@@ -470,4 +474,29 @@
           .get())
         .hasSize(2);
   }
+
+  @Test
+  public void votable() throws Exception {
+    PushOneCommit.Result r = createChange();
+    String triplet = project.get() + "~master~" + r.getChangeId();
+    gApi.changes().id(triplet).addReviewer(user.username);
+    ChangeInfo c = gApi.changes().id(triplet).get(EnumSet.of(
+        ListChangesOption.DETAILED_LABELS));
+    LabelInfo codeReview = c.labels.get("Code-Review");
+    assertThat(codeReview.all).hasSize(1);
+    ApprovalInfo approval = codeReview.all.get(0);
+    assertThat(approval._accountId).isEqualTo(user.id.get());
+    assertThat(approval.value).isEqualTo(0);
+
+    ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+    blockLabel(cfg, "Code-Review", REGISTERED_USERS, "refs/heads/*");
+    saveProjectConfig(project, cfg);
+    c = gApi.changes().id(triplet).get(EnumSet.of(
+        ListChangesOption.DETAILED_LABELS));
+    codeReview = c.labels.get("Code-Review");
+    assertThat(codeReview.all).hasSize(1);
+    approval = codeReview.all.get(0);
+    assertThat(approval._accountId).isEqualTo(user.id.get());
+    assertThat(approval.value).isNull();
+  }
 }
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 23e3685..5429b95 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
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_CONTENT;
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
+import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 
 import com.google.common.base.Predicate;
@@ -40,6 +41,7 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.MergeableInfo;
+import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -52,6 +54,8 @@
 
 import java.io.ByteArrayOutputStream;
 import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -540,6 +544,25 @@
       .isEqualTo(in.message);
   }
 
+  @Test
+  public void patch() throws Exception {
+    PushOneCommit.Result r = createChange();
+    ChangeApi changeApi = gApi.changes()
+        .id(r.getChangeId());
+    BinaryResult bin = changeApi
+        .revision(r.getCommit().name())
+        .patch();
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    bin.writeTo(os);
+    String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
+    DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
+    ChangeInfo change = changeApi.get();
+    RevisionInfo rev = change.revisions.get(change.currentRevision);
+    String date = dateFormat.format(rev.commit.author.date);
+    assertThat(res).isEqualTo(
+        String.format(PATCH, r.getCommitId().name(), date, r.getChangeId()));
+  }
+
   private void merge(PushOneCommit.Result r) throws Exception {
     revision(r).review(ReviewInput.approve());
     revision(r).submit();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 1624da2..9fcd931 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -19,6 +19,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.http.HttpStatus.SC_FORBIDDEN;
 import static org.apache.http.HttpStatus.SC_CONFLICT;
 import static org.apache.http.HttpStatus.SC_NOT_FOUND;
 import static org.apache.http.HttpStatus.SC_NO_CONTENT;
@@ -35,6 +36,7 @@
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ApprovalInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -210,6 +212,22 @@
   }
 
   @Test
+  public void publishEditRestWithoutCLA() throws Exception {
+    setUseContributorAgreements(InheritableBoolean.TRUE);
+    PatchSet oldCurrentPatchSet = getCurrentPatchSet(changeId);
+    assertThat(modifier.createEdit(change, oldCurrentPatchSet)).isEqualTo(
+        RefUpdate.Result.NEW);
+    assertThat(
+        modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
+            RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
+    RestResponse r = adminSession.post(urlPublish());
+    assertThat(r.getStatusCode()).isEqualTo(SC_FORBIDDEN);
+    setUseContributorAgreements(InheritableBoolean.FALSE);
+    r = adminSession.post(urlPublish());
+    assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
+  }
+
+  @Test
   public void rebaseEdit() throws Exception {
     assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
     assertThat(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
index 248e1fe..21efc87 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -276,7 +276,7 @@
       }
     }
 
-    Splitter s = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings();
+    Splitter s = Splitter.on(CharMatcher.whitespace()).omitEmptyStrings();
     assertThat(filtered).containsExactlyElementsIn(
         Ordering.natural().sortedCopy(s.split(out))).inOrder();
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index 66c99f1..24bb72b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -52,15 +52,14 @@
     commonActionsAssertions(actions);
     // We want to treat a single change in a topic not as a whole topic,
     // so regardless of how submitWholeTopic is configured:
-    noSubmitWholeTopicAssertions(actions);
+    noSubmitWholeTopicAssertions(actions, 1);
   }
 
   @Test
-  public void revisionActionsTwoChangeChangesInTopic() throws Exception {
+  public void revisionActionsTwoChangesInTopic() throws Exception {
     String changeId = createChangeWithTopic().getChangeId();
     approve(changeId);
-    // create another change with the same topic
-    createChangeWithTopic().getChangeId();
+    String changeId2 = createChangeWithTopic().getChangeId();
     Map<String, ActionInfo> actions = getActions(changeId);
     commonActionsAssertions(actions);
     if (isSubmitWholeTopicEnabled()) {
@@ -68,14 +67,19 @@
       assertThat(info.enabled).isNull();
       assertThat(info.label).isEqualTo("Submit whole topic");
       assertThat(info.method).isEqualTo("POST");
-      assertThat(info.title).isEqualTo("Other changes in this topic are not ready");
+      assertThat(info.title).isEqualTo("This change depends on other " +
+          "changes which are not ready");
     } else {
-      noSubmitWholeTopicAssertions(actions);
+      noSubmitWholeTopicAssertions(actions, 1);
+
+      assertThat(getActions(changeId2).get("submit")).isNull();
+      approve(changeId2);
+      noSubmitWholeTopicAssertions(getActions(changeId2), 2);
     }
   }
 
   @Test
-  public void revisionActionsTwoChangeChangesInTopic_conflicting() throws Exception {
+  public void revisionActionsTwoChangesInTopic_conflicting() throws Exception {
     String changeId = createChangeWithTopic().getChangeId();
     approve(changeId);
 
@@ -99,38 +103,66 @@
       assertThat(info.label).isEqualTo("Submit whole topic");
       assertThat(info.method).isEqualTo("POST");
       assertThat(info.title).isEqualTo(
-          "Clicking the button would fail for other changes in the topic");
+          "Clicking the button would fail for other changes");
     } else {
-      noSubmitWholeTopicAssertions(actions);
+      noSubmitWholeTopicAssertions(actions, 1);
     }
   }
 
   @Test
-  public void revisionActionsTwoChangeChangesInTopicReady() throws Exception {
-    String changeId = createChangeWithTopic().getChangeId();
+  public void revisionActionsTwoChangesInTopicWithAncestorReady()
+      throws Exception {
+    String changeId = createChange().getChangeId();
     approve(changeId);
+    approve(changeId);
+    String changeId1 = createChangeWithTopic().getChangeId();
+    approve(changeId1);
     // create another change with the same topic
     String changeId2 = createChangeWithTopic().getChangeId();
     approve(changeId2);
-    Map<String, ActionInfo> actions = getActions(changeId);
+    Map<String, ActionInfo> actions = getActions(changeId1);
     commonActionsAssertions(actions);
     if (isSubmitWholeTopicEnabled()) {
       ActionInfo info = actions.get("submit");
       assertThat(info.enabled).isTrue();
       assertThat(info.label).isEqualTo("Submit whole topic");
       assertThat(info.method).isEqualTo("POST");
-      assertThat(info.title).isEqualTo("Submit all 2 changes of the same topic");
+      assertThat(info.title).isEqualTo("Submit all 2 changes of the same " +
+          "topic (3 changes including ancestors " +
+          "and other changes related by topic)");
     } else {
-      noSubmitWholeTopicAssertions(actions);
+      noSubmitWholeTopicAssertions(actions, 2);
     }
   }
 
-  private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions) {
+  @Test
+  public void revisionActionsReadyWithAncestors() throws Exception {
+    String changeId = createChange().getChangeId();
+    approve(changeId);
+    approve(changeId);
+    String changeId1 = createChange().getChangeId();
+    approve(changeId1);
+    String changeId2 = createChangeWithTopic().getChangeId();
+    approve(changeId2);
+    Map<String, ActionInfo> actions = getActions(changeId2);
+    commonActionsAssertions(actions);
+    // The topic contains only one change, so standard text applies
+    noSubmitWholeTopicAssertions(actions, 3);
+  }
+
+  private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions,
+      int nrChanges) {
     ActionInfo info = actions.get("submit");
     assertThat(info.enabled).isTrue();
     assertThat(info.label).isEqualTo("Submit");
     assertThat(info.method).isEqualTo("POST");
-    assertThat(info.title).isEqualTo("Submit patch set 1 into master");
+    if (nrChanges == 1) {
+      assertThat(info.title).isEqualTo("Submit patch set 1 into master");
+    } else {
+      assertThat(info.title).isEqualTo(String.format(
+          "Submit patch set 1 and ancestors (%d changes " +
+          "altogether) into master", nrChanges));
+    }
   }
 
   private void commonActionsAssertions(Map<String, ActionInfo> actions) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index 80ca561..c80d867 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -33,6 +33,7 @@
   public static final String SETTINGS_CONTACT = "/settings/contact";
   public static final String SETTINGS_PROJECTS = "/settings/projects";
   public static final String SETTINGS_NEW_AGREEMENT = "/settings/new-agreement";
+  public static final String SETTINGS_EXTENSION = "/settings/x/";
   public static final String REGISTER = "/register";
 
   public static final String MINE = "/";
@@ -128,6 +129,10 @@
     return ADMIN_GROUPS + "uuid-" + uuid;
   }
 
+  public static String toSettings(String pluginName, String path) {
+    return SETTINGS_EXTENSION + pluginName + "/" + path;
+  }
+
   private static String status(Status status) {
     switch (status) {
       case ABANDONED:
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 cb0d5f3..44c2ba4 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
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.FileInfo;
 import com.google.gerrit.extensions.common.MergeableInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
@@ -57,6 +58,11 @@
 
   CommentApi comment(String id) throws RestApiException;
 
+  /**
+   * Returns patch of revision.
+   */
+  BinaryResult patch() throws RestApiException;
+
   Map<String, ActionInfo> actions() throws RestApiException;
 
   /**
@@ -180,6 +186,11 @@
     }
 
     @Override
+    public BinaryResult patch() throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public Map<String, ActionInfo> actions() throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 13baf6b..cdfe0c6 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -35,6 +35,7 @@
   public Boolean starred;
   public Boolean reviewed;
   public Boolean mergeable;
+  public Boolean submittable;
   public Integer insertions;
   public Integer deletions;
 
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java
index 4047c68..e22625c 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java
@@ -16,12 +16,24 @@
 
 public enum GerritUiExtensionPoint {
   /* ChangeScreen */
+  CHANGE_SCREEN_HEADER,
+  CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS,
+  CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS,
   CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
 
+  /* MyPasswordScreen */
+  PASSWORD_SCREEN_BOTTOM,
+
+  /* MyPreferencesScreen */
+  PREFERENCES_SCREEN_BOTTOM,
+
   /* MyProfileScreen */
-  PROFILE_SCREEN_BOTTOM;
+  PROFILE_SCREEN_BOTTOM,
+
+  /* ProjectInfoScreen */
+  PROJECT_INFO_SCREEN_TOP, PROJECT_INFO_SCREEN_BOTTOM;
 
   public enum Key {
-    CHANGE_ID
+    ACCOUNT_INFO, CHANGE_ID, PROJECT_NAME
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AccountInfo.java
similarity index 97%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AccountInfo.java
index 36fa98d..872861a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AccountInfo.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.client.account;
+package com.google.gerrit.client.info;
 
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java
index 4b29b49..f0379ea 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/AvatarImage.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.client;
 
-import com.google.gerrit.client.account.AccountInfo;
-import com.google.gerrit.client.account.AccountInfo.AvatarInfo;
 import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.info.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo.AvatarInfo;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gwt.event.dom.client.LoadEvent;
 import com.google.gwt.event.dom.client.LoadHandler;
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 cda3060..0123869 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
@@ -28,6 +28,7 @@
 import static com.google.gerrit.common.PageLinks.SETTINGS;
 import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
 import static com.google.gerrit.common.PageLinks.SETTINGS_CONTACT;
+import static com.google.gerrit.common.PageLinks.SETTINGS_EXTENSION;
 import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
 import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS;
 import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT;
@@ -65,6 +66,7 @@
 import com.google.gerrit.client.admin.ProjectListScreen;
 import com.google.gerrit.client.admin.ProjectScreen;
 import com.google.gerrit.client.api.ExtensionScreen;
+import com.google.gerrit.client.api.ExtensionSettingsScreen;
 import com.google.gerrit.client.change.ChangeScreen;
 import com.google.gerrit.client.change.FileTable;
 import com.google.gerrit.client.changes.AccountDashboardScreen;
@@ -720,6 +722,16 @@
           return new NewAgreementScreen(skip(token));
         }
 
+        if (matchPrefix(SETTINGS_EXTENSION, token)) {
+          ExtensionSettingsScreen view =
+              new ExtensionSettingsScreen(skip(token));
+          if (view.isFound()) {
+            return view;
+          } else {
+            return new NotFoundScreen();
+          }
+        }
+
         return new NotFoundScreen();
       }
     });
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 56bc500..d0294f2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client;
 
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gwt.i18n.client.DateTimeFormat;
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 070a868..705f111 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
@@ -20,7 +20,6 @@
 
 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;
@@ -34,6 +33,7 @@
 import com.google.gerrit.client.extensions.TopMenu;
 import com.google.gerrit.client.extensions.TopMenuItem;
 import com.google.gerrit.client.extensions.TopMenuList;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.patches.UnifiedPatchScreen;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
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 2844b5e..c6eb2de 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
@@ -98,6 +98,7 @@
   String errorDialogErrorType();
   String errorDialogGlass();
   String errorDialogTitle();
+  String extensionPanel();
   String loadingPluginsDialog();
   String fileColumnHeader();
   String fileCommentBorder();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.java
index 29d1b63..00036a8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client;
 
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.AnchorElement;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index 59d65f6..02c22e1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.client.account;
 
 import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
index fb3e139..157ee46 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
@@ -15,7 +15,9 @@
 package com.google.gerrit.client.account;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.api.ExtensionPanel;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
@@ -91,6 +93,10 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    ExtensionPanel extensionPanel =
+        createExtensionPoint(GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM);
+    extensionPanel.addStyleName(Gerrit.RESOURCES.css().extensionPanel());
+    add(extensionPanel);
 
     if (password == null) {
       display();
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 a4cfd21..18bf400 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
@@ -18,7 +18,9 @@
 import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.PAGESIZE_CHOICES;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.StringListPanel;
+import com.google.gerrit.client.api.ExtensionPanel;
 import com.google.gerrit.client.config.ConfigServerApi;
 import com.google.gerrit.client.extensions.TopMenuItem;
 import com.google.gerrit.client.rpc.GerritCallback;
@@ -220,6 +222,11 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    ExtensionPanel extensionPanel =
+        createExtensionPoint(GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM);
+    extensionPanel.addStyleName(Gerrit.RESOURCES.css().extensionPanel());
+    add(extensionPanel);
+
     AccountApi.self().view("preferences")
         .get(new ScreenLoadCallback<Preferences>(this) {
       @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
index 028d553..0b2b1cc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GerritUiExtensionPoint;
-import com.google.gerrit.client.api.ExtensionPanel;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.reviewdb.client.Account;
@@ -88,7 +87,7 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    add(new ExtensionPanel(GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM));
+    add(createExtensionPoint(GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM));
     display(Gerrit.getUserAccount());
     display();
   }
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 d00d557..b21d28d 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
@@ -15,28 +15,70 @@
 package com.google.gerrit.client.account;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.api.ExtensionPanel;
+import com.google.gerrit.client.api.ExtensionSettingsScreen;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.MenuScreen;
 import com.google.gerrit.common.PageLinks;
 
+import java.util.HashSet;
+import java.util.Set;
+
 public abstract class SettingsScreen extends MenuScreen {
+  private final Set<String> allMenuNames;
+  private final Set<String> ambiguousMenuNames;
+
   public SettingsScreen() {
     setRequiresSignIn(true);
 
-    link(Util.C.tabAccountSummary(), PageLinks.SETTINGS);
-    link(Util.C.tabPreferences(), PageLinks.SETTINGS_PREFERENCES);
-    link(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
-    link(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
+    allMenuNames = new HashSet<>();
+    ambiguousMenuNames = new HashSet<>();
+
+    linkByGerrit(Util.C.tabAccountSummary(), PageLinks.SETTINGS);
+    linkByGerrit(Util.C.tabPreferences(), PageLinks.SETTINGS_PREFERENCES);
+    linkByGerrit(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
+    linkByGerrit(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
     if (Gerrit.info().hasSshd()) {
-      link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
+      linkByGerrit(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
     }
     if (Gerrit.info().auth().isHttpPasswordSettingsEnabled()) {
-      link(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
+      linkByGerrit(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
     }
-    link(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
-    link(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);
+    linkByGerrit(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
+    linkByGerrit(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);
     if (Gerrit.info().auth().useContributorAgreements()) {
-      link(Util.C.tabAgreements(), PageLinks.SETTINGS_AGREEMENTS);
+      linkByGerrit(Util.C.tabAgreements(), PageLinks.SETTINGS_AGREEMENTS);
     }
+
+    for (String pluginName : ExtensionSettingsScreen.Definition.plugins()) {
+      for (ExtensionSettingsScreen.Definition def :
+          Natives.asList(ExtensionSettingsScreen.Definition.get(pluginName))) {
+        if (!allMenuNames.add(def.getMenu())) {
+          ambiguousMenuNames.add(def.getMenu());
+        }
+      }
+    }
+
+    for (String pluginName : ExtensionSettingsScreen.Definition.plugins()) {
+      for (ExtensionSettingsScreen.Definition def :
+          Natives.asList(ExtensionSettingsScreen.Definition.get(pluginName))) {
+        linkByPlugin(pluginName, def.getMenu(),
+            PageLinks.toSettings(pluginName, def.getPath()));
+      }
+    }
+  }
+
+  private void linkByGerrit(String text, String target) {
+    allMenuNames.add(text);
+    link(text, target);
+  }
+
+  private void linkByPlugin(String pluginName, String text, String target) {
+    if (ambiguousMenuNames.contains(text)) {
+      text += " ("+ pluginName + ")";
+    }
+    link(text, target);
   }
 
   @Override
@@ -44,4 +86,12 @@
     super.onInitUI();
     setPageTitle(Util.C.settingsHeading());
   }
+
+  protected ExtensionPanel createExtensionPoint(
+      GerritUiExtensionPoint extensionPoint) {
+    ExtensionPanel extensionPanel = new ExtensionPanel(extensionPoint);
+    extensionPanel.putObject(GerritUiExtensionPoint.Key.ACCOUNT_INFO,
+        Gerrit.getUserAccountInfo());
+    return extensionPanel;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
index 4f46d39..7b4f74c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
@@ -19,7 +19,7 @@
 
 import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.groups.GroupApi;
 import com.google.gerrit.client.groups.GroupAuditEventInfo;
 import com.google.gerrit.client.groups.GroupInfo;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index 1e3f918..7c0c8f6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -17,9 +17,9 @@
 import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.VoidResult;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.groups.GroupApi;
 import com.google.gerrit.client.groups.GroupInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
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 4edcfa6..9724000 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
@@ -15,11 +15,13 @@
 package com.google.gerrit.client.admin;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.StringListPanel;
 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.api.ExtensionPanel;
 import com.google.gerrit.client.change.Resources;
 import com.google.gerrit.client.config.DownloadInfo.DownloadCommandInfo;
 import com.google.gerrit.client.config.DownloadInfo.DownloadSchemeInfo;
@@ -114,6 +116,12 @@
       }
     });
 
+    ExtensionPanel extensionPanelTop =
+        new ExtensionPanel(GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP);
+    extensionPanelTop.put(GerritUiExtensionPoint.Key.PROJECT_NAME,
+        getProjectKey().get());
+    add(extensionPanelTop);
+
     add(new ProjectDownloadPanel(getProjectKey().get(), true));
 
     initDescription();
@@ -126,6 +134,12 @@
     add(pluginOptionsPanel);
     add(saveProject);
     add(actionsGrid);
+
+    ExtensionPanel extensionPanelBottom =
+        new ExtensionPanel(GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM);
+    extensionPanelBottom.put(GerritUiExtensionPoint.Key.PROJECT_NAME,
+        getProjectKey().get());
+    add(extensionPanelBottom);
   }
 
   @Override
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 c5f0af5..b30a1cb 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
@@ -16,7 +16,7 @@
 
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.History;
@@ -35,12 +35,14 @@
   private static native void init0() /*-{
     var serverUrl = @com.google.gwt.core.client.GWT::getHostPageBaseURL()();
     var ScreenDefinition = @com.google.gerrit.client.api.ExtensionScreen.Definition::TYPE;
+    var SettingsScreenDefinition = @com.google.gerrit.client.api.ExtensionSettingsScreen.Definition::TYPE;
     var PanelDefinition = @com.google.gerrit.client.api.ExtensionPanel.Definition::TYPE;
     $wnd.Gerrit = {
       JsonString: @com.google.gerrit.client.rpc.NativeString::TYPE,
       events: {},
       plugins: {},
       screens: {},
+      settingsScreens: {},
       panels: {},
       change_actions: {},
       edit_actions: {},
@@ -87,6 +89,11 @@
         var s = new ScreenDefinition(r,c);
         (this.screens[p] || (this.screens[p]=[])).push(s);
       },
+      settingsScreen: function(p,m,c){this._settingsScreen(this.getPluginName(),p,m,c)},
+      _settingsScreen: function(n,p,m,c){
+        var s = new SettingsScreenDefinition(p,m,c);
+        (this.settingsScreens[n] || (this.settingsScreens[n]=[])).push(s);
+      },
       panel: function(i,c){this._panel(this.getPluginName(),i,c)},
       _panel: function(n,i,c){
         var p = new PanelDefinition(n,c);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
index 6ee9b44..0702cba 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
@@ -66,6 +66,12 @@
     }
   }
 
+  public void putObject(GerritUiExtensionPoint.Key key, JavaScriptObject value) {
+    for (Context ctx : contexts) {
+      ctx.putObject(key.name(), value);
+    }
+  }
+
   @Override
   protected void onLoad() {
     super.onLoad();
@@ -122,6 +128,7 @@
     final native void put(String k, String v) /*-{ this.p[k] = v; }-*/;
     final native void putInt(String k, int v) /*-{ this.p[k] = v; }-*/;
     final native void putBoolean(String k, boolean v) /*-{ this.p[k] = v; }-*/;
+    final native void putObject(String k, JavaScriptObject v) /*-{ this.p[k] = v; }-*/;
 
     private static final native Context create(
         JavaScriptObject T,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionSettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionSettingsScreen.java
new file mode 100644
index 0000000..c351bbf
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionSettingsScreen.java
@@ -0,0 +1,144 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.api;
+
+import com.google.gerrit.client.account.SettingsScreen;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Element;
+
+import java.util.Set;
+
+/** SettingsScreen contributed by a plugin. */
+public class ExtensionSettingsScreen extends SettingsScreen {
+  private Context ctx;
+
+  public ExtensionSettingsScreen(String token) {
+    if (token.contains("?")) {
+      token = token.substring(0, token.indexOf('?'));
+    }
+    String name;
+    String rest;
+    int s = token.indexOf('/');
+    if (0 < s) {
+      name = token.substring(0, s);
+      rest = token.substring(s + 1);
+    } else {
+      name = token;
+      rest = "";
+    }
+    ctx = create(name, rest);
+  }
+
+  private Context create(String name, String rest) {
+    for (Definition def : Natives.asList(Definition.get(name))) {
+      if (def.matches(rest)) {
+        return Context.create(def, this);
+      }
+    }
+    return null;
+  }
+
+  public boolean isFound() {
+    return ctx != null;
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    setHeaderVisible(false);
+    ctx.onLoad();
+  }
+
+  @Override
+  protected void onUnload() {
+    super.onUnload();
+    for (JavaScriptObject u : Natives.asList(ctx.unload())) {
+      ApiGlue.invoke(u);
+    }
+  }
+
+  public static class Definition extends JavaScriptObject {
+    static final JavaScriptObject TYPE = init();
+    private static native JavaScriptObject init() /*-{
+      function SettingsScreenDefinition(p, m, c) {
+        this.path = p;
+        this.menu = m;
+        this.onLoad = c;
+      };
+      return SettingsScreenDefinition;
+    }-*/;
+
+    public static native JsArray<Definition> get(String n)
+    /*-{ return $wnd.Gerrit.settingsScreens[n] || [] }-*/;
+
+    public static final Set<String> plugins() {
+      return Natives.keys(settingsScreens());
+    }
+
+    private static final native NativeMap<NativeString> settingsScreens()
+    /*-{ return $wnd.Gerrit.settingsScreens; }-*/;
+
+    public final native String getPath() /*-{ return this.path; }-*/;
+    public final native String getMenu() /*-{ return this.menu; }-*/;
+
+    final native boolean matches(String t)
+    /*-{ return this.path == t; }-*/;
+
+    protected Definition() {
+    }
+  }
+
+  static class Context extends JavaScriptObject {
+    static final Context create(
+        Definition def,
+        ExtensionSettingsScreen view) {
+      return create(TYPE, def, view, view.getBody().getElement());
+    }
+
+    final native void onLoad() /*-{ this._d.onLoad(this) }-*/;
+    final native JsArray<JavaScriptObject> unload() /*-{ return this._u }-*/;
+
+    private static final native Context create(
+        JavaScriptObject T,
+        Definition d,
+        ExtensionSettingsScreen s,
+        Element e)
+    /*-{ return new T(d,s,e) }-*/;
+
+    private static final JavaScriptObject TYPE = init();
+    private static final native JavaScriptObject init() /*-{
+      var T = function(d,s,e) {
+        this._d = d;
+        this._s = s;
+        this._u = [];
+        this.body = e;
+      };
+      T.prototype = {
+        setTitle: function(t){this._s.@com.google.gerrit.client.ui.Screen::setPageTitle(Ljava/lang/String;)(t)},
+        setWindowTitle: function(t){this._s.@com.google.gerrit.client.ui.Screen::setWindowTitle(Ljava/lang/String;)(t)},
+        show: function(){$entry(this._s.@com.google.gwtexpui.user.client.View::display()())},
+        onUnload: function(f){this._u.push(f)},
+      };
+      return T;
+    }-*/;
+
+    protected Context() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
index 4eec47a..c3b2338 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
@@ -59,6 +59,7 @@
       on: function(e,f){G.on(e,f)},
       onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
       screen: function(p,c){G._screen(this.name,p,c)},
+      settingsScreen: function(p,m,c){G._settingsScreen(this.name,p,m,c)},
       panel: function(i,c){G._panel(this.name,i,c)},
 
       url: function (u){return G.url(this._url(u))},
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 5a311ec..2c7d137 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GerritUiExtensionPoint;
-import com.google.gerrit.client.account.AccountInfo.AvatarInfo;
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.api.ChangeGlue;
 import com.google.gerrit.client.api.ExtensionPanel;
@@ -37,6 +36,7 @@
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.diff.DiffApi;
 import com.google.gerrit.client.diff.FileInfo;
+import com.google.gerrit.client.info.AccountInfo.AvatarInfo;
 import com.google.gerrit.client.projects.ConfigInfoCache;
 import com.google.gerrit.client.projects.ConfigInfoCache.Entry;
 import com.google.gerrit.client.rpc.CallbackGroup;
@@ -87,6 +87,7 @@
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Panel;
 import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.ToggleButton;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
@@ -149,6 +150,9 @@
   private FileTable.Mode fileTableMode;
 
   @UiField HTMLPanel headerLine;
+  @UiField SimplePanel headerExtension;
+  @UiField SimplePanel headerExtensionMiddle;
+  @UiField SimplePanel headerExtensionRight;
   @UiField Style style;
   @UiField ToggleButton star;
   @UiField Anchor permalink;
@@ -226,10 +230,7 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    ExtensionPanel extensionPanel =
-        new ExtensionPanel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK);
-    extensionPanel.putInt(GerritUiExtensionPoint.Key.CHANGE_ID, changeId.get());
-    changeExtension.add(extensionPanel);
+    addExtensionPoints();
     CallbackGroup group = new CallbackGroup();
     if (Gerrit.isSignedIn()) {
       ChangeList.query("change:" + changeId.get() + " has:draft",
@@ -266,6 +267,24 @@
         }));
   }
 
+  private void addExtensionPoints() {
+    addExtensionPoint(GerritUiExtensionPoint.CHANGE_SCREEN_HEADER,
+        headerExtension);
+    addExtensionPoint(GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS,
+        headerExtensionMiddle);
+    addExtensionPoint(GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS,
+        headerExtensionRight);
+    addExtensionPoint(
+        GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+        changeExtension);
+  }
+
+  private void addExtensionPoint(GerritUiExtensionPoint extensionPoint, Panel p) {
+    ExtensionPanel extensionPanel = new ExtensionPanel(extensionPoint);
+    extensionPanel.putInt(GerritUiExtensionPoint.Key.CHANGE_ID, changeId.get());
+    p.add(extensionPanel);
+  }
+
   void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) {
     RestApi call = ChangeApi.detail(changeId.get());
     ChangeList.addOptions(call, EnumSet.of(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
index ed3be38..06ae4ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
@@ -81,7 +81,6 @@
       display: inline-block;
       position: relative;
       height: HEADER_HEIGHT;
-      width: 300px;
     }
     .star {
       position: absolute;
@@ -323,6 +322,15 @@
       vertical-align: bottom;
     }
 
+    .headerExtension {
+      display: inline-block;
+      float: right;
+    }
+
+    .headerExtension>div>div {
+      float: left;
+    }
+
     .changeExtension {
       padding-top: 5px;
     }
@@ -337,6 +345,7 @@
               <ui:attribute name='title'/>
             </g:Anchor> - <span ui:field='statusText' class='{style.statusText}'/></ui:msg>
           </span>
+          <g:SimplePanel ui:field='headerExtension' styleName='{style.headerExtension}'/>
         </div>
       </div>
 
@@ -374,6 +383,7 @@
           <g:Button ui:field='deleteRevision' styleName='' visible='false'>
             <div><ui:msg>Delete Revision</ui:msg></div>
           </g:Button>
+          <g:SimplePanel ui:field='headerExtensionMiddle' styleName='{style.headerExtension}'/>
         </div>
       </div>
 
@@ -388,6 +398,7 @@
           <g:Button ui:field='download' styleName=''>
             <div><ui:msg>Download</ui:msg></div>
           </g:Button>
+          <g:SimplePanel ui:field='headerExtensionRight' styleName='{style.headerExtension}'/>
         </g:FlowPanel>
         <c:StarIcon ui:field='star' styleName='{style.star}' title='Star the change (Shortcut: s)'>
           <ui:attribute name='title'/>
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 68b230f..bfaff93 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
@@ -18,12 +18,12 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.WebLinkInfo;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.changes.ChangeInfo;
 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.config.GitwebInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.InlineHyperlink;
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 ce2a104..6d39198 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
@@ -265,7 +265,8 @@
     AccountGeneralPreferences pref =
         Gerrit.getUserAccount().getGeneralPreferences();
 
-    if (scheme != null && scheme != pref.getDownloadUrl()) {
+    if (Gerrit.isSignedIn() && scheme != null
+        && scheme != pref.getDownloadUrl()) {
       pref.setDownloadUrl(scheme);
       PreferenceInput in = PreferenceInput.create();
       in.downloadScheme(scheme);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
index f192a71..aceed57 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -15,13 +15,13 @@
 package com.google.gerrit.client.change;
 
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
-import com.google.gerrit.client.account.AccountInfo.AvatarInfo;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
 import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.info.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo.AvatarInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.common.PageLinks;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
index 7af247a..1c0486d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
@@ -15,10 +15,10 @@
 package com.google.gerrit.client.change;
 
 import com.google.gerrit.client.FormatUtil;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.admin.Util;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.groups.GroupBaseInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.SuggestAfterTypingNCharsOracle;
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 ce35747..22a7155 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
@@ -17,12 +17,12 @@
 import com.google.gerrit.client.ConfirmationCallback;
 import com.google.gerrit.client.ConfirmationDialog;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
 import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
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 7f93500..5b7e304 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
@@ -15,9 +15,9 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.WebLinkInfo;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.diff.FileInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
@@ -93,7 +93,7 @@
   public final native String branch() /*-{ return this.branch; }-*/;
   public final native String topic() /*-{ return this.topic; }-*/;
   public final native String changeId() /*-{ return this.change_id; }-*/;
-  public final native boolean mergeable() /*-{ return this.mergeable || false; }-*/;
+  public final native boolean mergeable() /*-{ return this.mergeable ? true : false; }-*/;
   public final native int insertions() /*-{ return this.insertions; }-*/;
   public final native int deletions() /*-{ return this.deletions; }-*/;
   private final native String statusRaw() /*-{ return this.status; }-*/;
@@ -107,7 +107,6 @@
   public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
   public final native String currentRevision() /*-{ return this.current_revision; }-*/;
   public final native void setCurrentRevision(String r) /*-{ this.current_revision = r; }-*/;
-  private final native void setSubmittable(boolean x) /*-{ this.submittable = x; }-*/;
   public final native NativeMap<RevisionInfo> revisions() /*-{ return this.revisions; }-*/;
   public final native RevisionInfo revision(String n) /*-{ return this.revisions[n]; }-*/;
   public final native JsArray<MessageInfo> messages() /*-{ return this.messages; }-*/;
@@ -135,7 +134,6 @@
 
   public final boolean submittable() {
     init();
-    getMissingLabelIndex();
     return _submittable();
   }
 
@@ -143,8 +141,6 @@
   /*-{ return this.submittable ? true : false; }-*/;
 
   /**
-   * As a side effect this.submittable is evaluated and set accordingly.
-   *
    * @return the index of the missing label or -1
    *         if no label is missing, or if more than one label is missing.
    */
@@ -168,7 +164,6 @@
           if (ret != -1) {
             // more than one label is missing, so it's unclear which to quick
             // approve, return -1
-            setSubmittable(false);
             return -1;
           } else {
             ret = i;
@@ -181,11 +176,9 @@
 
         case REJECT: // Submit cannot happen, do not quick approve.
         case IMPOSSIBLE:
-          setSubmittable(false);
           return -1;
       }
     }
-    setSubmittable(ret == -1);
     return ret;
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index f955264..ae1dfb2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -18,8 +18,8 @@
 import static com.google.gerrit.client.FormatUtil.shortFormat;
 
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.ui.AccountLinkPanel;
 import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.ChangeLink;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
index c69ee57..9088c1c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.client.changes;
 
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.diff.CommentRange;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
index 498799c..4265203 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -122,20 +122,18 @@
         }
       }
     }, KeyDownEvent.getType());
+
     updateContextTimer = new Timer() {
       @Override
       public void run() {
         if (prefs.context() == WHOLE_FILE_CONTEXT) {
           contextEntireFile.setValue(true);
         }
-        if (view.canEnableRenderEntireFile(prefs)) {
+        if (view.canRenderEntireFile(prefs)) {
           renderEntireFile.setEnabled(true);
+          renderEntireFile.setValue(prefs.renderEntireFile());
         } else {
-          if (prefs.renderEntireFile()) {
-            prefs.renderEntireFile(false);
-            renderEntireFile.setValue(false);
-            view.updateRenderEntireFile();
-          }
+          renderEntireFile.setValue(false);
           renderEntireFile.setEnabled(false);
         }
         view.setContext(prefs.context());
@@ -167,10 +165,16 @@
     autoHideDiffTableHeader.setValue(!prefs.autoHideDiffTableHeader());
     manualReview.setValue(prefs.manualReview());
     expandAllComments.setValue(prefs.expandAllComments());
-    renderEntireFile.setValue(prefs.renderEntireFile());
-    renderEntireFile.setEnabled(view.canEnableRenderEntireFile(prefs));
     setTheme(prefs.theme());
 
+    if (view.canRenderEntireFile(prefs)) {
+      renderEntireFile.setValue(prefs.renderEntireFile());
+      renderEntireFile.setEnabled(true);
+    } else {
+      renderEntireFile.setValue(false);
+      renderEntireFile.setEnabled(false);
+    }
+
     mode.setEnabled(prefs.syntaxHighlighting());
     if (prefs.syntaxHighlighting()) {
       setMode(view.getCmFromSide(DisplaySide.B).getStringOption("mode"));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
index a8b6065..3d01aee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
@@ -435,7 +435,7 @@
       keyMap.on("C", commentManager.insertNewDraft(cm));
     }
     cm.addKeyMap(keyMap);
-    if (prefs.renderEntireFile()) {
+    if (renderEntireFile()) {
       cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
     }
   }
@@ -580,11 +580,6 @@
     chunkManager = new ChunkManager(this, cmA, cmB, diffTable.scrollbar);
     skipManager = new SkipManager(this, commentManager);
 
-    if (prefs.renderEntireFile() && !canEnableRenderEntireFile(prefs)) {
-      // CodeMirror is too slow to layout an entire huge file.
-      prefs.renderEntireFile(false);
-    }
-
     operation(new Runnable() {
       @Override
       public void run() {
@@ -650,14 +645,19 @@
       .set("keyMap", "vim_ro")
       .set("theme", prefs.theme().name().toLowerCase())
       .set("value", meta != null ? contents : "")
-      .set("viewportMargin", prefs.renderEntireFile() ? POSITIVE_INFINITY : 10));
+      .set("viewportMargin", renderEntireFile() ? POSITIVE_INFINITY : 10));
   }
 
   DiffInfo.IntraLineStatus getIntraLineStatus() {
     return diff.intralineStatus();
   }
 
-  boolean canEnableRenderEntireFile(DiffPreferences prefs) {
+  boolean renderEntireFile() {
+    return prefs.renderEntireFile() && canRenderEntireFile(prefs);
+  }
+
+  boolean canRenderEntireFile(DiffPreferences prefs) {
+    // CodeMirror is too slow to layout an entire huge file.
     return fileSize.compareTo(FileSize.HUGE) < 0
         || (prefs.context() != WHOLE_FILE_CONTEXT && prefs.context() < 100);
   }
@@ -738,6 +738,7 @@
       public void run() {
         skipManager.removeAll();
         skipManager.render(context, diff);
+        updateRenderEntireFile();
       }
     });
   }
@@ -960,13 +961,14 @@
   void updateRenderEntireFile() {
     cmA.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
     cmB.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
-    if (prefs.renderEntireFile()) {
+
+    boolean entireFile = renderEntireFile();
+    if (entireFile) {
       cmA.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
       cmB.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
     }
-
-    cmA.setOption("viewportMargin", prefs.renderEntireFile() ? POSITIVE_INFINITY : 10);
-    cmB.setOption("viewportMargin", prefs.renderEntireFile() ? POSITIVE_INFINITY : 10);
+    cmA.setOption("viewportMargin", entireFile ? POSITIVE_INFINITY : 10);
+    cmB.setOption("viewportMargin", entireFile ? POSITIVE_INFINITY : 10);
   }
 
   void resizeCodeMirror() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
index d1f53a5..1e6c8e3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
@@ -78,7 +78,7 @@
 
   private final DownloadPanel downloadPanel;
   private final DownloadSchemeInfo schemeInfo;
-  private final DownloadScheme urlType;
+  private final DownloadScheme scheme;
 
   public DownloadUrlLink(DownloadPanel downloadPanel,
       DownloadSchemeInfo schemeInfo, String text) {
@@ -94,11 +94,11 @@
 
     this.downloadPanel = downloadPanel;
     this.schemeInfo = schemeInfo;
-    this.urlType = urlType;
+    this.scheme = urlType;
   }
 
   public DownloadScheme getUrlType() {
-    return urlType;
+    return scheme;
   }
 
   @Override
@@ -108,12 +108,13 @@
 
     select();
 
-    if (Gerrit.isSignedIn() && urlType != null) {
+    AccountGeneralPreferences pref =
+        Gerrit.getUserAccount().getGeneralPreferences();
+    if (Gerrit.isSignedIn() && scheme != null
+        && scheme != pref.getDownloadUrl()) {
       // If the user is signed-in, remember this choice for future panels.
       //
-      AccountGeneralPreferences pref =
-          Gerrit.getUserAccount().getGeneralPreferences();
-      pref.setDownloadUrl(urlType);
+      pref.setDownloadUrl(scheme);
       com.google.gerrit.client.account.Util.ACCOUNT_SVC.changePreferences(pref,
           new AsyncCallback<VoidResult>() {
             @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index e463607..698546d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -239,7 +239,9 @@
     super.registerKeys();
     cm.addKeyMap(KeyMap.create()
         .on("Ctrl-L", gotoLine())
-        .on("Cmd-L", gotoLine()));
+        .on("Cmd-L", gotoLine())
+        .on("Cmd-S", save())
+        .on("Ctrl-S", save()));
   }
 
   private Runnable gotoLine() {
@@ -367,9 +369,6 @@
         .set("keyMap", "default")
         .set("theme", prefs.theme().name().toLowerCase())
         .set("mode", mode != null ? mode.mode() : null));
-    cm.addKeyMap(KeyMap.create()
-        .on("Cmd-S", save())
-        .on("Ctrl-S", save()));
   }
 
   private void renderLinks(EditFileInfo editInfo,
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 2fca8b2..c26c437 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
@@ -118,6 +118,10 @@
   cursor: pointer;
 }
 
+.extensionPanel {
+  padding-top: 10px;
+}
+
 /** MenuScreen **/
 .menuScreenMenuBar {
   background: topMenuColor;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java
index 02deb28..93be87b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.client.groups;
 
 import com.google.gerrit.client.VoidResult;
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.RestApi;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java
index 2d1de67..f62ae22 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupAuditEventInfo.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client.groups;
 
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java
index f1e4e87..6142e5b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupInfo.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client.groups;
 
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/MemberList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/MemberList.java
index d788a1d..d63c212 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/MemberList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/MemberList.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client.groups;
 
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index 63823ea..2420e7a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -16,9 +16,9 @@
 
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.changes.CommentApi;
 import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.CommentPanel;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLinkPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLinkPanel.java
index 2633e3b..288549f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLinkPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLinkPanel.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.client.AvatarImage;
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccountInfoCache;
 import com.google.gerrit.reviewdb.client.Account;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
index 78350db..60c23df 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
@@ -16,7 +16,7 @@
 
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.account.AccountApi;
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gwt.core.client.JsArray;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
index 748cd3c..77f40df 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.client.AvatarImage;
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.BlurHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java
index cf49ce7..d944690 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MenuScreen.java
@@ -49,6 +49,11 @@
   }
 
   @Override
+  protected FlowPanel getBody() {
+    return body;
+  }
+
+  @Override
   protected void add(final Widget w) {
     body.add(w);
   }
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 33c1e70..53d6713 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
@@ -312,7 +312,6 @@
     return schema;
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public void replace(ChangeData cd) throws IOException {
     Term id = QueryBuilder.idTerm(cd);
@@ -332,7 +331,6 @@
     }
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public void delete(Change.Id id) throws IOException {
     Term idTerm = QueryBuilder.idTerm(id);
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index c975ef9..b52e7af 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -103,7 +103,6 @@
     '//gerrit-lucene:lucene',
     '//gerrit-oauth:oauth',
     '//gerrit-openid:openid',
-    '//gerrit-solr:solr',
     '//lib:args4j',
     '//lib:gwtorm',
     '//lib:protobuf',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 139af9e..0a0d000 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -77,7 +77,6 @@
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
-import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
@@ -383,8 +382,6 @@
     switch (indexType) {
       case LUCENE:
         return luceneModule != null ? luceneModule : new LuceneIndexModule();
-      case SOLR:
-        return new SolrIndexModule();
       default:
         throw new IllegalStateException("unsupported index.type = " + indexType);
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
index db436d5..8b90e54 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
@@ -141,7 +141,7 @@
                 MoreExecutors.directExecutor());
           }
 
-          mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+          mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
               new AsyncFunction<List<?>, Void>() {
                   @Override
                 public ListenableFuture<Void> apply(List<?> input)
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 64dd514..5bedfe3 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
@@ -35,7 +35,6 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.index.SiteIndexer;
-import com.google.gerrit.solr.SolrIndexModule;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
@@ -122,9 +121,6 @@
       case LUCENE:
         changeIndexModule = new LuceneIndexModule(version, threads, outputBase);
         break;
-      case SOLR:
-        changeIndexModule = new SolrIndexModule(false, threads, outputBase);
-        break;
       default:
         throw new IllegalStateException("unsupported index.type");
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DB2Initializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DB2Initializer.java
new file mode 100644
index 0000000..3f6abcf
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DB2Initializer.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
+
+import com.google.gerrit.pgm.init.api.Section;
+
+
+public class DB2Initializer implements DatabaseConfigInitializer {
+
+  @Override
+  public void initConfig(Section databaseSection) {
+    final String defPort = "50001";
+    databaseSection.string("Server hostname", "hostname", "localhost");
+    databaseSection.string("Server port", "port", defPort, false);
+    databaseSection.string("Database name", "database", "gerrit");
+    databaseSection.string("Database username", "username", username());
+    databaseSection.password("username", "password");
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
index 607d6b4..a0c24a6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
@@ -30,6 +30,8 @@
   protected void configure() {
     bind(SitePaths.class).toInstance(site);
     bind(DatabaseConfigInitializer.class).annotatedWith(
+        Names.named("db2")).to(DB2Initializer.class);
+    bind(DatabaseConfigInitializer.class).annotatedWith(
         Names.named("h2")).to(H2Initializer.class);
     bind(DatabaseConfigInitializer.class).annotatedWith(
         Names.named("jdbc")).to(JDBCInitializer.class);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index 3fcc911..abea521 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -84,6 +84,8 @@
       libraries.mysqlDriver.downloadRequired();
     } else if (dci instanceof OracleInitializer) {
       libraries.oracleDriver.downloadRequired();
+    } else if (dci instanceof DB2Initializer) {
+      libraries.db2Driver.downloadRequired();
     }
 
     dci.initConfig(database);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index acb8a6b..a177fe7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -51,9 +51,6 @@
     ui.header("Index");
 
     IndexType type = index.select("Type", "type", IndexType.LUCENE);
-    if (type == IndexType.SOLR) {
-      index.string("Solr Index URL", "url", "localhost:9983");
-    }
     if (site.isNew && type == IndexType.LUCENE) {
       LuceneChangeIndex.setReady(
           site, ChangeSchemas.getLatest().getVersion(), true);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index 7c7b14d..869e1c4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -39,6 +39,8 @@
 
   /* final */LibraryDownloader bouncyCastleProvider;
   /* final */LibraryDownloader bouncyCastleSSL;
+  /* final */LibraryDownloader db2Driver;
+  /* final */LibraryDownloader db2DriverLicense;
   /* final */LibraryDownloader mysqlDriver;
   /* final */LibraryDownloader oracleDriver;
 
@@ -87,16 +89,25 @@
     LibraryDownloader dl = (LibraryDownloader) field.get(this);
     dl.setName(get(cfg, n, "name"));
     dl.setJarUrl(get(cfg, n, "url"));
-    dl.setSHA1(get(cfg, n, "sha1"));
+    dl.setSHA1(getOptional(cfg, n, "sha1"));
     dl.setRemove(get(cfg, n, "remove"));
     for (String d : cfg.getStringList("library", n, "needs")) {
       dl.addNeeds((LibraryDownloader) getClass().getDeclaredField(d).get(this));
     }
   }
 
+  private static String getOptional(Config cfg, String name, String key) {
+    return doGet(cfg, name, key, false);
+  }
+
   private static String get(Config cfg, String name, String key) {
+    return doGet(cfg, name, key, true);
+  }
+
+  private static final String doGet(Config cfg, String name, String key,
+      boolean required) {
     String val = cfg.getString("library", name, key);
-    if (val == null || val.isEmpty()) {
+    if ((val == null || val.isEmpty()) && required) {
       throw new IllegalStateException("Variable library." + name + "." + key
           + " is required within " + RESOURCE_FILE);
     }
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 00c7c58..9c9843e 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
@@ -300,6 +300,8 @@
 
   private void verifyFileChecksum() {
     if (sha1 == null) {
+      System.err.println();
+      System.err.flush();
       return;
     }
     Hasher h = Hashing.sha1().newHasher();
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
index 36e8921..20dc4ce 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
@@ -39,3 +39,17 @@
   url = file:///u01/app/oracle/product/11.2.0/xe/jdbc/lib/ojdbc6.jar
   sha1 = 2f89cd9176772c3a6c261ce6a8e3d0d4425f5679
   remove = ojdbc6.jar
+
+[library "db2Driver"]
+  name = DB2 Type 4 JDBC driver (10.5)
+  url = file:///opt/ibm/db2/V10.5/java/db2jcc4.jar
+  sha1 = 9344d4fd41d6511f2d1d1deb7759056495b3a39b
+  needs = db2DriverLicense
+  remove = db2jcc4.jar
+
+# Omit SHA-1 for license JAR as it's not stable and depends on the product
+# the customer has purchased.
+[library "db2DriverLicense"]
+  name = DB2 Type 4 JDBC driver license (10.5)
+  url = file:///opt/ibm/db2/V10.5/java/db2jcc_license_cu.jar
+  remove = db2jcc_license_cu.jar
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
index b5fd373..23fa308 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.plugin.client;
 
 import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.plugin.client.extension.Panel;
 import com.google.gerrit.plugin.client.screen.Screen;
 import com.google.gwt.core.client.GWT;
@@ -56,6 +57,10 @@
   public final native void refreshMenuBar()
   /*-{ return this.refreshMenuBar() }-*/;
 
+  /** @return the current user */
+  public final native AccountInfo getCurrentUser()
+  /*-{ return this.getCurrentUser() }-*/;
+
   /** Check if user is signed in. */
   public final native boolean isSignedIn()
   /*-{ return this.isSignedIn() }-*/;
@@ -94,6 +99,19 @@
   /*-{ this.screen(new $wnd.RegExp(p), e) }-*/;
 
   /**
+   * Register a settings screen displayed at {@code /#/settings/x/plugin/token}.
+   *
+   * @param token literal anchor token appearing after the plugin name.
+   * @param entry callback function invoked to create the settings screen widgets.
+   */
+  public final void settingsScreen(String token, String menu, Screen.EntryPoint entry) {
+    settingsScreen(token, menu, wrap(entry));
+  }
+
+  private final native void settingsScreen(String t, String m, JavaScriptObject e)
+  /*-{ this.settingsScreen(t, m, e) }-*/;
+
+  /**
    * Register a panel for a UI extension point.
    *
    * @param extensionPoint the UI extension point for which the panel should be
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java
index b09d477..6d4e719 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java
@@ -69,6 +69,8 @@
     final native int getBoolean(String k, boolean d) /*-{
       return this.p.hasOwnProperty(k) ? this.p[k] : d
     }-*/;
+    final native JavaScriptObject getObject(String k)
+    /*-{ return this.p[k]; }-*/;
 
 
     final native void detach(Panel p) /*-{
@@ -101,4 +103,8 @@
   public int getBoolean(GerritUiExtensionPoint.Key key, boolean defaultValue) {
     return ctx.getBoolean(key.name(), defaultValue);
   }
+
+  public JavaScriptObject getObject(GerritUiExtensionPoint.Key key) {
+    return ctx.getObject(key.name());
+  }
 }
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index bea53ec..c7bd8c9 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -211,6 +211,7 @@
     '//gerrit-server/src/main/prolog:common',
     '//lib:args4j',
     '//lib:grappa',
+    '//lib:guava',
     '//lib:gwtorm',
     '//lib:truth',
     '//lib/bouncycastle:bcprov',
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 1f8aa0c..0569e5f 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
@@ -31,6 +31,7 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.FileInfo;
 import com.google.gerrit.extensions.common.MergeableInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -41,6 +42,7 @@
 import com.google.gerrit.server.change.DraftComments;
 import com.google.gerrit.server.change.FileResource;
 import com.google.gerrit.server.change.Files;
+import com.google.gerrit.server.change.GetPatch;
 import com.google.gerrit.server.change.GetRevisionActions;
 import com.google.gerrit.server.change.ListRevisionComments;
 import com.google.gerrit.server.change.ListRevisionDrafts;
@@ -79,6 +81,7 @@
   private final RevisionResource revision;
   private final Provider<Files> files;
   private final Provider<Files.ListFiles> listFiles;
+  private final Provider<GetPatch> getPatch;
   private final Provider<PostReview> review;
   private final Provider<Mergeable> mergeable;
   private final FileApiImpl.Factory fileApi;
@@ -103,6 +106,7 @@
       Reviewed.DeleteReviewed deleteReviewed,
       Provider<Files> files,
       Provider<Files.ListFiles> listFiles,
+      Provider<GetPatch> getPatch,
       Provider<PostReview> review,
       Provider<Mergeable> mergeable,
       FileApiImpl.Factory fileApi,
@@ -127,6 +131,7 @@
     this.putReviewed = putReviewed;
     this.deleteReviewed = deleteReviewed;
     this.listFiles = listFiles;
+    this.getPatch = getPatch;
     this.mergeable = mergeable;
     this.fileApi = fileApi;
     this.listComments = listComments;
@@ -357,6 +362,15 @@
   }
 
   @Override
+  public BinaryResult patch() throws RestApiException {
+    try {
+      return getPatch.get().apply(revision);
+    } catch (IOException e) {
+      throw new RestApiException("Cannot get patch", e);
+    }
+  }
+
+  @Override
   public Map<String, ActionInfo> actions() throws RestApiException {
     return revisionActions.apply(revision).value();
   }
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 0c38c29..acdf004 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
@@ -137,6 +137,7 @@
   private final GitRepositoryManager repoManager;
   private final ProjectCache projectCache;
   private final MergeUtil.Factory mergeUtilFactory;
+  private final Submit submit;
   private final IdentifiedUser.GenericFactory userFactory;
   private final ChangeData.Factory changeDataFactory;
   private final FileInfoJson fileInfoJson;
@@ -161,6 +162,7 @@
       GitRepositoryManager repoManager,
       ProjectCache projectCache,
       MergeUtil.Factory mergeUtilFactory,
+      Submit submit,
       IdentifiedUser.GenericFactory uf,
       ChangeData.Factory cdf,
       FileInfoJson fileInfoJson,
@@ -180,6 +182,7 @@
     this.repoManager = repoManager;
     this.userFactory = uf;
     this.projectCache = projectCache;
+    this.submit = submit;
     this.mergeUtilFactory = mergeUtilFactory;
     this.fileInfoJson = fileInfoJson;
     this.accountLoaderFactory = ailf;
@@ -383,6 +386,7 @@
     // the response and avoid making a request to /submit_type from the UI.
     out.mergeable = in.getStatus() == Change.Status.MERGED
         ? null : cd.isMergeable();
+    out.submittable = submit.submittable(cd);
     ChangedLines changedLines = cd.changedLines();
     if (changedLines != null) {
       out.insertions = changedLines.insertions;
@@ -597,6 +601,12 @@
         PatchSetApproval psa = current.get(accountId, lt.getName());
         if (psa != null) {
           value = Integer.valueOf(psa.getValue());
+          if (value == 0) {
+            // This may be a dummy approval that was inserted when the reviewer
+            // was added. Explicitly check whether the user can vote on this
+            // label.
+            value = labelNormalizer.canVote(ctl, lt, accountId) ? 0 : null;
+          }
           date = psa.getGranted();
         } else {
           // Either the user cannot vote on this label, or they were added as a
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index 3ec18f3..6c0ee3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -207,6 +208,15 @@
           changeInserterFactory.create(refControl.getProjectControl(),
               change, c);
 
+      ChangeMessage msg = new ChangeMessage(new ChangeMessage.Key(change.getId(),
+          ChangeUtil.messageUUID(db.get())),
+          me.getAccountId(),
+          ins.getPatchSet().getCreatedOn(),
+          ins.getPatchSet().getId());
+      msg.setMessage(String.format("Uploaded patch set %s.",
+          ins.getPatchSet().getPatchSetId()));
+
+      ins.setMessage(msg);
       validateCommit(git, refControl, c, me, ins);
       updateRef(git, rw, c, change, ins.getPatchSet());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
index a7f8dea..72f8c35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -54,6 +54,7 @@
       rw.parseBody(commit);
       CommitInfo info = json.create(ChangeJson.NO_OPTIONS)
           .toCommit(rsrc.getControl(), rw, commit, addLinks);
+      info.commit = commit.name();
       Response<CommitInfo> r = Response.ok(info);
       if (rsrc.isCacheable()) {
         r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
index afd11569..3c4d79d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
@@ -144,7 +144,7 @@
     }
     b.append("From ").append(commit.getName())
      .append(' ')
-     .append("Mon Sep 17 00:00:00 2001\n")
+     .append("Mon Sep 17 00:00:00 2001\n") // Fixed timestamp to match output of C Git's format-patch
      .append("From: ").append(author.getName())
      .append(" <").append(author.getEmailAddress()).append(">\n")
      .append("Date: ").append(formatDate(author)).append('\n')
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
index cd3b3b1..9f7eb93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.common.base.CharMatcher.WHITESPACE;
-
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.ChangeHooks;
@@ -45,7 +43,8 @@
 
 @Singleton
 public class HashtagsUtil {
-  private static final CharMatcher LEADER = WHITESPACE.or(CharMatcher.is('#'));
+  private static final CharMatcher LEADER =
+      CharMatcher.whitespace().or(CharMatcher.is('#'));
   private static final String PATTERN = "(?:\\s|\\A)#[\\p{L}[0-9]-_]+";
 
   private final ChangeUpdate.Factory updateFactory;
@@ -69,7 +68,7 @@
 
   public static String cleanupHashtag(String hashtag) {
     hashtag = LEADER.trimLeadingFrom(hashtag);
-    hashtag = WHITESPACE.trimTrailingFrom(hashtag);
+    hashtag = CharMatcher.whitespace().trimTrailingFrom(hashtag);
     return hashtag;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
index 5db46ae..b88931e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Optional;
+import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -84,6 +85,12 @@
     public Response<?> apply(ChangeResource rsrc, Publish.Input in)
         throws AuthException, ResourceConflictException, NoSuchChangeException,
         IOException, OrmException {
+      Capable r =
+          rsrc.getControl().getProjectControl().canPushToAtLeastOneRef();
+      if (r != Capable.OK) {
+        throw new AuthException(r.getMessage());
+      }
+
       Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
       if (!edit.isPresent()) {
         throw new ResourceConflictException(String.format(
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 3d4dc26..6bba656 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
@@ -14,18 +14,12 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.gerrit.common.data.SubmitRecord.Status.OK;
-
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -46,9 +40,9 @@
 import com.google.gerrit.server.git.ChangeSet;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeOp;
+import com.google.gerrit.server.git.MergeSuperSet;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
@@ -64,9 +58,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -77,16 +68,21 @@
 
   private static final String DEFAULT_TOOLTIP =
       "Submit patch set ${patchSet} into ${branch}";
+  private static final String DEFAULT_TOOLTIP_ANCESTORS =
+      "Submit patch set ${patchSet} and ancestors (${submitSize} changes " +
+      "altogether) into ${branch}";
   private static final String DEFAULT_TOPIC_TOOLTIP =
-      "Submit all ${topicSize} changes of the same topic";
-  private static final String BLOCKED_TOPIC_TOOLTIP =
-      "Other changes in this topic are not ready";
-  private static final String BLOCKED_HIDDEN_TOPIC_TOOLTIP =
-      "Other hidden changes in this topic are not ready";
-  private static final String CLICK_FAILURE_OTHER_TOOLTIP =
-      "Clicking the button would fail for other changes in the topic";
+      "Submit all ${topicSize} changes of the same topic " +
+      "(${submitSize} changes including ancestors and other " +
+      "changes related by topic)";
+  private static final String BLOCKED_SUBMIT_TOOLTIP =
+      "This change depends on other changes which are not ready";
+  private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
+      "This change depends on other hidden changes which are not ready";
   private static final String CLICK_FAILURE_TOOLTIP =
-      "Clicking the button would fail.";
+      "Clicking the button would fail";
+  private static final String CLICK_FAILURE_OTHER_TOOLTIP =
+      "Clicking the button would fail for other changes";
 
   public static class Output {
     transient Change change;
@@ -100,11 +96,14 @@
   private final GitRepositoryManager repoManager;
   private final ChangeData.Factory changeDataFactory;
   private final ChangeMessagesUtil cmUtil;
+  private final ChangeControl.GenericFactory changeControlFactory;
   private final Provider<MergeOp> mergeOpProvider;
+  private final MergeSuperSet mergeSuperSet;
   private final AccountsCollection accounts;
   private final ChangesCollection changes;
   private final String label;
   private final ParameterizedString titlePattern;
+  private final ParameterizedString titlePatternWithAncestors;
   private final String submitTopicLabel;
   private final ParameterizedString submitTopicTooltip;
   private final boolean submitWholeTopic;
@@ -115,7 +114,9 @@
       GitRepositoryManager repoManager,
       ChangeData.Factory changeDataFactory,
       ChangeMessagesUtil cmUtil,
+      ChangeControl.GenericFactory changeControlFactory,
       Provider<MergeOp> mergeOpProvider,
+      MergeSuperSet mergeSuperSet,
       AccountsCollection accounts,
       ChangesCollection changes,
       @GerritServerConfig Config cfg,
@@ -124,7 +125,9 @@
     this.repoManager = repoManager;
     this.changeDataFactory = changeDataFactory;
     this.cmUtil = cmUtil;
+    this.changeControlFactory = changeControlFactory;
     this.mergeOpProvider = mergeOpProvider;
+    this.mergeSuperSet = mergeSuperSet;
     this.accounts = accounts;
     this.changes = changes;
     this.label = MoreObjects.firstNonNull(
@@ -133,6 +136,10 @@
     this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull(
         cfg.getString("change", null, "submitTooltip"),
         DEFAULT_TOOLTIP));
+    this.titlePatternWithAncestors = new ParameterizedString(
+        MoreObjects.firstNonNull(
+            cfg.getString("change", null, "submitTooltipAncestors"),
+            DEFAULT_TOOLTIP_ANCESTORS));
     submitWholeTopic = wholeTopicEnabled(cfg);
     this.submitTopicLabel = MoreObjects.firstNonNull(
         Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
@@ -170,16 +177,7 @@
           rsrc.getPatchSet().getRevision().get()));
     }
 
-    List<Change> changes;
-    if (submitWholeTopic && !Strings.isNullOrEmpty(change.getTopic())) {
-      changes = new ArrayList<>();
-      for (ChangeData cd : getChangesByTopic(change.getTopic())) {
-        changes.add(cd.change());
-      }
-    } else {
-      changes = Arrays.asList(change);
-    }
-    ChangeSet submittedChanges = ChangeSet.create(changes);
+    ChangeSet submittedChanges = ChangeSet.create(change);
 
     try {
       ReviewDb db = dbProvider.get();
@@ -207,22 +205,24 @@
   }
 
   /**
-   * @param changeList list of changes to be submitted at once
+   * @param cs set of changes to be submitted at once
    * @param identifiedUser the user who is checking to submit
    * @return a reason why any of the changes is not submittable or null
    */
-  private String problemsForSubmittingChanges(
-      List<ChangeData> changeList,
-      IdentifiedUser identifiedUser) {
+  private String problemsForSubmittingChangeset(
+      ChangeSet cs, IdentifiedUser identifiedUser) {
     try {
-      for (ChangeData c : changeList) {
-        ChangeControl changeControl = c.changeControl().forUser(
-            identifiedUser);
-        if (!changeControl.isVisible(dbProvider.get())) {
-          return BLOCKED_HIDDEN_TOPIC_TOOLTIP;
+      ReviewDb db = dbProvider.get();
+      for (PatchSet.Id psId : cs.patchIds()) {
+        ChangeControl changeControl = changeControlFactory
+            .controlFor(psId.getParentKey(), identifiedUser);
+        ChangeData c = changeDataFactory.create(db, changeControl);
+
+        if (!changeControl.isVisible(db)) {
+          return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
         }
         if (!changeControl.canSubmit()) {
-          return BLOCKED_TOPIC_TOOLTIP;
+          return BLOCKED_SUBMIT_TOOLTIP;
         }
         // Recheck mergeability rather than using value stored in the index,
         // which may be stale.
@@ -237,17 +237,33 @@
         if (!mergeable) {
           return CLICK_FAILURE_OTHER_TOOLTIP;
         }
-        checkSubmitRule(c, c.currentPatchSet(), false);
+        MergeOp.checkSubmitRule(c);
       }
     } catch (ResourceConflictException e) {
-      return BLOCKED_TOPIC_TOOLTIP;
-    } catch (OrmException e) {
+      return BLOCKED_SUBMIT_TOOLTIP;
+    } catch (NoSuchChangeException | OrmException e) {
       log.error("Error checking if change is submittable", e);
       throw new OrmRuntimeException("Could not determine problems for the change", e);
     }
     return null;
   }
 
+  /**
+   * Check if there are any problems with the given change. It doesn't take
+   * any problems of related changes into account.
+   * <p>
+   * @param cd the change to check for submittability
+   * @return if the change has any problems for submission
+   */
+  public boolean submittable(ChangeData cd) {
+    try {
+      MergeOp.checkSubmitRule(cd);
+      return true;
+    } catch (ResourceConflictException | OrmException e) {
+      return false;
+    }
+  }
+
   @Override
   public UiAction.Description getDescription(RevisionResource resource) {
     PatchSet.Id current = resource.getChange().currentPatchSetId();
@@ -260,7 +276,7 @@
     ChangeData cd = changeDataFactory.create(db, resource.getControl());
 
     try {
-      checkSubmitRule(cd, cd.currentPatchSet(), false);
+      MergeOp.checkSubmitRule(cd);
     } catch (ResourceConflictException e) {
       visible = false;
     } catch (OrmException e) {
@@ -282,40 +298,55 @@
       throw new OrmRuntimeException("Could not determine mergeability", e);
     }
 
-    List<ChangeData> changesByTopic = null;
-    if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
-      changesByTopic = getChangesByTopic(topic);
+    ChangeSet cs;
+    try {
+      cs = mergeSuperSet.completeChangeSet(db,
+          ChangeSet.create(cd.change()));
+    } catch (OrmException | IOException e) {
+      throw new OrmRuntimeException("Could not determine complete set of " +
+          "changes to be submitted", e);
     }
-    if (submitWholeTopic
+
+    int topicSize = 0;
+    if (!Strings.isNullOrEmpty(topic)) {
+      topicSize = getChangesByTopic(topic).size();
+    }
+    boolean treatWithTopic = submitWholeTopic
         && !Strings.isNullOrEmpty(topic)
-        && changesByTopic.size() > 1) {
+        && topicSize > 1;
+
+    String submitProblems = problemsForSubmittingChangeset(cs,
+        resource.getUser());
+    if (submitProblems != null) {
+      return new UiAction.Description()
+        .setLabel(treatWithTopic ? submitTopicLabel : label)
+        .setTitle(submitProblems)
+        .setVisible(true)
+        .setEnabled(false);
+    }
+
+    if (treatWithTopic) {
       Map<String, String> params = ImmutableMap.of(
-          "topicSize", String.valueOf(changesByTopic.size()));
-      String topicProblems = problemsForSubmittingChanges(changesByTopic,
-          resource.getUser());
-      if (topicProblems != null) {
-        return new UiAction.Description()
-          .setLabel(submitTopicLabel)
-          .setTitle(topicProblems)
-          .setVisible(true)
-          .setEnabled(false);
-      } else {
-        return new UiAction.Description()
+          "topicSize", String.valueOf(topicSize),
+          "submitSize", String.valueOf(cs.size()));
+      return new UiAction.Description()
           .setLabel(submitTopicLabel)
           .setTitle(Strings.emptyToNull(
               submitTopicTooltip.replace(params)))
           .setVisible(true)
           .setEnabled(Boolean.TRUE.equals(enabled));
-      }
     } else {
       RevId revId = resource.getPatchSet().getRevision();
       Map<String, String> params = ImmutableMap.of(
           "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
           "branch", resource.getChange().getDest().getShortName(),
-          "commit", ObjectId.fromString(revId.get()).abbreviate(7).name());
+          "commit", ObjectId.fromString(revId.get()).abbreviate(7).name(),
+          "submitSize", String.valueOf(cs.size()));
+      ParameterizedString tp = cs.size() > 1 ? titlePatternWithAncestors :
+          titlePattern;
       return new UiAction.Description()
         .setLabel(label)
-        .setTitle(Strings.emptyToNull(titlePattern.replace(params)))
+        .setTitle(Strings.emptyToNull(tp.replace(params)))
         .setVisible(true)
         .setEnabled(Boolean.TRUE.equals(enabled));
     }
@@ -340,95 +371,6 @@
         .orNull();
   }
 
-  private List<SubmitRecord> checkSubmitRule(ChangeData cd,
-      PatchSet patchSet, boolean force)
-          throws ResourceConflictException, OrmException {
-    List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
-        .setPatchSet(patchSet)
-        .evaluate();
-    Optional<SubmitRecord> ok = findOkRecord(results);
-    if (ok.isPresent()) {
-      // Rules supplied a valid solution.
-      return ImmutableList.of(ok.get());
-    } else if (force) {
-      return results;
-    } else if (results.isEmpty()) {
-      throw new IllegalStateException(String.format(
-          "SubmitRuleEvaluator.evaluate returned empty list for %s in %s",
-          patchSet.getId(),
-          cd.change().getProject().get()));
-    }
-
-    for (SubmitRecord record : results) {
-      switch (record.status) {
-        case CLOSED:
-          throw new ResourceConflictException("change is closed");
-
-        case RULE_ERROR:
-          throw new ResourceConflictException(String.format(
-              "rule error: %s",
-              record.errorMessage));
-
-        case NOT_READY:
-          StringBuilder msg = new StringBuilder();
-          for (SubmitRecord.Label lbl : record.labels) {
-            switch (lbl.status) {
-              case OK:
-              case MAY:
-                continue;
-
-              case REJECT:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("blocked by ").append(lbl.label);
-                continue;
-
-              case NEED:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label);
-                continue;
-
-              case IMPOSSIBLE:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label)
-                   .append(" (check project access)");
-                continue;
-
-              default:
-                throw new IllegalStateException(String.format(
-                    "Unsupported SubmitRecord.Label %s for %s in %s",
-                    lbl.toString(),
-                    patchSet.getId(),
-                    cd.change().getProject().get()));
-            }
-          }
-          throw new ResourceConflictException(msg.toString());
-
-        default:
-          throw new IllegalStateException(String.format(
-              "Unsupported SubmitRecord %s for %s in %s",
-              record,
-              patchSet.getId().getId(),
-              cd.change().getProject().get()));
-      }
-    }
-    throw new IllegalStateException();
-  }
-
-  private static Optional<SubmitRecord> findOkRecord(Collection<SubmitRecord> in) {
-    return Iterables.tryFind(in, new Predicate<SubmitRecord>() {
-      @Override
-      public boolean apply(SubmitRecord input) {
-        return input.status == OK;
-      }
-    });
-  }
-
   static String status(Change change) {
     return change != null ? change.getStatus().name().toLowerCase() : "deleted";
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
index 71e25a5..1f6dfe4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
@@ -64,9 +64,7 @@
       if (cs.ids().size() > 1) {
         return json.create(EnumSet.of(
             ListChangesOption.CURRENT_REVISION,
-            ListChangesOption.CURRENT_COMMIT,
-            ListChangesOption.DETAILED_LABELS,
-            ListChangesOption.LABELS))
+            ListChangesOption.CURRENT_COMMIT))
           .format(cs.ids());
       } else {
         return Collections.emptyList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
index b01ae2f1..c6ba605 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -80,7 +80,7 @@
   }
 
   private static boolean isPluginNameSane(String pluginName) {
-    return CharMatcher.JAVA_LETTER_OR_DIGIT
+    return CharMatcher.javaLetterOrDigit()
         .or(CharMatcher.is('-'))
         .matchesAllOf(pluginName);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
index 6c3b499..a18a3a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 
 /** A set of changes grouped together to be submitted atomically.*/
@@ -29,6 +30,7 @@
     ImmutableSet.Builder<Project.NameKey> pb = ImmutableSet.builder();
     ImmutableSet.Builder<Branch.NameKey> bb = ImmutableSet.builder();
     ImmutableSet.Builder<Change.Id> ib = ImmutableSet.builder();
+    ImmutableSet.Builder<PatchSet.Id> psb = ImmutableSet.builder();
     ImmutableSetMultimap.Builder<Project.NameKey, Branch.NameKey> pbb =
         ImmutableSetMultimap.builder();
     ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb =
@@ -42,13 +44,14 @@
       pb.add(project);
       bb.add(branch);
       ib.add(c.getId());
+      psb.add(c.currentPatchSetId());
       pbb.put(project, branch);
       pcb.put(project, c.getId());
       cbb.put(branch, c.getId());
     }
 
-    return new AutoValue_ChangeSet(pb.build(), bb.build(),
-        ib.build(), pbb.build(), pcb.build(), cbb.build());
+    return new AutoValue_ChangeSet(pb.build(), bb.build(), ib.build(),
+        psb.build(), pbb.build(), pcb.build(), cbb.build());
   }
 
   public static ChangeSet create(Change change) {
@@ -58,6 +61,7 @@
   public abstract ImmutableSet<Project.NameKey> projects();
   public abstract ImmutableSet<Branch.NameKey> branches();
   public abstract ImmutableSet<Change.Id> ids();
+  public abstract ImmutableSet<PatchSet.Id> patchIds();
   public abstract ImmutableSetMultimap<Project.NameKey, Branch.NameKey>
       branchesByProject();
   public abstract ImmutableSetMultimap<Project.NameKey, Change.Id>
@@ -69,4 +73,8 @@
   public int hashCode() {
     return ids().hashCode();
   }
+
+  public int size() {
+    return ids().size();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
index 8fa5d4e..7b1161c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.change.EmailReviewComments;
 import com.google.gerrit.server.mail.MergedSender;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -37,7 +36,7 @@
 import java.util.concurrent.ExecutorService;
 
 public class EmailMerge implements Runnable, RequestContext {
-  private static final Logger log = LoggerFactory.getLogger(EmailReviewComments.class);
+  private static final Logger log = LoggerFactory.getLogger(EmailMerge.class);
 
   public interface Factory {
     EmailMerge create(Change.Id changeId, Account.Id submitter);
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 648d125..d945f77 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
@@ -225,7 +225,7 @@
     });
   }
 
-  private List<SubmitRecord> checkSubmitRule(ChangeData cd)
+  public static List<SubmitRecord> checkSubmitRule(ChangeData cd)
       throws ResourceConflictException, OrmException {
     PatchSet patchSet = cd.currentPatchSet();
     List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
@@ -237,7 +237,9 @@
       return ImmutableList.of(ok.get());
     } else if (results.isEmpty()) {
       throw new IllegalStateException(String.format(
-          "SubmitRuleEvaluator.evaluate returned empty list for %s in %s",
+          "SubmitRuleEvaluator.evaluate for change %s " +
+          "returned empty list for %s in %s",
+          cd.getId(),
           patchSet.getId(),
           cd.change().getProject().get()));
     }
@@ -245,15 +247,17 @@
     for (SubmitRecord record : results) {
       switch (record.status) {
         case CLOSED:
-          throw new ResourceConflictException("change is closed");
+          throw new ResourceConflictException(String.format(
+              "change %s is closed", cd.getId()));
 
         case RULE_ERROR:
           throw new ResourceConflictException(String.format(
-              "rule error: %s",
-              record.errorMessage));
+              "rule error for change %s: %s",
+              cd.getId(), record.errorMessage));
 
         case NOT_READY:
           StringBuilder msg = new StringBuilder();
+          msg.append(cd.getId() + ":");
           for (SubmitRecord.Label lbl : record.labels) {
             switch (lbl.status) {
               case OK:
@@ -261,32 +265,27 @@
                 continue;
 
               case REJECT:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("blocked by ").append(lbl.label);
+                msg.append(" blocked by ").append(lbl.label);
+                msg.append(";");
                 continue;
 
               case NEED:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label);
+                msg.append(" needs ").append(lbl.label);
+                msg.append(";");
                 continue;
 
               case IMPOSSIBLE:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label)
+                msg.append(" needs ").append(lbl.label)
                 .append(" (check project access)");
+                msg.append(";");
                 continue;
 
               default:
                 throw new IllegalStateException(String.format(
-                    "Unsupported SubmitRecord.Label %s for %s in %s",
+                    "Unsupported SubmitRecord.Label %s for %s in %s in %s",
                     lbl.toString(),
                     patchSet.getId(),
+                    cd.getId(),
                     cd.change().getProject().get()));
             }
           }
@@ -303,21 +302,37 @@
     throw new IllegalStateException();
   }
 
-  private void checkPermissions(ChangeSet cs)
+  private void checkSubmitRulesAndState(ChangeSet cs)
       throws ResourceConflictException, OrmException {
+
+    StringBuilder msgbuf = new StringBuilder();
+    List<Change.Id> problemChanges = new ArrayList<>();
     for (Change.Id id : cs.ids()) {
-      ChangeData cd = changeDataFactory.create(db, id);
-      if (cd.change().getStatus() != Change.Status.NEW){
-        throw new OrmException("Change " + cd.change().getChangeId()
-            + " is in state " + cd.change().getStatus());
-      } else {
-        records.put(cd.change().getId(), checkSubmitRule(cd));
+      try {
+        ChangeData cd = changeDataFactory.create(db, id);
+        if (cd.change().getStatus() != Change.Status.NEW){
+          throw new ResourceConflictException("Change " +
+              cd.change().getChangeId() + " is in state " +
+              cd.change().getStatus());
+        } else {
+          records.put(cd.change().getId(), checkSubmitRule(cd));
+        }
+      } catch (ResourceConflictException e) {
+        msgbuf.append(e.getMessage() + "\n");
+        problemChanges.add(id);
       }
     }
+    String reason = msgbuf.toString();
+    if (!reason.isEmpty()) {
+        throw new ResourceConflictException("The change could not be " +
+            "submitted because it depends on change(s) " +
+            problemChanges.toString() + ", which could not be submitted " +
+            "because:\n" + reason);
+    }
   }
 
   public void merge(ReviewDb db, ChangeSet changes, IdentifiedUser caller,
-      boolean checkPermissions) throws NoSuchChangeException,
+      boolean checkSubmitRules) throws NoSuchChangeException,
       OrmException, ResourceConflictException {
     logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
     this.db = db;
@@ -325,9 +340,9 @@
     try {
       ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes);
       logDebug("Calculated to merge {}", cs);
-      if (checkPermissions) {
-        logDebug("Checking permissions");
-        checkPermissions(cs);
+      if (checkSubmitRules) {
+        logDebug("Checking submit rules and state");
+        checkSubmitRulesAndState(cs);
       }
       try {
         integrateIntoHistory(cs, caller);
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 b0107cc..52932c4 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
@@ -635,7 +635,7 @@
 
   private static LabelValue parseLabelValue(String src) {
     List<String> parts = ImmutableList.copyOf(
-        Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings().limit(2)
+        Splitter.on(CharMatcher.whitespace()).omitEmptyStrings().limit(2)
         .split(src));
     if (parts.isEmpty()) {
       throw new IllegalArgumentException("empty value");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 28f7f7e..afb7c22 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -38,7 +38,7 @@
  */
 public class IndexModule extends LifecycleModule {
   public enum IndexType {
-    LUCENE, SOLR
+    LUCENE
   }
 
   /** Type of secondary index. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
index 45b7c4d..31fbb40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
@@ -69,7 +69,7 @@
 
   @Override
   public void onGitReferenceUpdated(final Event event) {
-    Futures.transform(
+    Futures.transformAsync(
         executor.submit(new GetChanges(event)),
         new AsyncFunction<List<Change>, List<Void>>() {
           @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
index 775e967..1bc6b05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
@@ -213,7 +213,7 @@
     }
 
     try {
-      mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+      mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
           new AsyncFunction<List<?>, Void>() {
             @Override
             public ListenableFuture<Void> apply(List<?> input) {
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 d73a95d..7a4fb6e 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
@@ -318,7 +318,7 @@
   }
 
   private static boolean isValidParameterName(String name) {
-    return CharMatcher.JAVA_LETTER_OR_DIGIT
+    return CharMatcher.javaLetterOrDigit()
         .or(CharMatcher.is('-'))
         .matchesAllOf(name) && !name.startsWith("-");
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DB2.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DB2.java
new file mode 100644
index 0000000..4ad8e2f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DB2.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import static com.google.gerrit.server.schema.JdbcUtil.hostname;
+import static com.google.gerrit.server.schema.JdbcUtil.port;
+
+import com.google.gerrit.server.config.ConfigSection;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+public class DB2 extends BaseDataSourceType {
+  private Config cfg;
+
+  @Inject
+  public DB2(@GerritServerConfig final Config cfg) {
+    super("com.ibm.db2.jcc.DB2Driver");
+    this.cfg = cfg;
+  }
+
+  @Override
+  public String getUrl() {
+    final StringBuilder b = new StringBuilder();
+    final ConfigSection dbc = new ConfigSection(cfg, "database");
+    b.append("jdbc:db2://");
+    b.append(hostname(dbc.optional("hostname")));
+    b.append(port(dbc.optional("port")));
+    b.append("/");
+    b.append(dbc.required("database"));
+    return b.toString();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
index f500444..f50f123 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
@@ -21,6 +21,7 @@
 
   @Override
   protected void configure() {
+    bind(DataSourceType.class).annotatedWith(Names.named("db2")).to(DB2.class);
     bind(DataSourceType.class).annotatedWith(Names.named("h2")).to(H2.class);
     bind(DataSourceType.class).annotatedWith(Names.named("jdbc")).to(JDBC.class);
     bind(DataSourceType.class).annotatedWith(Names.named("mysql")).to(MySql.class);
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 038da50..7918df4 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_109> C = Schema_109.class;
+  public static final Class<Schema_110> C = Schema_110.class;
 
   public static int getBinaryVersion() {
     return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_110.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_110.java
new file mode 100644
index 0000000..9e0f112
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_110.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_110 extends SchemaVersion {
+  @Inject
+  Schema_110(Provider<Schema_109> prior) {
+    super(prior);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index 195a3e0..ad2ab90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -84,6 +84,7 @@
     final String url = bbc.getString("submodule", id, "url");
     final String path = bbc.getString("submodule", id, "path");
     String branch = bbc.getString("submodule", id, "branch");
+    SubmoduleSubscription ss = null;
 
     try {
       if (url != null && url.length() > 0 && path != null && path.length() > 0
@@ -116,8 +117,10 @@
             }
             Project.NameKey projectKey = new Project.NameKey(projectName);
             if (projectCache.get(projectKey) != null) {
-              return new SubmoduleSubscription(superProjectBranch,
-                  new Branch.NameKey(projectKey, branch), path);
+              ss = new SubmoduleSubscription(
+                  superProjectBranch,
+                  new Branch.NameKey(new Project.NameKey(projectName), branch),
+                  path);
             }
           }
         }
@@ -126,6 +129,6 @@
       // Error in url syntax (in fact it is uri syntax)
     }
 
-    return null;
+    return ss;
   }
 }
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 32bc9c6..6c3a39a 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
@@ -25,6 +25,7 @@
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -177,6 +178,16 @@
     return r;
   }
 
+  public static PermissionRule blockLabel(ProjectConfig project,
+      String labelName, AccountGroup.UUID group, String ref) {
+    PermissionRule r =
+        grant(project, Permission.LABEL + labelName, newRule(project, group),
+            ref);
+    r.setBlock();
+    r.setRange(-1, 1);
+    return r;
+  }
+
   public static PermissionRule deny(ProjectConfig project,
       String permissionName, AccountGroup.UUID group, String ref) {
     PermissionRule r = grant(project, permissionName, newRule(project, group), ref);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index bd672f3..3945da7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -190,6 +190,28 @@
         expectedSubscriptions);
   }
 
+  @Test
+  public void testSubmodulesParseWithSubProjectFound() throws Exception {
+    Map<String, SubmoduleSection> sectionsToReturn = new TreeMap<>();
+    sectionsToReturn.put("a/b", new SubmoduleSection(
+        "ssh://localhost/a/b", "a/b", "."));
+
+    Map<String, String> reposToBeFound = new HashMap<>();
+    reposToBeFound.put("a/b", "a/b");
+    reposToBeFound.put("b", "b");
+
+    Branch.NameKey superBranchNameKey =
+        new Branch.NameKey(new Project.NameKey("super-project"),
+            "refs/heads/master");
+
+    Set<SubmoduleSubscription> expectedSubscriptions = Sets.newHashSet();
+    expectedSubscriptions
+        .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
+            new Project.NameKey("a/b"), "refs/heads/master"), "a/b"));
+    execute(superBranchNameKey, sectionsToReturn, reposToBeFound,
+        expectedSubscriptions);
+  }
+
   private void execute(final Branch.NameKey superProjectBranch,
       final Map<String, SubmoduleSection> sectionsToReturn,
       final Map<String, String> reposToBeFound,
@@ -217,10 +239,9 @@
             projectNameCandidate = projectNameCandidate.substring(0, //
                 projectNameCandidate.length() - Constants.DOT_GIT_EXT.length());
           }
-          if (projectNameCandidate.equals(reposToBeFound.get(id))) {
+          if (reposToBeFound.containsValue(projectNameCandidate)) {
             expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
                 .andReturn(createNiceMock(ProjectState.class));
-            break;
           } else {
             expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
                 .andReturn(null);
diff --git a/gerrit-solr/BUCK b/gerrit-solr/BUCK
deleted file mode 100644
index ec3c728..0000000
--- a/gerrit-solr/BUCK
+++ /dev/null
@@ -1,20 +0,0 @@
-java_library(
-  name = 'solr',
-  srcs = glob(['src/main/java/**/*.java']),
-  deps = [
-    '//gerrit-antlr:query_exception',
-    '//gerrit-extension-api:api',
-    '//gerrit-lucene:query_builder',
-    '//gerrit-reviewdb:server',
-    '//gerrit-server:server',
-    '//lib:guava',
-    '//lib:gwtorm',
-    '//lib/guice:guice',
-    '//lib/jgit:jgit',
-    '//lib/log:api',
-    '//lib/lucene:analyzers-common',
-    '//lib/lucene:core',
-    '//lib/solr:solrj',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java
deleted file mode 100644
index 0faa691..0000000
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.solr;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.ChangeSchemas;
-import com.google.inject.Inject;
-import com.google.inject.ProvisionException;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Map;
-
-class IndexVersionCheck implements LifecycleListener {
-  public static final Map<String, Integer> SCHEMA_VERSIONS = ImmutableMap.of(
-      SolrChangeIndex.CHANGES_OPEN, ChangeSchemas.getLatest().getVersion(),
-      SolrChangeIndex.CHANGES_CLOSED, ChangeSchemas.getLatest().getVersion());
-
-  public static Path solrIndexConfig(SitePaths sitePaths) {
-    return sitePaths.index_dir.resolve("gerrit_index.config");
-  }
-
-  private final SitePaths sitePaths;
-
-  @Inject
-  IndexVersionCheck(SitePaths sitePaths) {
-    this.sitePaths = sitePaths;
-  }
-
-  @Override
-  public void start() {
-    // TODO Query schema version from a special meta-document
-    Path path = solrIndexConfig(sitePaths);
-    try {
-      FileBasedConfig cfg = new FileBasedConfig(path.toFile(), FS.detect());
-      cfg.load();
-      for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
-        int schemaVersion = cfg.getInt("index", e.getKey(), "schemaVersion", 0);
-        if (schemaVersion != e.getValue()) {
-          throw new ProvisionException(String.format(
-              "wrong index schema version for \"%s\": expected %d, found %d%s",
-              e.getKey(), e.getValue(), schemaVersion, upgrade()));
-        }
-      }
-    } catch (IOException e) {
-      throw new ProvisionException("unable to read " + path);
-    } catch (ConfigInvalidException e) {
-      throw new ProvisionException("invalid config file " + path);
-    }
-  }
-
-  @Override
-  public void stop() {
-    // Do nothing.
-  }
-
-  private final String upgrade() {
-    return "\nRun reindex to rebuild the index:\n"
-        + "$ java -jar gerrit.war reindex -d "
-        + sitePaths.site_path.toAbsolutePath();
-  }
-}
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
deleted file mode 100644
index b9e47954..0000000
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
+++ /dev/null
@@ -1,338 +0,0 @@
-// 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.solr;
-
-import static com.google.gerrit.server.index.IndexRewriteImpl.CLOSED_STATUSES;
-import static com.google.gerrit.server.index.IndexRewriteImpl.OPEN_STATUSES;
-import static com.google.gerrit.solr.IndexVersionCheck.SCHEMA_VERSIONS;
-import static com.google.gerrit.solr.IndexVersionCheck.solrIndexConfig;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lucene.QueryBuilder;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.ChangeField;
-import com.google.gerrit.server.index.ChangeIndex;
-import com.google.gerrit.server.index.FieldDef.FillArgs;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexRewriteImpl;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeDataSource;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
-
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.analysis.util.CharArraySet;
-import org.apache.lucene.search.Query;
-import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.client.solrj.SolrQuery.SortClause;
-import org.apache.solr.client.solrj.SolrServer;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.CloudSolrServer;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.common.SolrInputDocument;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/** Secondary index implementation using a remote Solr instance. */
-class SolrChangeIndex implements ChangeIndex, LifecycleListener {
-  public static final String CHANGES_OPEN = "changes_open";
-  public static final String CHANGES_CLOSED = "changes_closed";
-  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
-
-  private final Provider<ReviewDb> db;
-  private final ChangeData.Factory changeDataFactory;
-  private final FillArgs fillArgs;
-  private final SitePaths sitePaths;
-  private final IndexCollection indexes;
-  private final CloudSolrServer openIndex;
-  private final CloudSolrServer closedIndex;
-  private final Schema<ChangeData> schema;
-  private final QueryBuilder queryBuilder;
-
-  SolrChangeIndex(
-      @GerritServerConfig Config cfg,
-      Provider<ReviewDb> db,
-      ChangeData.Factory changeDataFactory,
-      FillArgs fillArgs,
-      SitePaths sitePaths,
-      IndexCollection indexes,
-      Schema<ChangeData> schema,
-      String base) throws IOException {
-    this.db = db;
-    this.changeDataFactory = changeDataFactory;
-    this.fillArgs = fillArgs;
-    this.sitePaths = sitePaths;
-    this.indexes = indexes;
-    this.schema = schema;
-
-    String url = cfg.getString("index", null, "url");
-    if (Strings.isNullOrEmpty(url)) {
-      throw new IllegalStateException("index.url must be supplied");
-    }
-
-    queryBuilder = new QueryBuilder(
-        new StandardAnalyzer(CharArraySet.EMPTY_SET));
-
-    base = Strings.nullToEmpty(base);
-    openIndex = new CloudSolrServer(url);
-    openIndex.setDefaultCollection(base + CHANGES_OPEN);
-
-    closedIndex = new CloudSolrServer(url);
-    closedIndex.setDefaultCollection(base + CHANGES_CLOSED);
-  }
-
-  @Override
-  public void start() {
-    indexes.setSearchIndex(this);
-    indexes.addWriteIndex(this);
-  }
-
-  @Override
-  public void stop() {
-    openIndex.shutdown();
-    closedIndex.shutdown();
-  }
-
-  @Override
-  public Schema<ChangeData> getSchema() {
-    return schema;
-  }
-
-  @Override
-  public void close() {
-    stop();
-  }
-
-  @Override
-  public void replace(ChangeData cd) throws IOException {
-    String id = cd.getId().toString();
-    SolrInputDocument doc = toDocument(cd);
-    try {
-      if (cd.change().getStatus().isOpen()) {
-        closedIndex.deleteById(id);
-        openIndex.add(doc);
-      } else {
-        openIndex.deleteById(id);
-        closedIndex.add(doc);
-      }
-    } catch (OrmException | SolrServerException e) {
-      throw new IOException(e);
-    }
-    commit(openIndex);
-    commit(closedIndex);
-  }
-
-  @Override
-  public void delete(Change.Id id) throws IOException {
-    String idString = Integer.toString(id.get());
-    delete(idString, openIndex);
-    delete(idString, closedIndex);
-  }
-
-  private void delete(String id, CloudSolrServer index) throws IOException {
-    try {
-      index.deleteById(id);
-      commit(index);
-    } catch (SolrServerException e) {
-      throw new IOException(e);
-    }
-  }
-
-  @Override
-  public void deleteAll() throws IOException {
-    try {
-      openIndex.deleteByQuery("*:*");
-      closedIndex.deleteByQuery("*:*");
-    } catch (SolrServerException e) {
-      throw new IOException(e);
-    }
-    commit(openIndex);
-    commit(closedIndex);
-  }
-
-  @Override
-  public ChangeDataSource getSource(Predicate<ChangeData> p, int start, int limit)
-      throws QueryParseException {
-    Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
-    List<SolrServer> indexes = Lists.newArrayListWithCapacity(2);
-    if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
-      indexes.add(openIndex);
-    }
-    if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
-      indexes.add(closedIndex);
-    }
-    return new QuerySource(indexes, queryBuilder.toQuery(p), start, limit,
-        getSorts());
-  }
-
-  private static List<SortClause> getSorts() {
-    return ImmutableList.of(
-        new SortClause(
-          ChangeField.UPDATED.getName(), SolrQuery.ORDER.desc),
-        new SortClause(
-          ChangeField.LEGACY_ID.getName(), SolrQuery.ORDER.desc));
-  }
-
-  private void commit(SolrServer server) throws IOException {
-    try {
-      server.commit();
-    } catch (SolrServerException e) {
-      throw new IOException(e);
-    }
-  }
-
-  private class QuerySource implements ChangeDataSource {
-    private final List<SolrServer> servers;
-    private final SolrQuery query;
-
-    public QuerySource(List<SolrServer> indexes, Query q, int start, int limit,
-        List<SortClause> sorts) {
-      this.servers = indexes;
-
-      query = new SolrQuery(q.toString());
-      query.setParam("shards.tolerant", true);
-      query.setParam("rows", Integer.toString(limit));
-      if (start != 0) {
-        query.setParam("start", Integer.toString(start));
-      }
-      query.setFields(ID_FIELD);
-      query.setSorts(sorts);
-    }
-
-    @Override
-    public int getCardinality() {
-      return 10; // TODO: estimate from solr?
-    }
-
-    @Override
-    public boolean hasChange() {
-      return false;
-    }
-
-    @Override
-    public String toString() {
-      return query.getQuery();
-    }
-
-    @Override
-    public ResultSet<ChangeData> read() throws OrmException {
-      try {
-        // TODO Sort documents during merge to select only top N.
-        SolrDocumentList docs = new SolrDocumentList();
-        for (SolrServer index : servers) {
-          docs.addAll(index.query(query).getResults());
-        }
-
-        List<ChangeData> result = Lists.newArrayListWithCapacity(docs.size());
-        for (SolrDocument doc : docs) {
-          Integer v = (Integer) doc.getFieldValue(ID_FIELD);
-          result.add(
-              changeDataFactory.create(db.get(), new Change.Id(v.intValue())));
-        }
-
-        final List<ChangeData> r = Collections.unmodifiableList(result);
-        return new ResultSet<ChangeData>() {
-          @Override
-          public Iterator<ChangeData> iterator() {
-            return r.iterator();
-          }
-
-          @Override
-          public List<ChangeData> toList() {
-            return r;
-          }
-
-          @Override
-          public void close() {
-            // Do nothing.
-          }
-        };
-      } catch (SolrServerException e) {
-        throw new OrmException(e);
-      }
-    }
-  }
-
-  private SolrInputDocument toDocument(ChangeData cd) {
-    SolrInputDocument result = new SolrInputDocument();
-    for (Values<ChangeData> values : schema.buildFields(cd, fillArgs)) {
-      add(result, values);
-    }
-    return result;
-  }
-
-  private void add(SolrInputDocument doc, Values<ChangeData> values) {
-    String name = values.getField().getName();
-    FieldType<?> type = values.getField().getType();
-
-    if (type == FieldType.INTEGER) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, value);
-      }
-    } else if (type == FieldType.LONG) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, value);
-      }
-    } else if (type == FieldType.TIMESTAMP) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, ((Timestamp) value).getTime());
-      }
-    } else if (type == FieldType.EXACT
-        || type == FieldType.PREFIX
-        || type == FieldType.FULL_TEXT) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, value);
-      }
-    } else {
-      throw FieldType.badFieldType(type);
-    }
-  }
-
-  @Override
-  public void markReady(boolean ready) throws IOException {
-    // TODO Move the schema version information to a special meta-document
-    FileBasedConfig cfg = new FileBasedConfig(
-        solrIndexConfig(sitePaths).toFile(),
-        FS.detect());
-    for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
-      cfg.setInt("index", e.getKey(), "schemaVersion",
-          ready ? e.getValue() : -1);
-    }
-    cfg.save();
-  }
-}
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
deleted file mode 100644
index 0133e33..0000000
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// 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.solr;
-
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.ChangeIndex;
-import com.google.gerrit.server.index.ChangeSchemas;
-import com.google.gerrit.server.index.FieldDef.FillArgs;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.lib.Config;
-
-import java.io.IOException;
-
-public class SolrIndexModule extends LifecycleModule {
-  private final boolean checkVersion;
-  private final int threads;
-  private final String base;
-
-  public SolrIndexModule() {
-    this(true, 0, null);
-  }
-
-  public SolrIndexModule(boolean checkVersion, int threads, String base) {
-    this.checkVersion = checkVersion;
-    this.threads = threads;
-    this.base = base;
-  }
-
-  @Override
-  protected void configure() {
-    install(new IndexModule(threads));
-    bind(ChangeIndex.class).to(SolrChangeIndex.class);
-    listener().to(SolrChangeIndex.class);
-    if (checkVersion) {
-      listener().to(IndexVersionCheck.class);
-    }
-  }
-
-  @Provides
-  @Singleton
-  IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
-    return IndexConfig.fromConfig(cfg);
-  }
-
-  @Provides
-  @Singleton
-  public SolrChangeIndex getChangeIndex(@GerritServerConfig Config cfg,
-      Provider<ReviewDb> db,
-      ChangeData.Factory changeDataFactory,
-      SitePaths sitePaths,
-      IndexCollection indexes,
-      FillArgs fillArgs) throws IOException {
-    return new SolrChangeIndex(cfg, db, changeDataFactory, fillArgs, sitePaths,
-        indexes, ChangeSchemas.getLatest(), base);
-  }
-}
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 35f6084..1f82849 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -17,7 +17,6 @@
     '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//gerrit-server/src/main/prolog:common',
-    '//gerrit-solr:solr',
     '//gerrit-sshd:sshd',
     '//lib:guava',
     '//lib:gwtorm',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 22f3f0b..50c822e 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -61,7 +61,6 @@
 import com.google.gerrit.server.securestore.SecureStoreClassName;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
-import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
@@ -302,9 +301,6 @@
       case LUCENE:
         changeIndexModule = new LuceneIndexModule();
         break;
-      case SOLR:
-        changeIndexModule = new SolrIndexModule();
-        break;
       default:
         throw new IllegalStateException("unsupported index.type");
     }
diff --git a/lib/BUCK b/lib/BUCK
index 6a6871e..1dbac0a 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -26,9 +26,9 @@
 
 maven_jar(
   name = 'gwtorm_client',
-  id = 'com.google.gerrit:gwtorm:1.14-14-gf54f1f1',
-  bin_sha1 = 'c02267e0245dd06930ea64a2d7c5ddc5ba6d9cfb',
-  src_sha1 = '3d17ae8a173eb34d89098c748f28cddd5080adbc',
+  id = 'com.google.gerrit:gwtorm:1.14-16-gc4e356a',
+  bin_sha1 = '01225468065812bbe5f27972df6dafa9d796d833',
+  src_sha1 = '3622460ed58684cb33f786e3748637c8eea324f9',
   license = 'Apache2.0',
   repository = GERRIT,
 )
@@ -58,8 +58,8 @@
 
 maven_jar(
   name = 'guava',
-  id = 'com.google.guava:guava:18.0',
-  sha1 = 'cce0823396aa693798f8882e64213b1772032b09',
+  id = 'com.google.guava:guava:19.0-rc1',
+  sha1 = '0364538ac107b8943a1f4d68ac50f1b0421bb983',
   license = 'Apache2.0',
 )
 
@@ -78,15 +78,15 @@
 
 maven_jar(
   name = 'jsch',
-  id = 'com.jcraft:jsch:0.1.51',
-  sha1 = '6ceee2696b07cc320d0e1aaea82c7b40768aca0f',
+  id = 'com.jcraft:jsch:0.1.53',
+  sha1 = '658b682d5c817b27ae795637dfec047c63d29935',
   license = 'jsch',
 )
 
 maven_jar(
   name = 'servlet-api-3_1',
-  id = 'org.apache.tomcat:tomcat-servlet-api:8.0.5',
-  sha1 = '9ef01afc25481b82aa8f3615db536869f2dc961e',
+  id = 'org.apache.tomcat:tomcat-servlet-api:8.0.24',
+  sha1 = '5d9e2e895e3111622720157d0aa540066d5fce3a',
   license = 'Apache2.0',
   exclude = ['META-INF/NOTICE', 'META-INF/LICENSE'],
 )
@@ -193,8 +193,8 @@
 
 maven_jar(
   name = 'truth',
-  id = 'com.google.truth:truth:0.26',
-  sha1 = 'b5802815625d82f39c33219299771f3d64301b06',
+  id = 'com.google.truth:truth:0.27',
+  sha1 = 'bd17774d2dc0fffa884d42c07d2537e86c67acd6',
   license = 'DO_NOT_DISTRIBUTE',
   exported_deps = [
     ':guava',
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index 66a12c1..f8feb63 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -43,8 +43,8 @@
 
 maven_jar(
   name = 'asciidoctor',
-  id = 'org.asciidoctor:asciidoctorj:1.5.0',
-  sha1 = '192df5660f72a0fb76966dcc64193b94fba65f99',
+  id = 'org.asciidoctor:asciidoctorj:1.5.2',
+  sha1 = '39d33f739ec1c46f6e908a725264eb74b23c9f99',
   license = 'Apache2.0',
   visibility = [],
   attach_source = False,
@@ -52,8 +52,8 @@
 
 maven_jar(
   name = 'jruby',
-  id = 'org.jruby:jruby-complete:1.7.4',
-  sha1 = '74984d84846523bd7da49064679ed1ccf199e1db',
+  id = 'org.jruby:jruby-complete:1.7.18',
+  sha1 = 'a1be3e1790aace5c99614a87785454d875eb21c2',
   license = 'DO_NOT_DISTRIBUTE',
   visibility = [],
   attach_source = False,
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index bdc2709..a7244f2 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -3,8 +3,8 @@
 include_defs('//lib/codemirror/closure.defs')
 
 REPO = MAVEN_CENTRAL
-VERSION = '5.3'
-SHA1 = 'd39474aece4e58c2b0fcc71530ae223ec0d4d855'
+VERSION = '5.5'
+SHA1 = 'd9cee6fe3de8e02372b1ac1e9a627224a4f649a7'
 
 if REPO == MAVEN_CENTRAL:
   URL = REPO + 'org/webjars/codemirror/%s/codemirror-%s.jar' % (VERSION, VERSION)
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
index 2e65abc..d02916f 100644
--- a/lib/jetty/BUCK
+++ b/lib/jetty/BUCK
@@ -1,12 +1,12 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '9.2.10.v20150310'
+VERSION = '9.2.12.v20150709'
 EXCLUDE = ['about.html']
 
 maven_jar(
   name = 'servlet',
   id = 'org.eclipse.jetty:jetty-servlet:' + VERSION,
-  sha1 = '9e923adf1671af7da09dba778e132ab0a9c62415',
+  sha1 = '50116cac18ad893c9628f0a1984390464b133921',
   license = 'Apache2.0',
   deps = [':security'],
   exclude = EXCLUDE,
@@ -15,7 +15,7 @@
 maven_jar(
   name = 'security',
   id = 'org.eclipse.jetty:jetty-security:' + VERSION,
-  sha1 = 'b56228088355023117ba9a9de0da00d652a7e655',
+  sha1 = '9ace95998fbaae8425b2621c90230a229a554784',
   license = 'Apache2.0',
   deps = [':server'],
   exclude = EXCLUDE,
@@ -25,7 +25,7 @@
 maven_jar(
   name = 'servlets',
   id = 'org.eclipse.jetty:jetty-servlets:' + VERSION,
-  sha1 = 'b48a9bb30e9d5e73dcedf8039f96abdb04a3892c',
+  sha1 = 'a1f9e7874e1db2f664213f524463d12bd5ab5db4',
   license = 'Apache2.0',
   exclude = EXCLUDE,
   visibility = [
@@ -37,7 +37,7 @@
 maven_jar(
   name = 'server',
   id = 'org.eclipse.jetty:jetty-server:' + VERSION,
-  sha1 = '0e6b8bff28b3e9ca6254415d2aa49603a5887fe8',
+  sha1 = '8c90ceffb6954385b024899d334192947d0e4077',
   license = 'Apache2.0',
   exported_deps = [
     ':continuation',
@@ -49,7 +49,7 @@
 maven_jar(
   name = 'jmx',
   id = 'org.eclipse.jetty:jetty-jmx:' + VERSION,
-  sha1 = 'fa94eb39f1dd63c40efe44471664f8f70bc7ca2e',
+  sha1 = '8bc0288abba26dbbf4e9225d6fe6fa6348f8da05',
   license = 'Apache2.0',
   exported_deps = [
     ':continuation',
@@ -61,7 +61,7 @@
 maven_jar(
   name = 'continuation',
   id = 'org.eclipse.jetty:jetty-continuation:' + VERSION,
-  sha1 = '1c9bc80037e9898974ada7318f11c984363d4707',
+  sha1 = '0578cb87b78b71eeda91f5dfa3e8bfbafb55cced',
   license = 'Apache2.0',
   exclude = EXCLUDE,
 )
@@ -69,7 +69,7 @@
 maven_jar(
   name = 'http',
   id = 'org.eclipse.jetty:jetty-http:' + VERSION,
-  sha1 = '886b628f62cd518bbb04b37bd1b308fa19340a53',
+  sha1 = '9a6c83f52c70c28e2272d83866b4111cd15ddbc5',
   license = 'Apache2.0',
   exported_deps = [':io'],
   exclude = EXCLUDE,
@@ -78,7 +78,7 @@
 maven_jar(
   name = 'io',
   id = 'org.eclipse.jetty:jetty-io:' + VERSION,
-  sha1 = '29bc6a5e2049d9858bfa811f2728a7a8efcdc1c0',
+  sha1 = 'c02e9e303d231a589e0c8866c1ee89bcdeb40a55',
   license = 'Apache2.0',
   exported_deps = [':util'],
   exclude = EXCLUDE,
@@ -88,7 +88,7 @@
 maven_jar(
   name = 'util',
   id = 'org.eclipse.jetty:jetty-util:' + VERSION,
-  sha1 = '90cc75668dc9a9885108733d4d46420907cf863c',
+  sha1 = 'd99d38adfdb5ec677643f04fa862554b0bb8b42e',
   license = 'Apache2.0',
   exclude = EXCLUDE,
   visibility = [],
diff --git a/lib/solr/BUCK b/lib/solr/BUCK
deleted file mode 100644
index cd39742..0000000
--- a/lib/solr/BUCK
+++ /dev/null
@@ -1,33 +0,0 @@
-include_defs('//lib/maven.defs')
-
-# Java client library to use Solr over the network.
-maven_jar(
-  name = 'solrj',
-  id = 'org.apache.solr:solr-solrj:4.3.1',
-  sha1 = '433fe37796e67eaeb4452f69eb1fae2de27cb7a8',
-  license = 'Apache2.0',
-  deps = [
-    ':noggit',
-    ':zookeeper',
-    '//lib/httpcomponents:httpclient',
-    '//lib/httpcomponents:httpmime',
-    '//lib/commons:io',
-  ],
-)
-
-maven_jar(
-  name = 'noggit',
-  id = 'org.noggit:noggit:0.5',
-  sha1 = '8e6e65624d2e09a30190c6434abe23b7d4e5413c',
-  license = 'Apache2.0',
-  visibility = [],
-)
-
-maven_jar(
-  name = 'zookeeper',
-  id = 'org.apache.zookeeper:zookeeper:3.4.5',
-  sha1 = 'c0f69fb36526552a8f0bc548a6c33c49cf08e562',
-  license = 'Apache2.0',
-  deps = ['//lib/log:api'],
-  visibility = [],
-)
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index c70b288..2ff713f 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit c70b2881cbb9ad706603d7f6fa855b233aea6135
+Subproject commit 2ff713f3d3f0bbabf00b73c74705cd3741f61a70
diff --git a/plugins/replication b/plugins/replication
index 7a3a89f..cb9e977 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 7a3a89fc983b9bcb5b2c965affd7a83bb6b10bb2
+Subproject commit cb9e977ddbaa76214c8cefbeb74aa8420ce1d912
diff --git a/tools/eclipse/gerrit_gwt_debug.launch b/tools/eclipse/gerrit_gwt_debug.launch
index 3533211..c3c58ff 100644
--- a/tools/eclipse/gerrit_gwt_debug.launch
+++ b/tools/eclipse/gerrit_gwt_debug.launch
@@ -16,7 +16,7 @@
 </listAttribute>
 <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gerrit.gwtdebug.GerritGwtDebugLauncher"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noprecompile -src ${resource_loc:/gerrit} -workDir ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui com.google.gerrit.GerritGwtUI -- --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../gerrit_testsite"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noprecompile -src ${resource_loc:/gerrit} -workDir ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui com.google.gerrit.GerritGwtUI -src ${resource_loc:/gerrit}/gerrit-plugin-gwtui/src/main/java -- --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../gerrit_testsite"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1024M&#10;-XX:MaxPermSize=256M&#10;-Dgerrit.disable-gwtui-recompile=true"/>
 </launchConfiguration>