Merge "Add SSH command to set group members"
diff --git a/.buckversion b/.buckversion
index 2c4c008..8c4a95b 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-410fcf3420cb06e62cbe9ee93eff931fa9a9b1a2
+a3aadacd7c1ccd819420a73975a08ba67110decb
diff --git a/.gitignore b/.gitignore
index 78e784d5..b979660 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
 /buck-out
 /local.properties
 *.pyc
+/gwt-unitCache
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index a3bfe27..fab71dd 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -747,7 +747,8 @@
 
 For every configured label `My-Name` in the project, there is a
 corresponding permission `label-My-Name` with a range corresponding to
-the defined values.
+the defined values. There is also a corresponding `labelAs-My-Name`
+permission that enables editing another user's label.
 
 Gerrit comes pre-configured with a default 'Code-Review' label that can
 be granted to groups within projects, enabling functionality for that
@@ -1266,6 +1267,27 @@
 command, but also to the web UI results pagination size.
 
 
+[[capability_runAs]]
+Run As
+~~~~~~
+
+Allow users to impersonate any other user with the `X-Gerrit-RunAs`
+HTTP header on REST API calls, or the link:cmd-suexec.html[suexec]
+SSH command.
+
+When impersonating an administrator the Administrate Server capability
+is not honored.  This security feature tries to prevent a role with
+Run As capability from modifying the access controls in All-Projects,
+however modification may still be possible if the impersonated user
+has permission to push or submit changes on `refs/meta/config`.  Run
+As also blocks using most capabilities including Create User, Run
+Garbage Collection, etc., unless the capability is also explicitly
+granted to a group the administrator is a member of.
+
+Administrators do not automatically inherit this capability; it must
+be explicitly granted.
+
+
 [[capability_runGC]]
 Run Garbage Collection
 ~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 126b2a0..d426508 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -25,6 +25,10 @@
 	operating system, and other details about the environment
 	that Gerrit Code Review is running in.
 
+--width::
+-w::
+	Width of the output table.
+
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index 8404a97..ab9fadf 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -32,6 +32,11 @@
 -n::
 	Show client hostnames as IP addresses instead of DNS hostname.
 
+--wide::
+-w::
+	Do not format the output to the terminal width (default of
+	80 columns).
+
 DISPLAY
 -------
 
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index f99e342..b75aeeb 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -37,6 +37,13 @@
 ---------
 Intended for interactive use only.
 
+OPTIONS
+-------
+--wide::
+-w::
+	Do not format the output to the terminal width (default of
+	80 columns).
+
 DISPLAY
 -------
 
diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt
index baffd53..78fc361 100644
--- a/Documentation/cmd-suexec.txt
+++ b/Documentation/cmd-suexec.txt
@@ -19,10 +19,14 @@
 
 DESCRIPTION
 -----------
-The suexec command can only be invoked by the magic user `Gerrit
-Code Review` and permits executing any other command as any other
+The suexec command permits executing any other command as any other
 registered user account.
 
+suexec can only be invoked by the magic user `Gerrit Code Review`,
+or any user granted granted the link:access-control.html#capability_runAs[Run As]
+capability. The run as capability is permitted to be used only if
+link:config-gerrit.html[auth.enableRunAs] is true.
+
 OPTIONS
 -------
 
@@ -39,7 +43,8 @@
 ACCESS
 ------
 Caller must be the magic user Gerrit Code Review using the SSH
-daemon's host key or a key on this daemon's peer host key ring.
+daemon's host key, or a key on this daemon's peer host key ring,
+or a user granted the Run As capability.
 
 SCRIPTING
 ---------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 865dd05..8d134ff 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -390,6 +390,18 @@
 +
 By default this is set to false.
 
+[[auth.enableRunAs]]auth.enableRunAs::
++
+If true HTTP REST APIs will accept the `X-Gerrit-RunAs` HTTP request
+header from any users granted the link:access-control.html#capability_runAs[Run As]
+capability. The header and capability permit the authenticated user
+to impersonate another account.
++
+If false the feature is disabled and cannot be re-enabled without
+editing gerrit.config and restarting the server.
++
+Default is true.
+
 [[cache]]Section cache
 ~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index dbb9006..5aca9a4 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -53,8 +53,7 @@
 ----
 
 In Eclipse, choose 'Import existing project' and select the `gerrit` project
-from the current working directory.  Do not import any of the other Maven
-based projects.
+from the current working directory.
 
 Expand the `gerrit` project, right-click on the `buck-out` folder, select
 'Properties', and then under 'Attributes' check 'Derived'.
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 84cb1e0..daf83cf 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -271,6 +271,10 @@
 Developers concerned with stable branches are encouraged to backport or push
 patchsets to these branches, even if no new release is planned.
 
+Fixes that are known to be needed for a particular release should be pushed
+for review on that release's stable branch.  It will then be included in
+the master branch when the stable branch is merged back.
+
 
 GERRIT
 ------
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 0594d93..cf2ac03 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2597,40 +2597,62 @@
 [[label-info]]
 LabelInfo
 ~~~~~~~~~
-The `LabelInfo` entity contains information about a label on a change.
+The `LabelInfo` entity contains information about a label on a change, always
+corresponding to the current patch set.
 
+There are two options that control the contents of `LabelInfo`:
+link:#labels[`LABELS`] and link:#detailed-labels[`DETAILED_LABELS`].
+
+* For a quick summary of the state of labels, use `LABELS`.
+* For detailed information about labels, including exact numeric votes for all
+  users and the allowed range of votes for the current user, use `DETAILED_LABELS`.
+
+Common fields
+^^^^^^^^^^^^^
 [options="header",width="50%",cols="1,^1,5"]
 |===========================
 |Field Name    ||Description
-|`approved`    |optional|The user who approved this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`rejected`    |optional|The user who rejected this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`recommended` |optional|The user who recommended this label on the
-change as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`disliked`    |optional|The user who disliked this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`value`       |optional|The voting value of the user who
-recommended/disliked this label on the change if it is not
-"`+1`"/"`-1`". +
-Only set if link:#labels[labels] are requested.
 |`optional`    |not set if `false`|
 Whether the label is optional. Optional means the label may be set, but
 it's neither necessary for submission nor does it block submission if
 set.
+|===========================
+
+Fields set by `LABELS`
+^^^^^^^^^^^^^^^^^^^^^^
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
+|`approved`    |optional|One user who approved this label on the change
+(voted the maximum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`rejected`    |optional|One user who rejected this label on the change
+(voted the minimum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`recommended` |optional|One user who recommended this label on the
+change (voted positively, but not the maximum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`disliked`    |optional|One user who disliked this label on the change
+(voted negatively, but not the minimum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`value`       |optional|The voting value of the user who
+recommended/disliked this label on the change if it is not
+"`+1`"/"`-1`".
+|===========================
+
+Fields set by `DETAILED_LABELS`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
 |`all`         |optional|List of all approvals for this label as a list
-of link:#approval-info[ApprovalInfo] entities. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+of link:#approval-info[ApprovalInfo] entities.
 |`values`      |optional|A map of all values that are allowed for this
 label. The map maps the values ("`-2`", "`-1`", " `0`", "`+1`", "`+2`")
-to the value descriptions. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+to the value descriptions.
 |===========================
 
+
 [[restore-input]]
 RestoreInput
 ~~~~~~~~~~~~
@@ -2705,6 +2727,10 @@
 after the review is stored. +
 Allowed values are `NONE`, `OWNER`, `OWNER_REVIEWERS` and `ALL`. +
 If not set, the default is `ALL`.
+|`on_behalf_of`|optional|
+link:rest-api-accounts.html#account-id[\{account-id\}] the review
+should be posted on behalf of. To use this option the caller must
+have been granted `labelAs-NAME` permission for all keys of labels.
 |============================
 
 [[reviewer-info]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
new file mode 100644
index 0000000..70f564c
--- /dev/null
+++ b/Documentation/rest-api-config.txt
@@ -0,0 +1,137 @@
+Gerrit Code Review - /config/ REST API
+======================================
+
+This page describes the config related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+[[config-endpoints]]
+Config Endpoints
+---------------
+
+[[list-capabilities]]
+List Capabilities
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /config/server/capabilities'
+
+Lists the capabilities that are available in the system. There are two
+kinds of capabilities: core and plugin-owned capabilities.
+
+As result a map of link:#capability-info[CapabilityInfo] entities is
+returned.
+
+The entries in the map are sorted by capability ID.
+
+.Request
+----
+  GET /config/server/capabilities/ HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "accessDatabase": {
+      "kind": "gerritcodereview#capability",
+      "id": "accessDatabase",
+      "name": "Access Database"
+    },
+    "administrateServer": {
+      "kind": "gerritcodereview#capability",
+      "id": "administrateServer",
+      "name": "Administrate Server"
+    },
+    "createAccount": {
+      "kind": "gerritcodereview#capability",
+      "id": "createAccount",
+      "name": "Create Account"
+    },
+    "createGroup": {
+      "kind": "gerritcodereview#capability",
+      "id": "createGroup",
+      "name": "Create Group"
+    },
+    "createProject": {
+      "kind": "gerritcodereview#capability",
+      "id": "createProject",
+      "name": "Create Project"
+    },
+    "emailReviewers": {
+      "kind": "gerritcodereview#capability",
+      "id": "emailReviewers",
+      "name": "Email Reviewers"
+    },
+    "flushCaches": {
+      "kind": "gerritcodereview#capability",
+      "id": "flushCaches",
+      "name": "Flush Caches"
+    },
+    "killTask": {
+      "kind": "gerritcodereview#capability",
+      "id": "killTask",
+      "name": "Kill Task"
+    },
+    "priority": {
+      "kind": "gerritcodereview#capability",
+      "id": "priority",
+      "name": "Priority"
+    },
+    "queryLimit": {
+      "kind": "gerritcodereview#capability",
+      "id": "queryLimit",
+      "name": "Query Limit"
+    },
+    "runGC": {
+      "kind": "gerritcodereview#capability",
+      "id": "runGC",
+      "name": "Run Garbage Collection"
+    },
+    "startReplication": {
+      "kind": "gerritcodereview#capability",
+      "id": "startReplication",
+     "name": "Start Replication"
+    },
+    "streamEvents": {
+      "kind": "gerritcodereview#capability",
+      "id": "streamEvents",
+      "name": "Stream Events"
+    },
+    "viewCaches": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewCaches",
+      "name": "View Caches"
+    },
+    "viewConnections": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewConnections",
+      "name": "View Connections"
+    },
+    "viewQueue": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewQueue",
+      "name": "View Queue"
+    }
+  }
+----
+
+[[capability-info]]
+CapabilityInfo
+~~~~~~~~~~~~~~
+The `CapabilityInfo` entity contains information about a capability.
+
+[options="header",width="50%",cols="1,5"]
+|=================================
+|Field Name           |Description
+|`kind`               |`gerritcodereview#capability`
+|`id`                 |capability ID
+|`name`               |capability name
+|=================================
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 4785350..d8f021b 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -934,7 +934,8 @@
 [verse]
 'PUT /groups/link:#group-id[\{group-id\}]/groups/link:#group-id[\{group-id\}]'
 
-Includes a group into a Gerrit internal group.
+Includes an internal or external group into a Gerrit internal group.
+External groups must be specified using the UUID.
 
 .Request
 ----
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index ccc8604..c0dde1c 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -13,6 +13,8 @@
   Account related REST endpoints
 link:rest-api-changes.html[/changes/]::
   Change related REST endpoints
+link:rest-api-config.html[/config/]::
+  Config related REST endpoints
 link:rest-api-groups.html[/groups/]::
   Group related REST endpoints
 link:rest-api-projects.html[/projects/]::
diff --git a/ReleaseNotes/ReleaseNotes-2.6.txt b/ReleaseNotes/ReleaseNotes-2.6.txt
index 12986f6..0c21182 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.txt
@@ -95,11 +95,6 @@
 
 Search
 ^^^^^^
-* Animate search bar by expanding & unexpanding
-+
-When the search bar is used, expand it to allow for more text to be
-visible. When it is blurred, shrink it back to the original size.
-
 * Suggest projects, groups and users in search panel
 +
 Suggest projects, groups and users in the search panel as parameter for
@@ -1192,7 +1187,7 @@
 ordering issue between the SearchPanel and the HintTextBox.
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1661[Issue 1661]:
-  Update links to Change-Id and Signed-off-by docu on project info
+  Update links to Change-Id and Signed-off-by documentation on project info
   screen
 
 * Use href="javascript;" for All {Side-by-Side,Unified} links
@@ -1232,6 +1227,12 @@
 URL-unescape the path portion of a change history token to correctly
 handle paths with URL-escapable characters, i.e. '+', ' ', etc.
 
+* link:https://code.google.com/p/gerrit/issues/detail?id=1915[Issue 1915]:
+Don't show non-visible drafts in the diff screens.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1801[Issue 1801]:
+Correctly keep patch set ordering after a new patch set is added via
+the Web UI.
 
 REST API
 ~~~~~~~~
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
index d14449d..94f4646 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -141,6 +141,13 @@
 Get diff of a file in a revision]
 
 
