Merge changes I3bbfc470,I628c4c99

* changes:
  Prevent injecting IdentifiedUser for project REST endpoints
  Refactor UiAction specific code out of change specific code
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 9c964e0..db806cc 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -600,7 +600,7 @@
 
 [[category_push_merge]]
 Push Merge Commits
-~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~
 
 The `Push Merge Commit` access right permits the user to upload merge
 commits.  It's an add-on to the <<category_push,Push>> access right, and
diff --git a/Documentation/cmd-version.txt b/Documentation/cmd-version.txt
index d1f94ea..aa08848 100644
--- a/Documentation/cmd-version.txt
+++ b/Documentation/cmd-version.txt
@@ -1,5 +1,5 @@
 gerrit version
-================
+==============
 
 NAME
 ----
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 3c35054..339b0c0 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -335,7 +335,17 @@
 +
 Target for the "Obtain Password" link.  Used only when `auth.type` is
 `LDAP`, `LDAP_BIND` or `CUSTOM_EXTENSION`.
+
+[[auth.switchAccountUrl]]auth.switchAccountUrl::
 +
+URL to switch user identities and login as a different account than
+the currently active account.  This is disabled by default except when
+`auth.type` is `OPENID` and `DEVELOPMENT_BECOME_ANY_ACCOUNT`.  If set
+the "Switch Account" link is displayed next to "Sign Out".
++
+When `auth.type` does not normally enable this URL administrators may
+set this to `login/` or `$canonicalWebUrl/login`, allowing users to
+begin a new web session.
 
 [[auth.cookiePath]]auth.cookiePath::
 +
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 7df55fe..a1cf9ee 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -177,7 +177,7 @@
 
 [[label_abbreviation]]
 `label.Label-Name.abbreviation`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 An abbreviated name for a label shown as a compact column header, for
 example on project dashboards. Defaults to all the uppercase characters
diff --git a/Documentation/config-login-register.txt b/Documentation/config-login-register.txt
index d0e0fc5..867f0d4 100644
--- a/Documentation/config-login-register.txt
+++ b/Documentation/config-login-register.txt
@@ -135,4 +135,9 @@
   git clone ssh://user@localhost:29418/REPOSITORY_NAME.git
 
   user@host:~$
-----
\ No newline at end of file
+----
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index b1561cb..3800473 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -108,3 +108,8 @@
 [database]
         password = secret_pasword
 ----
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index c06d16a..f9d0d0e 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -83,7 +83,7 @@
 
 
 Create the Actual Release
----------------------------
+-------------------------
 
 To create a Gerrit release the following steps have to be done:
 
diff --git a/Documentation/error-has-duplicates.txt b/Documentation/error-has-duplicates.txt
index e9e42f4..b5175c0 100644
--- a/Documentation/error-has-duplicates.txt
+++ b/Documentation/error-has-duplicates.txt
@@ -1,5 +1,5 @@
-... has duplicates
-==================
+\... has duplicates
+===================
 
 With this error message Gerrit rejects to push a commit if its commit
 message contains a Change-ID for which multiple changes can be found
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 7dbdf94..96e19cc 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -947,7 +947,7 @@
 
 [[email-id]]
 \{email-id\}
-~~~~~~~~~~~~~~
+~~~~~~~~~~~~
 An email address, or `preferred` for the preferred email address of the
 user.
 
@@ -958,7 +958,7 @@
 
 [[ssh-key-id]]
 \{ssh-key-id\}
