Merge "Bump database schema version"
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 f95d9d1..610b65f 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3772,6 +3772,18 @@
 'upload-pack' on the server.
 
 
+[[submodule]]
+=== Section submodule
+
+[[submodule.verbosesuperprojectupdate]]submodule.verboseSuperprojectUpdate
++
+When using link:user-submodules.html#automatic_update[automatic superproject updates]
+this option will determine if the submodule commit messages are included into
+the commit message of the superproject update.
++
+By default this is true.
+
+
 [[user]]
 === Section user
 
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/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 4224c76..d752c85 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
 ----
@@ -3944,25 +3948,25 @@
 === CommitInfo
 The `CommitInfo` entity contains information about a commit.
 
-[options="header",cols="1,6"]
-|==========================
-|Field Name    |Description
-|`commit`      |The commit ID.
-|`parents`     |
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
+|`commit`      ||The commit ID.
+|`parents`     ||
 The parent commits of this commit as a list of
 link:#commit-info[CommitInfo] entities. In each parent
 only the `commit` and `subject` fields are populated.
-|`author`      |The author of the commit as a
+|`author`      ||The author of the commit as a
 link:#git-person-info[GitPersonInfo] entity.
-|`committer`   |The committer of the commit as a
+|`committer`   ||The committer of the commit as a
 link:#git-person-info[GitPersonInfo] entity.
-|`subject`     |
+|`subject`     ||
 The subject of the commit (header line of the commit message).
-|`message`     |The commit message.
+|`message`     ||The commit message.
 |`web_links`   |optional|
 Links to the commit in external sites as a list of
 link:#web-link-info[WebLinkInfo] entities.
-|==========================
+|===========================
 
 [[diff-content]]
 === DiffContent
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/Documentation/user-submodules.txt b/Documentation/user-submodules.txt
index 8411595..151ac71 100644
--- a/Documentation/user-submodules.txt
+++ b/Documentation/user-submodules.txt
@@ -97,6 +97,7 @@
 gitlinks and a .gitmodules file with all required info) and if so,
 a new submodule subscription is registered.
 
+[[automatic_update]]
 == Automatic Update of Superprojects
 
 After a superproject is subscribed to a submodule, it is not
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..f7f7192 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;
 
@@ -470,4 +472,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/git/SubmoduleSubscriptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index ad63e3c..707852f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -16,6 +16,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.gerrit.acceptance.GerritConfig;
+
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -50,6 +52,31 @@
   }
 
   @Test
+  @GerritConfig(name = "submodule.verboseSuperprojectUpdate", value = "false")
+  public void testSubmoduleShortCommitMessage() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+    pushChangeTo(subRepo, "master");
+    createSubscription(superRepo, "master", "subscribed-to-project", "master");
+
+    // The first update doesn't include any commit messages
+    ObjectId subRepoId = pushChangeTo(subRepo, "master");
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subRepoId);
+    expectToHaveCommitMessage(superRepo, "master",
+        "Updated git submodules\n\n");
+
+    // Any following update also has a short message
+    subRepoId = pushChangeTo(subRepo, "master");
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subRepoId);
+    expectToHaveCommitMessage(superRepo, "master",
+        "Updated git submodules\n\n");
+  }
+
+  @Test
+
   public void testSubmoduleCommitMessage() throws Exception {
     TestRepository<?> superRepo = createProjectWithPush("super-project");
     TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
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-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%
rename from gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/DialogVisibleHandler.java
rename 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 4edcfa65..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 9f7e515..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
@@ -215,12 +215,8 @@
         EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
         new TabChangeListCallback(Tab.CHERRY_PICKS, info.project(), revision));
 