+Config
+^^^^^^
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-config.html#get-capabilities[
+Get capabilities]
+
+
 Projects
 ^^^^^^^^
 
@@ -164,6 +171,28 @@
 Get child project]
 
 
+Capabilities
+~~~~~~~~~~~~
+
+
+New global capabilities are added.
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/access_control.html#capability_generateHttpPassword[
+Generate Http Password] Allows non-administrator users to generate HTTP
+passwords for users other than themself.
++
+This capability would typically be assigned to a non-interactive group
+to be able to generate HTTP passwords for users from a tool or web service
+that uses the Gerrit REST API.
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/access_control.html#capability_runAs[
+Run As] Allows users to impersonate other users by setting the `X-Gerrit-RunAs`
+HTTP header on REST API calls.
++
+Site administrators do not inherit this capability;  it must be granted
+explicitly.
+
+
 Plugins
 ~~~~~~~
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index b8de9ae..e18aee2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -70,6 +70,9 @@
   /** Maximum result limit per executed query. */
   public static final String QUERY_LIMIT = "queryLimit";
 
+  /** Ability to impersonate another user. */
+  public static final String RUN_AS = "runAs";
+
   /** Can run the Git garbage collection. */
   public static final String RUN_GC = "runGC";
 
@@ -103,6 +106,7 @@
     NAMES_ALL.add(KILL_TASK);
     NAMES_ALL.add(PRIORITY);
     NAMES_ALL.add(QUERY_LIMIT);
+    NAMES_ALL.add(RUN_AS);
     NAMES_ALL.add(RUN_GC);
     NAMES_ALL.add(START_REPLICATION);
     NAMES_ALL.add(STREAM_EVENTS);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 0585651..10d0b13 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -29,6 +29,7 @@
   public static final String FORGE_COMMITTER = "forgeCommitter";
   public static final String FORGE_SERVER = "forgeServerAsCommitter";
   public static final String LABEL = "label-";
+  public static final String LABEL_AS = "labelAs-";
   public static final String OWNER = "owner";
   public static final String PUBLISH_DRAFTS = "publishDrafts";
   public static final String PUSH = "push";
@@ -43,6 +44,7 @@
 
   private static final List<String> NAMES_LC;
   private static final int labelIndex;
+  private static final int labelAsIndex;
 
   static {
     NAMES_LC = new ArrayList<String>();
@@ -58,6 +60,7 @@
     NAMES_LC.add(PUSH_TAG.toLowerCase());
     NAMES_LC.add(PUSH_SIGNED_TAG.toLowerCase());
     NAMES_LC.add(LABEL.toLowerCase());
+    NAMES_LC.add(LABEL_AS.toLowerCase());
     NAMES_LC.add(REBASE.toLowerCase());
     NAMES_LC.add(REMOVE_REVIEWER.toLowerCase());
     NAMES_LC.add(SUBMIT.toLowerCase());
@@ -67,15 +70,18 @@
     NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase());
 
     labelIndex = NAMES_LC.indexOf(Permission.LABEL);
+    labelAsIndex = NAMES_LC.indexOf(Permission.LABEL_AS.toLowerCase());
   }
 
   /** @return true if the name is recognized as a permission name. */
   public static boolean isPermission(String varName) {
-    String lc = varName.toLowerCase();
-    if (lc.startsWith(LABEL)) {
-      return LABEL.length() < lc.length();
-    }
-    return NAMES_LC.contains(lc);
+    return isLabel(varName)
+        || isLabelAs(varName)
+        || NAMES_LC.contains(varName.toLowerCase());
+  }
+
+  public static boolean hasRange(String varName) {
+    return isLabel(varName) || isLabelAs(varName);
   }
 
   /** @return true if the permission name is actually for a review label. */
@@ -83,11 +89,30 @@
     return varName.startsWith(LABEL) && LABEL.length() < varName.length();
   }
 
+  /** @return true if the permission is for impersonated review labels. */
+  public static boolean isLabelAs(String var) {
+    return var.startsWith(LABEL_AS) && LABEL_AS.length() < var.length();
+  }
+
   /** @return permission name for the given review label. */
   public static String forLabel(String labelName) {
     return LABEL + labelName;
   }
 