-~~~~~~~~~~~~
+~~~~~~~~~~~~~~
 The sequence number of the SSH key.
 
 
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
new file mode 100644
index 0000000..099970c
--- /dev/null
+++ b/Documentation/rest-api-plugins.txt
@@ -0,0 +1,106 @@
+Gerrit Code Review - /plugins/ REST API
+=======================================
+
+This page describes the plugin related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+Endpoints
+---------
+
+[[plugin-endpoints]]
+Plugin Endpoints
+----------------
+
+[[install-plugin]]
+Install Plugin
+~~~~~~~~~~~~~~
+[verse]
+'PUT /plugins/link:#plugin-id[\{plugin-id\}]'
+
+Installs a new plugin on the Gerrit server. If a plugin with the
+specified name already exists it is overwritten.
+
+The plugin jar can either be sent as binary data in the request body
+or a URL to the plugin jar must be provided in the request body inside
+a link:#plugin-input[PluginInput] entity.
+
+.Request
+----
+  PUT /plugins/delete-project HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "url": "file:///gerrit/plugins/delete-project/delete-project-2.8.jar"
+  }
+----
+
+To provide the plugin jar as binary data in the request body the
+following curl command can be used:
+
+----
+  curl --digest --user admin:TNNuLkWsIV8w -X PUT --data-binary @delete-project-2.8.jar 'http://gerrit:8080/a/plugins/delete-project'
+----
+
+As response a link:#plugin-info[PluginInfo] entity is returned that
+describes the plugin.
+
+.Response
+----
+  HTTP/1.1 201 Created
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "kind": "gerritcodereview#plugin",
+    "id": "delete-project",
+    "version": "2.8"
+  }
+----
+
+If an existing plugin was overwritten the response is "`200 OK`".
+
+
+[[ids]]
+IDs
+---
+
+[[plugin-id]]
+\{plugin-id\}
+~~~~~~~~~~~~~
+The ID of the plugin.
+
+
+[[json-entities]]
+JSON Entities
+-------------
+
+[[plugin-info]]
+PluginInfo
+~~~~~~~~~~
+The `PluginInfo` entity describes a plugin.
+
+[options="header",width="50%",cols="1,6"]
+|======================
+|Field Name|Description
+|`kind`    |`gerritcodereview#plugin`
+|`id`      |The ID of the plugin.
+|`version` |The version of the plugin.
+|======================
+
+[[plugin-input]]
+PluginInput
+~~~~~~~~~~~
+The `PluginInput` entity describes a plugin that should be installed.
+
+[options="header",width="50%",cols="1,6"]
+|======================
+|Field Name|Description
+|`url`     |URL to the plugin jar.
+|======================
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index e3fc105..7eed6ef 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -19,6 +19,8 @@
   Config related REST endpoints
 link:rest-api-groups.html[/groups/]::
   Group related REST endpoints
+link:rest-api-plugins.html[/plugins/]::
+  Plugin related REST endpoints
 link:rest-api-projects.html[/projects/]::
   Project related REST endpoints
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index 324f00b..f85e333 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -28,6 +28,7 @@
   protected String registerText;
   protected String loginUrl;
   protected String loginText;
+  protected String switchAccountUrl;
   protected String httpPasswordUrl;
   protected String reportBugUrl;
   protected boolean gitBasicAuth;
@@ -75,6 +76,14 @@
     registerUrl = u;
   }
 
