Merge "Set commit ID in result of the /changes/*/revisions/*/commit endpoint"
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index 31b4538..d1108b5 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -21,7 +21,7 @@
   [--empty-commit]
   [--max-object-size-limit <N>]
   [--plugin-config <PARAM> ...]
-  { <NAME> | --name <NAME> }
+  { <NAME> }
 --
 
 == DESCRIPTION
@@ -49,11 +49,6 @@
 	Required; name of the new project to create.  If name ends
 	with `.git` the suffix will be automatically removed.
 
---name::
--n::
-	Deprecated alias for the <NAME> argument. This option may
-	be removed in a future release.
-
 --branch::
 -b::
 	Name of the initial branch(es) in the newly created project.
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/config-themes.txt b/Documentation/config-themes.txt
index 5c3a448..b165e37 100644
--- a/Documentation/config-themes.txt
+++ b/Documentation/config-themes.txt
@@ -133,9 +133,6 @@
 block in the header or footer will execute before Gerrit has defined
 the function and is ready to register the hook callback.
 
-The function `gerrit_addHistoryHook` is deprecated and may be
-removed in a future release.
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
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 081f370..0efc395 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -962,6 +962,107 @@
 [[ui_extension]]
 == UI Extension
 
+[[panels]]
+=== Panels
+
+GWT plugins can contribute panels to Gerrit screens.
+
+Gerrit screens define extension points where plugins can add GWT
+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.
+
+** The following parameters are provided:
+*** `GerritUiExtensionPoint.Key.CHANGE_ID`:
++
+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]
+----
+public class MyPlugin extends PluginEntryPoint {
+  @Override
+  public void onPluginLoad() {
+    Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+        new Panel.EntryPoint() {
+          @Override
+          public void onLoad(Panel panel) {
+            panel.setWidget(new InlineLabel("My Panel for change "
+                + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
+          }
+        });
+  }
+}
+----
+
+[[actions]]
+=== Actions
+
 Plugins can contribute UI actions on core Gerrit pages. This is useful
 for workflow customization or exposing plugin functionality through the
 UI in addition to SSH commands and the REST API.
@@ -1661,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-accounts.txt b/Documentation/rest-api-accounts.txt
index 3fdebb0..caab598 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1526,42 +1526,45 @@
 The `PreferencesInfo` entity contains information about a user's preferences.
 
 [options="header",cols="1,^1,5"]
-|=====================================
-|Field Name              ||Description
-|`changes_per_page`               ||
+|============================================
+|Field Name                     ||Description
+|`changes_per_page`             ||
 The number of changes to show on each page.
 Allowed values are `10`, `25`, `50`, `100`.
-|`show_site_header`   |not set if `false`|
+|`show_site_header`             |not set if `false`|
 Whether the site header should be shown.
-|`use_flash_clipboard`     |not set if `false`|
+|`use_flash_clipboard`          |not set if `false`|
 Whether to use the flash clipboard widget.
-|`download_scheme`      ||
+|`download_scheme`              ||
 The type of download URL the user prefers to use.
-|`download_command`     ||
+|`download_command`             ||
 The type of download command the user prefers to use.
-|`copy_self_on_email`       |not set if `false`|
+|`copy_self_on_email`           |not set if `false`|
 Whether to CC me on comments I write.
-|`date_format`         ||
+|`date_format`                  ||
 The format to display the date in.
 Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`.
-|`time_format`     ||
+|`time_format`                  ||
 The format to display the time in.
 Allowed values are `HHMM_12`, `HHMM_24`.
-|`relative_date_in_change_table`  |not set if `false`|
+|`relative_date_in_change_table`|not set if `false`|
 Whether to show relative dates in the changes table.
-|`size_bar_in_change_table`      |not set if `false`|
+|`size_bar_in_change_table`     |not set if `false`|
 Whether to show the change sizes as colored bars in the change table.
-|`legacycid_in_change_table`      |not set if `false`|
+|`legacycid_in_change_table`    |not set if `false`|
 Whether to show change number in the change table.
-|`mute_common_path_prefixes` |not set if `false`|
+|`mute_common_path_prefixes`    |not set if `false`|
 Whether to mute common path prefixes in file names in the file table.
-|`review_category_strategy`   ||
+|`review_category_strategy`     ||
 The strategy used to displayed info in the review category column.
 Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
-|`diff_view`     ||
+|`diff_view`                    ||
 The type of diff view to show.
 Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
-|=====================================
+|`my`                           ||
+The menu items of the `MY` top menu as a list of
+link:rest-api-config.html#top-menu-item-info[TopMenuItemInfo] entities.
+|============================================
 
 [[preferences-input]]
 === PreferencesInput
@@ -1569,42 +1572,45 @@
 user preferences. Fields which are not set will not be updated.
 
 [options="header",cols="1,^1,5"]
-|=====================================
-|Field Name              ||Description
-|`changes_per_page`               |optional|
+|============================================
+|Field Name                     ||Description
+|`changes_per_page`             |optional|
 The number of changes to show on each page.
 Allowed values are `10`, `25`, `50`, `100`.
-|`show_site_header`   |optional|
+|`show_site_header`             |optional|
 Whether the site header should be shown.
-|`use_flash_clipboard`     |optional|
+|`use_flash_clipboard`          |optional|
 Whether to use the flash clipboard widget.
-|`download_scheme`      |optional|
+|`download_scheme`              |optional|
 The type of download URL the user prefers to use.
-|`download_command`     |optional|
+|`download_command`             |optional|
 The type of download command the user prefers to use.
-|`copy_self_on_email`       |optional|
+|`copy_self_on_email`           |optional|
 Whether to CC me on comments I write.
-|`date_format`         |optional|
+|`date_format`                  |optional|
 The format to display the date in.
 Allowed values are `STD`, `US`, `ISO`, `EURO`, `UK`.
-|`time_format`     |optional|
+|`time_format`                  |optional|
 The format to display the time in.
 Allowed values are `HHMM_12`, `HHMM_24`.
-|`relative_date_in_change_table`  |optional|
+|`relative_date_in_change_table`|optional|
 Whether to show relative dates in the changes table.
-|`size_bar_in_change_table`      |optional|
+|`size_bar_in_change_table`     |optional|
 Whether to show the change sizes as colored bars in the change table.
-|`legacycid_in_change_table`      |optional|
+|`legacycid_in_change_table`    |optional|
 Whether to show change number in the change table.
-|`mute_common_path_prefixes` |optional|
+|`mute_common_path_prefixes`    |optional|
 Whether to mute common path prefixes in file names in the file table.
-|`review_category_strategy`   |optional|
+|`review_category_strategy`     |optional|
 The strategy used to displayed info in the review category column.
 Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
-|`diff_view`     |optional|
+|`diff_view`                    |optional|
 The type of diff view to show.
 Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
-|=====================================
+|`my`                           |optional|
+The menu items of the `MY` top menu as a list of
+link:rest-api-config.html#top-menu-item-info[TopMenuItemInfo] entities.
+|============================================
 
 [[query-limit-info]]
 === QueryLimitInfo
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 1300628..94f8ac9 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1073,7 +1073,11 @@
 --
 
 Returns a list of all changes which are submitted when
-link:#submit-change[\{submit\}] is called for this change.
+link:#submit-change[\{submit\}] is called for this change,
+including the current change itself.
+
+An empty list is returned if this change will be submitted
+by itself (no other changes).
 
 .Request
 ----
@@ -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/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 6762411..516ae51 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1284,6 +1284,18 @@
 The number of open files.
 |============================
 
+[[plugin-config-info]]
+=== PluginConfigInfo
+The `PluginConfigInfo` entity contains information about Gerrit
+extensions by plugins.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
+|`has_avatars` |not set if `false`|
+Whether an avatar provider is registered.
+|===========================
+
 [[receive-info]]
 === ReceiveInfo
 The `ReceiveInfo` entity contains information about the configuration
@@ -1326,7 +1338,9 @@
 GerritInfo] entity.
 |`gitweb `                 |optional|
 Information about the link:config-gerrit.html#gitweb[gitweb]
-configuration as link:#git-web-info[GitwebInfo] entity.
+|`plugin `                 ||
+Information about Gerrit extensions by plugins as
+link:#plugin-config-info[PluginConfigInfo] entity.
 |`receive`                 |optional|
 Information about the receive-pack configuration as a
 link:#receive-info[ReceiveInfo] entity.
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-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 342fe4c..6bf3f7f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -51,14 +51,14 @@
     String id2 = getChangeId(c2_1);
     pushHead(testRepo, "refs/for/master", false);
 
-    assertSubmittedTogether(id1, id1);
+    assertSubmittedTogether(id1);
     assertSubmittedTogether(id2, id2, id1);
   }
 
   @Test
   public void respectsWholeTopicAndAncestors() throws Exception {
     RevCommit initialHead = getRemoteHead();
-    // Create two independant commits and push.
+    // Create two independent commits and push.
     RevCommit c1_1 = commitBuilder()
         .add("a.txt", "1")
         .message("subject: 1")
@@ -78,8 +78,45 @@
       assertSubmittedTogether(id1, id2, id1);
       assertSubmittedTogether(id2, id2, id1);
     } else {
-      assertSubmittedTogether(id1, id1);
-      assertSubmittedTogether(id2, id2);
+      assertSubmittedTogether(id1);
+      assertSubmittedTogether(id2);
+    }
+  }
+
+  @Test
+  public void testTopicChaining() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+    // Create two independent commits and push.
+    RevCommit c1_1 = commitBuilder()
+        .add("a.txt", "1")
+        .message("subject: 1")
+        .create();
+    String id1 = getChangeId(c1_1);
+    pushHead(testRepo, "refs/for/master/" + name("connectingTopic"), false);
+
+    testRepo.reset(initialHead);
+    RevCommit c2_1 = commitBuilder()
+        .add("b.txt", "2")
+        .message("subject: 2")
+        .create();
+    String id2 = getChangeId(c2_1);
+    pushHead(testRepo, "refs/for/master/" + name("connectingTopic"), false);
+
+    RevCommit c3_1 = commitBuilder()
+        .add("b.txt", "2")
+        .message("subject: 2")
+        .create();
+    String id3 = getChangeId(c3_1);
+    pushHead(testRepo, "refs/for/master/" + name("unrelated-topic"), false);
+
+    if (isSubmitWholeTopicEnabled()) {
+      assertSubmittedTogether(id1, id2, id1);
+      assertSubmittedTogether(id2, id2, id1);
+      assertSubmittedTogether(id3, id3, id2, id1);
+    } else {
+      assertSubmittedTogether(id1);
+      assertSubmittedTogether(id2);
+      assertSubmittedTogether(id3, id3, id2);
     }
   }
 
@@ -102,8 +139,8 @@
     String id2 = getChangeId(c2_1);
     pushHead(testRepo, "refs/for/master", false);
 
-    assertSubmittedTogether(id1, id1);
-    assertSubmittedTogether(id2, id2);
+    assertSubmittedTogether(id1);
+    assertSubmittedTogether(id2);
   }
 
   private void assertSubmittedTogether(String chId, String... expected)
diff --git a/gerrit-common/src/main/java/com/google/gerrit/Common.gwt.xml b/gerrit-common/src/main/java/com/google/gerrit/Common.gwt.xml
index 468b477..80bd2cb 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/Common.gwt.xml
+++ b/gerrit-common/src/main/java/com/google/gerrit/Common.gwt.xml
@@ -16,5 +16,6 @@
 <module>
   <inherits name='com.google.gerrit.reviewdb.ReviewDB' />
   <inherits name='com.google.gwtjsonrpc.GWTJSONRPC'/>
+  <inherits name="com.google.gwt.logging.Logging"/>
   <source path='common' />
 </module>
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-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
index 99f1c4c..189445a 100644
--- a/gerrit-gwtexpui/BUCK
+++ b/gerrit-gwtexpui/BUCK
@@ -7,6 +7,7 @@
   resources = [
     SRC + 'clippy/client/clippy.css',
     SRC + 'clippy/client/clippy.swf',
+    SRC + 'clippy/client/CopyableLabelText.properties',
   ],
   provided_deps = ['//lib/gwt:user'],
   deps = [
@@ -84,6 +85,7 @@
   name = 'UserAgent',
   srcs = glob([SRC + 'user/client/*.java']),
   gwt_xml = SRC + 'user/User.gwt.xml',
+  resources = [SRC + 'user/client/tooltip.css'],
   provided_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyCss.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyCss.java
index 68495e8..05a1861 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyCss.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyCss.java
@@ -18,5 +18,6 @@
 
 public interface ClippyCss extends CssResource {
   String label();
-  String control();
+  String copier();
+  String swf();
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
index e34814f..5da8b1e 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
@@ -16,6 +16,7 @@
 
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.BlurHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -24,9 +25,12 @@
 import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
 import com.google.gwt.http.client.URL;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HasText;
@@ -35,6 +39,7 @@
 import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+import com.google.gwtexpui.user.client.Tooltip;
 import com.google.gwtexpui.user.client.UserAgent;
 
 /**
@@ -71,6 +76,7 @@
   private int visibleLen;
   private Label textLabel;
   private TextBox textBox;
+  private Button copier;
   private Element swf;
 
   public CopyableLabel() {
@@ -111,7 +117,32 @@
       });
       content.add(textLabel);
     }
-    embedMovie();
+
+    if (UserAgent.hasJavaScriptClipboard()) {
+      copier = new Button("&#x1f4cb;"); // CLIPBOARD
+      copier.setStyleName(ClippyResources.I.css().copier());
+      Tooltip.addStyle(copier);
+      Tooltip.setLabel(copier, CopyableLabelText.I.tooltip());
+      copier.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          copy();
+        }
+      });
+      copier.addMouseOutHandler(new MouseOutHandler() {
+        @Override
+        public void onMouseOut(MouseOutEvent event) {
+          Tooltip.setLabel(copier, CopyableLabelText.I.tooltip());
+        }
+      });
+
+      FlowPanel p = new FlowPanel();
+      p.getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
+      p.add(copier);
+      content.add(p);
+    } else {
+      embedMovie();
+    }
   }
 
   /**
@@ -127,12 +158,13 @@
   }
 
   private void embedMovie() {
-    if (flashEnabled && UserAgent.hasFlash && text.length() > 0) {
+    if (copier == null && flashEnabled && !text.isEmpty()
+        && UserAgent.Flash.isInstalled()) {
       final String flashVars = "text=" + URL.encodeQueryString(getText());
       final SafeHtmlBuilder h = new SafeHtmlBuilder();
 
       h.openElement("div");
-      h.setStyleName(ClippyResources.I.css().control());
+      h.setStyleName(ClippyResources.I.css().swf());
 
       h.openElement("object");
       h.setWidth(SWF_WIDTH);
@@ -236,4 +268,35 @@
     }
     textLabel.setVisible(true);
   }
+
+  private void copy() {
+    TextBox t = new TextBox();
+    try {
+      t.setText(getText());
+      content.add(t);
+      t.selectAll();
+
+      boolean ok = execCommand("copy");
+      Tooltip.setLabel(copier, ok
+          ? CopyableLabelText.I.copied()
+          : CopyableLabelText.I.failed());
+      if (!ok) {
+        // Disable JavaScript clipboard and try flash movie in another instance.
+        UserAgent.disableJavaScriptClipboard();
+      }
+    } finally {
+      t.removeFromParent();
+    }
+  }
+
+  private static boolean execCommand(String command) {
+    try {
+      return nativeExec(command);
+    } catch (Exception e) {
+      return false;
+    }
+  }
+
+  private static native boolean nativeExec(String c)
+  /*-{ return !! $doc.execCommand(c) }-*/;
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabelText.java
similarity index 60%
copy from gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java
copy to gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabelText.java
index d242db6..4d1b837 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabelText.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// 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.
@@ -12,10 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gwtexpui.user.client;
+package com.google.gwtexpui.clippy.client;
 
-import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.Constants;
 
-public interface DialogVisibleHandler extends EventHandler {
-  public void onDialogVisible(DialogVisibleEvent event);
+interface CopyableLabelText extends Constants {
+  static final CopyableLabelText I = GWT.create(CopyableLabelText.class);
+
+  String tooltip();
+  String copied();
+  String failed();
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabelText.properties b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabelText.properties
new file mode 100644
index 0000000..cf93bfa
--- /dev/null
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabelText.properties
@@ -0,0 +1,3 @@
+tooltip = Copy to clipboard
+copied = Copied
+failed = Failed !
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clippy.css b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clippy.css
index b962df3..b25e006 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clippy.css
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clippy.css
@@ -16,10 +16,24 @@
 .label {
   vertical-align: top;
 }
-.control {
+.swf, .copier {
   margin-left: 5px;
-  display: inline-block !important;
   height: 14px;
   width: 14px;
+}
+.swf {
+  display: inline-block !important;
   overflow: hidden;
 }
+.copier {
+  display: inline-block;
+  font-size: 12px;
+  vertical-align: top;
+  padding: 0;
+  border: 0;
+  background-color: inherit;
+  cursor: pointer;
+}
+.copier:focus {
+  outline: none;
+}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
index 191ffd7..a85d704 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
@@ -28,9 +28,9 @@
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -42,7 +42,7 @@
 import java.util.Map;
 
 
-public class KeyHelpPopup extends PluginSafePopupPanel implements
+public class KeyHelpPopup extends PopupPanel implements
     KeyPressHandler, KeyDownHandler {
   private final FocusPanel focus;
 
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/AutoCenterDialogBox.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/AutoCenterDialogBox.java
index 78ea8d6..54d8eca 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/AutoCenterDialogBox.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/AutoCenterDialogBox.java
@@ -18,9 +18,10 @@
 import com.google.gwt.event.logical.shared.ResizeHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.DialogBox;
 
 /** A DialogBox that automatically re-centers itself if the window changes */
-public class AutoCenterDialogBox extends PluginSafeDialogBox {
+public class AutoCenterDialogBox extends DialogBox {
   private HandlerRegistration recenter;
 
   public AutoCenterDialogBox() {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleEvent.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleEvent.java
deleted file mode 100644
index 74218b4..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleEvent.java
+++ /dev/null
@@ -1,60 +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.gwtexpui.user.client;
-
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.user.client.ui.Widget;
-
-public class DialogVisibleEvent extends GwtEvent<DialogVisibleHandler> {
-  private static Type<DialogVisibleHandler> TYPE;
-
-  public static Type<DialogVisibleHandler> getType() {
-    if (TYPE == null) {
-      TYPE = new Type<>();
-    }
-    return TYPE;
-  }
-
-  private final Widget parent;
-  private final boolean visible;
-
-  DialogVisibleEvent(Widget w, boolean visible) {
-    this.parent = w;
-    this.visible = visible;
-  }
-
-  public boolean contains(Widget c) {
-    for (; c != null; c = c.getParent()) {
-      if (c == parent) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public boolean isVisible() {
-    return visible;
-  }
-
-  @Override
-  public Type<DialogVisibleHandler> getAssociatedType() {
-    return getType();
-  }
-
-  @Override
-  protected void dispatch(DialogVisibleHandler handler) {
-    handler.onDialogVisible(this);
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/PluginSafeDialogBox.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/PluginSafeDialogBox.java
deleted file mode 100644
index 80bfba1..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/PluginSafeDialogBox.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.user.client;
-
-import com.google.gwt.user.client.ui.DialogBox;
-
-/**
- * A DialogBox that can appear over Flash movies and Java applets.
- * <p>
- * Some browsers have issues with placing a &lt;div&gt; (such as that used by
- * the DialogBox implementation) over top of native UI such as that used by the
- * Flash plugin. Often the native UI leaks over top of the &lt;div&gt;, which is
- * not the desired behavior for a dialog box.
- * <p>
- * This implementation hides the native resources by setting their display
- * property to 'none' when the dialog is shown, and restores them back to their
- * prior setting when the dialog is hidden.
- * */
-public class PluginSafeDialogBox extends DialogBox {
-  public PluginSafeDialogBox() {
-    this(false);
-  }
-
-  public PluginSafeDialogBox(final boolean autoHide) {
-    this(autoHide, true);
-  }
-
-  public PluginSafeDialogBox(final boolean autoHide, final boolean modal) {
-    super(autoHide, modal);
-  }
-
-  @Override
-  public void setVisible(final boolean show) {
-    UserAgent.fireDialogVisible(this, show);
-    super.setVisible(show);
-  }
-
-  @Override
-  public void show() {
-    UserAgent.fireDialogVisible(this, true);
-    super.show();
-  }
-
-  @Override
-  public void hide(final boolean autoClosed) {
-    UserAgent.fireDialogVisible(this, false);
-    super.hide(autoClosed);
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/PluginSafePopupPanel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/PluginSafePopupPanel.java
deleted file mode 100644
index 1ed8f99..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/PluginSafePopupPanel.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.user.client;
-
-import com.google.gwt.user.client.ui.PopupPanel;
-
-/**
- * A PopupPanel that can appear over Flash movies and Java applets.
- * <p>
- * Some browsers have issues with placing a &lt;div&gt; (such as that used by
- * the PopupPanel implementation) over top of native UI such as that used by the
- * Flash plugin. Often the native UI leaks over top of the &lt;div&gt;, which is
- * not the desired behavior for a dialog box.
- * <p>
- * This implementation hides the native resources by setting their display
- * property to 'none' when the dialog is shown, and restores them back to their
- * prior setting when the dialog is hidden.
- * */
-public class PluginSafePopupPanel extends PopupPanel {
-  public PluginSafePopupPanel() {
-    this(false);
-  }
-
-  public PluginSafePopupPanel(final boolean autoHide) {
-    this(autoHide, true);
-  }
-
-  public PluginSafePopupPanel(final boolean autoHide, final boolean modal) {
-    super(autoHide, modal);
-  }
-
-  @Override
-  public void setVisible(final boolean show) {
-    UserAgent.fireDialogVisible(this, show);
-    super.setVisible(show);
-  }
-
-  @Override
-  public void show() {
-    UserAgent.fireDialogVisible(this, true);
-    super.show();
-  }
-
-  @Override
-  public void hide(final boolean autoClosed) {
-    UserAgent.fireDialogVisible(this, false);
-    super.hide(autoClosed);
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/Tooltip.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/Tooltip.java
new file mode 100644
index 0000000..e3ab034
--- /dev/null
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/Tooltip.java
@@ -0,0 +1,80 @@
+// 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.gwtexpui.user.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.UIObject;
+
+/** Displays custom tooltip message below an element. */
+public class Tooltip {
+  interface Resources extends ClientBundle {
+    static final Resources I = GWT.create(Resources.class);
+
+    @Source("tooltip.css")
+    Css css();
+  }
+
+  interface Css extends CssResource {
+    String tooltip();
+  }
+
+  static {
+    Resources.I.css().ensureInjected();
+  }
+
+  /**
+   * Add required supporting style to enable custom tooltip rendering.
+   *
+   * @param o widget whose element should display a tooltip on hover.
+   */
+  public static void addStyle(UIObject o) {
+    addStyle(o.getElement());
+  }
+
+  /**
+   * Add required supporting style to enable custom tooltip rendering.
+   *
+   * @param e element that should display a tooltip on hover.
+   */
+  public static void addStyle(Element e) {
+    e.addClassName(Resources.I.css().tooltip());
+  }
+
+  /**
+   * Set the text displayed on hover.
+   *
+   * @param o widget whose hover text is being set.
+   * @param text message to display on hover.
+   */
+  public static void setLabel(UIObject o, String text) {
+   setLabel(o.getElement(), text);
+  }
+
+  /**
+   * Set the text displayed on hover.
+   *
+   * @param e element whose hover text is being set.
+   * @param text message to display on hover.
+   */
+  public static void setLabel(Element e, String text) {
+    e.setAttribute("aria-label", text != null ? text : "");
+  }
+
+  private Tooltip() {
+  }
+}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/UserAgent.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/UserAgent.java
index c654902..2ffa7c5d 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/UserAgent.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/UserAgent.java
@@ -15,11 +15,7 @@
 package com.google.gwtexpui.user.client;
 
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.shared.EventBus;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.SimpleEventBus;
 import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Widget;
 
 /**
  * User agent feature tests we don't create permutations for.
@@ -31,36 +27,101 @@
  * trivial compared to the time developers lose building their application.
  */
 public class UserAgent {
-  /** Does the browser have ShockwaveFlash plugin enabled? */
-  public static final boolean hasFlash = hasFlash();
-  private static final EventBus bus = new SimpleEventBus();
+  private static boolean jsClip = guessJavaScriptClipboard();
 
-  public static HandlerRegistration addDialogVisibleHandler(
-      DialogVisibleHandler handler) {
-    return bus.addHandler(DialogVisibleEvent.getType(), handler);
+  public static boolean hasJavaScriptClipboard() {
+    return jsClip;
   }
 
-  static void fireDialogVisible(Widget w, boolean visible) {
-    bus.fireEvent(new DialogVisibleEvent(w, visible));
+  public static void disableJavaScriptClipboard() {
+    jsClip = false;
   }
 
-  private static native boolean hasFlash()
-  /*-{
-    if (navigator.plugins && navigator.plugins.length) {
-      if (navigator.plugins['Shockwave Flash'])     return true;
-      if (navigator.plugins['Shockwave Flash 2.0']) return true;
+  private static native boolean nativeHasCopy()
+  /*-{ return $doc['queryCommandSupported'] && $doc.queryCommandSupported('copy') }-*/;
 
-    } else if (navigator.mimeTypes && navigator.mimeTypes.length) {
-      var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
-      if (mimeType && mimeType.enabledPlugin) return true;
-
-    } else {
-      try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7'); return true; } catch (e) {}
-      try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'); return true; } catch (e) {}
-      try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash');   return true; } catch (e) {}
+  private static boolean guessJavaScriptClipboard() {
+    String ua = Window.Navigator.getUserAgent();
+    int chrome = major(ua, "Chrome/");
+    if (chrome > 0) {
+      return 42 <= chrome;
     }
+
+    int ff = major(ua, "Firefox/");
+    if (ff > 0) {
+      return 41 <= ff;
+    }
+
+    int opera = major(ua, "OPR/");
+    if (opera > 0) {
+      return 29 <= opera;
+    }
+
+    int msie = major(ua, "MSIE ");
+    if (msie > 0) {
+      return 9 <= msie;
+    }
+
+    if (nativeHasCopy()) {
+      // Firefox 39.0 lies and says it supports copy, then fails.
+      // So we try this after the browser specific test above.
+      return true;
+    }
+
+    // Safari is not planning to support document.execCommand('copy').
+    // Assume the browser does not have the feature.
     return false;
-  }-*/;
+  }
+
+  private static int major(String ua, String product) {
+    int entry = ua.indexOf(product);
+    if (entry >= 0) {
+      String s = ua.substring(entry + product.length());
+      String p = s.split("[ /;,.)]", 2)[0];
+      try {
+        return Integer.parseInt(p);
+      } catch (NumberFormatException nan) {
+      }
+    }
+    return -1;
+  }
+
+  public static class Flash {
+    private static boolean checked;
+    private static boolean installed;
+
+    /**
+     * Does the browser have ShockwaveFlash plugin installed?
+     * <p>
+     * This method may still return true if the user has disabled Flash or set
+     * the plugin to "click to run".
+     */
+    public static boolean isInstalled() {
+      if (!checked) {
+        installed = hasFlash();
+        checked = true;
+      }
+      return installed;
+    }
+
+    private static native boolean hasFlash()
+    /*-{
+      if (navigator.plugins && navigator.plugins.length) {
+        if (navigator.plugins['Shockwave Flash'])     return true;
+        if (navigator.plugins['Shockwave Flash 2.0']) return true;
+
+      } else if (navigator.mimeTypes && navigator.mimeTypes.length) {
+        var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
+        if (mimeType && mimeType.enabledPlugin) return true;
+
+      } else {
+        try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7'); return true; } catch (e) {}
+        try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'); return true; } catch (e) {}
+        try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash');   return true; } catch (e) {}
+      }
+      return false;
+    }-*/;
+  }
 
   /**
    * Test for and disallow running this application in an &lt;iframe&gt;.
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/tooltip.css b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/tooltip.css
new file mode 100644
index 0000000..1aeb015
--- /dev/null
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/tooltip.css
@@ -0,0 +1,54 @@
+/* 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.
+ */
+
+.tooltip {
+  position: relative;
+}
+
+.tooltip:hover:before {
+  position: absolute;
+  z-index: 51;
+  border: solid;
+  border-color: #333 transparent;
+  border-width: 0 4px 4px 4px;
+  pointer-events: none;
+  content: "";
+
+  top: auto;
+  right: 50%;
+  bottom: -5px;
+  margin-right: -5px;
+}
+
+.tooltip:hover:after {
+  position: absolute;
+  z-index: 50;
+  font: normal normal 11px/1.5 Helvetica, arial, sans-serif;
+  text-align: center;
+  white-space: pre;
+  pointer-events: none;
+  background: rgba(0,0,0,.7);
+  color: #fff;
+  border-radius: 3px;
+  padding: 5px;
+  content: attr(aria-label);
+
+  top: 100%;
+  right: 50%;
+  margin-top: 5px;
+  -webkit-transform: translateX(50%);
+  -ms-transform: translateX(50%);
+  transform: translateX(50%)
+}
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
new file mode 100644
index 0000000..e22625c
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/GerritUiExtensionPoint.java
@@ -0,0 +1,39 @@
+// 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;
+
+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,
+
+  /* ProjectInfoScreen */
+  PROJECT_INFO_SCREEN_TOP, PROJECT_INFO_SCREEN_BOTTOM;
+
+  public enum Key {
+    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 7f17f4f..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;
@@ -91,6 +91,10 @@
   }
 
   private void loadAvatar(AccountInfo account, int size, boolean addPopup) {
+    if (!Gerrit.info().plugin().hasAvatars()) {
+      return;
+    }
+
      // TODO Kill /accounts/*/avatar URL.
     String u = account.email();
     if (Gerrit.isSignedIn()
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/ErrorDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
index 700c701..a5c5659 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
@@ -26,13 +26,13 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
 
 /** A dialog box showing an error message, when bad things happen. */
-public class ErrorDialog extends PluginSafePopupPanel {
+public class ErrorDialog extends PopupPanel {
   private final Label text;
   private final FlowPanel body;
   private final Button closey;
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/NotSignedInDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java
index 83c32cd..a372f03 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/NotSignedInDialog.java
@@ -26,10 +26,9 @@
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 /** A dialog box telling the user they are not signed in. */
-public class NotSignedInDialog extends PluginSafePopupPanel implements CloseHandler<PopupPanel> {
+public class NotSignedInDialog extends PopupPanel implements CloseHandler<PopupPanel> {
   private Button signin;
   private boolean buttonClicked;
 
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 1220a37..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;
@@ -22,10 +22,10 @@
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
-public class UserPopupPanel extends PluginSafePopupPanel {
+public class UserPopupPanel extends PopupPanel {
   interface Binder extends UiBinder<Widget, UserPopupPanel> {}
   private static final Binder binder = GWT.create(Binder.class);
 
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 6867bab..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;
@@ -37,6 +39,7 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwtexpui.user.client.UserAgent;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -136,16 +139,19 @@
     legacycidInChangeTable = new CheckBox(Util.C.showLegacycidInChangeTable());
     muteCommonPathPrefixes = new CheckBox(Util.C.muteCommonPathPrefixes());
 
-    final Grid formGrid = new Grid(11, 2);
+    boolean flashClippy = !UserAgent.hasJavaScriptClipboard() && UserAgent.Flash.isInstalled();
+    final Grid formGrid = new Grid(10 + (flashClippy ? 1 : 0), 2);
 
     int row = 0;
     formGrid.setText(row, labelIdx, "");
     formGrid.setWidget(row, fieldIdx, showSiteHeader);
     row++;
 
-    formGrid.setText(row, labelIdx, "");
-    formGrid.setWidget(row, fieldIdx, useFlashClipboard);
-    row++;
+    if (flashClippy) {
+      formGrid.setText(row, labelIdx, "");
+      formGrid.setWidget(row, fieldIdx, useFlashClipboard);
+      row++;
+    }
 
     formGrid.setText(row, labelIdx, "");
     formGrid.setWidget(row, fieldIdx, copySelfOnEmails);
@@ -216,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 0c4ed5d..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
@@ -19,6 +19,7 @@
 import com.google.gerrit.client.AvatarImage;
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.reviewdb.client.Account;
@@ -44,14 +45,16 @@
     HorizontalPanel h = new HorizontalPanel();
     add(h);
 
-    VerticalPanel v = new VerticalPanel();
-    v.addStyleName(Gerrit.RESOURCES.css().avatarInfoPanel());
-    h.add(v);
-    avatar = new AvatarImage();
-    v.add(avatar);
-    changeAvatar = new Anchor(Util.C.changeAvatar(), "", "_blank");
-    changeAvatar.setVisible(false);
-    v.add(changeAvatar);
+    if (Gerrit.info().plugin().hasAvatars()) {
+      VerticalPanel v = new VerticalPanel();
+      v.addStyleName(Gerrit.RESOURCES.css().avatarInfoPanel());
+      h.add(v);
+      avatar = new AvatarImage();
+      v.add(avatar);
+      changeAvatar = new Anchor(Util.C.changeAvatar(), "", "_blank");
+      changeAvatar.setVisible(false);
+      v.add(changeAvatar);
+    }
 
     if (LocaleInfo.getCurrentLocale().isRTL()) {
       labelIdx = 1;
@@ -84,6 +87,7 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    add(createExtensionPoint(GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM));
     display(Gerrit.getUserAccount());
     display();
   }
@@ -95,19 +99,21 @@
   }
 
   void display(final Account account) {
-    avatar.setAccount(FormatUtil.asInfo(account), 93, false);
-    new RestApi("/accounts/").id("self").view("avatar.change.url")
-        .get(new AsyncCallback<NativeString>() {
-          @Override
-          public void onSuccess(NativeString changeUrl) {
-            changeAvatar.setHref(changeUrl.asString());
-            changeAvatar.setVisible(true);
-          }
+    if (Gerrit.info().plugin().hasAvatars()) {
+      avatar.setAccount(FormatUtil.asInfo(account), 93, false);
+      new RestApi("/accounts/").id("self").view("avatar.change.url")
+          .get(new AsyncCallback<NativeString>() {
+            @Override
+            public void onSuccess(NativeString changeUrl) {
+              changeAvatar.setHref(changeUrl.asString());
+              changeAvatar.setVisible(true);
+            }
 
-          @Override
-          public void onFailure(Throwable caught) {
-          }
-        });
+            @Override
+            public void onFailure(Throwable caught) {
+            }
+          });
+    }
 
     int row = 0;
     if (Gerrit.info().auth().siteHasUsernames()) {
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 e4a5446..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;
@@ -30,17 +30,20 @@
     ActionContext.init();
     HtmlTemplate.init();
     Plugin.init();
-    addHistoryHook();
   }
 
   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: {},
       revision_actions: {},
@@ -86,6 +89,16 @@
         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);
+        (this.panels[i] || (this.panels[i]=[])).push(p);
+      },
 
       url: function (d) {
         if (d && d.length > 0)
@@ -203,14 +216,6 @@
     };
   }-*/;
 
-  /** Install deprecated {@code gerrit_addHistoryHook()} function. */
-  private static native void addHistoryHook() /*-{
-    $wnd.gerrit_addHistoryHook = function(h) {
-      var p = @com.google.gwt.user.client.Window.Location::getPath()();
-      $wnd.Gerrit.on('history', function(t) { h(p + "#" + t) })
-     };
-  }-*/;
-
   private static void install(JavaScriptObject cb, Plugin p) throws Exception {
     try {
       pluginName = p.name();
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
new file mode 100644
index 0000000..0702cba
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
@@ -0,0 +1,156 @@
+// 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.GerritUiExtensionPoint;
+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 com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ExtensionPanel extends FlowPanel {
+  private static final Logger logger =
+      Logger.getLogger(ExtensionPanel.class.getName());
+  private final GerritUiExtensionPoint extensionPoint;
+  private final List<Context> contexts;
+
+  public ExtensionPanel(GerritUiExtensionPoint extensionPoint) {
+    this.extensionPoint = extensionPoint;
+    this.contexts = create();
+  }
+
+  private List<Context> create() {
+    List<Context> contexts = new ArrayList<>();
+    for (Definition def : Natives.asList(Definition.get(extensionPoint.name()))) {
+      SimplePanel p = new SimplePanel();
+      add(p);
+      contexts.add(Context.create(def, p));
+    }
+    return contexts;
+  }
+
+  public void put(GerritUiExtensionPoint.Key key, String value) {
+    for (Context ctx : contexts) {
+      ctx.put(key.name(), value);
+    }
+  }
+
+  public void putInt(GerritUiExtensionPoint.Key key, int value) {
+    for (Context ctx : contexts) {
+      ctx.putInt(key.name(), value);
+    }
+  }
+
+  public void putBoolean(GerritUiExtensionPoint.Key key, boolean value) {
+    for (Context ctx : contexts) {
+      ctx.putBoolean(key.name(), value);
+    }
+  }
+
+  public void putObject(GerritUiExtensionPoint.Key key, JavaScriptObject value) {
+    for (Context ctx : contexts) {
+      ctx.putObject(key.name(), value);
+    }
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    for (Context ctx : contexts) {
+      try {
+        ctx.onLoad();
+      } catch (RuntimeException e) {
+        logger.log(Level.SEVERE,
+            "Failed to load extension panel for extension point "
+                + extensionPoint.name() + " from plugin " + ctx.getPluginName()
+                + ": " + e.getMessage());
+      }
+    }
+  }
+
+  @Override
+  protected void onUnload() {
+    super.onUnload();
+    for (Context ctx : contexts) {
+      for (JavaScriptObject u : Natives.asList(ctx.unload())) {
+        ApiGlue.invoke(u);
+      }
+    }
+  }
+
+  static class Definition extends JavaScriptObject {
+    static final JavaScriptObject TYPE = init();
+    private static native JavaScriptObject init() /*-{
+      function PanelDefinition(n, c) {
+        this.pluginName = n;
+        this.onLoad = c;
+      };
+      return PanelDefinition;
+    }-*/;
+
+    static native JsArray<Definition> get(String i)
+    /*-{ return $wnd.Gerrit.panels[i] || [] }-*/;
+
+    protected Definition() {
+    }
+  }
+
+  static class Context extends JavaScriptObject {
+    static final Context create(
+        Definition def,
+        SimplePanel panel) {
+      return create(TYPE, def, panel.getElement());
+    }
+
+    final native void onLoad() /*-{ this._d.onLoad(this) }-*/;
+    final native JsArray<JavaScriptObject> unload() /*-{ return this._u }-*/;
+    final native String getPluginName() /*-{ return this._d.pluginName; }-*/;
+
+    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,
+        Definition d,
+        Element e)
+    /*-{ return new T(d,e) }-*/;
+
+    private static final JavaScriptObject TYPE = init();
+    private static final native JavaScriptObject init() /*-{
+      var T = function(d,e) {
+        this._d = d;
+        this._u = [];
+        this.body = e;
+        this.p = {};
+      };
+      T.prototype = {
+        onUnload: function(f){this._u.push(f)},
+      };
+      return T;
+    }-*/;
+
+    protected Context() {
+    }
+  }
+}
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 6acb420..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,8 @@
       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))},
       get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PopupHelper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PopupHelper.java
index 95d010c..5f28e14 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PopupHelper.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PopupHelper.java
@@ -22,7 +22,6 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 class PopupHelper {
   static PopupHelper popup(ActionContext ctx, Element panel) {
@@ -34,7 +33,7 @@
 
   private final ActionButton activatingButton;
   private final FlowPanel panel;
-  private PluginSafePopupPanel popup;
+  private PopupPanel popup;
 
   PopupHelper(ActionButton button, Element child) {
     activatingButton = button;
@@ -44,7 +43,7 @@
   }
 
   void show() {
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    final PopupPanel p = new PopupPanel(true);
     p.setStyleName(Resources.I.style().popup());
     p.addAutoHidePartner(activatingButton.getElement());
     p.addCloseHandler(new CloseHandler<PopupPanel>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
index c560f6d..28943ab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
@@ -30,7 +30,6 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 abstract class ActionMessageBox extends Composite {
   interface Binder extends UiBinder<HTMLPanel, ActionMessageBox> {}
@@ -41,7 +40,7 @@
   }
 
   private final Button activatingButton;
-  private PluginSafePopupPanel popup;
+  private PopupPanel popup;
 
   @UiField Style style;
   @UiField NpTextArea message;
@@ -62,7 +61,7 @@
       return;
     }
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    final PopupPanel p = new PopupPanel(true);
     p.setStyleName(style.popup());
     p.addAutoHidePartner(activatingButton.getElement());
     p.addCloseHandler(new CloseHandler<PopupPanel>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
index 3a3ffe2..b2cac89 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
@@ -21,7 +21,6 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 class AddFileAction {
   private final Change.Id changeId;
@@ -54,7 +53,7 @@
     }
     addBox.clearPath();
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    final PopupPanel p = new PopupPanel(true);
     p.setStyleName(style.replyBox());
     p.addAutoHidePartner(addButton.getElement());
     p.addCloseHandler(new CloseHandler<PopupPanel>() {
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 d4470fd..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
@@ -18,9 +18,10 @@
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountInfo.AvatarInfo;
+import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.api.ChangeGlue;
+import com.google.gerrit.client.api.ExtensionPanel;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
@@ -35,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;
@@ -85,6 +87,8 @@
 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;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
@@ -146,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;
@@ -167,6 +174,7 @@
   @UiField Topic topic;
   @UiField Element actionText;
   @UiField Element actionDate;
+  @UiField SimplePanel changeExtension;
 
   @UiField Actions actions;
   @UiField Labels labels;
@@ -222,6 +230,7 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    addExtensionPoints();
     CallbackGroup group = new CallbackGroup();
     if (Gerrit.isSignedIn()) {
       ChangeList.query("change:" + changeId.get() + " has:draft",
@@ -258,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 830369d..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;
@@ -322,6 +321,19 @@
       height: 16px !important;
       vertical-align: bottom;
     }
+
+    .headerExtension {
+      display: inline-block;
+      float: right;
+    }
+
+    .headerExtension>div>div {
+      float: left;
+    }
+
+    .changeExtension {
+      padding-top: 5px;
+    }
   </ui:style>
 
   <g:HTMLPanel styleName='{style.cs2}'>
@@ -333,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>
 
@@ -370,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>
 
@@ -384,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'/>
@@ -472,6 +487,7 @@
           </table>
           <hr/>
           <c:Labels ui:field='labels' styleName='{style.labels}'/>
+          <g:SimplePanel ui:field='changeExtension' styleName='{style.changeExtension}'/>
           <div id='change_plugins'/>
         </td>
         <td class='{style.relatedColumn}'>
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/DeleteFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java
index ce17013..c00644c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DeleteFileAction.java
@@ -21,7 +21,6 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 class DeleteFileAction {
   private final Change.Id changeId;
@@ -51,7 +50,7 @@
     }
     deleteBox.clearPath();
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    final PopupPanel p = new PopupPanel(true);
     p.setStyleName(style.replyBox());
     p.addAutoHidePartner(deleteButton.getElement());
     p.addCloseHandler(new CloseHandler<PopupPanel>() {
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/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index dbc0620..7f4fb59 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -228,9 +228,11 @@
           new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
     } else {
       // TODO(sbeller): show only on latest revision
-      ChangeApi.change(info.legacyId().get()).view("submitted_together")
-          .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
-              info.project(), revision));
+      if (info.status().isOpen()) {
+        ChangeApi.change(info.legacyId().get()).view("submitted_together")
+            .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
+                info.project(), revision));
+      }
     }
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java
index d4b6c42..ded427a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RenameFileAction.java
@@ -21,7 +21,6 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 class RenameFileAction {
   private final Change.Id changeId;
@@ -51,7 +50,7 @@
     }
     renameBox.clearPath();
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    final PopupPanel p = new PopupPanel(true);
     p.setStyleName(style.replyBox());
     p.addAutoHidePartner(renameButton.getElement());
     p.addCloseHandler(new CloseHandler<PopupPanel>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
index cccab34..303bb47 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
@@ -28,7 +28,6 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 class ReplyAction {
   private final PatchSet.Id psId;
@@ -105,7 +104,7 @@
       replyBox.replyTo(msg);
     }
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true, false);
+    final PopupPanel p = new PopupPanel(true, false);
     p.setStyleName(style.replyBox());
     p.addAutoHidePartner(replyButton.getElement());
     p.addAutoHidePartner(quickApproveButton.getElement());
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/change/RightSidePopdownAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
index 1bf6f6c..a3b16f9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RightSidePopdownAction.java
@@ -23,7 +23,6 @@
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 
 abstract class RightSidePopdownAction {
   private final ChangeScreen.Style style;
@@ -49,7 +48,7 @@
       return;
     }
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true) {
+    final PopupPanel p = new PopupPanel(true) {
       @Override
       public void setPopupPosition(int left, int top) {
         top -= Document.get().getBodyOffsetTop();
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/config/ServerInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ServerInfo.java
index 63cf721..1820afb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ServerInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ServerInfo.java
@@ -23,6 +23,7 @@
   public final native DownloadInfo download() /*-{ return this.download; }-*/;
   public final native GerritInfo gerrit() /*-{ return this.gerrit; }-*/;
   public final native GitwebInfo gitweb() /*-{ return this.gitweb; }-*/;
+  public final native PluginConfigInfo plugin() /*-{ return this.plugin; }-*/;
   public final native SshdInfo sshd() /*-{ return this.sshd; }-*/;
   public final native SuggestInfo suggest() /*-{ return this.suggest; }-*/;
   public final native UserConfigInfo user() /*-{ return this.user; }-*/;
@@ -59,6 +60,13 @@
     }
   }
 
+  public static class PluginConfigInfo extends JavaScriptObject {
+    public final native boolean hasAvatars() /*-{ return this.has_avatars || false; }-*/;
+
+    protected PluginConfigInfo() {
+    }
+  }
+
   public static class SshdInfo extends JavaScriptObject {
     protected SshdInfo() {
     }
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/patches/PatchBrowserPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java
index e60ce76..2962fb1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchBrowserPopup.java
@@ -22,14 +22,14 @@
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.DialogBox;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
 import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.HidePopupPanelCommand;
-import com.google.gwtexpui.user.client.PluginSafeDialogBox;
 
-class PatchBrowserPopup extends PluginSafeDialogBox implements
+class PatchBrowserPopup extends DialogBox implements
     PositionCallback, ResizeHandler {
   private final Patch.Key callerKey;
   private final PatchTable fileList;
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-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
index dc27790..86c31f2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
@@ -25,6 +25,7 @@
 import com.google.gwt.event.dom.client.KeyUpHandler;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.DialogBox;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwt.user.client.ui.Label;
@@ -33,12 +34,11 @@
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.HidePopupPanelCommand;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
-import com.google.gwtexpui.user.client.PluginSafeDialogBox;
 
 /** A popup containing all projects. */
 public class ProjectListPopup {
   private HighlightingProjectsTable projectsTab;
-  private PluginSafeDialogBox popup;
+  private DialogBox popup;
   private NpTextBox filterTxt;
   private HorizontalPanel filterPanel;
   private String match;
@@ -155,7 +155,7 @@
       }
     });
 
-    popup = new PluginSafeDialogBox();
+    popup = new DialogBox();
     popup.setModal(false);
     popup.setText(popupText);
   }
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-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
index 8667cfd..5a0ad22 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
@@ -1,3 +1,2 @@
-#Tue Sep 02 16:59:24 PDT 2008
 eclipse.preferences.version=1
 line.separator=\n
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 56e5873..3c3508c 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -59,7 +59,12 @@
       <include>lib/gerrit/BUCK</include>
       <include>lib/gwt/BUCK</include>
       <excludes>
+        <exclude>**/client/</exclude>
+        <exclude>**/public/</exclude>
+        <exclude>**/*.css</exclude>
+        <exclude>**/*.png</exclude>
         <exclude>**/*.java</exclude>
+        <exclude>**/*.gwt.xml</exclude>
       </excludes>
     </fileSet>
 
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
index 8667cfd..5a0ad22 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
@@ -1,3 +1,2 @@
-#Tue Sep 02 16:59:24 PDT 2008
 eclipse.preferences.version=1
 line.separator=\n
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
index b19312c..b224bf6 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/BUCK
@@ -10,7 +10,6 @@
     'Gerrit-ApiType: plugin',
     'Gerrit-ApiVersion: ${gerritApiVersion}',
     'Gerrit-Module: ${package}.Module',
-    'Gerrit-SshModule: ${package}.SshModule',
     'Gerrit-HttpModule: ${package}.HttpModule',
   ],
 )
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/ow2/BUCK b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/ow2/BUCK
new file mode 100644
index 0000000..db6c76c
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/lib/ow2/BUCK
@@ -0,0 +1,32 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+VERSION = '5.0.3'
+
+maven_jar(
+  name = 'ow2-asm',
+  id = 'org.ow2.asm:asm:' + VERSION,
+  sha1 = 'dcc2193db20e19e1feca8b1240dbbc4e190824fa',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'ow2-asm-analysis',
+  id = 'org.ow2.asm:asm-analysis:' + VERSION,
+  sha1 = 'c7126aded0e8e13fed5f913559a0dd7b770a10f3',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'ow2-asm-tree',
+  id = 'org.ow2.asm:asm-tree:' + VERSION,
+  sha1 = '287749b48ba7162fb67c93a026d690b29f410bed',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'ow2-asm-util',
+  id = 'org.ow2.asm:asm-util:' + VERSION,
+  sha1 = '1512e5571325854b05fb1efce1db75fcced54389',
+  license = 'ow2',
+)
+
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md
index 4c56ed6..e225bab 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/resources/Documentation/build.md
@@ -30,8 +30,12 @@
   cd @PLUGIN@ && ln -s bucklets/buckversion .buckversion
 ```
 
-To build the plugin, issue the following command:
+Add link to the .watchmanconfig file:
+```
+  cd @PLUGIN@ && ln -s bucklets/watchmanconfig .watchmanconfig
+```
 
+To build the plugin, issue the following command:
 
 ```
   buck build plugin
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 bf19352..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
@@ -14,6 +14,9 @@
 
 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;
 import com.google.gwt.core.client.JavaScriptObject;
@@ -54,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() }-*/;
@@ -91,6 +98,33 @@
   private final native void screenRegex(String p, JavaScriptObject e)
   /*-{ 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
+   *        registered.
+   * @param entry callback function invoked to create the panel widgets.
+   */
+  public final void panel(GerritUiExtensionPoint extensionPoint, Panel.EntryPoint entry) {
+    panel(extensionPoint.name(), wrap(entry));
+  }
+
+  private final native void panel(String i, JavaScriptObject e)
+  /*-{ this.panel(i, e) }-*/;
+
   protected Plugin() {
   }
 
@@ -105,4 +139,11 @@
         @com.google.gerrit.plugin.client.screen.Screen::new(Lcom/google/gerrit/plugin/client/screen/Screen$Context;)(c));
     });
   }-*/;
+
+  private static final native JavaScriptObject wrap(Panel.EntryPoint b) /*-{
+    return $entry(function(c){
+      b.@com.google.gerrit.plugin.client.extension.Panel.EntryPoint::onLoad(Lcom/google/gerrit/plugin/client/extension/Panel;)(
+        @com.google.gerrit.plugin.client.extension.Panel::new(Lcom/google/gerrit/plugin/client/extension/Panel$Context;)(c));
+    });
+  }-*/;
 }
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
new file mode 100644
index 0000000..6d4e719
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java
@@ -0,0 +1,110 @@
+// 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.plugin.client.extension;
+
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+/**
+ * Panel that extends a Gerrit core screen contributed by this plugin.
+ *
+ * Panel should be registered early at module load:
+ *
+ * <pre>
+ * &#064;Override
+ * public void onModuleLoad() {
+ *   Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+ *       new Panel.EntryPoint() {
+ *         &#064;Override
+ *         public void onLoad(Panel panel) {
+ *           panel.setWidget(new Label(&quot;World&quot;));
+ *         }
+ *       });
+ * }
+ * </pre>
+ */
+public class Panel extends SimplePanel {
+  /** Initializes a panel for display. */
+  public interface EntryPoint {
+    /**
+     * Invoked when the panel has been created.
+     * <p>
+     * The implementation should create a single widget to define the content of
+     * this panel and add it to the passed panel instance.
+     * <p>
+     * To use multiple widgets, compose them in panels such as {@code FlowPanel}
+     * and add only the top level widget to the panel.
+     * <p>
+     * The panel is already attached to the browser DOM.
+     * Any widgets added to the screen will immediately receive {@code onLoad()}.
+     * GWT will fire {@code onUnload()} when the panel is removed from the UI,
+     * generally caused by the user navigating to another screen.
+     *
+     * @param panel panel that will contain the panel widget.
+     */
+    public void onLoad(Panel panel);
+  }
+
+  static final class Context extends JavaScriptObject {
+    final native Element body() /*-{ return this.body }-*/;
+
+    final native String get(String k) /*-{ return this.p[k]; }-*/;
+    final native int getInt(String k, int d) /*-{
+      return this.p.hasOwnProperty(k) ? this.p[k] : d
+    }-*/;
+    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) /*-{
+      this.onUnload($entry(function(){
+        p.@com.google.gwt.user.client.ui.Widget::onDetach()();
+      }));
+    }-*/;
+
+    protected Context() {
+    }
+  }
+
+  private final Context ctx;
+
+  Panel(Context ctx) {
+    super(ctx.body());
+    this.ctx = ctx;
+    onAttach();
+    ctx.detach(this);
+  }
+
+  public String get(GerritUiExtensionPoint.Key key) {
+    return ctx.get(key.name());
+  }
+
+  public int getInt(GerritUiExtensionPoint.Key key, int defaultValue) {
+    return ctx.getInt(key.name(), defaultValue);
+  }
+
+  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-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
index 8667cfd..5a0ad22 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
@@ -1,3 +1,2 @@
-#Tue Sep 02 16:59:24 PDT 2008
 eclipse.preferences.version=1
 line.separator=\n
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/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 6c4ec5e..d231767 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -59,10 +59,6 @@
 import java.util.Set;
 
 class GetCapabilities implements RestReadView<AccountResource> {
-  @Deprecated
-  @Option(name = "--format", usage = "(deprecated) output format")
-  private OutputFormat format;
-
   @Option(name = "-q", metaVar = "CAP", usage = "Capability to inspect")
   void addQuery(String name) {
     if (query == null) {
@@ -142,22 +138,9 @@
       }
     }
 
-    if (format == OutputFormat.TEXT) {
-      StringBuilder sb = new StringBuilder();
-      for (Map.Entry<String, Object> e : have.entrySet()) {
-        sb.append(e.getKey());
-        if (!(e.getValue() instanceof Boolean)) {
-          sb.append(": ");
-          sb.append(e.getValue().toString());
-        }
-        sb.append('\n');
-      }
-      return BinaryResult.create(sb.toString());
-    } else {
-      return OutputFormat.JSON.newGson().toJsonTree(
-        have,
-        new TypeToken<Map<String, Object>>() {}.getType());
-    }
+    return OutputFormat.JSON.newGson().toJsonTree(
+      have,
+      new TypeToken<Map<String, Object>>() {}.getType());
   }
 
   private boolean want(String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
index d181c35..837e1ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
@@ -45,6 +45,7 @@
 
   private final AccountControl accountControl;
   private final AccountLoader accountLoader;
+  private final AccountCache accountCache;
   private final ReviewDb db;
   private final boolean suggest;
   private final int suggestFrom;
@@ -71,10 +72,12 @@
   @Inject
   SuggestAccounts(AccountControl.Factory accountControlFactory,
       AccountLoader.Factory accountLoaderFactory,
+      AccountCache accountCache,
       ReviewDb db,
       @GerritServerConfig Config cfg) {
     accountControl = accountControlFactory.get();
     accountLoader = accountLoaderFactory.create(true);
+    this.accountCache = accountCache;
     this.db = db;
     this.suggestFrom = cfg.getInt("suggest", null, "from", 0);
 
@@ -111,12 +114,12 @@
     Map<Account.Id, String> queryEmail = new HashMap<>();
 
     for (Account p : db.accounts().suggestByFullName(a, b, limit)) {
-      addSuggestion(matches, p.getId());
+      addSuggestion(matches, p);
     }
     if (matches.size() < limit) {
       for (Account p : db.accounts()
           .suggestByPreferredEmail(a, b, limit - matches.size())) {
-        addSuggestion(matches, p.getId());
+        addSuggestion(matches, p);
       }
     }
     if (matches.size() < limit) {
@@ -149,11 +152,20 @@
     return m;
   }
 
-  private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account.Id id) {
+  private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account a) {
+    if (!a.isActive()) {
+      return false;
+    }
+    Account.Id id = a.getId();
     if (!map.containsKey(id) && accountControl.canSee(id)) {
       map.put(id, accountLoader.get(id));
       return true;
     }
     return false;
   }
+
+  private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account.Id id) {
+    Account a = accountCache.get(id).getAccount();
+    return addSuggestion(map, a);
+  }
 }
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/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/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index 2083c27..d4a7bfc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -90,8 +90,7 @@
         throw new ResourceConflictException(
             "cannot rebase merge commits or commit with no ancestor");
       }
-      rebaseChange.get().rebase(repo, rw, control, rsrc.getPatchSet().getId(),
-          rsrc.getUser(), findBaseRev(rw, rsrc, input));
+      rebaseChange.get().rebase(repo, rw, rsrc, findBaseRev(rw, rsrc, input));
     } catch (InvalidChangeOperationException e) {
       throw new ResourceConflictException(e.getMessage());
     } catch (NoSuchChangeException e) {
@@ -129,6 +128,7 @@
     if (baseChange == null) {
       return null;
     }
+
     if (!baseChange.getProject().equals(change.getProject())) {
       throw new ResourceConflictException(
           "base change is in wrong project: " + baseChange.getProject());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
index 5813625..030aa92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
@@ -22,7 +22,6 @@
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
@@ -97,9 +96,7 @@
    *
    * @param git the repository.
    * @param rw the RevWalk.
-   * @param changeControl the control of the change to rebase.
-   * @param patchSetId the patch set ID to rebase.
-   * @param uploader the user that creates the rebased patch set.
+   * @param rsrc revision to rebase.
    * @param newBaseRev the commit that should be the new base.
    * @throws NoSuchChangeException if the change to which the patch set belongs
    *     does not exist or is not visible to the user.
@@ -110,30 +107,33 @@
    * @throws InvalidChangeOperationException if rebase is not possible or not
    *     allowed.
    */
-  public void rebase(Repository git, RevWalk rw, ChangeControl changeControl,
-      PatchSet.Id patchSetId, IdentifiedUser uploader, String newBaseRev)
-      throws NoSuchChangeException, EmailException, OrmException, IOException,
-      ResourceConflictException, InvalidChangeOperationException {
-    Change change = changeControl.getChange();
+  public void rebase(Repository git, RevWalk rw, RevisionResource rsrc,
+      String newBaseRev) throws NoSuchChangeException, EmailException,
+          OrmException, IOException, ResourceConflictException,
+          InvalidChangeOperationException {
+    Change change = rsrc.getChange();
+    PatchSet patchSet = rsrc.getPatchSet();
+    IdentifiedUser uploader = (IdentifiedUser) rsrc.getControl().getCurrentUser();
+
     try (ObjectInserter inserter = git.newObjectInserter()) {
       String baseRev = newBaseRev;
       if (baseRev == null) {
-        baseRev = findBaseRevision(
-            patchSetId, db.get(), change.getDest(), git, rw);
+        baseRev = findBaseRevision(patchSet, change.getDest(), git, rw);
       }
+
       ObjectId baseObjectId = git.resolve(baseRev);
       if (baseObjectId == null) {
         throw new InvalidChangeOperationException(
           "Cannot rebase: Failed to resolve baseRev: " + baseRev);
       }
-      RevCommit baseCommit = rw.parseCommit(baseObjectId);
 
+      RevCommit baseCommit = rw.parseCommit(baseObjectId);
       PersonIdent committerIdent =
           uploader.newCommitterIdent(TimeUtil.nowTs(), serverTimeZone);
 
-      rebase(git, rw, inserter, change, patchSetId,
+      rebase(git, rw, inserter, change, patchSet.getId(),
           uploader, baseCommit, mergeUtilFactory.create(
-              changeControl.getProjectControl().getProjectState(), true),
+              rsrc.getControl().getProjectControl().getProjectState(), true),
           committerIdent, true, ValidatePolicy.GERRIT);
     } catch (MergeConflictException e) {
       throw new ResourceConflictException(e.getMessage());
@@ -147,9 +147,7 @@
    * this commit's parent, or the destination branch tip in the case where the
    * parent's change is merged.
    *
-   * @param patchSetId patch set ID for which the new base commit should be
-   *     found.
-   * @param db the ReviewDb.
+   * @param patchSet patch set for which the new base commit should be found.
    * @param destBranch the destination branch.
    * @param git the repository.
    * @param rw the RevWalk.
@@ -159,16 +157,10 @@
    * @throws IOException if accessing the repository fails.
    * @throws OrmException if accessing the database fails.
    */
-  private static String findBaseRevision(PatchSet.Id patchSetId,
-      ReviewDb db, Branch.NameKey destBranch, Repository git, RevWalk rw)
+  private String findBaseRevision(PatchSet patchSet,
+      Branch.NameKey destBranch, Repository git, RevWalk rw)
       throws InvalidChangeOperationException, IOException, OrmException {
     String baseRev = null;
-
-    PatchSet patchSet = db.patchSets().get(patchSetId);
-    if (patchSet == null) {
-      throw new InvalidChangeOperationException(
-          "Patch set " + patchSetId + " not found");
-    }
     RevCommit commit = rw.parseCommit(
         ObjectId.fromString(patchSet.getRevision().get()));
 
@@ -183,9 +175,9 @@
 
     RevId parentRev = new RevId(commit.getParent(0).name());
 
-    for (PatchSet depPatchSet : db.patchSets().byRevision(parentRev)) {
+    for (PatchSet depPatchSet : db.get().patchSets().byRevision(parentRev)) {
       Change.Id depChangeId = depPatchSet.getId().getParentKey();
-      Change depChange = db.changes().get(depChangeId);
+      Change depChange = db.get().changes().get(depChangeId);
       if (!depChange.getDest().equals(destBranch)) {
         continue;
       }
@@ -203,7 +195,7 @@
               + " dependent change.");
         }
         PatchSet latestDepPatchSet =
-            db.patchSets().get(depChange.currentPatchSetId());
+            db.get().patchSets().get(depChange.currentPatchSetId());
         baseRev = latestDepPatchSet.getRevision().get();
       }
       break;
@@ -343,30 +335,21 @@
     return objectId;
   }
 
-  public boolean canRebase(ChangeResource r) {
-    Change c = r.getChange();
-    return canRebase(c.getProject(), c.currentPatchSetId(), c.getDest());
-  }
-
   public boolean canRebase(RevisionResource r) {
-    return canRebase(r.getChange().getProject(),
-        r.getPatchSet().getId(), r.getChange().getDest());
+    return canRebase(r.getPatchSet(), r.getChange().getDest());
   }
 
-  public boolean canRebase(Project.NameKey project, PatchSet.Id patchSetId,
-      Branch.NameKey branch) {
-    try (Repository git = gitManager.openRepository(project)) {
-      try (RevWalk rw = new RevWalk(git)) {
-        findBaseRevision(patchSetId, db.get(), branch, git, rw);
-        return true;
-      } catch (InvalidChangeOperationException e) {
-        return false;
-      } catch (OrmException | IOException e) {
-        log.warn("Error checking if patch set " + patchSetId + " on " + branch
-            + " can be rebased", e);
-        return false;
-      }
-    } catch (IOException err) {
+  private boolean canRebase(PatchSet patchSet, Branch.NameKey dest) {
+    try (Repository git = gitManager.openRepository(dest.getParentKey());
+        RevWalk rw = new RevWalk(git)) {
+      findBaseRevision(patchSet, dest, git, rw);
+      return true;
+    } catch (InvalidChangeOperationException e) {
+      return false;
+    } catch (OrmException | IOException e) {
+      log.warn(String.format(
+          "Error checking if patch set %s on %s can be rebased",
+          patchSet.getId(), dest), e);
       return false;
     }
   }
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 2a7d03e..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
@@ -32,6 +32,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 
@@ -60,12 +61,14 @@
     try {
       ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(),
           ChangeSet.create(resource.getChange()));
-      return json.create(EnumSet.of(
-          ListChangesOption.CURRENT_REVISION,
-          ListChangesOption.CURRENT_COMMIT,
-          ListChangesOption.DETAILED_LABELS,
-          ListChangesOption.LABELS))
-        .format(cs.ids());
+      if (cs.ids().size() > 1) {
+        return json.create(EnumSet.of(
+            ListChangesOption.CURRENT_REVISION,
+            ListChangesOption.CURRENT_COMMIT))
+          .format(cs.ids());
+      } else {
+        return Collections.emptyList();
+      }
     } catch (OrmException | IOException e) {
       log.error("Error on getting a ChangeSet", e);
       throw e;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 970bb41..e1b0db0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -74,6 +74,7 @@
 import com.google.gerrit.server.change.MergeabilityCacheImpl;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.EmailMerge;
 import com.google.gerrit.server.git.GitModule;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.NotesBranchUtil;
@@ -192,6 +193,7 @@
     factory(GroupDetailFactory.Factory.class);
     factory(GroupInfoCacheFactory.Factory.class);
     factory(GroupMembers.Factory.class);
+    factory(EmailMerge.Factory.class);
     factory(MergedSender.Factory.class);
     factory(MergeFailSender.Factory.class);
     factory(MergeUtil.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index 5934759..8c9b862 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -24,11 +24,13 @@
 import com.google.gerrit.extensions.config.CloneCommand;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.avatar.AvatarProvider;
 import com.google.gerrit.server.change.ArchiveFormat;
 import com.google.gerrit.server.change.GetArchive;
 import com.google.gerrit.server.change.Submit;
@@ -56,6 +58,7 @@
   private final AllUsersName allUsersName;
   private final String anonymousCowardName;
   private final GitwebConfig gitwebConfig;
+  private final DynamicItem<AvatarProvider> avatar;
 
   @Inject
   public GetServerInfo(
@@ -69,7 +72,8 @@
       AllProjectsName allProjectsName,
       AllUsersName allUsersName,
       @AnonymousCowardName String anonymousCowardName,
-      GitwebConfig gitwebConfig) {
+      GitwebConfig gitwebConfig,
+      DynamicItem<AvatarProvider> avatar) {
     this.config = config;
     this.authConfig = authConfig;
     this.realm = realm;
@@ -81,6 +85,7 @@
     this.allUsersName = allUsersName;
     this.anonymousCowardName = anonymousCowardName;
     this.gitwebConfig = gitwebConfig;
+    this.avatar = avatar;
   }
 
   @Override
@@ -94,6 +99,7 @@
             archiveFormats);
     info.gerrit = getGerritInfo(config, allProjectsName, allUsersName);
     info.gitweb = getGitwebInfo(gitwebConfig);
+    info.plugin = getPluginInfo();
     info.sshd = getSshdInfo(config);
     info.suggest = getSuggestInfo(config);
     info.user = getUserInfo(anonymousCowardName);
@@ -255,6 +261,12 @@
     return info;
   }
 
+  private PluginConfigInfo getPluginInfo() {
+    PluginConfigInfo info = new PluginConfigInfo();
+    info.hasAvatars = toBoolean(avatar.get() != null);
+    return info;
+  }
+
   private SshdInfo getSshdInfo(Config cfg) {
     String[] addr = cfg.getStringList("sshd", null, "listenAddress");
     if (addr.length == 1 && isOff(addr[0])) {
@@ -298,6 +310,7 @@
     public DownloadInfo download;
     public GerritInfo gerrit;
     public GitwebInfo gitweb;
+    public PluginConfigInfo plugin;
     public SshdInfo sshd;
     public SuggestInfo suggest;
     public UserConfigInfo user;
@@ -357,6 +370,10 @@
     public GitwebType type;
   }
 
+  public static class PluginConfigInfo {
+    public Boolean hasAvatars;
+  }
+
   public static class SshdInfo {
   }
 
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
new file mode 100644
index 0000000..7b1161c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
@@ -0,0 +1,119 @@
+// 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.git;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Account;
+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.mail.MergedSender;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+import com.google.inject.assistedinject.Assisted;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutorService;
+
+public class EmailMerge implements Runnable, RequestContext {
+  private static final Logger log = LoggerFactory.getLogger(EmailMerge.class);
+
+  public interface Factory {
+    EmailMerge create(Change.Id changeId, Account.Id submitter);
+  }
+
+  private final ExecutorService sendEmailsExecutor;
+  private final MergedSender.Factory mergedSenderFactory;
+  private final SchemaFactory<ReviewDb> schemaFactory;
+  private final ThreadLocalRequestContext requestContext;
+
+  private final Change.Id changeId;
+  private final Account.Id submitter;
+  private ReviewDb db;
+
+  @Inject
+  EmailMerge(@EmailReviewCommentsExecutor ExecutorService executor,
+      MergedSender.Factory mergedSenderFactory,
+      SchemaFactory<ReviewDb> schemaFactory,
+      ThreadLocalRequestContext requestContext,
+      @Assisted Change.Id changeId,
+      @Assisted @Nullable Account.Id submitter) {
+    this.sendEmailsExecutor = executor;
+    this.mergedSenderFactory = mergedSenderFactory;
+    this.schemaFactory = schemaFactory;
+    this.requestContext = requestContext;
+    this.changeId = changeId;
+    this.submitter = submitter;
+  }
+
+  void sendAsync() {
+    sendEmailsExecutor.submit(this);
+  }
+
+  @Override
+  public void run() {
+    RequestContext old = requestContext.setContext(this);
+    try {
+      MergedSender cm = mergedSenderFactory.create(changeId);
+      if (submitter != null) {
+        cm.setFrom(submitter);
+      }
+      cm.send();
+    } catch (Exception e) {
+      log.error("Cannot email merged notification for " + changeId, e);
+    } finally {
+      requestContext.setContext(old);
+      if (db != null) {
+        db.close();
+        db = null;
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "send-email merged";
+  }
+
+  @Override
+  public CurrentUser getCurrentUser() {
+    throw new OutOfScopeException("No user on email thread");
+  }
+
+  @Override
+  public Provider<ReviewDb> getReviewDbProvider() {
+    return new Provider<ReviewDb>() {
+      @Override
+      public ReviewDb get() {
+        if (db == null) {
+          try {
+            db = schemaFactory.open();
+          } catch (OrmException e) {
+            throw new ProvisionException("Cannot open ReviewDb", e);
+          }
+        }
+        return db;
+      }
+    };
+  }
+}
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 942dcf0..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
@@ -45,13 +45,9 @@
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.config.GerritRequestModule;
-import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
 import com.google.gerrit.server.git.strategy.SubmitStrategy;
@@ -59,7 +55,6 @@
 import com.google.gerrit.server.git.validators.MergeValidationException;
 import com.google.gerrit.server.git.validators.MergeValidators;
 import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.mail.MergedSender;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -72,21 +67,10 @@
 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.gerrit.server.ssh.SshInfo;
-import com.google.gerrit.server.util.RequestContext;
-import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.servlet.RequestScoped;
-
-import com.jcraft.jsch.HostKey;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -106,18 +90,15 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.net.SocketAddress;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.Callable;
 
 /**
  * Merges changes in submission order into a single branch.
@@ -148,22 +129,19 @@
   private final GitRepositoryManager repoManager;
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final LabelNormalizer labelNormalizer;
-  private final MergedSender.Factory mergedSenderFactory;
+  private final EmailMerge.Factory mergedSenderFactory;
   private final MergeSuperSet mergeSuperSet;
   private final MergeValidators.Factory mergeValidatorsFactory;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final ProjectCache projectCache;
   private final InternalChangeQuery internalChangeQuery;
-  private final SchemaFactory<ReviewDb> schemaFactory;
   private final PersonIdent serverIdent;
   private final SubmitStrategyFactory submitStrategyFactory;
   private final Provider<SubmoduleOp> subOpProvider;
   private final TagCache tagCache;
-  private final WorkQueue workQueue;
 
   private final Map<Change.Id, List<SubmitRecord>> records;
   private final Map<Change.Id, CodeReviewCommit> commits;
-  private final PerThreadRequestScope.Scoper threadScoper;
   private String logPrefix;
 
   private ProjectState destProject;
@@ -184,25 +162,22 @@
       ChangeData.Factory changeDataFactory,
       ChangeHooks hooks,
       ChangeIndexer indexer,
-      Injector injector,
       ChangeMessagesUtil cmUtil,
       ChangeUpdate.Factory updateFactory,
       GitReferenceUpdated gitRefUpdated,
       GitRepositoryManager repoManager,
       IdentifiedUser.GenericFactory identifiedUserFactory,
       LabelNormalizer labelNormalizer,
-      MergedSender.Factory mergedSenderFactory,
+      EmailMerge.Factory mergedSenderFactory,
       MergeSuperSet mergeSuperSet,
       MergeValidators.Factory mergeValidatorsFactory,
       PatchSetInfoFactory patchSetInfoFactory,
       ProjectCache projectCache,
       InternalChangeQuery internalChangeQuery,
-      SchemaFactory<ReviewDb> schemaFactory,
       @GerritPersonIdent PersonIdent serverIdent,
       SubmitStrategyFactory submitStrategyFactory,
       Provider<SubmoduleOp> subOpProvider,
-      TagCache tagCache,
-      WorkQueue workQueue) {
+      TagCache tagCache) {
     this.accountCache = accountCache;
     this.approvalsUtil = approvalsUtil;
     this.changeControlFactory = changeControlFactory;
@@ -221,67 +196,17 @@
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.projectCache = projectCache;
     this.internalChangeQuery = internalChangeQuery;
-    this.schemaFactory = schemaFactory;
     this.serverIdent = serverIdent;
     this.submitStrategyFactory = submitStrategyFactory;
     this.subOpProvider = subOpProvider;
     this.tagCache = tagCache;
-    this.workQueue = workQueue;
+
     commits = new HashMap<>();
     pendingRefUpdates = new HashMap<>();
     openBranches = new HashMap<>();
     pendingRefUpdates = new HashMap<>();
     records = new HashMap<>();
     mergeTips = new HashMap<>();
-
-    Injector child = injector.createChildInjector(new AbstractModule() {
-      @Override
-      protected void configure() {
-        bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
-        bind(RequestScopePropagator.class)
-            .to(PerThreadRequestScope.Propagator.class);
-        bind(PerThreadRequestScope.Propagator.class);
-        install(new GerritRequestModule());
-
-        bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
-            new Provider<SocketAddress>() {
-              @Override
-              public SocketAddress get() {
-                throw new OutOfScopeException("No remote peer on merge thread");
-              }
-            });
-        bind(SshInfo.class).toInstance(new SshInfo() {
-          @Override
-          public List<HostKey> getHostKeys() {
-            return Collections.emptyList();
-          }
-        });
-      }
-
-      @Provides
-      public PerThreadRequestScope.Scoper provideScoper(
-          final PerThreadRequestScope.Propagator propagator,
-          final Provider<RequestScopedReviewDbProvider> dbProvider) {
-        final RequestContext requestContext = new RequestContext() {
-          @Override
-          public CurrentUser getCurrentUser() {
-            throw new OutOfScopeException("No user on merge thread");
-          }
-
-          @Override
-          public Provider<ReviewDb> getReviewDbProvider() {
-            return dbProvider.get();
-          }
-        };
-        return new PerThreadRequestScope.Scoper() {
-          @Override
-          public <T> Callable<T> scope(Callable<T> callable) {
-            return propagator.scope(requestContext, callable);
-          }
-        };
-      }
-    });
-    threadScoper = child.getInstance(PerThreadRequestScope.Scoper.class);
   }
 
   private void setDestProject(Branch.NameKey destBranch) throws MergeException {
@@ -300,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)
@@ -312,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()));
     }
@@ -320,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:
@@ -336,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()));
             }
           }
@@ -378,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;
@@ -400,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);
@@ -1020,20 +960,15 @@
       db.rollback();
     }
     update.commit();
-    final Change change = c;
-    try {
-      threadScoper.scope(new Callable<Void>(){
-        @Override
-        public Void call() throws Exception {
-          sendMergedEmail(change, submitter);
-          return null;
-        }
-      }).call();
-    } catch (Exception e) {
-      logError("internal server error", e);
-    }
-
     indexer.index(db, c);
+
+    try {
+      mergedSenderFactory.create(
+          c.getId(),
+          submitter != null ? submitter.getAccountId() : null).sendAsync();
+    } catch (Exception e) {
+      log.error("Cannot email merged notification for " + c.getId(), e);
+    }
     if (submitter != null && mergeResultRev != null) {
       try {
         hooks.doChangeMergedHook(c,
@@ -1188,38 +1123,6 @@
     }
   }
 
-  private void sendMergedEmail(final Change c, final PatchSetApproval from) {
-    workQueue.getDefaultQueue()
-        .submit(new Runnable() {
-      @Override
-      public void run() {
-        PatchSet patchSet;
-        try (ReviewDb reviewDb = schemaFactory.open()) {
-          patchSet = reviewDb.patchSets().get(c.currentPatchSetId());
-        } catch (Exception e) {
-          logError("Cannot send email for submitted patch set " + c.getId(), e);
-          return;
-        }
-
-        try {
-          MergedSender cm = mergedSenderFactory.create(c.getId());
-          if (from != null) {
-            cm.setFrom(from.getAccountId());
-          }
-          cm.setPatchSet(patchSet);
-          cm.send();
-        } catch (Exception e) {
-          logError("Cannot send email for submitted patch set " + c.getId(), e);
-        }
-      }
-
-      @Override
-      public String toString() {
-        return "send-email merged";
-      }
-    });
-  }
-
   private ChangeControl changeControl(Change c) throws NoSuchChangeException {
     return changeControlFactory.controlFor(
         c, identifiedUserFactory.create(c.getOwner()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
index c525799..70e7ab4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
@@ -138,7 +138,7 @@
 
           if (!hashes.isEmpty()) {
             // Merged changes are ok to exclude
-            List<ChangeData> destChanges = queryProvider.get()
+            Iterable<ChangeData> destChanges = queryProvider.get()
                 .byCommitsOnBranchNotMerged(cd.change().getDest(), hashes);
 
             for (ChangeData chd : destChanges) {
@@ -193,4 +193,4 @@
     logError(msg);
     throw new OrmException(msg);
   }
-}
\ No newline at end of file
+}
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/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index b3973d2..daf0c10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -303,23 +303,38 @@
         }
       };
 
-  /** Commit id of any PatchSet on the change */
+  /** Commit ID of any patch set on the change, using prefix match. */
   public static final FieldDef<ChangeData, Iterable<String>> COMMIT =
       new FieldDef.Repeatable<ChangeData, String>(
           ChangeQueryBuilder.FIELD_COMMIT, FieldType.PREFIX, false) {
         @Override
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
-          Set<String> revisions = Sets.newHashSet();
-          for (PatchSet ps : input.patchSets()) {
-            if (ps.getRevision() != null) {
-              revisions.add(ps.getRevision().get());
-            }
-          }
-          return revisions;
+          return getRevisions(input);
         }
       };
 
+  /** Commit ID of any patch set on the change, using exact match. */
+  public static final FieldDef<ChangeData, Iterable<String>> EXACT_COMMIT =
+      new FieldDef.Repeatable<ChangeData, String>(
+          "exactcommit", FieldType.EXACT, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return getRevisions(input);
+        }
+      };
+
+  private static Set<String> getRevisions(ChangeData cd) throws OrmException {
+    Set<String> revisions = Sets.newHashSet();
+    for (PatchSet ps : cd.patchSets()) {
+      if (ps.getRevision() != null) {
+        revisions.add(ps.getRevision().get());
+      }
+    }
+    return revisions;
+  }
+
   /** Tracking id extracted from a footer. */
   public static final FieldDef<ChangeData, Iterable<String>> TR =
       new FieldDef.Repeatable<ChangeData, String>(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index 200b7ff..2e85847 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -241,6 +241,7 @@
       ChangeField.EDITBY,
       ChangeField.REVIEWEDBY);
 
+  @Deprecated
   static final Schema<ChangeData> V21 = schema(
       ChangeField.LEGACY_ID,
       ChangeField.ID,
@@ -273,6 +274,40 @@
       ChangeField.EDITBY,
       ChangeField.REVIEWEDBY);
 
+  static final Schema<ChangeData> V22 = schema(
+      ChangeField.LEGACY_ID,
+      ChangeField.ID,
+      ChangeField.STATUS,
+      ChangeField.PROJECT,
+      ChangeField.PROJECTS,
+      ChangeField.REF,
+      ChangeField.EXACT_TOPIC,
+      ChangeField.FUZZY_TOPIC,
+      ChangeField.UPDATED,
+      ChangeField.FILE_PART,
+      ChangeField.PATH,
+      ChangeField.OWNER,
+      ChangeField.REVIEWER,
+      ChangeField.COMMIT,
+      ChangeField.TR,
+      ChangeField.LABEL,
+      ChangeField.COMMIT_MESSAGE,
+      ChangeField.COMMENT,
+      ChangeField.CHANGE,
+      ChangeField.APPROVAL,
+      ChangeField.MERGEABLE,
+      ChangeField.ADDED,
+      ChangeField.DELETED,
+      ChangeField.DELTA,
+      ChangeField.HASHTAG,
+      ChangeField.COMMENTBY,
+      ChangeField.PATCH_SET,
+      ChangeField.GROUP,
+      ChangeField.EDITBY,
+      ChangeField.REVIEWEDBY,
+      ChangeField.EXACT_COMMIT);
+
+
   private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
     return new Schema<>(ImmutableList.copyOf(fields));
   }
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/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index fa138fe..5210577 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -61,8 +61,8 @@
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
@@ -431,7 +431,10 @@
 
   @Operator
   public Predicate<ChangeData> commit(String id) {
-    return new CommitPredicate(AbbreviatedObjectId.fromString(id));
+    if (id.length() == Constants.OBJECT_ID_STRING_LENGTH) {
+      return new ExactCommitPredicate(id);
+    }
+    return new CommitPrefixPredicate(id);
   }
 
   @Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPrefixPredicate.java
similarity index 67%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPrefixPredicate.java
index 3dd7c61..5966651 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPrefixPredicate.java
@@ -19,25 +19,19 @@
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-import org.eclipse.jgit.lib.ObjectId;
-
-class CommitPredicate extends IndexPredicate<ChangeData> {
-  private final AbbreviatedObjectId abbrevId;
-
-  CommitPredicate(AbbreviatedObjectId id) {
-    super(ChangeField.COMMIT, id.name());
-    this.abbrevId = id;
+class CommitPrefixPredicate extends IndexPredicate<ChangeData> {
+  CommitPrefixPredicate(String prefix) {
+    super(ChangeField.COMMIT, prefix);
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
+    String prefix = getValue().toLowerCase();
     for (PatchSet p : object.patchSets()) {
-      if (p.getRevision() != null && p.getRevision().get() != null) {
-        final ObjectId id = ObjectId.fromString(p.getRevision().get());
-        if (abbrevId.prefixCompare(id) == 0) {
-          return true;
-        }
+      if (p.getRevision() != null
+          && p.getRevision().get() != null
+          && p.getRevision().get().startsWith(prefix)) {
+        return true;
       }
     }
     return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactCommitPredicate.java
similarity index 64%
copy from gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactCommitPredicate.java
index 3dd7c61..0528a13 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactCommitPredicate.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 The Android Open Source Project
+// 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.
@@ -19,25 +19,20 @@
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-import org.eclipse.jgit.lib.ObjectId;
+import java.util.Objects;
 
-class CommitPredicate extends IndexPredicate<ChangeData> {
-  private final AbbreviatedObjectId abbrevId;
-
-  CommitPredicate(AbbreviatedObjectId id) {
-    super(ChangeField.COMMIT, id.name());
-    this.abbrevId = id;
+class ExactCommitPredicate extends IndexPredicate<ChangeData> {
+  ExactCommitPredicate(String id) {
+    super(ChangeField.COMMIT, id);
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
+    String id = getValue().toLowerCase();
     for (PatchSet p : object.patchSets()) {
-      if (p.getRevision() != null && p.getRevision().get() != null) {
-        final ObjectId id = ObjectId.fromString(p.getRevision().get());
-        if (abbrevId.prefixCompare(id) == 0) {
-          return true;
-        }
+      if (p.getRevision() != null
+          && Objects.equals(p.getRevision().get(), id)) {
+        return true;
       }
     }
     return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 77a24cc..4a612dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -15,10 +15,14 @@
 package com.google.gerrit.server.query.change;
 
 import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.or;
 import static com.google.gerrit.server.query.Predicate.not;
+import static com.google.gerrit.server.query.Predicate.or;
 import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -31,7 +35,6 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.util.ArrayList;
@@ -63,8 +66,8 @@
     return new ChangeStatusPredicate(status);
   }
 
-  private static Predicate<ChangeData> commit(AbbreviatedObjectId id) {
-    return new CommitPredicate(id);
+  private static Predicate<ChangeData> commit(String id) {
+    return new ExactCommitPredicate(id);
   }
 
   private final QueryProcessor qp;
@@ -116,17 +119,38 @@
         open()));
   }
 
-  public List<ChangeData> byCommitsOnBranchNotMerged(Branch.NameKey branch,
+  public Iterable<ChangeData> byCommitsOnBranchNotMerged(Branch.NameKey branch,
       List<String> hashes) throws OrmException {
-    List<Predicate<ChangeData>> commits = new ArrayList<>();
+    return byCommitsOnBranchNotMerged(branch, hashes, 100);
+  }
+
+  @VisibleForTesting
+  Iterable<ChangeData> byCommitsOnBranchNotMerged(Branch.NameKey branch,
+      List<String> hashes, int batchSize) throws OrmException {
+    List<Predicate<ChangeData>> commits = new ArrayList<>(hashes.size());
     for (String s : hashes) {
-      commits.add(commit(AbbreviatedObjectId.fromString(s)));
+      commits.add(commit(s));
     }
-    return query(and(
-        ref(branch),
-        project(branch.getParentKey()),
-        not(status(Change.Status.MERGED)),
-        or(commits)));
+    int numBatches = (hashes.size() / batchSize) + 1;
+    List<Predicate<ChangeData>> queries = new ArrayList<>(numBatches);
+    for (List<Predicate<ChangeData>> batch : Iterables.partition(commits, batchSize)) {
+      queries.add(and(
+            ref(branch),
+            project(branch.getParentKey()),
+            not(status(Change.Status.MERGED)),
+            or(batch)));
+    }
+    try {
+      return FluentIterable.from(qp.queryChanges(queries))
+        .transformAndConcat(new Function<QueryResult, List<ChangeData>>() {
+          @Override
+          public List<ChangeData> apply(QueryResult in) {
+            return in.changes();
+          }
+        });
+    } catch (QueryParseException e) {
+      throw new OrmException(e);
+    }
   }
 
   public List<ChangeData> byProjectOpen(Project.NameKey project)
@@ -139,12 +163,8 @@
     return query(and(new ExactTopicPredicate(schema(indexes), topic), open()));
   }
 
-  public List<ChangeData> byCommitPrefix(String prefix) throws OrmException {
-    return query(commit(AbbreviatedObjectId.fromString(prefix)));
-  }
-
   public List<ChangeData> byCommit(ObjectId id) throws OrmException {
-    return query(commit(AbbreviatedObjectId.fromObjectId(id)));
+    return query(commit(id.name()));
   }
 
   public List<ChangeData> byProjectGroups(Project.NameKey project,
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-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_110.java
similarity index 65%
rename from gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_110.java
index d242db6..9e0f112 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_110.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// 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.
@@ -12,10 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gwtexpui.user.client;
+package com.google.gerrit.server.schema;
 
-import com.google.gwt.event.shared.EventHandler;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-public interface DialogVisibleHandler extends EventHandler {
-  public void onDialogVisible(DialogVisibleEvent event);
+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/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 330cb4b..1b993df 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -83,6 +83,7 @@
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
@@ -117,6 +118,7 @@
   @Inject protected IdentifiedUser.GenericFactory userFactory;
   @Inject protected InMemoryDatabase schemaFactory;
   @Inject protected InMemoryRepositoryManager repoManager;
+  @Inject protected InternalChangeQuery internalChangeQuery;
   @Inject protected NotesMigration notesMigration;
   @Inject protected ProjectControl.GenericFactory projectControlFactory;
   @Inject protected SchemaCreator schemaCreator;
@@ -1136,6 +1138,40 @@
     assertThat(actual.get(1).reviewed).isTrue();
   }
 
+  @Test
+  public void byCommitsOnBranchNotMerged() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    int n = 10;
+    List<String> shas = new ArrayList<>(n);
+    List<Integer> expectedIds = new ArrayList<>(n);
+    Branch.NameKey dest = null;
+    for (int i = 0; i < n; i++) {
+      ChangeInserter ins = newChange(repo, null, null, null, null);
+      ins.insert();
+      if (dest == null) {
+        dest = ins.getChange().getDest();
+      }
+      shas.add(ins.getPatchSet().getRevision().get());
+      expectedIds.add(ins.getChange().getId().get());
+    }
+
+    for (int i = 1; i <= 11; i++) {
+      Iterable<ChangeData> cds =
+          internalChangeQuery.byCommitsOnBranchNotMerged(dest, shas, i);
+      Iterable<Integer> ids = FluentIterable.from(cds).transform(
+          new Function<ChangeData, Integer>() {
+            @Override
+            public Integer apply(ChangeData in) {
+              return in.getId().get();
+            }
+          });
+      String name = "batch size " + i;
+      assertThat(ids).named(name).hasSize(n);
+      assertThat(ids).named(name)
+          .containsExactlyElementsIn(expectedIds);
+    }
+  }
+
   protected ChangeInserter newChange(
       TestRepository<Repo> repo,
       @Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
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-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 7dc558e..3ad5156 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -46,15 +46,6 @@
 @RequiresCapability(GlobalCapability.CREATE_PROJECT)
 @CommandMetaData(name = "create-project", description = "Create a new project and associated Git repository")
 final class CreateProjectCommand extends SshCommand {
-  @Option(name = "--name", aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created (deprecated option)")
-  void setProjectNameFromOption(String name) {
-    if (projectName != null) {
-      throw new IllegalArgumentException("NAME already supplied");
-    } else {
-      projectName = name;
-    }
-  }
-
   @Option(name = "--suggest-parents", aliases = {"-S"}, usage = "suggest parent candidates, "
       + "if this option is used all other options and arguments are ignored")
   private boolean suggestParent;
@@ -129,16 +120,8 @@
       usage = "plugin configuration parameter with format '<plugin-name>.<parameter-name>=<value>'")
   private List<String> pluginConfigValues;
 
-  private String projectName;
-
   @Argument(index = 0, metaVar = "NAME", usage = "name of project to be created")
-  void setProjectNameFromArgument(String name) {
-    if (projectName != null) {
-      throw new IllegalArgumentException("--name already supplied");
-    } else {
-      projectName = name;
-    }
-  }
+  private String projectName;
 
   @Inject
   private GerritApi gApi;
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 6b61e07..2ff713f 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit 6b61e07bb1c8ad99c6288e72d579bfcd704b0860
+Subproject commit 2ff713f3d3f0bbabf00b73c74705cd3741f61a70
diff --git a/plugins/download-commands b/plugins/download-commands
index 334f725..99e61fb 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 334f7253855551bea40f0f27b68e3d7aa71fe4af
+Subproject commit 99e61fb06a4505a9558c23a56213cb32ceaa9cca
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>