+  /** @return permission name to apply a label for another user. */
+  public static String forLabelAs(String labelName) {
+    return LABEL_AS + labelName;
+  }
+
+  public static String extractLabel(String varName) {
+    if (isLabel(varName)) {
+      return varName.substring(LABEL.length());
+    } else if (isLabelAs(varName)) {
+      return varName.substring(LABEL_AS.length());
+    }
+    return null;
+  }
+
   public static boolean canBeOnAllProjects(String ref, String permissionName) {
     if (AccessSection.ALL.equals(ref)) {
       return !OWNER.equals(permissionName);
@@ -110,15 +135,8 @@
     return name;
   }
 
-  public boolean isLabel() {
-    return isLabel(getName());
-  }
-
   public String getLabel() {
-    if (isLabel()) {
-      return getName().substring(LABEL.length());
-    }
-    return null;
+    return extractLabel(getName());
   }
 
   public Boolean getExclusiveGroup() {
@@ -223,8 +241,10 @@
   }
 
   private static int index(Permission a) {
-    if (a.isLabel()) {
+    if (isLabel(a.getName())) {
       return labelIndex;
+    } else if (isLabelAs(a.getName())) {
+      return labelAsIndex;
     }
 
     int index = NAMES_LC.indexOf(a.getName().toLowerCase());
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
index 3490dd7..0363fd6 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
@@ -86,7 +86,7 @@
   }
 
   public String getLabel() {
-    return isLabel() ? getName().substring(Permission.LABEL.length()) : null;
+    return Permission.extractLabel(getName());
   }
 
   public int getMin() {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index 904c5c7..ee6cc95 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProjectAccess {
@@ -28,6 +29,7 @@
   protected boolean isConfigVisible;
   protected boolean canUpload;
   protected LabelTypes labelTypes;
+  protected Map<String, String> capabilities;
 
   public ProjectAccess() {
   }
@@ -112,4 +114,12 @@
   public void setLabelTypes(LabelTypes labelTypes) {
     this.labelTypes = labelTypes;
   }
+
+  public Map<String, String> getCapabilities() {
+    return capabilities;
+  }
+
+  public void setCapabilities(Map<String, String> capabilities) {
+    this.capabilities = capabilities;
+  }
 }
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index cf38248..fbd493f 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -87,11 +87,17 @@
 java_test(
   name = 'ui_tests',
   srcs = glob(['src/test/java/**/*.java']),
+  resources = glob(['src/test/resources/**/*']) + [
+    'src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml',
+  ],
   deps = [
     ':ui_module',
     '//lib:junit',
     '//lib/gwt:dev',
+    '//lib/gwt:user',
+    '//lib/gwt:gwt-test-utils',
     '//lib/jgit:jgit',
   ],
   source_under_test = [':ui_module'],
+  visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 344104d..b734b41 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -93,9 +93,8 @@
 
   public AccessSectionEditor(ProjectAccess access) {
     projectAccess = access;
-
-    permissionSelector =
-        new ValueListBox<String>(PermissionNameRenderer.INSTANCE);
+    permissionSelector = new ValueListBox<String>(
+        new PermissionNameRenderer(access.getCapabilities()));
     permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
       @Override
       public void onValueChange(ValueChangeEvent<String> event) {
@@ -222,12 +221,15 @@
     List<String> perms = new ArrayList<String>();
 
     if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
-      for (String varName : Util.C.capabilityNames().keySet()) {
+      for (String varName : projectAccess.getCapabilities().keySet()) {
         addPermission(varName, perms);
       }
     } else if (RefConfigSection.isValid(value.getName())) {
       for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
-        addPermission(Permission.LABEL + t.getName(), perms);
+        addPermission(Permission.forLabel(t.getName()), perms);
+      }
+      for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
+        addPermission(Permission.forLabelAs(t.getName()), perms);
       }
       for (String varName : Util.C.permissionNames().keySet()) {
         addPermission(varName, perms);
@@ -282,7 +284,7 @@
     @Override
     public PermissionEditor create(int index) {
       PermissionEditor subEditor =
-          new PermissionEditor(projectAccess.getProjectName(), readOnly, value,
+          new PermissionEditor(projectAccess, readOnly, value,
               projectAccess.getLabelTypes());
       permissionContainer.insert(subEditor, index);
       return subEditor;
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 51ff978..7aaf04d 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
@@ -58,6 +58,7 @@
   private Button delInclude;
 
   private FlowPanel noMembersInfo;
+  private AccountGroupSuggestOracle accountGroupSuggestOracle;
 
   public AccountGroupMembersScreen(final GroupInfo toShow, final String token) {
     super(toShow, token);
@@ -109,9 +110,10 @@
   }
 
   private void initIncludeList() {
+    accountGroupSuggestOracle = new AccountGroupSuggestOracle();
     addIncludeBox =
         new AddMemberBox(Util.C.buttonAddIncludedGroup(),
-            Util.C.defaultAccountGroupName(), new AccountGroupSuggestOracle());
+            Util.C.defaultAccountGroupName(), accountGroupSuggestOracle);
 
     addIncludeBox.addClickHandler(new ClickHandler() {
       @Override
@@ -187,13 +189,18 @@
   }
 
   void doAddNewInclude() {
-    final String groupName = addIncludeBox.getText();
+    String groupName = addIncludeBox.getText();
     if (groupName.length() == 0) {
       return;
     }
 
+    AccountGroup.UUID uuid = accountGroupSuggestOracle.getUUID(groupName);
+    if (uuid == null) {
+      return;
+    }
+
     addIncludeBox.setEnabled(false);
-    GroupApi.addIncludedGroup(getGroupUUID(), groupName,
+    GroupApi.addIncludedGroup(getGroupUUID(), uuid.get(),
         new GerritCallback<GroupInfo>() {
           public void onSuccess(final GroupInfo result) {
             addIncludeBox.setEnabled(true);
@@ -290,28 +297,12 @@
           return str == null ? "" : str;
         }
       };
-      int insertPosition = table.getRowCount();
-      int left = 1;
-      int right = table.getRowCount() - 1;
-      while (left <= right) {
-        int middle = (left + right) >>> 1; // (left+right)/2
-        AccountInfo i = getRowItem(middle);
-        int cmp = c.compare(i, info);
-
-        if (cmp < 0) {
-          left = middle + 1;
-        } else if (cmp > 0) {
-          right = middle - 1;
-        } else {
-          // group is already contained in the table
-          return;
-        }
+      int insertPos = getInsertRow(c, info);
+      if (insertPos >= 0) {
+        table.insertRow(insertPos);
+        applyDataRowStyle(insertPos);
+        populate(insertPos, info);
       }
-      insertPosition = left;
-
-      table.insertRow(insertPosition);
-      applyDataRowStyle(insertPosition);
-      populate(insertPosition, info);
     }
 
     void populate(final int row, final AccountInfo i) {
@@ -405,29 +396,12 @@
           return (str == null) ? "" : str;
         }
       };
-
-      int insertPosition = table.getRowCount();
-      int left = 1;
-      int right = table.getRowCount() - 1;
-      while (left <= right) {
-        int middle = (left + right) >>> 1; // (left+right)/2
-        GroupInfo i = getRowItem(middle);
-        int cmp = c.compare(i, info);
-
-        if (cmp < 0) {
-          left = middle + 1;
-        } else if (cmp > 0) {
-          right = middle - 1;
-        } else {
-          // group is already contained in the table
-          return;
-        }
+      int insertPos = getInsertRow(c, info);
+      if (insertPos >= 0) {
+        table.insertRow(insertPos);
+        applyDataRowStyle(insertPos);
+        populate(insertPos, info);
       }
-      insertPosition = left;
-
-      table.insertRow(insertPosition);
-      applyDataRowStyle(insertPosition);
-      populate(insertPosition, info);
     }
 
     void populate(final int row, final GroupInfo i) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index f4c0b55..e505860 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -125,8 +125,6 @@
   String refErrorPrintable();
   String errorsMustBeFixed();
 
-  Map<String, String> capabilityNames();
-
   String sectionTypeReference();
   String sectionTypeSection();
   Map<String, String> sectionNames();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index ce27780..2f2345d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -144,41 +144,6 @@
 refErrorPrintable = References may contain only printable characters
 errorsMustBeFixed = Errors must be fixed before committing changes.
 
-# Capability Names
-capabilityNames = \
-  accessDatabase, \
-  administrateServer, \
-  createAccount, \
-  createGroup, \
-  createProject, \
-  emailReviewers, \
-  flushCaches, \
-  killTask, \
-  priority, \
-  queryLimit, \
-  runGC, \
-  startReplication, \
-  streamEvents, \
-  viewCaches, \
-  viewConnections, \
-  viewQueue
-accessDatabase = Access Database
-administrateServer = Administrate Server
-createAccount = Create Account
-createGroup = Create Group
-createProject = Create Project
-emailReviewers = Email Reviewers
-flushCaches = Flush Caches
-killTask = Kill Task
-priority = Priority
-queryLimit = Query Limit
-runGC = Run Garbage Collection
-startReplication = Start Replication
-streamEvents = Stream Events
-viewCaches = View Caches
-viewConnections = View Connections
-viewQueue = View Queue
-
 # Section Names
 sectionTypeReference = Reference:
 sectionTypeSection = Section:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index 3b4d7d4..d0c52c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -19,6 +19,7 @@
 public interface AdminMessages extends Messages {
   String group(String name);
   String label(String name);
+  String labelAs(String name);
   String project(String name);
   String deletedGroup(int id);
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index 7f8cd56..28f3fcc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -1,5 +1,6 @@
 group = Group {0}
 label = Label {0}
+labelAs = Label {0} (On Behalf Of)
 project = Project {0}
 deletedGroup = Deleted Group {0}
 deletedReference = Reference {0} was deleted
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index dac0b6a..bed6b4a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.client.ui.FilteredUserInterface;
 import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
 import com.google.gerrit.common.PageLinks;
+import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
 import com.google.gwt.http.client.URL;
@@ -55,10 +56,10 @@
   protected void onLoad() {
     super.onLoad();
     display();
-    refresh();
+    refresh(false);
   }
 
-  private void refresh() {
+  private void refresh(final boolean open) {
     setToken(subname == null || "".equals(subname) ? ADMIN_GROUPS
         : ADMIN_GROUPS + "?filter=" + URL.encodeQueryString(subname));
     GroupMap.match(subname,
@@ -66,8 +67,13 @@
             new GerritCallback<GroupMap>() {
               @Override
               public void onSuccess(GroupMap result) {
-                groups.display(result, subname);
-                groups.finishDisplay();
+                if (open && result.values().length() > 0) {
+                  Gerrit.display(PageLinks.toGroup(
+                      result.values().get(0).getGroupUUID()));
+                } else {
+                  groups.display(result, subname);
+                  groups.finishDisplay();
+                }
               }
             }));
   }
@@ -99,7 +105,7 @@
       @Override
       public void onKeyUp(KeyUpEvent event) {
         subname = filterTxt.getValue();
-        refresh();
+        refresh(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
       }
     });
     hp.add(filterTxt);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index 9848c18..028fb87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -24,6 +24,7 @@
 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.common.data.ProjectAccess;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
@@ -107,17 +108,19 @@
   private PermissionRange.WithDefaults validRange;
   private boolean isDeleted;
 
-  public PermissionEditor(Project.NameKey projectName,
+  public PermissionEditor(ProjectAccess projectAccess,
       boolean readOnly,
       AccessSection section,
       LabelTypes labelTypes) {
     this.readOnly = readOnly;
     this.section = section;
-    this.projectName = projectName;
+    this.projectName = projectAccess.getProjectName();
     this.labelTypes = labelTypes;
 
-    normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
-    deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
+    PermissionNameRenderer nameRenderer =
+        new PermissionNameRenderer(projectAccess.getCapabilities());
+    normalName = new ValueLabel<String>(nameRenderer);
+    deletedName = new ValueLabel<String>(nameRenderer);
 
     initWidget(uiBinder.createAndBindUi(this));
     groupToAdd.setProject(projectName);
@@ -262,7 +265,7 @@
   public void setValue(Permission value) {
     this.value = value;
 
-    if (value.isLabel()) {
+    if (Permission.hasRange(value.getName())) {
       LabelType lt = labelTypes.byLabel(value.getLabel());
       if (lt != null) {
         validRange = new PermissionRange.WithDefaults(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
index ad3473c..d8ee195 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -22,37 +22,54 @@
 import java.util.Map;
 
 class PermissionNameRenderer implements Renderer<String> {
-  static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer();
-
-  private static final Map<String, String> all;
+  private static final Map<String, String> permissions;
 
   static {
-    all = new HashMap<String, String>();
-    for (Map.Entry<String, String> e : Util.C.capabilityNames().entrySet()) {
-      all.put(e.getKey(), e.getValue());
-      all.put(e.getKey().toLowerCase(), e.getValue());
-    }
+    permissions = new HashMap<String, String>();
     for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
-      all.put(e.getKey(), e.getValue());
-      all.put(e.getKey().toLowerCase(), e.getValue());
+      permissions.put(e.getKey(), e.getValue());
+      permissions.put(e.getKey().toLowerCase(), e.getValue());
     }
   }
 
+  private final Map<String, String> fromServer;
+
+  PermissionNameRenderer(Map<String, String> allFromOutside) {
+    fromServer = allFromOutside;
+  }
+
   @Override
   public String render(String varName) {
-    if (Permission.isLabel(varName)) {
-      return Util.M.label(new Permission(varName).getLabel());
+    if (Permission.isLabelAs(varName)) {
+      return Util.M.labelAs(Permission.extractLabel(varName));
+    } else if (Permission.isLabel(varName)) {
+      return Util.M.label(Permission.extractLabel(varName));
     }
 
-    String desc = all.get(varName);
-    if (desc == null) {
-      desc = all.get(varName.toLowerCase());
+    String desc = permissions.get(varName);
+    if (desc != null) {
+      return desc;
     }
-    return desc != null ? desc : varName;
+
+    desc = fromServer.get(varName);
+    if (desc != null) {
+      return desc;
+    }
+
+    desc = permissions.get(varName.toLowerCase());
+    if (desc != null) {
+      return desc;
+    }
+
+    desc = fromServer.get(varName.toLowerCase());
+    if (desc != null) {
+      return desc;
+    }
+    return varName;
   }
 
   @Override
   public void render(String object, Appendable appendable) throws IOException {
     appendable.append(render(object));
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 96824f3..28f5d85 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -18,7 +18,12 @@
 import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.config.CapabilityInfo;
+import com.google.gerrit.client.config.ConfigServerApi;
+import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccessSection;
@@ -33,6 +38,7 @@
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Label;
@@ -41,8 +47,10 @@
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProjectAccessScreen extends ProjectScreen {
@@ -90,6 +98,8 @@
 
   private ProjectAccess access;
 
+  private NativeMap<CapabilityInfo> capabilityMap;
+
   public ProjectAccessScreen(final Project.NameKey toShow) {
     super(toShow);
   }
@@ -107,18 +117,36 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    CallbackGroup cbs = new CallbackGroup();
+    ConfigServerApi.capabilities(
+        cbs.add(new AsyncCallback<NativeMap<CapabilityInfo>>() {
+          @Override
+          public void onSuccess(NativeMap<CapabilityInfo> result) {
+            capabilityMap = result;
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // Handled by ScreenLoadCallback.onFailure().
+          }
+        }));
     Util.PROJECT_SVC.projectAccess(getProjectKey(),
-        new ScreenLoadCallback<ProjectAccess>(this) {
+        cbs.addGwtjsonrpc(new ScreenLoadCallback<ProjectAccess>(this) {
           @Override
           public void preDisplay(ProjectAccess access) {
             displayReadOnly(access);
           }
-        });
+        }));
     savedPanel = ACCESS;
   }
 
   private void displayReadOnly(ProjectAccess access) {
     this.access = access;
+    Map<String, String> allCapabilities = new HashMap<String, String>();
+    for (CapabilityInfo c : Natives.asList(capabilityMap.values())) {
+      allCapabilities.put(c.id(), c.name());
+    }
+    this.access.setCapabilities(allCapabilities);
     accessEditor.setEditing(false);
     UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
     edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index ee58420..331afee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.client.ui.ProjectsTable;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
+import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
 import com.google.gwt.http.client.URL;
@@ -64,10 +65,10 @@
   protected void onLoad() {
     super.onLoad();
     display();
-    refresh();
+    refresh(false);
   }
 
-  private void refresh() {
+  private void refresh(final boolean open) {
     setToken(subname == null || "".equals(subname) ? ADMIN_PROJECTS
         : ADMIN_PROJECTS + "?filter=" + URL.encodeQueryString(subname));
     ProjectMap.match(subname,
@@ -75,7 +76,12 @@
             new GerritCallback<ProjectMap>() {
               @Override
               public void onSuccess(ProjectMap result) {
-                projects.display(result);
+                if (open && result.values().length() > 0) {
+                  Gerrit.display(PageLinks.toProject(
+                      result.values().get(0).name_key()));
+                } else {
+                  projects.display(result);
+                }
               }
             }));
   }
@@ -153,7 +159,7 @@
       @Override
       public void onKeyUp(KeyUpEvent event) {
         subname = filterTxt.getValue();
-        refresh();
+        refresh(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
       }
     });
     hp.add(filterTxt);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index 6819086..8689465 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -88,6 +88,7 @@
   private KeyCommandSet keysAction;
   private HandlerRegistration regNavigation;
   private HandlerRegistration regAction;
+  private HandlerRegistration regDetailCache;
 
   private Grid patchesGrid;
   private ListBox patchesList;
@@ -132,6 +133,10 @@
       regAction.removeHandler();
       regAction = null;
     }
+    if (regDetailCache != null) {
+      regDetailCache.removeHandler();
+      regDetailCache = null;
+    }
     super.onUnload();
   }
 
@@ -152,7 +157,7 @@
     ChangeCache cache = ChangeCache.get(changeId);
 
     detailCache = cache.getChangeDetailCache();
-    detailCache.addValueChangeHandler(this);
+    regDetailCache = detailCache.addValueChangeHandler(this);
 
     addStyleName(Gerrit.RESOURCES.css().changeScreen());
     addStyleName(Gerrit.RESOURCES.css().screenNoHeader());
@@ -263,7 +268,7 @@
 
   @Override
   public void onValueChange(final ValueChangeEvent<ChangeDetail> event) {
-    if (isAttached()) {
+    if (isAttached() && isLastValueChangeHandler()) {
       // Until this screen is fully migrated to the new API, these calls must
       // happen sequentially after the ChangeDetail lookup, because we can't
       // start an async get at the source of every call that might trigger a
@@ -295,6 +300,15 @@
     }
   }
 
+  // Find the last attached screen.
+  // When DialogBox is used (i. e. CommentedActionDialog) then the original
+  // ChangeScreen is still in attached state.
+  // Use here the fact, that the handlers (ChangeScreen) are sorted.
+  private boolean isLastValueChangeHandler() {
+    int count = detailCache.getHandlerCount();
+    return count > 0 && detailCache.getHandler(count - 1) == this;
+  }
+
   private void display(final ChangeDetail detail) {
     displayTitle(detail.getChange().getKey(), detail.getChange().getSubject());
     discardDiffBaseIfNotApplicable(detail.getChange().getId());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
new file mode 100644
index 0000000..45abbd6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
@@ -0,0 +1,25 @@
+// 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.config;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CapabilityInfo extends JavaScriptObject {
+  public final native String id() /*-{ return this.id; }-*/;
+  public final native String name() /*-{ return this.name; }-*/;
+
+  protected CapabilityInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
new file mode 100644
index 0000000..b283a0d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
@@ -0,0 +1,30 @@
+// 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.config;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * A collection of static methods which work on the Gerrit REST API for server
+ * configuration.
+ */
+public class ConfigServerApi {
+  /** map of the server wide capabilities (core & plugins). */
+  public static void capabilities(AsyncCallback<NativeMap<CapabilityInfo>> cb) {
+    new RestApi("/config/server/capabilities/").get(cb);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
index 9fd858e..904b535 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
@@ -60,6 +60,7 @@
   @Override
   protected void onInitUI() {
     super.onInitUI();
+    setHeaderVisible(false);
     add(diffTable = new DiffTable());
   }
 
@@ -103,6 +104,7 @@
     if (cmB != null) {
       cmB.refresh();
     }
+    Window.enableScrolling(false);
   }
 
   @Override
@@ -120,6 +122,7 @@
       cmB.getWrapperElement().removeFromParent();
       cmB = null;
     }
+    Window.enableScrolling(true);
   }
 
   private void display(DiffInfo diff) {
@@ -141,7 +144,6 @@
     });
     cmA.on("scroll", doScroll(cmB));
     cmB.on("scroll", doScroll(cmA));
-    Window.enableScrolling(false);
   }
 
   private CodeMirror displaySide(DiffInfo.FileMeta meta, String contents,
@@ -150,7 +152,7 @@
       contents = "";
     }
     Configuration cfg = Configuration.create()
-      .set("readOnly", "nocursor")
+      .set("readOnly", true)
       .set("lineNumbers", true)
       .set("tabSize", 2)
       .set("mode", getContentType(meta))
@@ -259,35 +261,32 @@
         : null;
   }
 
-  private static class EditIterator {
+  static class EditIterator {
     private final JsArrayString lines;
     private final int startLine;
     private int currLineIndex;
     private int currLineOffset;
 
-    private EditIterator(JsArrayString lineArray, int start) {
+    EditIterator(JsArrayString lineArray, int start) {
       lines = lineArray;
       startLine = start;
     }
 
-    private LineCharacter advance(int numOfChar) {
+    LineCharacter advance(int numOfChar) {
       while (currLineIndex < lines.length()) {
-        String line = lines.get(currLineIndex).substring(currLineOffset);
-        int lengthWithNewline = line.length() + 1;
+        int lengthWithNewline =
+            lines.get(currLineIndex).length() - currLineOffset + 1;
         if (numOfChar < lengthWithNewline) {
           LineCharacter at = LineCharacter.create(
               startLine + currLineIndex,
               numOfChar + currLineOffset);
           currLineOffset += numOfChar;
-          if (currLineOffset == line.length()) {
-            advanceLine();
-          }
           return at;
         }
         numOfChar -= lengthWithNewline;
         advanceLine();
       }
-      throw new IllegalStateException("LineIterator index out of bound");
+      throw new IllegalStateException("EditIterator index out of bound");
     }
 
     private void advanceLine() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
index 1edb8fd..0793527 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
@@ -25,6 +25,7 @@
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 
+import java.util.Comparator;
 import java.util.Iterator;
 
 public abstract class FancyFlexTable<RowItem> extends Composite {
@@ -58,6 +59,71 @@
     setRowItem(table.getCellFormatter().getElement(row, 0), item);
   }
 
+  /**
+   * Finds an item in the table.
+   *
+   * @param comparator comparator by which the items in the table are sorted
+   * @param item the item that should be found
+   * @return if the item is found the number of the row that contains the item;
+   *         if the item is not found <code>-1</code>
+   */
+  protected int findRowItem(Comparator<RowItem> comparator, RowItem item) {
+    int row = lookupRowItem(comparator, item);
+    if (row < table.getRowCount()
+        && comparator.compare(item, getRowItem(row)) == 0) {
+      return row;
+    }
+    return -1;
+  }
+
+  /**
+   * Finds the number of the row where a new item should be inserted into the
+   * table.
+   *
+   * @param comparator comparator by which the items in the table are sorted
+   * @param item the new item that should be inserted
+   * @return if the item is not yet contained in the table, the number of the
+   *         row where the new item should be inserted; if the item is already
+   *         contained in the table <code>-1</code>
+   */
+  protected int getInsertRow(Comparator<RowItem> comparator, RowItem item) {
+    int row = lookupRowItem(comparator, item);
+    if (row >= table.getRowCount()
+        || comparator.compare(item, getRowItem(row)) != 0) {
+      return row;
+    }
+    return -1;
+  }
+
+  /**
+   * Makes a binary search for the given row item over the table.
+   *
+   * @param comparator comparator by which the items in the table are sorted
+   * @param item the item that should be looked up
+   * @return if the item is found the number of the row that contains the item;
+   *         if the item is not found the number of the row where the item
+   *         should be inserted according to the given comparator.
+   */
+  private int lookupRowItem(Comparator<RowItem> comparator, RowItem item) {
+    int left = 1;
+    int right = table.getRowCount() - 1;
+    while (left <= right) {
+      int middle = (left + right) >>> 1; // (left+right)/2
+      RowItem i = getRowItem(middle);
+      int cmp = comparator.compare(i, item);
+
+      if (cmp < 0) {
+        left = middle + 1;
+      } else if (cmp > 0) {
+        right = middle - 1;
+      } else {
+        // item is already contained in the table
+        return middle;
+      }
+    }
+    return left;
+  }
+
   protected void resetHtml(final SafeHtml body) {
     for (final Iterator<Widget> i = table.iterator(); i.hasNext();) {
       i.next();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
index 6dad875..d834eb2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
@@ -45,4 +45,13 @@
       ValueChangeHandler<T> handler) {
     return manager.addHandler(ValueChangeEvent.getType(), handler);
   }
+
+  public int getHandlerCount() {
+    return manager.getHandlerCount(ValueChangeEvent.getType());
+  }
+
+  public ValueChangeHandler<?> getHandler(int index) {
+    return manager.getHandler(ValueChangeEvent.getType(), index);
+  }
+
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 63c2636..afbd740 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -261,6 +261,9 @@
   }
 
   public void finishDisplay() {
+    if (currentRow >= table.getRowCount()) {
+      currentRow = -1;
+    }
     if (saveId != null) {
       movePointerTo(savedPositions.get(saveId));
     }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
index 8a8e9d5..9a7b64a 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
@@ -20,16 +20,13 @@
 public class LineCharacter extends JavaScriptObject {
   public static LineCharacter create(int line, int ch) {
     LineCharacter lineCh = createObject().cast();
-    return lineCh.setLine(line).setCh(ch);
+    lineCh.setLine(line);
+    lineCh.setCh(ch);
+    return lineCh;
   }
 
-  private final native LineCharacter setLine(int line) /*-{
-    this.line = line; return this;
-  }-*/;
-
-  private final native LineCharacter setCh(int ch) /*-{
-    this.ch = ch; return this;
-  }-*/;
+  public final native void setLine(int line) /*-{ this.line = line; }-*/;
+  public final native void setCh(int ch) /*-{ this.ch = ch; }-*/;
 
   public final native int getLine() /*-{ return this.line; }-*/;
   public final native int getCh() /*-{ return this.ch; }-*/;
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
new file mode 100644
index 0000000..f0bbd3d
--- /dev/null
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
@@ -0,0 +1,97 @@
+// 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.diff;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.client.diff.CodeMirrorDemo.EditIterator;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+import com.googlecode.gwt.test.GwtModule;
+import com.googlecode.gwt.test.GwtTest;
+
+import net.codemirror.lib.LineCharacter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/** Unit tests for EditIterator */
+@GwtModule("com.google.gerrit.GerritGwtUI")
+public class EditIteratorTest extends GwtTest {
+  private JsArrayString lines;
+
+  private void assertLineChsEqual(LineCharacter a, LineCharacter b) {
+    assertEquals(a.getLine() + "," + a.getCh(), b.getLine() + "," + b.getCh());
+  }
+
+  @Before
+  public void initialize() {
+    lines = (JsArrayString) JavaScriptObject.createArray();
+    lines.push("1st");
+    lines.push("2nd");
+    lines.push("3rd");
+  }
+
+  @Test
+  public void testNoAdvance() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(0, 0), iter.advance(0));
+  }
+
+  @Test
+  public void testSimpleAdvance() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(0, 1), iter.advance(1));
+  }
+
+  @Test
+  public void testEndsBeforeNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(0, 3), iter.advance(3));
+  }
+
+  @Test
+  public void testEndsOnNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(1, 0), iter.advance(4));
+  }
+
+  @Test
+  public void testAcrossNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(1, 1), iter.advance(5));
+  }
+
+  @Test
+  public void testContinueFromBeforeNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    iter.advance(3);
+    assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(7));
+  }
+
+  @Test
+  public void testContinueFromAfterNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    iter.advance(4);
+    assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(6));
+  }
+
+  @Test
+  public void testAcrossMultipleLines() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(10));
+  }
+}
diff --git a/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties b/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties
new file mode 100644
index 0000000..c0cbb30
--- /dev/null
+++ b/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties
@@ -0,0 +1 @@
+com.google.gerrit.GerritGwtUI = gwt-module
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 96792f0..9968e2a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -185,6 +185,7 @@
   public void setUserAccountId(Account.Id id) {
     key = new Key("id:" + id);
     val = new Val(id, 0, false, null, 0, null, null);
+    user = identified.runAs(id, user);
   }
 
   @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
new file mode 100644
index 0000000..ff65674
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -0,0 +1,123 @@
+// 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.httpd;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.servlet.ServletModule;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** Allows running a request as another user account. */
+@Singleton
+class RunAsFilter implements Filter {
+  private static final Logger log = LoggerFactory.getLogger(RunAsFilter.class);
+  private static final String RUN_AS = "X-Gerrit-RunAs";
+
+  static class Module extends ServletModule {
+    @Override
+    protected void configureServlets() {
+      filter("/*").through(RunAsFilter.class);
+    }
+  }
+
+  private final boolean enabled;
+  private final Provider<WebSession> session;
+  private final AccountResolver accountResolver;
+
+  @Inject
+  RunAsFilter(AuthConfig config,
+      Provider<WebSession> session,
+      AccountResolver accountResolver) {
+    this.enabled = config.isRunAsEnabled();
+    this.session = session;
+    this.accountResolver = accountResolver;
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest req = (HttpServletRequest) request;
+    HttpServletResponse res = (HttpServletResponse) response;
+
+    String runas = req.getHeader(RUN_AS);
+    if (runas != null) {
+      if (!enabled) {
+        RestApiServlet.replyError(res,
+            SC_FORBIDDEN,
+            RUN_AS + " disabled by auth.enableRunAs = false");
+        return;
+      }
+
+      CurrentUser self = session.get().getCurrentUser();
+      if (!self.getCapabilities().canRunAs()) {
+        RestApiServlet.replyError(res,
+            SC_FORBIDDEN,
+            "not permitted to use " + RUN_AS);
+        return;
+      }
+
+      Account target;
+      try {
+        target = accountResolver.find(runas);
+      } catch (OrmException e) {
+        log.warn("cannot resolve account for " + RUN_AS, e);
+        RestApiServlet.replyError(res,
+            SC_INTERNAL_SERVER_ERROR,
+            "cannot resolve " + RUN_AS);
+        return;
+      }
+      if (target == null) {
+        RestApiServlet.replyError(res,
+            SC_FORBIDDEN,
+            "no account matches " + RUN_AS);
+        return;
+      }
+      session.get().setUserAccountId(target.getId());
+    }
+
+    chain.doFilter(req, res);
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) {
+  }
+
+  @Override
+  public void destroy() {
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index bd25faf..47219ca 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
+import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
 import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
 import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
 import com.google.gerrit.reviewdb.client.Change;
@@ -102,6 +103,7 @@
     serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
     serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class);
     serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
+    serveRegex("^/(?:a/)?config/(.*)$").with(ConfigRestApiServlet.class);
     serveRegex("^/(?:a/)?groups/(.*)?$").with(GroupsRestApiServlet.class);
     serveRegex("^/(?:a/)?projects/(.*)?$").with(ProjectsRestApiServlet.class);
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 7878477..efd8e24 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -83,6 +83,7 @@
     if (wantSSL) {
       install(new RequireSslFilter.Module());
     }
+    install(new RunAsFilter.Module());
 
     switch (authConfig.getAuthType()) {
       case HTTP:
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 842fcaf..fac950b 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
@@ -841,8 +841,8 @@
     }
   }
 
-  static void replyError(HttpServletResponse res, int statusCode, String msg)
-      throws IOException {
+  public static void replyError(HttpServletResponse res, int statusCode,
+      String msg) throws IOException {
     res.setStatus(statusCode);
     CacheHeaders.setNotCacheable(res);
     replyText(null, res, msg);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
new file mode 100644
index 0000000..f951ad3
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
@@ -0,0 +1,32 @@
+// 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.httpd.rpc.config;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.config.ConfigCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ConfigRestApiServlet extends RestApiServlet {
+  private static final long serialVersionUID = 1L;
+
+  @Inject
+  ConfigRestApiServlet(RestApiServlet.Globals globals,
+      Provider<ConfigCollection> configCollection) {
+    super(globals, configCollection);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 86a6ef8..ae8c6bf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -51,6 +51,20 @@
   }
 
   /**
+   * Identity of the authenticated user.
+   * <p>
+   * In the normal case where a user authenticates as themselves
+   * {@code getRealUser() == this}.
+   * <p>
+   * If {@code X-Gerrit-RunAs} or {@code suexec} was used this method returns
+   * the identity of the account that has permission to act on behalf of this
+   * user.
+   */
+  public CurrentUser getRealUser() {
+    return this;
+  }
+
+  /**
    * Get the set of groups the user is currently a member of.
    * <p>
    * The returned set may be a subset of the user's actual groups; if the user's
@@ -76,11 +90,9 @@
 
   /** Capabilities available to this user account. */
   public CapabilityControl getCapabilities() {
-    CapabilityControl ctl = capabilities;
-    if (ctl == null) {
-      ctl = capabilityControlFactory.create(this);
-      capabilities = ctl;
+    if (capabilities == null) {
+      capabilities = capabilityControlFactory.create(this);
     }
-    return ctl;
+    return capabilities;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 3826293..8e61c9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -97,13 +97,20 @@
     public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupBackend, null, db, id);
+          groupBackend, null, db, id, null);
     }
 
     public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupBackend, Providers.of(remotePeer), null, id);
+          groupBackend, Providers.of(remotePeer), null, id,  null);
+    }
+
+    public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
+        @Nullable CurrentUser caller) {
+      return new IdentifiedUser(capabilityControlFactory,
+          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          groupBackend, Providers.of(remotePeer), null, id, caller);
     }
   }
 
@@ -152,7 +159,13 @@
     public IdentifiedUser create(Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupBackend, remotePeerProvider, dbProvider, id);
+          groupBackend, remotePeerProvider, dbProvider, id, null);
+    }
+
+    public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
+      return new IdentifiedUser(capabilityControlFactory,
+          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          groupBackend, remotePeerProvider, dbProvider, id, caller);
     }
   }
 