+  public String getSwitchAccountUrl() {
+    return switchAccountUrl;
+  }
+
+  public void setSwitchAccountUrl(String u) {
+    switchAccountUrl = u;
+  }
+
   public String getRegisterText() {
     return registerText;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 683f058..fb72085 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -18,9 +18,7 @@
 
 public interface GerritConstants extends Constants {
   String menuSignIn();
-  String menuSignOut();
   String menuRegister();
-  String menuSettings();
   String reportBug();
 
   String signInDialogTitle();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index defc7e4..6f3dca5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -1,7 +1,5 @@
 menuSignIn = Sign In
-menuSignOut = Sign Out
 menuRegister = Register
-menuSettings = Settings
 reportBug = Report Bug
 
 signInDialogTitle = Code Review - Sign In
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 d5bbd17..90348db 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
@@ -16,11 +16,12 @@
 
 import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.ui.InlineHyperlink;
+import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.user.client.PluginSafePopupPanel;
@@ -33,7 +34,8 @@
   @UiField Label userName;
   @UiField Label userEmail;
   @UiField Element userLinks;
-  @UiField Anchor logout;
+  @UiField AnchorElement switchAccount;
+  @UiField AnchorElement logout;
   @UiField InlineHyperlink settings;
 
   public UserPopupPanel(AccountInfo account, boolean canLogOut,
@@ -49,11 +51,22 @@
       userEmail.setText(account.email());
     }
     if (showSettingsLink) {
+      if (Gerrit.getConfig().getSwitchAccountUrl() != null) {
+        switchAccount.setHref(Gerrit.getConfig().getSwitchAccountUrl());
+      } else if (Gerrit.getConfig().getAuthType() == AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT
+          || Gerrit.getConfig().getAuthType() == AuthType.OPENID) {
+        switchAccount.setHref(Gerrit.selfRedirect("/login/"));
+      } else {
+        switchAccount.removeFromParent();
+        switchAccount = null;
+      }
       if (canLogOut) {
         logout.setHref(Gerrit.selfRedirect("/logout"));
       } else {
-        logout.setVisible(false);
+        logout.removeFromParent();
+        logout = null;
       }
+
     } else {
       settings.removeFromParent();
       settings = null;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.ui.xml
index 4a4f4ca..cd51485 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/UserPopupPanel.ui.xml
@@ -19,8 +19,6 @@
   xmlns:g='urn:import:com.google.gwt.user.client.ui'
   xmlns:gerrit='urn:import:com.google.gerrit.client'
   xmlns:u='urn:import:com.google.gerrit.client.ui'>
-  <ui:with field='constants' type='com.google.gerrit.client.GerritConstants'/>
-
   <ui:style>
     .panel {
       padding: 8px;
@@ -36,16 +34,20 @@
     .userName {
       font-weight: bold;
     }
-    .userLinks {
-      min-width: 175px;
-    }
     .email {
       padding-bottom: 6px;
     }
-    .logout {
-      padding-left: 16px;
+    .userLinks {
+      min-width: 250px;
+    }
+    .userLinksRight {
       float: right;
     }
+    .switchAccount {
+      border-right: 1px solid black;
+      padding-right: 0.5em;
+      margin-right: 0.5em;
+    }
   </ui:style>
 
   <g:HTMLPanel styleName='{style.panel}'>
@@ -59,9 +61,10 @@
       <u:InlineHyperlink ui:field='settings' targetHistoryToken='/settings/'>
         <ui:msg>Settings</ui:msg>
       </u:InlineHyperlink>
-      <g:Anchor ui:field='logout' styleName="{style.logout}">
-        <ui:text from='{constants.menuSignOut}' />
-      </g:Anchor>
+      <span class='{style.userLinksRight}'>
+        <a ui:field='switchAccount' class='{style.switchAccount}'><ui:msg>Switch Account</ui:msg></a
+        ><a ui:field='logout'><ui:msg>Sign Out</ui:msg></a>
+      </span>
     </div>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index e1219be..970b317 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -46,7 +46,6 @@
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.client.ui.UserActivityMonitor;
-import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -57,7 +56,6 @@
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.logical.shared.CloseEvent;
@@ -72,10 +70,8 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.ListBox;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.ToggleButton;
-import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
@@ -84,7 +80,6 @@
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
@@ -139,14 +134,13 @@
   @UiField Element actionDate;
 
   @UiField Actions actions;
-  @UiField Element revisionParent;
-  @UiField ListBox revisionList;
   @UiField Labels labels;
   @UiField CommitBox commit;
   @UiField RelatedChanges related;
   @UiField FileTable files;
   @UiField FlowPanel history;
 
+  @UiField Button revisions;
   @UiField Button download;
   @UiField Button reply;
   @UiField Button expandAll;
@@ -155,6 +149,7 @@
   @UiField QuickApprove quickApprove;
   private ReplyAction replyAction;
   private EditMessageAction editMessageAction;
+  private RevisionsAction revisionsAction;
   private DownloadAction downloadAction;
 
   public ChangeScreen2(Change.Id changeId, String revision, boolean openReplyBox) {
@@ -179,8 +174,10 @@
   void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) {
     RestApi call = ChangeApi.detail(changeId.get());
     ChangeList.addOptions(call, EnumSet.of(
-        ListChangesOption.ALL_REVISIONS,
-        ListChangesOption.CURRENT_ACTIONS));
+      ListChangesOption.CURRENT_ACTIONS,
+      fg && revision != null
+        ? ListChangesOption.ALL_REVISIONS
+        : ListChangesOption.CURRENT_REVISION));
     if (!fg) {
       call.background();
     }
@@ -247,7 +244,13 @@
     }
   }
 
-  private void renderDownload(ChangeInfo info, String revision) {
+  private void initRevisionsAction(ChangeInfo info, String revision) {
+    revisionsAction = new RevisionsAction(
+        info.legacy_id(), revision,
+        style, headerLine, revisions);
+  }
+
+  private void initDownloadAction(ChangeInfo info, String revision) {
     downloadAction = new DownloadAction(
         info.legacy_id(),
         info.project(),
@@ -341,14 +344,9 @@
     downloadAction.show();
   }
 
-  @UiHandler("revisionList")
-  void onChangeRevision(ChangeEvent e) {
-    int idx = revisionList.getSelectedIndex();
-    if (0 <= idx) {
-      String n = revisionList.getValue(idx);
-      revisionList.setEnabled(false);
-      Gerrit.display(PageLinks.toChange2(changeId, n));
-    }
+  @UiHandler("revisions")
+  void onRevision(ClickEvent e) {
+    revisionsAction.show();
   }
 
   @UiHandler("reply")
@@ -597,9 +595,9 @@
     renderOwner(info);
     renderReviewers(info);
     renderActionTextDate(info);
-    renderDownload(info, revision);
-    renderRevisions(info);
     renderHistory(info);
+    initRevisionsAction(info, revision);
+    initDownloadAction(info, revision);
     actions.display(info, revision);
 
     star.setValue(info.starred());
@@ -660,40 +658,13 @@
     ccText.setInnerSafeHtml(labels.formatUserList(cc.values()));
   }
 
-  private void renderRevisions(ChangeInfo info) {
-    if (info.revisions().size() == 1) {
-      UIObject.setVisible(revisionParent, false);
-      return;
-    }
-
-    JsArray<RevisionInfo> list = info.revisions().values();
-    RevisionInfo.sortRevisionInfoByNumber(list);
-    if (Gerrit.isSignedIn()
-        && Gerrit.getUserAccount().getGeneralPreferences()
-            .isReversePatchSetOrder()) {
-      Collections.reverse(Natives.asList(list));
-    }
-
-    int selected = -1;
-    for (int i = 0; i < list.length(); i++) {
-      RevisionInfo r = list.get(i);
-      revisionList.addItem(
-          r._number() + ": " + r.name().substring(0, 6),
-          "" + r._number());
-      if (revision.equals(r.name())) {
-        selected = i;
-      }
-    }
-    if (0 <= selected) {
-      revisionList.setSelectedIndex(selected);
-    }
-  }
-
   private void renderOwner(ChangeInfo info) {
     // TODO info card hover
-    ownerText.setInnerText(info.owner().name() != null
+    String name = info.owner().name() != null
         ? info.owner().name()
-        : Gerrit.getConfig().getAnonymousCowardName());
+        : Gerrit.getConfig().getAnonymousCowardName();
+    ownerText.setInnerText(name);
+    ownerText.setTitle(name);
   }
 
   private void renderSubmitType(String action) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
index 8e4b8f4..6cf7b7b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -61,6 +61,10 @@
       position: absolute;
       top: 0;
       left: 29px;
+      width: 245px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
     }
     .idStatus {
       position: absolute;
@@ -97,6 +101,7 @@
       margin: 0;
       padding-left: 2px;
       padding-right: 2px;
+      min-width: 100px;
     }
     .popdown button div {
       padding-left: 6px;
@@ -256,14 +261,14 @@
         </g:Button>
       </div>
 
-      <div class='{style.popdown}'>
-        <div ui:field='revisionParent' style='display: inline-block;'>
-          <ui:msg>Revision <g:ListBox ui:field='revisionList'/></ui:msg>
-        </div>
+      <g:FlowPanel styleName='{style.popdown}'>
+        <g:Button ui:field='revisions' styleName=''>
+          <div><ui:msg>Revisions</ui:msg></div>
+        </g:Button>
         <g:Button ui:field='download' styleName=''>
           <div><ui:msg>Download</ui:msg></div>
         </g:Button>
-      </div>
+      </g:FlowPanel>
     </g:HTMLPanel>
 
     <table class='{style.headerTable}'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java
index ce2fe7b..ff00e92 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java
@@ -19,4 +19,9 @@
   String nextChange();
   String openChange();
   String reviewedFileTitle();
+
+  String ps();
+  String commit();
+  String date();
+  String author();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties
index 95f378c..bb9450f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties
@@ -2,3 +2,8 @@
 nextChange = Next related change
 openChange = Open related change
 reviewedFileTitle = Mark file as reviewed (Shortcut: r)
+
+ps = PS
+commit = Commit
+date = Date
+author = Author
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsAction.java
new file mode 100644
index 0000000..d66127b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsAction.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.change;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+
+class RevisionsAction extends RightSidePopdownAction {
+  private final RevisionsBox revisionBox;
+
+  RevisionsAction(
+      Change.Id changeId,
+      String revision,
+      ChangeScreen2.Style style,
+      UIObject relativeTo,
+      Widget downloadButton) {
+    super(style, relativeTo, downloadButton);
+    this.revisionBox = new RevisionsBox(changeId, revision);
+  }
+
+  Widget getWidget() {
+    return revisionBox;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.java
new file mode 100644
index 0000000..c0b2112
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.java
@@ -0,0 +1,219 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.change;
+
+import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.changes.ChangeList;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.client.ui.FancyFlexTableImpl;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import java.util.Collections;
+import java.util.EnumSet;
+
+class RevisionsBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, RevisionsBox> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  private static final String OPEN;
+  private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
+
+  static {
+    OPEN = DOM.createUniqueId().replace('-', '_');
+    init(OPEN);
+  }
+
+  private static final native void init(String o) /*-{
+    $wnd[o] = $entry(function(e,i) {
+      return @com.google.gerrit.client.change.RevisionsBox::onOpen(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i);
+    });
+  }-*/;
+
+  private static boolean onOpen(NativeEvent e, int idx) {
+    if (link.handleAsClick(e.<Event> cast())) {
+      RevisionsBox t = getRevisionBox(e);
+      if (t != null) {
+        t.onOpenRow(idx);
+        e.preventDefault();
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static RevisionsBox getRevisionBox(NativeEvent event) {
+    com.google.gwt.user.client.Element e = event.getEventTarget().cast();
+    for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
+      EventListener l = DOM.getEventListener(e);
+      if (l instanceof RevisionsBox) {
+        return (RevisionsBox) l;
+      }
+    }
+    return null;
+  }
+
+  interface Style extends CssResource {
+    String current();
+    String legacy_id();
+    String commit();
+  }
+
+  private final Change.Id changeId;
+  private final String revision;
+  private boolean loaded;
+  private JsArray<RevisionInfo> revisions;
+
+  @UiField FlexTable table;
+  @UiField Style style;
+
+  RevisionsBox(Change.Id changeId, String revision) {
+    this.changeId = changeId;
+    this.revision = revision;
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  @Override
+  protected void onLoad() {
+    if (!loaded) {
+      RestApi call = ChangeApi.detail(changeId.get());
+      ChangeList.addOptions(call, EnumSet.of(
+          ListChangesOption.ALL_REVISIONS,
+          ListChangesOption.ALL_COMMITS));
+      call.get(new AsyncCallback<ChangeInfo>() {
+        @Override
+        public void onSuccess(ChangeInfo result) {
+          render(result.revisions());
+          loaded = true;
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      });
+    }
+  }
+
+  private void onOpenRow(int idx) {
+    closeParent();
+    Gerrit.display(url(revisions.get(idx)));
+  }
+
+  private void render(NativeMap<RevisionInfo> map) {
+    map.copyKeysIntoChildren("name");
+
+    revisions = map.values();
+    RevisionInfo.sortRevisionInfoByNumber(revisions);
+    Collections.reverse(Natives.asList(revisions));
+
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
+    header(sb);
+    for (int i = 0; i < revisions.length(); i++) {
+      revision(sb, i, revisions.get(i));
+    }
+
+    GWT.<FancyFlexTableImpl> create(FancyFlexTableImpl.class)
+      .resetHtml(table, sb);
+  }
+
+  private void header(SafeHtmlBuilder sb) {
+    sb.openTr()
+      .openTh()
+          .setStyleName(style.legacy_id())
+          .append(Resources.C.ps())
+          .closeTh()
+      .openTh().append(Resources.C.commit()).closeTh()
+      .openTh().append(Resources.C.date()).closeTh()
+      .openTh().append(Resources.C.author()).closeTh()
+      .closeTr();
+  }
+
+  private void revision(SafeHtmlBuilder sb, int index, RevisionInfo r) {
+    CommitInfo c = r.commit();
+    sb.openTr();
+    if (revision.equals(r.name())) {
+      sb.setStyleName(style.current());
+    }
+
+    sb.openTd()
+      .setStyleName(style.legacy_id())
+      .append(r._number())
+      .closeTd();
+
+    sb.openTd()
+      .setStyleName(style.commit())
+      .openAnchor()
+      .setAttribute("href", "#" + url(r))
+      .setAttribute("onclick", OPEN + "(event," + index + ")")
+      .append(r.name().substring(0, 10))
+      .closeAnchor()
+      .closeTd();
+
+    sb.openTd()
+      .append(FormatUtil.shortFormatDayTime(c.committer().date()))
+      .closeTd();
+
+    String an = c.author() != null ? c.author().name() : null;
+    String cn = c.committer() != null ? c.committer().name() : null;
+    sb.openTd();
+    sb.append(an);
+    if (!"".equals(an) && !"".equals(cn) && !an.equals(cn)) {
+      sb.append(" / ").append(cn);
+    }
+    sb.closeTd();
+
+    sb.closeTr();
+  }
+
+  private String url(RevisionInfo r) {
+    return PageLinks.toChange2(
+        changeId,
+        String.valueOf(r._number()));
+  }
+
+  private void closeParent() {
+    for (Widget w = getParent(); w != null; w = w.getParent()) {
+      if (w instanceof PopupPanel) {
+        ((PopupPanel) w).hide(true);
+        break;
+      }
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.ui.xml
new file mode 100644
index 0000000..bf1ddd4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.ui.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+  <ui:style type='com.google.gerrit.client.change.RevisionsBox.Style'>
+    @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+
+    .revisionBox {
+      min-width: 300px;
+      margin: 10px 0px 5px 5px;
+    }
+
+    .scroll {
+      min-width: 300px;
+      height: 200px;
+    }
+
+    .table {
+      border-spacing: 0;
+      width: 100%;
+    }
+
+    .table td, .table th {
+      padding-left: 5px;
+      padding-right: 5px;
+      border-right: 2px solid #ddd;
+      white-space: nowrap;
+    }
+
+    .table tr.current {
+      background-color: selectionColor;
+    }
+
+    .legacy_id {
+      min-width: 50px;
+      text-align: right;
+      font-weight: bold;
+    }
+
+    .commit {
+      font-family: monospace;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.revisionBox}'>
+    <g:ScrollPanel styleName='{style.scroll}'>
+      <g:FlexTable ui:field='table' styleName='{style.table}'/>
+    </g:ScrollPanel>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
index 2c7451a..2836e0f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.client.ui;
 
-import com.google.gerrit.client.ui.FancyFlexTable.MyFlexTable;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTMLTable;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 
 public class FancyFlexTableImpl {
-  public void resetHtml(final MyFlexTable myTable, final SafeHtml body) {
+  public void resetHtml(final FlexTable myTable, final SafeHtml body) {
     SafeHtml.setInnerHTML(getBodyElement(myTable), body);
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java
index 17e8ddd..34b6bee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java
@@ -14,16 +14,16 @@
 
 package com.google.gerrit.client.ui;
 
-import com.google.gerrit.client.ui.FancyFlexTable.MyFlexTable;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTMLTable;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 public class FancyFlexTableImplIE6 extends FancyFlexTableImpl {
   @Override
-  public void resetHtml(final MyFlexTable myTable, final SafeHtml bodyHtml) {
+  public void resetHtml(final FlexTable myTable, final SafeHtml bodyHtml) {
     final Element oldBody = getBodyElement(myTable);
     final Element newBody = parseBody(bodyHtml);
     assert newBody != null;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index 208d282..677a615 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -106,6 +106,7 @@
       case OPENID_SSO:
         break;
     }
+    config.setSwitchAccountUrl(cfg.getString("auth", null, "switchAccountUrl"));
     config.setUseContributorAgreements(cfg.getBoolean("auth",
         "contributoragreements", false));
     config.setGitDaemonUrl(cfg.getString("gerrit", null, "canonicalgiturl"));
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 7ba65d3..d8a2648 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
@@ -46,8 +46,6 @@
 import com.google.common.net.HttpHeaders;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.audit.HttpAuditEvent;
-import com.google.gerrit.extensions.annotations.CapabilityScope;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsCreate;
 import com.google.gerrit.extensions.restapi.AcceptsPost;
@@ -76,7 +74,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.OptionUtil;
 import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.CapabilityUtils;
 import com.google.gson.ExclusionStrategy;
 import com.google.gson.FieldAttributes;
 import com.google.gson.FieldNamingPolicy;
@@ -200,7 +198,8 @@
 
       List<IdString> path = splitPath(req);
       RestCollection<RestResource, RestResource> rc = members.get();
-      checkAccessAnnotations(null, rc.getClass());
+      CapabilityUtils.checkRequiresCapability(globals.currentUser,
+          null, rc.getClass());
 
       RestResource rsrc = TopLevelResource.INSTANCE;
       ViewData viewData = new ViewData(null, null);
@@ -238,7 +237,7 @@
           viewData = view(rc, req.getMethod(), path);
         }
       }
-      checkAccessAnnotations(viewData);
+      checkRequiresCapability(viewData);
 
       while (viewData.view instanceof RestCollection<?,?>) {
         @SuppressWarnings("unchecked")
@@ -279,7 +278,7 @@
             viewData = view(c, req.getMethod(), path);
           }
         }
-        checkAccessAnnotations(viewData);
+        checkRequiresCapability(viewData);
       }
 
       if (notModified(req, rsrc)) {
@@ -870,37 +869,9 @@
     return !("GET".equals(method) || "HEAD".equals(method));
   }
 
-  private void checkAccessAnnotations(ViewData viewData) throws AuthException {
-    checkAccessAnnotations(viewData.pluginName, viewData.view.getClass());
-  }
-
-  private void checkAccessAnnotations(String pluginName, Class<?> clazz)
-      throws AuthException {
-    RequiresCapability rc = clazz.getAnnotation(RequiresCapability.class);
-    if (rc != null) {
-      CurrentUser user = globals.currentUser.get();
-      CapabilityControl ctl = user.getCapabilities();
-      String capability = rc.value();
-
-     if (pluginName != null && !"gerrit".equals(pluginName)
-         && (rc.scope() == CapabilityScope.PLUGIN
-          || rc.scope() == CapabilityScope.CONTEXT)) {
-        capability = String.format("%s-%s", pluginName, rc.value());
-      } else if (rc.scope() == CapabilityScope.PLUGIN) {
-        log.error(String.format(
-            "Class %s uses @%s(scope=%s), but is not within a plugin",
-            clazz.getName(),
-            RequiresCapability.class.getSimpleName(),
-            CapabilityScope.PLUGIN.name()));
-        throw new AuthException("cannot check capability");
-      }
-
-      if (!ctl.canPerform(capability) && !ctl.canAdministrateServer()) {
-        throw new AuthException(String.format(
-            "Capability %s is required to access this resource",
-            capability));
-      }
-    }
+  private void checkRequiresCapability(ViewData viewData) throws AuthException {
+    CapabilityUtils.checkRequiresCapability(globals.currentUser,
+        viewData.pluginName, viewData.view.getClass());
   }
 
   private static void handleException(Throwable err, HttpServletRequest req,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index 461a263..35af351 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -48,6 +48,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
+import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.slf4j.Logger;
@@ -179,7 +180,8 @@
     detail.setCommands(Lists.newArrayList(Iterables.transform(
         UiActions.sorted(UiActions.plugins(UiActions.from(
           revisions,
-          new RevisionResource(new ChangeResource(control), patchSet)))),
+          new RevisionResource(new ChangeResource(control), patchSet),
+          Providers.of(user)))),
         new Function<UiAction.Description, UiCommandDetail>() {
           @Override
           public UiCommandDetail apply(UiAction.Description in) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
new file mode 100644
index 0000000..6b68032
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.extensions.annotations.CapabilityScope;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.inject.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.annotation.Annotation;
+
+public class CapabilityUtils {
+  private static final Logger log = LoggerFactory
+      .getLogger(CapabilityUtils.class);
+
+  public static void checkRequiresCapability(Provider<CurrentUser> userProvider,
+      String pluginName, Class<?> clazz)
+      throws AuthException {
+    RequiresCapability rc = getClassAnnotation(clazz, RequiresCapability.class);
+    if (rc != null) {
+      CurrentUser user = userProvider.get();
+      CapabilityControl ctl = user.getCapabilities();
+      if (ctl.canAdministrateServer()) {
+        return;
+      }
+
+      String capability = rc.value();
+      if (pluginName != null && !"gerrit".equals(pluginName)
+         && (rc.scope() == CapabilityScope.PLUGIN
+          || rc.scope() == CapabilityScope.CONTEXT)) {
+        capability = String.format("%s-%s", pluginName, rc.value());
+      } else if (rc.scope() == CapabilityScope.PLUGIN) {
+        log.error(String.format(
+            "Class %s uses @%s(scope=%s), but is not within a plugin",
+            clazz.getName(),
+            RequiresCapability.class.getSimpleName(),
+            CapabilityScope.PLUGIN.name()));
+        throw new AuthException("cannot check capability");
+      }
+
+      if (!ctl.canPerform(capability)) {
+        throw new AuthException(String.format(
+            "Capability %s is required to access this resource",
+            capability));
+      }
+    }
+  }
+
+  /**
+   * Find an instance of the specified annotation, walking up the inheritance
+   * tree if necessary.
+   *
+   * @param <T> Annotation type to search for
+   * @param clazz root class to search, may be null
+   * @param annotationClass class object of Annotation subclass to search for
+   * @return the requested annotation or null if none
+   */
+  private static <T extends Annotation> T getClassAnnotation(Class<?> clazz,
+      Class<T> annotationClass) {
+    for (; clazz != null; clazz = clazz.getSuperclass()) {
+      T t = clazz.getAnnotation(annotationClass);
+      if (t != null) {
+        return t;
+      }
+    }
+    return null;
+  }
+}
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 04c1882..663519f 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
@@ -124,7 +124,7 @@
 
   private final Provider<ReviewDb> db;
   private final LabelNormalizer labelNormalizer;
-  private final CurrentUser user;
+  private final Provider<CurrentUser> user;
   private final AnonymousUser anonymous;
   private final IdentifiedUser.GenericFactory userFactory;
   private final ChangeControl.GenericFactory changeControlGenericFactory;
@@ -145,7 +145,7 @@
   ChangeJson(
       Provider<ReviewDb> db,
       LabelNormalizer ln,
-      CurrentUser u,
+      Provider<CurrentUser> userProvider,
       AnonymousUser au,
       IdentifiedUser.GenericFactory uf,
       ChangeControl.GenericFactory ccf,
@@ -158,7 +158,7 @@
       Revisions revisions) {
     this.db = db;
     this.labelNormalizer = ln;
-    this.user = u;
+    this.user = userProvider;
     this.anonymous = au;
     this.userFactory = uf;
     this.changeControlGenericFactory = ccf;
@@ -257,7 +257,9 @@
     out.updated = in.getLastUpdatedOn();
     out._number = in.getId().get();
     out._sortkey = in.getSortKey();
-    out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
+    out.starred = user.get().getStarredChanges().contains(in.getId())
+        ? true
+        : null;
     out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
     out.labels = labelsFor(cd, has(LABELS), has(DETAILED_LABELS));
 
@@ -291,7 +293,8 @@
       out.actions = Maps.newTreeMap();
       for (UiAction.Description d : UiActions.from(
           changes,
-          new ChangeResource(control(cd)))) {
+          new ChangeResource(control(cd)),
+          user)) {
         out.actions.put(d.getId(), new ActionInfo(d));
       }
     }
@@ -312,7 +315,8 @@
       if (changeControlUserFactory != null) {
         ctrl = changeControlUserFactory.controlFor(cd.change(db));
       } else {
-        ctrl = changeControlGenericFactory.controlFor(cd.change(db), user);
+        ctrl = changeControlGenericFactory.controlFor(cd.change(db),
+            user.get());
       }
     } catch (NoSuchChangeException e) {
       return null;
@@ -777,7 +781,8 @@
       out.actions = Maps.newTreeMap();
       for (UiAction.Description d : UiActions.from(
           revisions,
-          new RevisionResource(new ChangeResource(control(cd)), in))) {
+          new RevisionResource(new ChangeResource(control(cd)), in),
+          user)) {
         out.actions.put(d.getId(), new ActionInfo(d));
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index 9aae367..fa64f4a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -20,11 +20,15 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
 import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.CapabilityUtils;
+import com.google.inject.Provider;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,13 +74,15 @@
 
   public static <R extends RestResource> Iterable<UiAction.Description> from(
       RestCollection<?, R> collection,
-      R resource) {
-    return from(collection.views(), resource);
+      R resource,
+      Provider<CurrentUser> userProvider) {
+    return from(collection.views(), resource, userProvider);
   }
 
   public static <R extends RestResource> Iterable<UiAction.Description> from(
       DynamicMap<RestView<R>> views,
-      final R resource) {
+      final R resource,
+      final Provider<CurrentUser> userProvider) {
     return Iterables.filter(
       Iterables.transform(
         views,
@@ -103,6 +109,13 @@
               return null;
             }
 
+            try {
+              CapabilityUtils.checkRequiresCapability(userProvider,
+                  e.getPluginName(), view.getClass());
+            } catch (AuthException exc) {
+              return null;
+            }
+
             UiAction.Description dsc =
                 ((UiAction<R>) view).getDescription(resource);
             if (dsc == null || !dsc.isVisible()) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index 0fa5d25..fa5ab53 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -17,10 +17,9 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Atomics;
-import com.google.gerrit.extensions.annotations.CapabilityScope;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.CapabilityUtils;
 import com.google.gerrit.server.args4j.SubcommandHandler;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -29,8 +28,6 @@
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.kohsuke.args4j.Argument;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.StringWriter;
@@ -43,9 +40,6 @@
  * Command that dispatches to a subcommand from its command table.
  */
 final class DispatchCommand extends BaseCommand {
-  private static final Logger log = LoggerFactory
-      .getLogger(DispatchCommand.class);
-
   interface Factory {
     DispatchCommand create(Map<String, CommandProvider> map);
   }
@@ -121,36 +115,16 @@
 
   private void checkRequiresCapability(Command cmd)
       throws UnloggedFailure {
-    RequiresCapability rc = cmd.getClass().getAnnotation(RequiresCapability.class);
-    if (rc != null) {
-      CurrentUser user = currentUser.get();
-      CapabilityControl ctl = user.getCapabilities();
-      String capability = rc.value();
-
-      if (cmd instanceof BaseCommand) {
-        String pluginName = ((BaseCommand) cmd).getPluginName();
-        if (pluginName != null && !"gerrit".equals(pluginName)
-            && (rc.scope() == CapabilityScope.PLUGIN
-             || rc.scope() == CapabilityScope.CONTEXT)) {
-          capability = String.format("%s-%s", pluginName, rc.value());
-        } else if (rc.scope() == CapabilityScope.PLUGIN) {
-          log.error(String.format(
-              "Class %s uses @%s(scope=%s), but is not within a plugin",
-              cmd.getClass().getName(),
-              RequiresCapability.class.getSimpleName(),
-              CapabilityScope.PLUGIN.name()));
-          throw new UnloggedFailure(
-              BaseCommand.STATUS_NOT_ADMIN,
-              "fatal: cannot check capability");
-        }
-      }
-
-      if (!ctl.canPerform(capability) && !ctl.canAdministrateServer()) {
-        String msg = String.format(
-            "fatal: %s does not have \"%s\" capability.",
-            user.getUserName(), capability);
-        throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
-      }
+    String pluginName = null;
+    if (cmd instanceof BaseCommand) {
+      pluginName = ((BaseCommand) cmd).getPluginName();
+    }
+    try {
+      CapabilityUtils.checkRequiresCapability(currentUser,
+          pluginName, cmd.getClass());
+    } catch (AuthException e) {
+      throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN,
+          e.getMessage());
     }
   }
 
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 6f412e4..fd066ef 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -73,6 +73,7 @@
   id = 'commons-io:commons-io:1.4',
   sha1 = 'a8762d07e76cfde2395257a5da47ba7c1dbd3dce',
   license = 'Apache2.0',
+  exclude = ['META-INF/MANIFEST.MF'],
 )
 
 maven_jar(