-    if (Gerrit.info().change().isSubmitWholeTopicEnabled()) {
-      // TODO(sbeller): show only on latest revision
-      ChangeApi.change(info.legacyId().get()).view("submitted_together")
-          .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
-              info.project(), revision));
-    } else if (info.topic() != null && !"".equals(info.topic())) {
+    if (!Gerrit.info().change().isSubmitWholeTopicEnabled()
+        && info.topic() != null && !"".equals(info.topic())) {
       StringBuilder topicQuery = new StringBuilder();
       topicQuery.append("status:open");
       topicQuery.append(" ").append(op("topic", info.topic()));
@@ -230,6 +226,13 @@
                      ListChangesOption.DETAILED_LABELS,
                      ListChangesOption.LABELS),
           new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
+    } else {
+      // TODO(sbeller): show only on latest 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..9f51d4a 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;
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/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-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
index 2744877..18d3443 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
@@ -20,8 +20,10 @@
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.ui.CheckBox;
@@ -30,14 +32,14 @@
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
 
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 
 public abstract class RebaseDialog extends CommentedActionDialog {
   private final SuggestBox base;
-  private final CheckBox cb;
-  private List<ChangeInfo> changes;
+  private final CheckBox changeParent;
+  private List<ChangeInfo> candidateChanges;
   private final boolean sendEnabled;
 
   public RebaseDialog(final String project, final String branch,
@@ -46,13 +48,15 @@
     this.sendEnabled = sendEnabled;
     sendButton.setText(Util.C.buttonRebaseChangeSend());
 
-    // create the suggestion box
+    // Create the suggestion box to filter over a list of recent changes
+    // open on the same branch. The list of candidates is primed by the
+    // changeParent CheckBox (below) getting enabled by the user.
     base = new SuggestBox(new HighlightSuggestOracle() {
       @Override
       protected void onRequestSuggestions(Request request, Callback done) {
         String query = request.getQuery().toLowerCase();
-        LinkedList<ChangeSuggestion> suggestions = new LinkedList<>();
-        for (final ChangeInfo ci : changes) {
+        List<ChangeSuggestion> suggestions = new ArrayList<>();
+        for (ChangeInfo ci : candidateChanges) {
           if (changeId.equals(ci.legacyId())) {
             continue;  // do not suggest current change
           }
@@ -71,23 +75,32 @@
         Util.C.rebasePlaceholderMessage());
     base.setStyleName(Gerrit.RESOURCES.css().rebaseSuggestBox());
 
-    // the checkbox which must be clicked before the change list is populated
-    cb = new CheckBox(Util.C.rebaseConfirmMessage());
-    cb.addClickHandler(new ClickHandler() {
+    // The changeParent checkbox must be clicked to load into browser memory
+    // a list of open changes from the same project and same branch that this
+    // change may rebase onto.
+    changeParent = new CheckBox(Util.C.rebaseConfirmMessage());
+    changeParent.addClickHandler(new ClickHandler() {
       @Override
       public void onClick(ClickEvent event) {
-        boolean checked = ((CheckBox) event.getSource()).getValue();
-        if (checked) {
+        if (changeParent.getValue()) {
           ChangeList.query(
-              "project:" + project + " AND branch:" + branch
-                  + " AND is:open NOT age:90d",
+              PageLinks.projectQuery(new Project.NameKey(project))
+                  + " " + PageLinks.op("branch", branch)
+                  + " is:open -age:90d",
               Collections.<ListChangesOption> emptySet(),
               new GerritCallback<ChangeList>() {
                 @Override
                 public void onSuccess(ChangeList result) {
-                  changes = Natives.asList(result);
+                  candidateChanges = Natives.asList(result);
                   updateControls(true);
                 }
+
+                @Override
+                public void onFailure(Throwable err) {
+                  updateControls(false);
+                  changeParent.setValue(false);
+                  super.onFailure(err);
+                }
               });
         } else {
           updateControls(false);
@@ -96,7 +109,7 @@
     });
 
     // add the checkbox and suggestbox widgets to the content panel
-    contentPanel.add(cb);
+    contentPanel.add(changeParent);
     contentPanel.add(base);
     contentPanel.setStyleName(Gerrit.RESOURCES.css().rebaseContentPanel());
   }
@@ -128,7 +141,7 @@
   }
 
   public String getBase() {
-    return cb.getValue() ? base.getText() : null;
+    return changeParent.getValue() ? base.getText() : null;
   }
 
   private static class ChangeSuggestion implements Suggestion {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index d6f7b72..44bf7aa 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -335,7 +335,7 @@
         @SuppressWarnings("rawtypes")
         Response<?> r = (Response) result;
         status = r.statusCode();
-        configureCaching(req, res, rsrc, r.caching());
+        configureCaching(req, res, rsrc, viewData.view, r.caching());
       } else if (result instanceof Response.Redirect) {
         CacheHeaders.setNotCacheable(res);
         res.sendRedirect(((Response.Redirect) result).location());
@@ -434,8 +434,9 @@
     return false;
   }
 
-  private static <T> void configureCaching(HttpServletRequest req,
-      HttpServletResponse res, RestResource rsrc, CacheControl c) {
+  private static <R extends RestResource> void configureCaching(
+      HttpServletRequest req, HttpServletResponse res, R rsrc,
+      RestView<R> view, CacheControl c) {
     if (isGetOrHead(req)) {
       switch (c.getType()) {
         case NONE:
@@ -443,13 +444,13 @@
           CacheHeaders.setNotCacheable(res);
           break;
         case PRIVATE:
-          addResourceStateHeaders(res, rsrc);
+          addResourceStateHeaders(res, rsrc, view);
           CacheHeaders.setCacheablePrivate(res,
               c.getAge(), c.getUnit(),
               c.isMustRevalidate());
           break;
         case PUBLIC:
-          addResourceStateHeaders(res, rsrc);
+          addResourceStateHeaders(res, rsrc, view);
           CacheHeaders.setCacheable(req, res,
               c.getAge(), c.getUnit(),
               c.isMustRevalidate());
@@ -460,12 +461,12 @@
     }
   }
 
-  private static void addResourceStateHeaders(
-      HttpServletResponse res, RestResource rsrc) {
-    if (rsrc instanceof RestResource.HasETag) {
-      res.setHeader(
-          HttpHeaders.ETAG,
-          ((RestResource.HasETag) rsrc).getETag());
+  private static  <R extends RestResource> void addResourceStateHeaders(
+      HttpServletResponse res, R rsrc, RestView<R> view) {
+    if (view instanceof ETagView) {
+      res.setHeader(HttpHeaders.ETAG, ((ETagView<R>) view).getETag(rsrc));
+    } else if (rsrc instanceof RestResource.HasETag) {
+      res.setHeader(HttpHeaders.ETAG, ((RestResource.HasETag) rsrc).getETag());
     }
     if (rsrc instanceof RestResource.HasLastModified) {
       res.setDateHeader(
@@ -476,7 +477,7 @@
 
   private void checkPreconditions(HttpServletRequest req)
       throws PreconditionFailedException {
-    if ("*".equals(req.getHeader("If-None-Match"))) {
+    if ("*".equals(req.getHeader(HttpHeaders.IF_NONE_MATCH))) {
       throw new PreconditionFailedException("Resource already exists");
     }
   }
@@ -825,7 +826,9 @@
       final BinaryResult src) throws IOException {
     BinaryResult gz;
     long len = src.getContentLength();
-    if (256 <= len && len <= (10 << 20)) {
+    if (len < 256) {
+      return src; // Do not compress very small payloads.
+    } else if (len <= (10 << 20)) {
       gz = compress(src);
       if (len <= gz.getContentLength()) {
         return src;
@@ -999,7 +1002,7 @@
     if (err != null) {
       RequestUtil.setErrorTraceAttribute(req, err);
     }
-    configureCaching(req, res, null, c);
+    configureCaching(req, res, null, null, c);
     res.setStatus(statusCode);
     replyText(req, res, msg);
   }
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/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/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 85cae58..868094e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -81,7 +81,7 @@
   private final GetTopic getTopic;
   private final PutTopic putTopic;
   private final PostReviewers postReviewers;
-  private final Provider<ChangeJson> changeJson;
+  private final ChangeJson.Factory changeJson;
   private final PostHashtags postHashtags;
   private final GetHashtags getHashtags;
   private final ListChangeComments listComments;
@@ -102,7 +102,7 @@
       GetTopic getTopic,
       PutTopic putTopic,
       PostReviewers postReviewers,
-      Provider<ChangeJson> changeJson,
+      ChangeJson.Factory changeJson,
       PostHashtags postHashtags,
       GetHashtags getHashtags,
       ListChangeComments listComments,
@@ -276,7 +276,7 @@
       if (u.isIdentifiedUser()) {
         ((IdentifiedUser) u).clearStarredChanges();
       }
-      return changeJson.get().addOptions(s).format(change);
+      return changeJson.create(s).format(change);
     } catch (OrmException e) {
       throw new RestApiException("Cannot retrieve change", e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index 5a6b274..6723db8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -53,7 +53,7 @@
   private final ChangeHooks hooks;
   private final AbandonedSender.Factory abandonedSenderFactory;
   private final Provider<ReviewDb> dbProvider;
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
   private final ChangeIndexer indexer;
   private final ChangeUpdate.Factory updateFactory;
   private final ChangeMessagesUtil cmUtil;
@@ -62,7 +62,7 @@
   Abandon(ChangeHooks hooks,
       AbandonedSender.Factory abandonedSenderFactory,
       Provider<ReviewDb> dbProvider,
-      ChangeJson json,
+      ChangeJson.Factory json,
       ChangeIndexer indexer,
       ChangeUpdate.Factory updateFactory,
       ChangeMessagesUtil cmUtil) {
@@ -90,8 +90,7 @@
       throw new ResourceConflictException("draft changes cannot be abandoned");
     }
     change = abandon(control, input.message, caller.getAccount());
-    ChangeInfo result = json.format(change);
-    return result;
+    return json.create(ChangeJson.NO_OPTIONS).format(change);
   }
 
   public Change abandon(ChangeControl control,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index 180b9ad..fd20868 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -36,16 +36,13 @@
 public class ActionJson {
   private final Revisions revisions;
   private final DynamicMap<RestView<ChangeResource>> changeViews;
-  private final RebaseChange rebaseChange;
 
   @Inject
   ActionJson(
       Revisions revisions,
-      DynamicMap<RestView<ChangeResource>> changeViews,
-      RebaseChange rebaseChange) {
+      DynamicMap<RestView<ChangeResource>> changeViews) {
     this.revisions = revisions;
     this.changeViews = changeViews;
-    this.rebaseChange = rebaseChange;
   }
 
   public Map<String, ActionInfo> format(RevisionResource rsrc) {
@@ -72,11 +69,14 @@
     Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser());
     for (UiAction.Description d : UiActions.from(
         changeViews,
-        new ChangeResource(ctl, rebaseChange),
+        new ChangeResource(ctl),
         userProvider)) {
       out.put(d.getId(), new ActionInfo(d));
     }
-    // TODO(sbeller): why do we need to treat followup specially here?
+
+    // The followup action is a client-side only operation that does not
+    // have a server side handler. It must be manually registered into the
+    // resulting action map.
     if (ctl.getChange().getStatus().isOpen()) {
       UiAction.Description descr = new UiAction.Description();
       PrivateInternals_UiActionDescription.setId(descr, "followup");
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 f152769..dc7f1b3 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
@@ -98,8 +98,9 @@
 import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 import com.google.gerrit.server.query.change.QueryResult;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
@@ -122,6 +123,12 @@
 
 public class ChangeJson {
   private static final Logger log = LoggerFactory.getLogger(ChangeJson.class);
+  public static final Set<ListChangesOption> NO_OPTIONS =
+      Collections.emptySet();
+
+  public interface Factory {
+    ChangeJson create(Set<ListChangesOption> options);
+  }
 
   private final Provider<ReviewDb> db;
   private final LabelNormalizer labelNormalizer;
@@ -141,12 +148,11 @@
   private final ChangeMessagesUtil cmUtil;
   private final Provider<ConsistencyChecker> checkerProvider;
   private final ActionJson actionJson;
-  private final RebaseChange rebaseChange;
 
   private AccountLoader accountLoader;
   private FixInput fix;
 
-  @Inject
+  @AssistedInject
   ChangeJson(
       Provider<ReviewDb> db,
       LabelNormalizer ln,
@@ -165,7 +171,7 @@
       ChangeMessagesUtil cmUtil,
       Provider<ConsistencyChecker> checkerProvider,
       ActionJson actionJson,
-      RebaseChange rebaseChange) {
+      @Assisted Set<ListChangesOption> options) {
     this.db = db;
     this.labelNormalizer = ln;
     this.userProvider = user;
@@ -183,18 +189,9 @@
     this.cmUtil = cmUtil;
     this.checkerProvider = checkerProvider;
     this.actionJson = actionJson;
-    this.rebaseChange = rebaseChange;
-    options = EnumSet.noneOf(ListChangesOption.class);
-  }
-
-  public ChangeJson addOption(ListChangesOption o) {
-    options.add(o);
-    return this;
-  }
-
-  public ChangeJson addOptions(Collection<ListChangesOption> o) {
-    options.addAll(o);
-    return this;
+    this.options = options.isEmpty()
+        ? EnumSet.noneOf(ListChangesOption.class)
+        : EnumSet.copyOf(options);
   }
 
   public ChangeJson fix(FixInput fix) {
@@ -600,6 +597,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
@@ -893,7 +896,7 @@
         && userProvider.get().isIdentifiedUser()) {
 
       actionJson.addRevisionActions(out,
-          new RevisionResource(new ChangeResource(ctl, rebaseChange), in));
+          new RevisionResource(new ChangeResource(ctl), in));
     }
 
     return out;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 265cb49..84658b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -36,16 +36,13 @@
       new TypeLiteral<RestView<ChangeResource>>() {};
 
   private final ChangeControl control;
-  private final RebaseChange rebaseChange;
 
-  public ChangeResource(ChangeControl control, RebaseChange rebaseChange) {
+  public ChangeResource(ChangeControl control) {
     this.control = control;
-    this.rebaseChange = rebaseChange;
   }
 
   protected ChangeResource(ChangeResource copy) {
     this.control = copy.control;
-    this.rebaseChange = copy.rebaseChange;
   }
 
   public ChangeControl getControl() {
@@ -60,7 +57,6 @@
     return getControl().getNotes();
   }
 
-
   // This includes all information relevant for ETag computation
   // unrelated to the UI.
   public void prepareETag(Hasher h, CurrentUser user) {
@@ -68,8 +64,8 @@
       .putInt(getChange().getRowVersion())
       .putInt(user.isIdentifiedUser()
           ? ((IdentifiedUser) user).getAccountId().get()
-          : 0)
-      .putBoolean(rebaseChange != null && rebaseChange.canRebase(this));
+          : 0);
+
     byte[] buf = new byte[20];
     ObjectId noteId;
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index 6540ef2..1648a5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -49,7 +49,6 @@
   private final ChangeUtil changeUtil;
   private final CreateChange createChange;
   private final ChangeIndexer changeIndexer;
-  private final RebaseChange rebaseChange;
 
   @Inject
   ChangesCollection(
@@ -59,8 +58,7 @@
       DynamicMap<RestView<ChangeResource>> views,
       ChangeUtil changeUtil,
       CreateChange createChange,
-      ChangeIndexer changeIndexer,
-      RebaseChange rebaseChange) {
+      ChangeIndexer changeIndexer) {
     this.user = user;
     this.changeControlFactory = changeControlFactory;
     this.queryFactory = queryFactory;
@@ -68,7 +66,6 @@
     this.changeUtil = changeUtil;
     this.createChange = createChange;
     this.changeIndexer = changeIndexer;
-    this.rebaseChange = rebaseChange;
   }
 
   @Override
@@ -105,7 +102,7 @@
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(id);
     }
-    return new ChangeResource(control, rebaseChange);
+    return new ChangeResource(control);
   }
 
   public ChangeResource parse(Change.Id id)
@@ -115,7 +112,7 @@
   }
 
   public ChangeResource parse(ChangeControl control) {
-    return new ChangeResource(control, rebaseChange);
+    return new ChangeResource(control);
   }
 
   @SuppressWarnings("unchecked")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
index 5958f21..f4b2f4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -25,19 +25,20 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
+import java.util.EnumSet;
+
 public class Check implements RestReadView<ChangeResource>,
     RestModifyView<ChangeResource, FixInput> {
-  private final ChangeJson json;
+  private final ChangeJson.Factory jsonFactory;
 
   @Inject
-  Check(ChangeJson json) {
-    this.json = json;
-    json.addOption(ListChangesOption.CHECK);
+  Check(ChangeJson.Factory json) {
+    this.jsonFactory = json;
   }
 
   @Override
   public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
-    return Response.withMustRevalidate(json.format(rsrc));
+    return Response.withMustRevalidate(newChangeJson().format(rsrc));
   }
 
   @Override
@@ -49,6 +50,10 @@
         && !ctl.getCurrentUser().getCapabilities().canMaintainServer()) {
       throw new AuthException("Cannot fix change");
     }
-    return Response.withMustRevalidate(json.fix(input).format(rsrc));
+    return Response.withMustRevalidate(newChangeJson().fix(input).format(rsrc));
+  }
+
+  private ChangeJson newChangeJson() {
+    return jsonFactory.create(EnumSet.of(ListChangesOption.CHECK));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index 5aaea3e..08117d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -43,12 +43,12 @@
     UiAction<RevisionResource> {
   private final Provider<ReviewDb> dbProvider;
   private final CherryPickChange cherryPickChange;
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
 
   @Inject
   CherryPick(Provider<ReviewDb> dbProvider,
       CherryPickChange cherryPickChange,
-      ChangeJson json) {
+      ChangeJson.Factory json) {
     this.dbProvider = dbProvider;
     this.cherryPickChange = cherryPickChange;
     this.json = json;
@@ -84,7 +84,7 @@
           cherryPickChange.cherryPick(revision.getChange(),
               revision.getPatchSet(), input.message, refName,
               refControl);
-      return json.format(cherryPickedChangeId);
+      return json.create(ChangeJson.NO_OPTIONS).format(cherryPickedChangeId);
     } catch (InvalidChangeOperationException e) {
       throw new BadRequestException(e.getMessage());
     } catch (MergeException e) {
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 39c0712..3ec18f3 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
@@ -85,7 +85,7 @@
   private final ProjectsCollection projectsCollection;
   private final CommitValidators.Factory commitValidatorsFactory;
   private final ChangeInserter.Factory changeInserterFactory;
-  private final ChangeJson json;
+  private final ChangeJson.Factory jsonFactory;
   private final ChangeUtil changeUtil;
   private final boolean allowDrafts;
 
@@ -97,7 +97,7 @@
       ProjectsCollection projectsCollection,
       CommitValidators.Factory commitValidatorsFactory,
       ChangeInserter.Factory changeInserterFactory,
-      ChangeJson json,
+      ChangeJson.Factory json,
       ChangeUtil changeUtil,
       @GerritServerConfig Config config) {
     this.db = db;
@@ -107,7 +107,7 @@
     this.projectsCollection = projectsCollection;
     this.commitValidatorsFactory = commitValidatorsFactory;
     this.changeInserterFactory = changeInserterFactory;
-    this.json = json;
+    this.jsonFactory = json;
     this.changeUtil = changeUtil;
     this.allowDrafts = config.getBoolean("change", "allowDrafts", true);
   }
@@ -219,6 +219,7 @@
       ins.setGroups(groups);
       ins.insert();
 
+      ChangeJson json = jsonFactory.create(ChangeJson.NO_OPTIONS);
       return Response.created(json.format(change.getId()));
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
index 8b64c1b..b66a18b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
@@ -23,30 +23,34 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.EnumSet;
+
 public class GetChange implements RestReadView<ChangeResource> {
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
+  private final EnumSet<ListChangesOption> options =
+      EnumSet.noneOf(ListChangesOption.class);
 
   @Option(name = "-o", usage = "Output options")
   void addOption(ListChangesOption o) {
-    json.addOption(o);
+    options.add(o);
   }
 
   @Option(name = "-O", usage = "Output option flags, in hex")
   void setOptionFlagsHex(String hex) {
-    json.addOptions(ListChangesOption.fromBits(Integer.parseInt(hex, 16)));
+    options.addAll(ListChangesOption.fromBits(Integer.parseInt(hex, 16)));
   }
 
   @Inject
-  GetChange(ChangeJson json) {
+  GetChange(ChangeJson.Factory json) {
     this.json = json;
   }
 
   @Override
   public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
-    return Response.withMustRevalidate(json.format(rsrc));
+    return Response.withMustRevalidate(json.create(options).format(rsrc));
   }
 
   Response<ChangeInfo> apply(RevisionResource rsrc) throws OrmException {
-    return Response.withMustRevalidate(json.format(rsrc));
+    return Response.withMustRevalidate(json.create(options).format(rsrc));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
index 0f0a6a2..a7f8dea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -33,14 +33,13 @@
 
 public class GetCommit implements RestReadView<RevisionResource> {
   private final GitRepositoryManager repoManager;
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
 
   @Option(name = "--links", usage = "Add weblinks")
   private boolean addLinks;
 
   @Inject
-  GetCommit(GitRepositoryManager repoManager,
-      ChangeJson json) {
+  GetCommit(GitRepositoryManager repoManager, ChangeJson.Factory json) {
     this.repoManager = repoManager;
     this.json = json;
   }
@@ -53,8 +52,9 @@
       String rev = rsrc.getPatchSet().getRevision().get();
       RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
       rw.parseBody(commit);
-      Response<CommitInfo> r = Response.ok(
-          json.toCommit(rsrc.getControl(), rw, commit, addLinks));
+      CommitInfo info = json.create(ChangeJson.NO_OPTIONS)
+          .toCommit(rsrc.getControl(), rw, commit, addLinks);
+      Response<CommitInfo> r = Response.ok(info);
       if (rsrc.isCacheable()) {
         r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
index afd11569..3c4d79d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
@@ -144,7 +144,7 @@
     }
     b.append("From ").append(commit.getName())
      .append(' ')
-     .append("Mon Sep 17 00:00:00 2001\n")
+     .append("Mon Sep 17 00:00:00 2001\n") // Fixed timestamp to match output of C Git's format-patch
      .append("From: ").append(author.getName())
      .append(" <").append(author.getEmailAddress()).append(">\n")
      .append("Date: ").append(formatDate(author)).append('\n')
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index 3ae6f6c..abbc640 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -39,17 +39,15 @@
   private final ActionJson delegate;
   private final Provider<InternalChangeQuery> queryProvider;
   private final Config config;
-  private final RebaseChange rebaseChange;
+
   @Inject
   GetRevisionActions(
       ActionJson delegate,
       Provider<InternalChangeQuery> queryProvider,
-      @GerritServerConfig Config config,
-      RebaseChange rebaseChange) {
+      @GerritServerConfig Config config) {
     this.delegate = delegate;
     this.queryProvider = queryProvider;
     this.config = config;
-    this.rebaseChange = rebaseChange;
   }
 
   @Override
@@ -68,7 +66,7 @@
     CurrentUser user = rsrc.getControl().getCurrentUser();
     try {
       for (ChangeData c : queryProvider.get().byTopicOpen(topic)) {
-        new ChangeResource(c.changeControl(), rebaseChange).prepareETag(h, user);
+        new ChangeResource(c.changeControl()).prepareETag(h, user);
       }
     } catch (OrmException e){
       throw new OrmRuntimeException(e);
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 49f9bb9..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
@@ -47,28 +47,29 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.EnumSet;
 
 @Singleton
 public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
     UiAction<RevisionResource> {
-
   private static final Logger log = LoggerFactory.getLogger(Rebase.class);
+  private static final EnumSet<ListChangesOption> OPTIONS = EnumSet.of(
+      ListChangesOption.CURRENT_REVISION,
+      ListChangesOption.CURRENT_COMMIT);
 
   private final GitRepositoryManager repoManager;
   private final Provider<RebaseChange> rebaseChange;
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
   private final Provider<ReviewDb> dbProvider;
 
   @Inject
   public Rebase(GitRepositoryManager repoManager,
       Provider<RebaseChange> rebaseChange,
-      ChangeJson json,
+      ChangeJson.Factory json,
       Provider<ReviewDb> dbProvider) {
     this.repoManager = repoManager;
     this.rebaseChange = rebaseChange;
-    this.json = json
-        .addOption(ListChangesOption.CURRENT_REVISION)
-        .addOption(ListChangesOption.CURRENT_COMMIT);
+    this.json = json;
     this.dbProvider = dbProvider;
   }
 
@@ -89,15 +90,14 @@
         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) {
       throw new ResourceNotFoundException(change.getId().toString());
     }
 
-    return json.format(change.getId());
+    return json.create(OPTIONS).format(change.getId());
   }
 
   private String findBaseRev(RevWalk rw, RevisionResource rsrc,
@@ -128,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/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 46d73d7..c09aa3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -53,7 +53,7 @@
   private final ChangeHooks hooks;
   private final RestoredSender.Factory restoredSenderFactory;
   private final Provider<ReviewDb> dbProvider;
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
   private final ChangeIndexer indexer;
   private final ChangeMessagesUtil cmUtil;
   private final ChangeUpdate.Factory updateFactory;
@@ -62,7 +62,7 @@
   Restore(ChangeHooks hooks,
       RestoredSender.Factory restoredSenderFactory,
       Provider<ReviewDb> dbProvider,
-      ChangeJson json,
+      ChangeJson.Factory json,
       ChangeIndexer indexer,
       ChangeMessagesUtil cmUtil,
       ChangeUpdate.Factory updateFactory) {
@@ -136,8 +136,7 @@
         db.patchSets().get(change.currentPatchSetId()),
         Strings.emptyToNull(input.message),
         dbProvider.get());
-    ChangeInfo result = json.format(change);
-    return result;
+    return json.create(ChangeJson.NO_OPTIONS).format(change);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index e6846e0..aaae6f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -44,12 +44,12 @@
 @Singleton
 public class Revert implements RestModifyView<ChangeResource, RevertInput>,
     UiAction<ChangeResource> {
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
   private final ChangeUtil changeUtil;
   private final PersonIdent myIdent;
 
   @Inject
-  Revert(ChangeJson json,
+  Revert(ChangeJson.Factory json,
       ChangeUtil changeUtil,
       @GerritPersonIdent PersonIdent myIdent) {
     this.json = json;
@@ -69,18 +69,19 @@
       throw new ResourceConflictException("change is " + status(change));
     }
 
+    Change.Id revertedChangeId;
     try {
-      Change.Id revertedChangeId =
-          changeUtil.revert(control, change.currentPatchSetId(),
-              Strings.emptyToNull(input.message),
-              new PersonIdent(myIdent, TimeUtil.nowTs()), new NoSshInfo());
-
-      return json.format(revertedChangeId);
+      revertedChangeId = changeUtil.revert(control,
+            change.currentPatchSetId(),
+            Strings.emptyToNull(input.message),
+            new PersonIdent(myIdent, TimeUtil.nowTs()),
+            new NoSshInfo());
     } catch (InvalidChangeOperationException e) {
       throw new BadRequestException(e.getMessage());
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(e.getMessage());
     }
+    return json.create(ChangeJson.NO_OPTIONS).format(revertedChangeId);
   }
 
   @Override
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 b9e9b3e..3d4dc26 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
@@ -182,8 +182,9 @@
     ChangeSet submittedChanges = ChangeSet.create(changes);
 
     try {
-      mergeOpProvider.get().merge(submittedChanges, caller, true);
-      change = dbProvider.get().changes().get(change.getId());
+      ReviewDb db = dbProvider.get();
+      mergeOpProvider.get().merge(db, submittedChanges, caller, true);
+      change = db.changes().get(change.getId());
     } catch (NoSuchChangeException e) {
       throw new OrmException("Submission failed", e);
     }
@@ -471,12 +472,12 @@
       RestModifyView<ChangeResource, SubmitInput> {
     private final Provider<ReviewDb> dbProvider;
     private final Submit submit;
-    private final ChangeJson json;
+    private final ChangeJson.Factory json;
 
     @Inject
     CurrentRevision(Provider<ReviewDb> dbProvider,
         Submit submit,
-        ChangeJson json) {
+        ChangeJson.Factory json) {
       this.dbProvider = dbProvider;
       this.submit = submit;
       this.json = json;
@@ -494,8 +495,9 @@
       } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
         throw new AuthException("current revision not accessible");
       }
+
       Output out = submit.apply(new RevisionResource(rsrc, ps), input);
-      return json.format(out.change);
+      return json.create(ChangeJson.NO_OPTIONS).format(out.change);
     }
   }
 }
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 c909239..71e25a5 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;
 
@@ -40,13 +41,12 @@
   private static final Logger log = LoggerFactory.getLogger(
       SubmittedTogether.class);
 
-  private final ChangeJson json;
-
+  private final ChangeJson.Factory json;
   private final Provider<ReviewDb> dbProvider;
   private final MergeSuperSet mergeSuperSet;
 
   @Inject
-  SubmittedTogether(ChangeJson json,
+  SubmittedTogether(ChangeJson.Factory json,
       Provider<ReviewDb> dbProvider,
       MergeSuperSet mergeSuperSet) {
     this.json = json;
@@ -61,12 +61,16 @@
     try {
       ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(),
           ChangeSet.create(resource.getChange()));
-      json.addOptions(EnumSet.of(
-          ListChangesOption.CURRENT_REVISION,
-          ListChangesOption.CURRENT_COMMIT,
-          ListChangesOption.DETAILED_LABELS,
-          ListChangesOption.LABELS));
-      return json.format(cs.ids());
+      if (cs.ids().size() > 1) {
+        return json.create(EnumSet.of(
+            ListChangesOption.CURRENT_REVISION,
+            ListChangesOption.CURRENT_COMMIT,
+            ListChangesOption.DETAILED_LABELS,
+            ListChangesOption.LABELS))
+          .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 c8360a9..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
@@ -69,10 +69,12 @@
 import com.google.gerrit.server.auth.UniversalAuthBackend;
 import com.google.gerrit.server.avatar.AvatarProvider;
 import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.change.ChangeKindCacheImpl;
 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;
@@ -186,10 +188,12 @@
     factory(AddReviewerSender.Factory.class);
     factory(CapabilityControl.Factory.class);
     factory(ChangeData.Factory.class);
+    factory(ChangeJson.Factory.class);
     factory(CreateChangeSender.Factory.class);
     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/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 8631147..7682b83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.base.Optional;
-import com.google.common.collect.Iterables;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -53,7 +52,6 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 
 import java.io.IOException;
-import java.util.Map;
 
 /**
  * Utility functions to manipulate change edits.
@@ -118,16 +116,16 @@
   public Optional<ChangeEdit> byChange(Change change, IdentifiedUser user)
       throws IOException {
     try (Repository repo = gitManager.openRepository(change.getProject())) {
-      String editRefPrefix = RefNames.refsEditPrefix(user.getAccountId(), change.getId());
-      Map<String, Ref> refs = repo.getRefDatabase().getRefs(editRefPrefix);
-      if (refs.isEmpty()) {
+      int n = change.currentPatchSetId().get();
+      String[] refNames = new String[n];
+      for (int i = n; i > 0; i--) {
+        refNames[i-1] = RefNames.refsEdit(user.getAccountId(), change.getId(),
+            new PatchSet.Id(change.getId(), i));
+      }
+      Ref ref = repo.getRefDatabase().firstExactRef(refNames);
+      if (ref == null) {
         return Optional.absent();
       }
-
-      // TODO(davido): Rather than failing when we encounter the corrupt state
-      // where there is more than one ref, we could silently delete all but the
-      // current one.
-      Ref ref = Iterables.getOnlyElement(refs.values());
       try (RevWalk rw = new RevWalk(repo)) {
         RevCommit commit = rw.parseCommit(ref.getObjectId());
         PatchSet basePs = getBasePatchSet(change, ref);
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 cc2ded6..648d125 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 {
@@ -291,12 +216,6 @@
     }
   }
 
-  private void openSchema() throws OrmException {
-    if (db == null) {
-      db = schemaFactory.open();
-    }
-  }
-
   private static Optional<SubmitRecord> findOkRecord(Collection<SubmitRecord> in) {
     return Iterables.tryFind(in, new Predicate<SubmitRecord>() {
       @Override
@@ -397,14 +316,13 @@
     }
   }
 
-  public void merge(ChangeSet changes, IdentifiedUser caller,
+  public void merge(ReviewDb db, ChangeSet changes, IdentifiedUser caller,
       boolean checkPermissions) throws NoSuchChangeException,
       OrmException, ResourceConflictException {
     logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
+    this.db = db;
     logDebug("Beginning merge of {}", changes);
     try {
-      openSchema();
-
       ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes);
       logDebug("Calculated to merge {}", cs);
       if (checkPermissions) {
@@ -420,10 +338,6 @@
     } catch (IOException e) {
       // Anything before the merge attempt is an error
       throw new OrmException(e);
-    } finally {
-      if (db != null) {
-        db.close();
-      }
     }
   }
 
@@ -433,7 +347,6 @@
     Map<Branch.NameKey, ListMultimap<SubmitType, ChangeData>> toSubmit =
         new HashMap<>();
     try {
-      openSchema();
       logDebug("Perform the merges");
       for (Project.NameKey project : cs.projects()) {
         openRepository(project);
@@ -1032,20 +945,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,
@@ -1200,38 +1108,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()));
@@ -1298,12 +1174,9 @@
   private void abandonAllOpenChanges(Project.NameKey destProject)
       throws NoSuchChangeException {
     try {
-      openSchema();
       for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
         abandonOneChange(cd.change());
       }
-      db.close();
-      db = null;
     } catch (IOException | OrmException e) {
       logWarn("Cannot abandon changes for deleted project ", e);
     }
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/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 898443e..25a9f32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1789,7 +1789,7 @@
     RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
     List<Change> changes = Lists.newArrayList(rsrc.getChange());
     try {
-      mergeOpProvider.get().merge(ChangeSet.create(changes),
+      mergeOpProvider.get().merge(db, ChangeSet.create(changes),
           (IdentifiedUser) changeCtl.getCurrentUser(), false);
     } catch (NoSuchChangeException e) {
       throw new OrmException(e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 34370f9..d83da3e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.util.SubmoduleSectionParser;
 import com.google.gwtorm.server.OrmException;
@@ -42,6 +43,7 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.BlobBasedConfig;
 import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -76,11 +78,13 @@
   private final Account account;
   private final ChangeHooks changeHooks;
   private final SubmoduleSectionParser.Factory subSecParserFactory;
+  private final boolean verboseSuperProject;
 
   @Inject
   public SubmoduleOp(
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       @GerritPersonIdent PersonIdent myIdent,
+      @GerritServerConfig Config cfg,
       GitRepositoryManager repoManager,
       GitReferenceUpdated gitRefUpdated,
       @Nullable Account account,
@@ -93,6 +97,8 @@
     this.account = account;
     this.changeHooks = changeHooks;
     this.subSecParserFactory = subSecParserFactory;
+    this.verboseSuperProject = cfg.getBoolean("submodule",
+        "verboseSuperprojectUpdate", true);
 
     updatedSubscribers = new HashSet<>();
   }
@@ -274,24 +280,25 @@
               ent.setObjectId(updateTo);
             }
           });
+          if (verboseSuperProject) {
+            msgbuf.append("Project: " + s.getSubmodule().getParentKey().get());
+            msgbuf.append(" " + s.getSubmodule().getShortName());
+            msgbuf.append(" " + updateTo.getName());
+            msgbuf.append("\n\n");
 
-          msgbuf.append("Project: " + s.getSubmodule().getParentKey().get());
-          msgbuf.append(" " + s.getSubmodule().getShortName());
-          msgbuf.append(" " + updateTo.getName());
-          msgbuf.append("\n\n");
+            try {
+              rw.markStart(newCommit);
 
-          try {
-            rw.markStart(newCommit);
-
-            if (oldId != null) {
-              rw.markUninteresting(rw.parseCommit(oldId));
+              if (oldId != null) {
+                rw.markUninteresting(rw.parseCommit(oldId));
+              }
+              for (RevCommit c : rw) {
+                msgbuf.append(c.getFullMessage() + "\n\n");
+              }
+            } catch (IOException e) {
+              logAndThrowSubmoduleException("Could not perform a revwalk to "
+                  + "create superproject commit message", e);
             }
-            for (RevCommit c : rw) {
-              msgbuf.append(c.getFullMessage() + "\n\n");
-            }
-          } catch (IOException e) {
-            logAndThrowSubmoduleException("Could not perform a revwalk to "
-                + "create superproject commit message", e);
           }
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 9ccf153..a4684a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -68,7 +68,7 @@
     this.showMetadata = showMetadata;
   }
 
-  public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeperately) {
+  public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeparately) {
     if (projectCtl.allRefsAreVisible(ImmutableSet.of(RefNames.REFS_CONFIG))) {
       Map<String, Ref> r = Maps.newHashMap(refs);
       if (!projectCtl.controlForRef(RefNames.REFS_CONFIG).isVisible()) {
@@ -136,11 +136,11 @@
     // If we have tags that were deferred, we need to do a revision walk
     // to identify what tags we can actually reach, and what we cannot.
     //
-    if (!deferredTags.isEmpty() && (!result.isEmpty() || filterTagsSeperately)) {
+    if (!deferredTags.isEmpty() && (!result.isEmpty() || filterTagsSeparately)) {
       TagMatcher tags = tagCache.get(projectName).matcher(
           tagCache,
           db,
-          filterTagsSeperately ? filter(db.getAllRefs()).values() : result.values());
+          filterTagsSeparately ? filter(db.getAllRefs()).values() : result.values());
       for (Ref tag : deferredTags) {
         if (tags.isReachable(tag)) {
           result.put(tag.getName(), tag);
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/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/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 299f0f1..bd6b297 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -38,7 +38,7 @@
 import java.util.regex.Pattern;
 
 public class QueryChanges implements RestReadView<TopLevelResource> {
-  private final ChangeJson json;
+  private final ChangeJson.Factory json;
   private final ChangeQueryBuilder qb;
   private final QueryProcessor imp;
   private final Provider<CurrentUser> user;
@@ -68,7 +68,7 @@
   }
 
   @Inject
-  QueryChanges(ChangeJson json,
+  QueryChanges(ChangeJson.Factory json,
       ChangeQueryBuilder qb,
       QueryProcessor qp,
       Provider<CurrentUser> user) {
@@ -141,7 +141,7 @@
       QueryParseException {
     int cnt = queries.size();
     List<QueryResult> results = imp.queryChanges(qb.parse(queries));
-    List<List<ChangeInfo>> res = json.addOptions(options)
+    List<List<ChangeInfo>> res = json.create(options)
         .formatQueryResults(results);
     for (int n = 0; n < cnt; n++) {
       List<ChangeInfo> info = res.get(n);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java
index fa8af3e..c3d2356 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java
@@ -21,7 +21,6 @@
 import com.google.inject.Provider;
 
 public class Schema_109 extends SchemaVersion {
-
   @Inject
   Schema_109(Provider<Schema_108> prior) {
     super(prior);
@@ -29,11 +28,8 @@
 
   @Override
   protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    String cmd = "UPDATE changes SET status = 'n' WHERE status = 's';";
-    ui.message("Running " + cmd);
     try (StatementExecutor e = newExecutor(db)) {
-      e.execute(cmd);
+      e.execute("UPDATE changes SET status = 'n' WHERE status = 's';");
     }
-    ui.message("done");
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index c046056..e953cbf 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -322,9 +322,9 @@
     update.commit();
 
     ChangeControl ctl = stubChangeControl(change1);
-    revRes1 = new RevisionResource(new ChangeResource(ctl, null), ps1);
-    revRes2 = new RevisionResource(new ChangeResource(ctl, null), ps2);
-    revRes3 = new RevisionResource(new ChangeResource(stubChangeControl(change2), null), ps3);
+    revRes1 = new RevisionResource(new ChangeResource(ctl), ps1);
+    revRes2 = new RevisionResource(new ChangeResource(ctl), ps2);
+    revRes3 = new RevisionResource(new ChangeResource(stubChangeControl(change2)), ps3);
   }
 
   private ChangeControl stubChangeControl(Change c) throws OrmException {
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-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/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/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/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>