@@ -183,6 +196,7 @@
   private GroupMembership effectiveGroups;
   private Set<Change.Id> starredChanges;
   private Collection<AccountProjectWatch> notificationFilters;
+  private CurrentUser realUser;
 
   private IdentifiedUser(
       CapabilityControl.Factory capabilityControlFactory,
@@ -192,7 +206,9 @@
       final Realm realm, final AccountCache accountCache,
       final GroupBackend groupBackend,
       @Nullable final Provider<SocketAddress> remotePeerProvider,
-      @Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
+      @Nullable final Provider<ReviewDb> dbProvider,
+      final Account.Id id,
+      @Nullable CurrentUser realUser) {
     super(capabilityControlFactory);
     this.canonicalUrl = canonicalUrl;
     this.accountCache = accountCache;
@@ -202,6 +218,12 @@
     this.remotePeerProvider = remotePeerProvider;
     this.dbProvider = dbProvider;
     this.accountId = id;
+    this.realUser = realUser != null ? realUser : this;
+  }
+
+  @Override
+  public CurrentUser getRealUser() {
+    return realUser;
   }
 
   // TODO(cranger): maybe get the state through the accountCache instead.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index cff3a35..e7a6004 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -78,7 +78,7 @@
    *        "Full Name <email@example.com>", just the email address, a full name
    *        if it is unique, an account ID, a user name or 'self' for the
    *        calling user
-   * @return the project
+   * @return the user, never null.
    * @throws UnprocessableEntityException thrown if the account ID cannot be
    *         resolved or if the account is not visible to the calling user
    */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 681bf3c..2cff009 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -15,8 +15,7 @@
 package com.google.gerrit.server.account;
 
 import com.google.common.base.Charsets;
-import com.google.common.io.CharStreams;
-import com.google.common.io.InputSupplier;
+import com.google.common.io.ByteSource;
 import com.google.gerrit.common.errors.InvalidSshKeyException;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -77,15 +76,14 @@
       max = Math.max(max, k.getKey().get());
     }
 
-    final InputStream in = input.raw.getInputStream();
-    String sshPublicKey =
-        CharStreams.toString(CharStreams.newReaderSupplier(
-            new InputSupplier<InputStream>() {
-              @Override
-              public InputStream getInput() {
-                return in;
-              }
-            }, Charsets.UTF_8));
+    final RawInput rawKey = input.raw;
+    String sshPublicKey = new ByteSource() {
+      @Override
+      public InputStream openStream() throws IOException {
+        return rawKey.getInputStream();
+      }
+    }.asCharSource(Charsets.UTF_8).read();
+
     try {
       AccountSshKey sshKey =
           sshKeyCache.create(new AccountSshKey.Id(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index fad9465..193ea04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -65,8 +65,12 @@
   /** @return true if the user can administer this server. */
   public boolean canAdministrateServer() {
     if (canAdministrateServer == null) {
-      canAdministrateServer = user instanceof PeerDaemonUser
-          || matchAny(capabilities.administrateServer, ALLOWED_RULE);
+      if (user.getRealUser() != user) {
+        canAdministrateServer = false;
+      } else {
+        canAdministrateServer = user instanceof PeerDaemonUser
+            || matchAny(capabilities.administrateServer, ALLOWED_RULE);
+      }
     }
     return canAdministrateServer;
   }
@@ -130,7 +134,6 @@
       || canAdministrateServer();
   }
 
-
   /** @return true if the user can access the database (with gsql). */
   public boolean canAccessDatabase() {
     return canPerform(GlobalCapability.ACCESS_DATABASE);
@@ -160,6 +163,11 @@
         || canAdministrateServer();
   }
 
+  /** @return true if the user can impersonate another user. */
+  public boolean canRunAs() {
+    return canPerform(GlobalCapability.RUN_AS);
+  }
+
   /** @return which priority queue the user's tasks should be submitted to. */
   public QueueProvider.QueueType getQueueType() {
     // If a non-generic group (that is not Anonymous Users or Registered Users)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index e796cbb..0601b8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -71,36 +71,41 @@
     if (input == null) {
       input = new Input();
     }
-    if (self.get() != rsrc.getUser()
-        && !self.get().getCapabilities().canAdministrateServer()) {
-      if (input.generate && !self.get().getCapabilities().canGenerateHttpPassword()) {
-        throw new AuthException("not allowed to generate HTTP password");
-      } else {
-        throw new AuthException("not allowed to set HTTP password");
-      }
-    }
-    if (rsrc.getUser().getUserName() == null) {
-      throw new ResourceConflictException("username must be set");
-    }
-    AccountExternalId.Key key =
-        new AccountExternalId.Key(SCHEME_USERNAME, rsrc.getUser().getUserName());
-    AccountExternalId id = dbProvider.get().accountExternalIds().get(key);
-    if (id == null) {
-      throw new ResourceNotFoundException();
-    }
+    input.httpPassword = Strings.emptyToNull(input.httpPassword);
 
     String newPassword;
     if (input.generate) {
+      if (self.get() != rsrc.getUser()
+          && !self.get().getCapabilities().canGenerateHttpPassword()) {
+        throw new AuthException("not allowed to generate HTTP password");
+      }
       newPassword = generate();
-    } else {
-      if (!Strings.isNullOrEmpty(input.httpPassword)
+
+    } else if (input.httpPassword == null) {
+      if (self.get() != rsrc.getUser()
           && !self.get().getCapabilities().canAdministrateServer()) {
+        throw new AuthException("not allowed to clear HTTP password");
+      }
+      newPassword = null;
+    } else {
+      if (!self.get().getCapabilities().canAdministrateServer()) {
         throw new AuthException("not allowed to set HTTP password directly, "
             + "need to be Gerrit administrator");
       }
-      newPassword = Strings.emptyToNull(input.httpPassword);
+      newPassword = input.httpPassword;
     }
 
+    if (rsrc.getUser().getUserName() == null) {
+      throw new ResourceConflictException("username must be set");
+    }
+
+    AccountExternalId id = dbProvider.get().accountExternalIds()
+        .get(new AccountExternalId.Key(
+            SCHEME_USERNAME,
+            rsrc.getUser().getUserName()));
+    if (id == null) {
+      throw new ResourceNotFoundException();
+    }
     id.setPassword(newPassword);
     dbProvider.get().accountExternalIds().update(Collections.singleton(id));
     accountCache.evict(rsrc.getUser().getAccountId());
@@ -110,7 +115,7 @@
         : Response.ok(newPassword);
   }
 
-  private String generate() {
+  private static String generate() {
     byte[] rand = new byte[LEN];
     rng.nextBytes(rand);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 549c3c5..fa63da0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -39,6 +40,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountsCollection;
 import com.google.gerrit.server.change.PostReview.Input;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
@@ -81,6 +83,18 @@
 
     /** Who to send email notifications to after review is stored. */
     public NotifyHandling notify = NotifyHandling.ALL;
+
+    /**
+     * Account ID, name, email address or username of another user. The review
+     * will be posted/updated on behalf of this named user instead of the
+     * caller. Caller must have the labelAs-$NAME permission granted for each
+     * label that appears in {@link #labels}. This is in addition to the named
+     * user also needing to have permission to use the labels.
+     * <p>
+     * {@link #strictLabels} impacts how labels is processed for the named user,
+     * not the caller.
+     */
+    public String onBehalfOf;
   }
 
   public static enum DraftHandling {
@@ -104,6 +118,7 @@
   }
 
   private final ReviewDb db;
+  private final AccountsCollection accounts;
   private final EmailReviewComments.Factory email;
   @Deprecated private final ChangeHooks hooks;
 
@@ -116,16 +131,22 @@
 
   @Inject
   PostReview(ReviewDb db,
+      AccountsCollection accounts,
       EmailReviewComments.Factory email,
       ChangeHooks hooks) {
     this.db = db;
+    this.accounts = accounts;
     this.email = email;
     this.hooks = hooks;
   }
 
   @Override
   public Object apply(RevisionResource revision, Input input)
-      throws AuthException, BadRequestException, OrmException {
+      throws AuthException, BadRequestException, OrmException,
+      UnprocessableEntityException {
+    if (input.onBehalfOf != null) {
+      revision = onBehalfOf(revision, input);
+    }
     if (input.labels != null) {
       checkLabels(revision, input.strictLabels, input.labels);
     }
@@ -171,6 +192,45 @@
     return output;
   }
 
+  private RevisionResource onBehalfOf(RevisionResource rev, Input in)
+      throws BadRequestException, AuthException, UnprocessableEntityException,
+      OrmException {
+    if (in.labels == null || in.labels.isEmpty()) {
+      throw new AuthException(String.format(
+          "label required to post review on behalf of \"%s\"",
+          in.onBehalfOf));
+    }
+
+    ChangeControl caller = rev.getControl();
+    Iterator<Map.Entry<String, Short>> itr = in.labels.entrySet().iterator();
+    while (itr.hasNext()) {
+      Map.Entry<String, Short> ent = itr.next();
+      LabelType type = caller.getLabelTypes().byLabel(ent.getKey());
+      if (type == null && in.strictLabels) {
+        throw new BadRequestException(String.format(
+            "label \"%s\" is not a configured label", ent.getKey()));
+      } else if (type == null) {
+        itr.remove();
+        continue;
+      }
+
+      PermissionRange r = caller.getRange(Permission.forLabelAs(type.getName()));
+      if (r == null || r.isEmpty() || !r.contains(ent.getValue())) {
+        throw new AuthException(String.format(
+            "not permitted to modify label \"%s\" on behalf of \"%s\"",
+            ent.getKey(), in.onBehalfOf));
+      }
+    }
+    if (in.labels.isEmpty()) {
+      throw new AuthException(String.format(
+          "label required to post review on behalf of \"%s\"",
+          in.onBehalfOf));
+    }
+
+    ChangeControl target = caller.forUser(accounts.parse(in.onBehalfOf));
+    return new RevisionResource(new ChangeResource(target), rev.getPatchSet());
+  }
+
   private void checkLabels(RevisionResource revision, boolean strict,
       Map<String, Short> labels) throws BadRequestException, AuthException {
     ChangeControl ctl = revision.getControl();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 9a804c1..06d2a71 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -37,6 +37,7 @@
   private final AuthType authType;
   private final String httpHeader;
   private final boolean trustContainerAuth;
+  private final boolean enableRunAs;
   private final boolean userNameToLowerCase;
   private final boolean gitBasicAuth;
   private final String logoutUrl;
@@ -64,6 +65,7 @@
     cookiePath = cfg.getString("auth", null, "cookiepath");
     cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
     trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
+    enableRunAs = cfg.getBoolean("auth", null, "enableRunAs", true);
     gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
     userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
 
@@ -164,6 +166,11 @@
     return trustContainerAuth;
   }
 
+  /** @return true if users with Run As capability can impersonate others. */
+  public boolean isRunAsEnabled() {
+    return enableRunAs;
+  }
+
   /** Whether user name should be converted to lower-case before validation */
   public boolean isUserNameToLowerCase() {
     return userNameToLowerCase;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
new file mode 100644
index 0000000..3a8bcc5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
@@ -0,0 +1,52 @@
+// 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class CapabilitiesCollection implements
+    ChildCollection<ConfigResource, CapabilityResource> {
+  private final DynamicMap<RestView<CapabilityResource>> views;
+  private final Provider<ListCapabilities> list;
+
+  @Inject
+  CapabilitiesCollection(DynamicMap<RestView<CapabilityResource>> views,
+      Provider<ListCapabilities> list) {
+    this.views = views;
+    this.list = list;
+  }
+
+  @Override
+  public RestView<ConfigResource> list() throws ResourceNotFoundException {
+    return list.get();
+  }
+
+  @Override
+  public CapabilityResource parse(ConfigResource parent, IdString id)
+      throws ResourceNotFoundException {
+    throw new ResourceNotFoundException(id);
+  }
+
+  @Override
+  public DynamicMap<RestView<CapabilityResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
new file mode 100644
index 0000000..c0a014c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -0,0 +1,42 @@
+// 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.config;
+
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
+
+public class CapabilityConstants extends TranslationBundle {
+  public static CapabilityConstants get() {
+    return NLS.getBundleFor(CapabilityConstants.class);
+  }
+
+  public String accessDatabase;
+  public String administrateServer;
+  public String createAccount;
+  public String createGroup;
+  public String createProject;
+  public String emailReviewers;
+  public String flushCaches;
+  public String killTask;
+  public String priority;
+  public String queryLimit;
+  public String runAs;
+  public String runGC;
+  public String startReplication;
+  public String streamEvents;
+  public String viewCaches;
+  public String viewConnections;
+  public String viewQueue;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
new file mode 100644
index 0000000..7e3c87e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
@@ -0,0 +1,23 @@
+// 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.config;
+
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class CapabilityResource extends ConfigResource {
+  public static final TypeLiteral<RestView<CapabilityResource>> CAPABILITY_KIND =
+      new TypeLiteral<RestView<CapabilityResource>>() {};
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
new file mode 100644
index 0000000..5ed007a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
@@ -0,0 +1,52 @@
+// 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.inject.Inject;
+
+public class ConfigCollection implements
+    RestCollection<TopLevelResource, ConfigResource> {
+  private final DynamicMap<RestView<ConfigResource>> views;
+
+  @Inject
+  ConfigCollection(DynamicMap<RestView<ConfigResource>> views) {
+    this.views = views;
+  }
+
+  @Override
+  public RestView<TopLevelResource> list() throws ResourceNotFoundException {
+    throw new ResourceNotFoundException();
+  }
+
+  @Override
+  public DynamicMap<RestView<ConfigResource>> views() {
+    return views;
+  }
+
+  @Override
+  public ConfigResource parse(TopLevelResource root, IdString id)
+      throws ResourceNotFoundException {
+    if (id.equals("server")) {
+      return new ConfigResource();
+    }
+    throw new ResourceNotFoundException(id);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
new file mode 100644
index 0000000..ec0e0c2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
@@ -0,0 +1,24 @@
+// 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.config;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class ConfigResource implements RestResource {
+  public static final TypeLiteral<RestView<ConfigResource>> CONFIG_KIND =
+      new TypeLiteral<RestView<ConfigResource>>() {};
+}
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 9370a89..160b33f 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
@@ -240,6 +240,7 @@
     install(new AuditModule());
     install(new com.google.gerrit.server.account.Module());
     install(new com.google.gerrit.server.change.Module());
+    install(new com.google.gerrit.server.config.Module());
     install(new com.google.gerrit.server.group.Module());
     install(new com.google.gerrit.server.project.Module());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
new file mode 100644
index 0000000..d92dfa7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -0,0 +1,54 @@
+// 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.config;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+
+import java.util.Map;
+
+/** List capabilities visible to the calling user. */
+public class ListCapabilities implements RestReadView<ConfigResource> {
+  @Override
+  public Map<String, CapabilityInfo> apply(ConfigResource resource)
+      throws AuthException, BadRequestException, ResourceConflictException,
+      IllegalArgumentException, SecurityException, IllegalAccessException,
+      NoSuchFieldException {
+    Map<String, CapabilityInfo> output = Maps.newTreeMap();
+    Class<? extends CapabilityConstants> bundleClass =
+        CapabilityConstants.get().getClass();
+    CapabilityConstants c = CapabilityConstants.get();
+    for (String id : GlobalCapability.getAllNames()) {
+      String name = (String) bundleClass.getField(id).get(c);
+      output.put(id, new CapabilityInfo(id, name));
+    }
+    return output;
+  }
+
+  public static class CapabilityInfo {
+    final String kind = "gerritcodereview#capability";
+    public String id;
+    public String name;
+
+    public CapabilityInfo(String id, String name) {
+      this.id = id;
+      this.name = name;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
new file mode 100644
index 0000000..81c2de9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -0,0 +1,30 @@
+// 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.config;
+
+import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class Module extends RestApiModule {
+  @Override
+  protected void configure() {
+    DynamicMap.mapOf(binder(), CONFIG_KIND);
+    DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+    child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 0f72aa0..6be2efc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -512,7 +512,7 @@
           if (isPermission(varName)) {
             Permission perm = as.getPermission(varName, true);
             loadPermissionRules(rc, ACCESS, refName, varName, groupsByName,
-                perm, perm.isLabel());
+                perm, Permission.hasRange(varName));
           }
         }
       }
@@ -869,7 +869,7 @@
       for (Permission permission : sort(as.getPermissions())) {
         have.add(permission.getName().toLowerCase());
 
-        boolean needRange = permission.isLabel();
+        boolean needRange = Permission.hasRange(permission.getName());
         List<String> rules = new ArrayList<String>();
         for (PermissionRule rule : sort(permission.getRules())) {
           GroupReference group = rule.getGroup();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index 86cebdc..998fc3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -98,10 +98,11 @@
     @Override
     public Address from(final Account.Id fromId) {
       if (fromId != null) {
-        final Account a = accountCache.get(fromId).getAccount();
-        if (a.getPreferredEmail() != null) {
-          return new Address(a.getFullName(), a.getPreferredEmail());
-        }
+        Account a = accountCache.get(fromId).getAccount();
+        String userEmail = a.getPreferredEmail();
+        return new Address(
+            a.getFullName(),
+            userEmail != null ? userEmail : srvAddr.getEmail());
       }
       return srvAddr;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index b46f1e7..fc1b808 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -126,6 +126,11 @@
     aId = psa != null ? toObjectId(db, psa) : null;
     bId = toObjectId(db, psb);
 
+    if ((psa != null && !control.isPatchVisible(db.patchSets().get(psa), db)) ||
+        (psb != null && !control.isPatchVisible(db.patchSets().get(psb), db))) {
+      throw new NoSuchChangeException(changeId);
+    }
+
     final Repository git;
     try {
       git = repoManager.openRepository(projectKey);
@@ -220,6 +225,9 @@
     // proper rename detection between the patch sets.
     //
     for (final PatchSet ps : db.patchSets().byChange(changeId)) {
+      if (!control.isPatchVisible(ps, db)) {
+        continue;
+      }
       String name = fileName;
       if (psa != null) {
         switch (changeType) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 5985de0..0c29ec8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.errors.InvalidRevisionException;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -80,7 +81,8 @@
 
   @Override
   public BranchInfo apply(ProjectResource rsrc, Input input)
-      throws BadRequestException, ResourceConflictException, IOException {
+      throws BadRequestException, AuthException, ResourceConflictException,
+      IOException {
     if (input == null) {
       input = new Input();
     }
@@ -124,7 +126,7 @@
       }
 
       if (!refControl.canCreate(rw, object)) {
-        throw new IllegalStateException("Cannot create \"" + ref + "\"");
+        throw new AuthException("Cannot create \"" + ref + "\"");
       }
 
       try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 59b7670..e191436 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -395,7 +395,7 @@
 
   /** The range of permitted values associated with a label permission. */
   public PermissionRange getRange(String permission) {
-    if (Permission.isLabel(permission)) {
+    if (Permission.hasRange(permission)) {
       return toRange(permission, access(permission));
     }
     return null;
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
new file mode 100644
index 0000000..d19fd22
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -0,0 +1,17 @@
+accessDatabase = Access Database
+administrateServer = Administrate Server
+createAccount = Create Account
+createGroup = Create Group
+createProject = Create Project
+emailReviewers = Email Reviewers
+flushCaches = Flush Caches
+killTask = Kill Task
+priority = Priority
+queryLimit = Query Limit
+runAs = Run As
+runGC = Run Garbage Collection
+startReplication = Start Replication
+streamEvents = Stream Events
+viewCaches = View Caches
+viewConnections = View Connections
+viewQueue = View Queue
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
new file mode 100644
index 0000000..f97ca55
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -0,0 +1,39 @@
+// 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.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+public class ListCapabilitiesTest {
+  @Test
+  public void testList() throws Exception {
+    Map<String, CapabilityInfo> m =
+        new ListCapabilities().apply(new ConfigResource());
+    for (String id : GlobalCapability.getAllNames()) {
+      assertTrue("contains " + id, m.containsKey(id));
+      assertEquals(id, m.get(id).id);
+      assertNotNull(id + " has name", m.get(id).name);
+    }
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index 29e3df0..2db2b67 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -102,12 +102,13 @@
   public void testUSER_NoPreferredEmailUser() {
     setFrom("USER");
 
-    final Account.Id user = user("A U. Thor", null);
+    final String name = "A U. Thor";
+    final Account.Id user = user(name, null);
 
     replay(accountCache);
     final Address r = create().from(user);
     assertNotNull(r);
-    assertEquals(ident.getName(), r.name);
+    assertEquals(name, r.name);
     assertEquals(ident.getEmailAddress(), r.email);
     verify(accountCache);
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
index ccf23e1..298a099 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.sshd.SshScope.Context;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -45,6 +46,7 @@
   private final SshScope sshScope;
   private final DispatchCommandProvider dispatcher;
 
+  private boolean enableRunAs;
   private Provider<CurrentUser> caller;
   private Provider<SshSession> session;
   private IdentifiedUser.GenericFactory userFactory;
@@ -66,36 +68,34 @@
       @CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
       final Provider<CurrentUser> caller, final Provider<SshSession> session,
       final IdentifiedUser.GenericFactory userFactory,
-      final SshScope.Context callingContext) {
+      final SshScope.Context callingContext,
+      AuthConfig config) {
     this.sshScope = sshScope;
     this.dispatcher = dispatcher;
     this.caller = caller;
     this.session = session;
     this.userFactory = userFactory;
     this.callingContext = callingContext;
+    this.enableRunAs = config.isRunAsEnabled();
     atomicCmd = Atomics.newReference();
   }
 
   @Override
   public void start(Environment env) throws IOException {
     try {
-      if (caller.get() instanceof PeerDaemonUser) {
-        parseCommandLine();
+      checkCanRunAs();
+      parseCommandLine();
 
-        final Context ctx = callingContext.subContext(newSession(), join(args));
-        final Context old = sshScope.set(ctx);
-        try {
-          final BaseCommand cmd = dispatcher.get();
-          cmd.setArguments(args.toArray(new String[args.size()]));
-          provideStateTo(cmd);
-          atomicCmd.set(cmd);
-          cmd.start(env);
-        } finally {
-          sshScope.set(old);
-        }
-
-      } else {
-        throw new UnloggedFailure(1, "fatal: Not a peer daemon");
+      final Context ctx = callingContext.subContext(newSession(), join(args));
+      final Context old = sshScope.set(ctx);
+      try {
+        final BaseCommand cmd = dispatcher.get();
+        cmd.setArguments(args.toArray(new String[args.size()]));
+        provideStateTo(cmd);
+        atomicCmd.set(cmd);
+        cmd.start(env);
+      } finally {
+        sshScope.set(old);
       }
     } catch (UnloggedFailure e) {
       String msg = e.getMessage();
@@ -108,6 +108,17 @@
     }
   }
 
+  private void checkCanRunAs() throws UnloggedFailure {
+    if (caller.get() instanceof PeerDaemonUser) {
+      // OK.
+    } else if (!enableRunAs) {
+      throw new UnloggedFailure(1,
+          "fatal: suexec disabled by auth.enableRunAs = false");
+    } else if (!caller.get().getCapabilities().canRunAs()) {
+      throw new UnloggedFailure(1, "fatal: suexec not permitted");
+    }
+  }
+
   private SshSession newSession() {
     final SocketAddress peer;
     if (peerAddress == null) {
@@ -115,8 +126,12 @@
     } else {
       peer = peerAddress;
     }
+    CurrentUser self = caller.get();
+    if (self instanceof PeerDaemonUser) {
+      self = null;
+    }
     return new SshSession(session.get(), peer,
-        userFactory.create(peer, accountId));
+        userFactory.runAs(peer, accountId, self));
   }
 
   private static String join(List<String> args) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 1c3d828..f5abf4b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -27,9 +27,11 @@
 
 import org.apache.mina.core.service.IoAcceptor;
 import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.session.ServerSession;
 import org.kohsuke.args4j.Option;
 
+import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
@@ -47,9 +49,28 @@
   @Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
   private boolean numeric;
 
+  @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
+  private boolean wide;
+
   @Inject
   private SshDaemon daemon;
 
+  private int hostNameWidth;
+  private int columns = 80;
+
+  @Override
+  public void start(final Environment env) throws IOException {
+    String s = env.getEnv().get(Environment.ENV_COLUMNS);
+    if (s != null && !s.isEmpty()) {
+      try {
+        columns = Integer.parseInt(s);
+      } catch (NumberFormatException err) {
+        columns = 80;
+      }
+    }
+    super.start(env);
+ }
+
   @Override
   protected void run() throws Failure {
     final IoAcceptor acceptor = daemon.getIoAcceptor();
@@ -71,6 +92,8 @@
       }
     });
 
+    hostNameWidth = wide ? Integer.MAX_VALUE : columns - 9 - 9 - 10 - 32;
+
     final long now = System.currentTimeMillis();
     stdout.print(String.format("%-8s %8s %8s   %-15s %s\n", //
         "Session", "Start", "Idle", "User", "Remote Host"));
@@ -83,7 +106,7 @@
       final long start = io.getCreationTime();
       final long idle = now - io.getLastIoTime();
 
-      stdout.print(String.format("%8s %8s %8s  %-15.15s %.30s\n", //
+      stdout.print(String.format("%8s %8s %8s   %-15.15s %s\n", //
           id(sd), //
           time(now, start), //
           age(idle), //
@@ -160,6 +183,11 @@
     if (host == null) {
       host = remoteAddress.toString();
     }
+
+    if (host.length() > hostNameWidth) {
+      return host.substring(0, hostNameWidth);
+    }
+
     return host;
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index fee5275..88973fc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -42,7 +42,7 @@
 @AdminHighPriorityCommand
 @CommandMetaData(name = "show-queue", descr = "Display the background work queues, including replication")
 final class ShowQueue extends SshCommand {
-  @Option(name = "-w", usage = "display without line width truncation")
+  @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
   private boolean wide;
 
   @Inject
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 2204422..9f37c9d 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -36,3 +36,25 @@
   main = 'compiler.py',
   visibility = ['PUBLIC'],
 )
+
+maven_jar(
+  name = 'gwt-test-utils',
+  id = 'com.googlecode.gwt-test-utils:gwt-test-utils:0.45-SNAPSHOT-20130612.054327-7',
+  sha1 = 'ad53b8a05df35fbe394db80d1d7d2913a646bfb3',
+  license = 'Apache2.0',
+  repository = 'http://oss.sonatype.org/content/repositories/snapshots',
+  deps = [
+    ':javassist',
+    '//lib/log:api',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'javassist',
+  id = 'org.javassist:javassist:3.16.1-GA',
+  sha1 = '315891b371395271977af518d4db5cee1a0bc9bf',
+  license = 'Apache2.0',
+  visibility = [],
+)
+
diff --git a/lib/maven.defs b/lib/maven.defs
index 8cc3a11..551c951 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -44,11 +44,17 @@
     raise NameError('expected id="groupId:artifactId:version"')
   group, artifact, version = parts
 
-  jar = path.join(name, artifact.lower() + '-' + version)
+  if 'SNAPSHOT' in version:
+    file_version = version.replace('-SNAPSHOT', '')
+    version = version.split('-')[0] + '-SNAPSHOT'
+  else:
+    file_version = version
+
+  jar = path.join(name, artifact.lower() + '-' + file_version)
   url = '/'.join([
     repository,
     group.replace('.', '/'), artifact, version,
-    artifact + '-' + version])
+    artifact + '-' + file_version])
 
   binjar = jar + '.jar'
   binurl = url + '.jar'
diff --git a/tools/download_file.py b/tools/download_file.py
index d75c984..5fbdb17 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -122,12 +122,17 @@
 if not path.exists(cache_ent):
   try:
     safe_mkdirs(path.dirname(cache_ent))
-    print('Download %s' % src_url, file=stderr)
-    check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url])
   except OSError as err:
     print('error creating directory %s: %s' %
           (path.dirname(cache_ent), err), file=stderr)
     exit(1)
+
+  print('Download %s' % src_url, file=stderr)
+  try:
+    check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url])
+  except OSError as err:
+    print('could not invoke curl: %s\nis curl installed?' % err, file=stderr)
+    exit(1)
   except CalledProcessError as err:
     print('error using curl: %s' % err, file=stderr)
     exit(1)
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index f8ed0a0..f2b51a9 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -30,6 +30,7 @@
     '//gerrit-acceptance-tests:acceptance_tests',
     '//gerrit-gwtdebug:gwtdebug',
     '//gerrit-gwtui:ui_module',
+    '//gerrit-gwtui:ui_tests',
     '//gerrit-httpd:httpd_tests',
     '//gerrit-main:main_lib',
     '//gerrit-server:server__compile',