Merge "Copy reviewed flags to new patch sets for identical files"
diff --git a/.gitmodules b/.gitmodules
index e45868b..2ac959c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,6 +10,6 @@
 	path = plugins/commit-message-length-validator
 	url = https://gerrit.googlesource.com/plugins/commit-message-length-validator
 
-[submodule "plugins/helloworld"]
-	path = plugins/helloworld
-	url = https://gerrit.googlesource.com/plugins/helloworld
+[submodule "plugins/cookbook-plugin"]
+	path = plugins/cookbook-plugin
+	url = https://gerrit.googlesource.com/plugins/cookbook-plugin
diff --git a/BUCK b/BUCK
index e454d37..b1a0c75 100644
--- a/BUCK
+++ b/BUCK
@@ -61,7 +61,7 @@
 
 java_library(
   name = 'plugin-lib',
-  deps = PLUGIN_API,
+  deps = PLUGIN_API + ['//lib:servlet-api-3_0'],
   export_deps = True,
   visibility = ['PUBLIC'],
 )
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 504f125..9c964e0 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1297,14 +1297,6 @@
 all projects.
 
 
-[[capability_startReplication]]
-Start Replication
-~~~~~~~~~~~~~~~~~
-
-Allow access to execute `replication start` command, if the
-replication plugin is installed on the server.
-
-
 [[capability_streamEvents]]
 Stream Events
 ~~~~~~~~~~~~~
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8e85b6d..3360cdf 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1975,8 +1975,8 @@
 from the user's account object matched under `ldap.accountBase`.
 Attributes such as `${dn}` or `${uidNumber}` may be useful.
 +
-Default is `(memberUid=${username})` for RFC 2307,
-and unset (disabled) for Active Directory.
+Default is `(|(memberUid=${username})(gidNumber=${gidNumber}))` for
+RFC 2307, and unset (disabled) for Active Directory.
 
 [[ldap.groupName]]ldap.groupName::
 +
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 051f9c2..b0a0e5f 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -310,6 +310,87 @@
 $ ssh -p 29418 review.example.com helloworld print
 ----
 
+[[capabilities]]
+Plugin Owned Capabilities
+-------------------------
+
+Plugins may provide their own capabilities and restrict usage of SSH
+commands to the users who are granted those capabilities.
+
+Plugins define the capabilities by overriding the `CapabilityDefinition`
+abstract class:
+
+====
+  public class PrintHelloCapability extends CapabilityDefinition {
+    @Override
+    public String getDescription() {
+      return "Print Hello";
+    }
+  }
+====
+
+If no Guice modules are declared in the manifest, UI commands may
+use auto-registration by providing an `@Export` annotation:
+
+====
+  @Export("printHello")
+  public class PrintHelloCapability extends CapabilityDefinition {
+  ...
+====
+
+Otherwise the capability must be bound in a plugin module:
+
+====
+  public class HelloWorldModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(CapabilityDefinition.class)
+        .annotatedWith(Exports.named("printHello"))
+        .to(PrintHelloCapability.class);
+    }
+  }
+====
+
+With a plugin-owned capability defined in this way, it is possible to restrict
+usage of an SSH command or UiAction to members of the group that were granted
+this capability in the usual way, using the `RequiresCapability` annotation:
+
+====
+  @RequiresCapability("printHello")
+  @CommandMetaData(name="print", description="Print greeting in different languages")
+  public final class PrintHelloWorldCommand extends SshCommand {
+  ...
+====
+
+Or with UiAction:
+
+====
+  @RequiresCapability("printHello")
+  public class SayHelloAction extends UiAction<RevisionResource>
+    implements RestModifyView<RevisionResource, SayHelloAction.Input> {
+  ...
+====
+
+Capability scope was introduced to differentiate between plugin-owned
+capabilities and core capabilities. Per default the scope of
+@RequiresCapability annotation is `CapabilityScope.CONTEXT`, that means:
++
+* when `@RequiresCapability` is used within a plugin the scope of the
+capability is assumed to be that plugin.
++
+* If `@RequiresCapability` is used within the core Gerrit Code Review server
+(and thus is outside of a plugin) the scope is the core server and will use
+the `GlobalCapability` known to Gerrit Code Review server.
+
+If a plugin needs to use a core capability name (e.g. "administrateServer")
+this can be specified by setting `scope = CapabilityScope.CORE`:
+
+====
+  @RequiresCapability(value = "administrateServer", scope =
+      CapabilityScope.CORE)
+  ...
+====
+
 [[http]]
 HTTP Servlets
 -------------
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 2cff3eb..7dbdf94 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -658,8 +658,7 @@
     "flushCaches": true,
     "viewConnections": true,
     "viewQueue": true,
-    "runGC": true,
-    "startReplication": true
+    "runGC": true
   }
 ----
 
@@ -1059,9 +1058,6 @@
 |`runGC`  |not set if `false`|Whether the user has the
 link:access-control.html#capability_runGC[Run Garbage Collection]
 capability.
-|`startReplication`  |not set if `false`|Whether the user has the
-link:access-control.html#capability_startReplication[Start Replication]
-capability.
 |=================================
 
 [[diff-preferences-info]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 6af6897..f566704 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -112,11 +112,6 @@
       "id": "runGC",
       "name": "Run Garbage Collection"
     },
-    "startReplication": {
-      "kind": "gerritcodereview#capability",
-      "id": "startReplication",
-     "name": "Start Replication"
-    },
     "streamEvents": {
       "kind": "gerritcodereview#capability",
       "id": "streamEvents",
diff --git a/ReleaseNotes/ReleaseNotes-2.6.2.txt b/ReleaseNotes/ReleaseNotes-2.6.2.txt
index cf963c2..2b78cb4 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.2.txt
@@ -38,6 +38,12 @@
 
 * Allow label values to be configured with no text.
 
+* link:https://code.google.com/p/gerrit/issues/detail?id=1966[Issue 1966]:
+Fix Gerrit plugins under Tomcat by avoiding Guice static filter.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2054[Issue 2054]:
+Expand capabilities of `ldap.groupMemberPattern`.
+
 
 No other changes since 2.6.1.
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 6c6cd26..a9ce827 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -100,6 +100,7 @@
     cfg.setString("httpd", null, "listenUrl", url);
     cfg.setString("sshd", null, "listenAddress", format(sshd));
     cfg.setString("cache", null, "directory", null);
+    cfg.setBoolean("sendemail", null, "enable", false);
     cfg.setInt("cache", "projects", "checkFrequency", 0);
     cfg.setInt("plugins", null, "checkFrequency", 0);
     cfg.save();
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 e18aee2..1fd98b6 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
@@ -76,9 +76,6 @@
   /** Can run the Git garbage collection. */
   public static final String RUN_GC = "runGC";
 
-  /** Forcefully restart replication to any configured destination. */
-  public static final String START_REPLICATION = "startReplication";
-
   /** Can perform streaming of Gerrit events. */
   public static final String STREAM_EVENTS = "streamEvents";
 
@@ -108,7 +105,6 @@
     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);
     NAMES_ALL.add(VIEW_CACHES);
     NAMES_ALL.add(VIEW_CONNECTIONS);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java
new file mode 100644
index 0000000..ede8b8c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java
@@ -0,0 +1,36 @@
+// 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.extensions.annotations;
+
+/** Declared scope of a capability named by {@link RequiresCapability}. */
+public enum CapabilityScope {
+  /**
+   * Scope is assumed based on the context.
+   *
+   * When {@code @RequiresCapability} is used within a plugin the scope of the
+   * capability is assumed to be that plugin.
+   *
+   * If {@code @RequiresCapability} is used within the core Gerrit Code Review
+   * server (and thus is outside of a plugin) the scope is the core server and
+   * will use {@link com.google.gerrit.common.data.GlobalCapability}.
+   */
+  CONTEXT,
+
+  /** Scope is only the plugin. */
+  PLUGIN,
+
+  /** Scope is the core server. */
+  CORE;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
index 382f4ea..b8e07d1 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
@@ -27,5 +27,9 @@
 @Target({ElementType.TYPE})
 @Retention(RUNTIME)
 public @interface RequiresCapability {
+  /**  Name of the capability required to invoke this action. */
   String value();
+
+  /** Scope of the named capability. */
+  CapabilityScope scope() default CapabilityScope.CONTEXT;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/CapabilityDefinition.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/CapabilityDefinition.java
new file mode 100644
index 0000000..aafb583
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/CapabilityDefinition.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.extensions.config;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Specifies a capability declared by a plugin. */
+@ExtensionPoint
+public abstract class CapabilityDefinition {
+  /** @return description of the capability. */
+  public abstract String getDescription();
+}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
index 41e4abc..f752780 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
@@ -15,10 +15,10 @@
 package com.google.gwtexpui.safehtml.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.regexp.shared.MatchResult;
 import com.google.gwt.regexp.shared.RegExp;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HTMLTable;
 import com.google.gwt.user.client.ui.HasHTML;
@@ -86,13 +86,13 @@
   }
 
   /** @return the existing inner HTML of any element. */
-  public static SafeHtml get(final Element e) {
-    return new SafeHtmlString(DOM.getInnerHTML(e));
+  public static SafeHtml get(Element e) {
+    return new SafeHtmlString(e.getInnerHTML());
   }
 
   /** Set the inner HTML of any element. */
-  public static Element set(final Element e, final SafeHtml str) {
-    DOM.setInnerHTML(e, str.asString());
+  public static Element setInnerHTML(Element e, SafeHtml str) {
+    e.setInnerHTML(str.asString());
     return e;
   }
 
@@ -109,8 +109,10 @@
   }
 
   /** Parse an HTML block and return the first (typically root) element. */
-  public static Element parse(final SafeHtml str) {
-    return DOM.getFirstChild(set(DOM.createDiv(), str));
+  public static com.google.gwt.user.client.Element parse(SafeHtml html) {
+    com.google.gwt.user.client.Element e = DOM.createDiv();
+    setInnerHTML(e, html);
+    return DOM.getFirstChild(e);
   }
 
   /** Convert bare http:// and https:// URLs into &lt;a href&gt; tags. */
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
index c2b98b0..2a356b0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -213,7 +213,7 @@
       <div class='{style.headerButtons}'>
         <g:Button ui:field='reply'
             styleName=''
-            title='Reply and score (Shortcut: r)'>
+            title='Reply and score (Shortcut: a)'>
           <ui:attribute name='title'/>
           <div><ui:msg>Reply&#8230;</ui:msg></div>
         </g:Button>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index 1bcab1a..ba71357 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -71,6 +71,7 @@
     String commentColumn();
     String deltaColumn1();
     String deltaColumn2();
+    String commonPrefix();
     String inserted();
     String deleted();
   }
@@ -227,7 +228,7 @@
       keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
           Util.C.patchTableOpenDiff()));
 
-      keysNavigation.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
+      keysAction.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
         @Override
         public void onKeyPress(KeyPressEvent event) {
           int row = getCurrentRow();
@@ -323,6 +324,7 @@
     private int row;
     private double start;
     private ProgressBar meter;
+    private String lastPath = "";
 
     private int inserted;
     private int deleted;
@@ -448,14 +450,42 @@
         .setStyleName(R.css().pathColumn())
         .openAnchor()
         .setAttribute("href", "#" + url(info))
-        .setAttribute("onclick", OPEN + "(event," + info._row() + ")")
-        .append(Patch.COMMIT_MSG.equals(info.path())
-            ? Util.C.commitMessage()
-            : info.path())
-        .closeAnchor()
+        .setAttribute("onclick", OPEN + "(event," + info._row() + ")");
+
+      String path = info.path();
+      if (Patch.COMMIT_MSG.equals(path)) {
+        sb.append(Util.C.commitMessage());
+      } else {
+        int commonPrefixLen = commonPrefix(path);
+        if (commonPrefixLen > 0) {
+          sb.openSpan().setStyleName(R.css().commonPrefix())
+            .append(path.substring(0, commonPrefixLen))
+            .closeSpan();
+        }
+        sb.append(path.substring(commonPrefixLen));
+        lastPath = path;
+      }
+
+      sb.closeAnchor()
         .closeTd();
     }
 
+    private int commonPrefix(String path) {
+      for (int n = path.length(); n > 0;) {
+        int s = path.lastIndexOf('/', n);
+        if (s < 0) {
+          return 0;
+        }
+
+        String p = path.substring(0, s + 1);
+        if (lastPath.startsWith(p)) {
+          return s + 1;
+        }
+        n = s - 1;
+      }
+      return 0;
+    }
+
     private void columnComments(SafeHtmlBuilder sb, FileInfo info) {
       JsArray<CommentInfo> cList = get(info.path(), comments);
       JsArray<CommentInfo> dList = get(info.path(), drafts);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
index 25724dc..943f9f1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -23,6 +23,12 @@
   white-space: nowrap;
   min-width: 600px;
 }
+.pathColumn a {
+  color: #000;
+}
+.commonPrefix {
+  color: #888;
+}
 
 .draftColumn,
 .newColumn,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
index e5c8dcf..6b13ba0a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
@@ -16,8 +16,8 @@
 
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.common.data.AccountInfoCache;
+import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HorizontalPanel;
@@ -37,12 +37,12 @@
     initWidget(hp);
   }
 
-  public void display(Change chg, Boolean starred, Boolean canEditCommitMessage,
+  public void display(ChangeDetail changeDetail, Boolean starred, Boolean canEditCommitMessage,
       PatchSetInfo info, AccountInfoCache acc,
       SubmitTypeRecord submitTypeRecord,
       CommentLinkProcessor commentLinkProcessor) {
-    infoBlock.display(chg, acc, submitTypeRecord);
-    messageBlock.display(chg.currentPatchSetId(), starred,
+    infoBlock.display(changeDetail, acc, submitTypeRecord);
+    messageBlock.display(changeDetail.getChange().currentPatchSetId(), starred,
         canEditCommitMessage, info.getMessage(), commentLinkProcessor);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
index b942824..b4ae2f3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
@@ -95,8 +95,9 @@
     table.getCellFormatter().addStyleName(row, 0, Gerrit.RESOURCES.css().header());
   }
 
-  public void display(final Change chg, final AccountInfoCache acc,
-      SubmitTypeRecord submitTypeRecord) {
+  public void display(final ChangeDetail changeDetail,
+      final AccountInfoCache acc, SubmitTypeRecord submitTypeRecord) {
+    final Change chg = changeDetail.getChange();
     final Branch.NameKey dst = chg.getDest();
 
     CopyableLabel changeIdLabel =
@@ -114,7 +115,7 @@
 
     table.setWidget(R_BRANCH, 1, new BranchLink(dst.getShortName(), chg
         .getProject(), chg.getStatus(), dst.get(), null));
-    table.setWidget(R_TOPIC, 1, topic(chg));
+    table.setWidget(R_TOPIC, 1, topic(changeDetail));
     table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
     table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
     table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
@@ -146,7 +147,8 @@
     }
   }
 
-  public Widget topic(final Change chg) {
+  public Widget topic(final ChangeDetail changeDetail) {
+    final Change chg = changeDetail.getChange();
     final Branch.NameKey dst = chg.getDest();
 
     FlowPanel fp = new FlowPanel();
@@ -154,9 +156,6 @@
     fp.add(new BranchLink(chg.getTopic(), chg.getProject(), chg.getStatus(),
            dst.get(), chg.getTopic()));
 
-    ChangeDetailCache detailCache = ChangeCache.get(chg.getId()).getChangeDetailCache();
-    ChangeDetail changeDetail = detailCache.get();
-
     if (changeDetail.canEditTopicName()) {
       final Image edit = new Image(Gerrit.RESOURCES.edit());
       edit.addStyleName(Gerrit.RESOURCES.css().link());
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 0a53b6d..0ec34a9 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
@@ -322,7 +322,7 @@
 
     dependencies.setAccountInfoCache(detail.getAccounts());
 
-    descriptionBlock.display(detail.getChange(),
+    descriptionBlock.display(detail,
         detail.isStarred(),
         detail.canEditCommitMessage(),
         detail.getCurrentPatchSetDetail().getInfo(),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
index 5f6a9c5..34c0638 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.diff.CommentRange;
 import com.google.gerrit.common.changes.Side;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
@@ -22,10 +23,11 @@
 import java.sql.Timestamp;
 
 public class CommentInfo extends JavaScriptObject {
-  public static CommentInfo createLine(String path, Side side, int line,
-      String in_reply_to, String message) {
+  public static CommentInfo createRange(String path, Side side, int line,
+      String in_reply_to, String message, CommentRange range) {
     CommentInfo info = createFile(path, side, in_reply_to, message);
-    info.setLine(line);
+    info.setRange(range);
+    info.setLine(range == null ? line : range.end_line());
     return info;
   }
 
@@ -82,6 +84,10 @@
 
   public final native boolean has_line() /*-{ return this.hasOwnProperty('line'); }-*/;
 
+  public final native CommentRange range() /*-{ return this.range; }-*/;
+
+  public final native void setRange(CommentRange range) /*-{ this.range = range; }-*/;
+
   protected CommentInfo() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
index 592e087..c96a67f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.changes;
 
+import com.google.gerrit.client.diff.CommentRange;
 import com.google.gerrit.common.changes.Side;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
@@ -29,6 +30,7 @@
     if (original.has_line()) {
       input.setLine(original.line());
     }
+    input.setRange(original.range());
     input.setInReplyTo(original.in_reply_to());
     input.setMessage(original.message());
     return input;
@@ -71,6 +73,10 @@
 
   public final native boolean has_line() /*-{ return this.hasOwnProperty('line'); }-*/;
 
+  public final native CommentRange range() /*-{ return this.range; }-*/;
+
+  public final native void setRange(CommentRange range) /*-{ this.range = range; }-*/;
+
   protected CommentInput() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 608a2c7..58a7042 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.client.ui.PatchLink;
 import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.PatchSetPublishDetail;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
@@ -317,9 +318,12 @@
   }
 
   private void display(final PatchSetPublishDetail r) {
+    ChangeDetail changeDetail = new ChangeDetail();
+    changeDetail.setChange(r.getChange());
+
     setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
         patchSetId.get()));
-    descBlock.display(r.getChange(), null, false, r.getPatchSetInfo(), r.getAccounts(),
+    descBlock.display(changeDetail, null, false, r.getPatchSetInfo(), r.getAccounts(),
        r.getSubmitTypeRecord(), commentLinkProcessor);
 
     if (r.getChange().getStatus().isOpen()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
index 7895c9b9..36f57b9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
@@ -32,12 +32,13 @@
 .summary {
   color: #777;
   position: absolute;
-  top: 2px;
+  top: 1px;
   left: 120px;
   width: 408px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
+  padding-bottom: 0.1em;
 }
 
 .date {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
new file mode 100644
index 0000000..f6fb1ba
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
@@ -0,0 +1,40 @@
+// 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 com.google.gwt.core.client.JavaScriptObject;
+
+public class CommentRange extends JavaScriptObject {
+  public static CommentRange create(int sl, int sc, int el, int ec) {
+    CommentRange r = createObject().cast();
+    r.set(sl, sc, el, ec);
+    return r;
+  }
+
+  public final native int start_line() /*-{ return this.start_line; }-*/;
+  public final native int start_character() /*-{ return this.start_character; }-*/;
+  public final native int end_line() /*-{ return this.end_line; }-*/;
+  public final native int end_character() /*-{ return this.end_character; }-*/;
+
+  private final native void set(int sl, int sc, int el, int ec) /*-{
+    this.start_line = sl;
+    this.start_character = sc;
+    this.end_line = el;
+    this.end_character = ec;
+  }-*/;
+
+  protected CommentRange() {
+  }
+}
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index c9fe8b0..aa8b0df 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -60,11 +60,11 @@
     .b .diff .CodeMirror-linenumber {
       background-color: #9f9;
     }
-    .padding, .fileCommentCell {
+    .padding, .fileComment {
       background-color: #eee;
     }
-    .fileCommentCell {
-      overflow-x: auto;
+    .fileComment {
+      line-height: 1;
     }
     .a .CodeMirror-vscrollbar {
       display: none !important;
@@ -105,19 +105,19 @@
       <tr>
         <td>
           <table class='{style.table}'>
-            <tr ui:field='patchsetNavRow'>
-              <td ui:field='patchsetNavCellA' class='{style.padding}'>
+            <tr ui:field='patchsetNavRow' class='{style.padding}'>
+              <td ui:field='patchsetNavCellA'>
                 <d:PatchSelectBox2 ui:field='patchSelectBoxA' />
               </td>
-              <td ui:field='patchsetNavCellB' class='{style.padding}'>
+              <td ui:field='patchsetNavCellB'>
                 <d:PatchSelectBox2 ui:field='patchSelectBoxB' />
               </td>
             </tr>
-            <tr ui:field='fileCommentRow'>
-              <td ui:field='fileCommentCellA' class='{style.padding}'>
+            <tr ui:field='fileCommentRow' class='{style.fileComment}'>
+              <td ui:field='fileCommentCellA'>
                 <d:FileCommentPanel ui:field='fileCommentPanelA' />
               </td>
-              <td ui:field='fileCommentCellB' class='{style.padding}'>
+              <td ui:field='fileCommentCellB'>
                 <d:FileCommentPanel ui:field='fileCommentPanelB' />
               </td>
             </tr>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
index 3e28898..9e6cd57 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
@@ -44,6 +44,7 @@
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
@@ -65,6 +66,7 @@
   private PublishedBox replyToBox;
   private Timer expandTimer;
 
+  @UiField Widget header;
   @UiField Element summary;
   @UiField Element date;
 
@@ -99,7 +101,7 @@
     };
     set(info);
 
-    addDomHandler(new ClickHandler() {
+    header.addDomHandler(new ClickHandler() {
       @Override
       public void onClick(ClickEvent event) {
         if (!isEdit()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
index f3e4997..52ef0ff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
@@ -26,6 +26,7 @@
       text-align: center;
       color: #fff;
       background-color: #aaa;
+      -webkit-border-radius: 2px;
     }
     .editArea { max-width: 637px; }
     button.button div {
@@ -42,11 +43,11 @@
 
   <g:HTMLPanel styleName='{res.style.commentBox}'>
     <div class='{res.style.contents}'>
-      <div class='{res.style.header}'>
+      <g:HTMLPanel ui:field='header' styleName='{res.style.header}'>
         <div class='{style.draft}'>Draft</div>
         <div ui:field='summary' class='{res.style.summary}'/>
         <div ui:field='date' class='{res.style.date}'/>
-      </div>
+      </g:HTMLPanel>
       <div ui:field='p_view' aria-hidden='true' style='display: NONE'>
         <g:HTML ui:field='message' styleName=''/>
         <div style='position: relative'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NavLinks2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
similarity index 63%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NavLinks2.java
rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
index d795319..4573d0d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NavLinks2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
@@ -16,10 +16,13 @@
 
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ReviewInfo;
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.patches.PatchUtil;
+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.RestApi;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Change;
@@ -27,9 +30,14 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Visibility;
 import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
@@ -38,28 +46,47 @@
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 import com.google.gwtorm.client.KeyUtil;
 
-class NavLinks2 extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, NavLinks2> {}
+class Header extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, Header> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
-  @UiField InlineHyperlink prevLink;
-  @UiField InlineHyperlink nextLink;
-  @UiField InlineHyperlink upLink;
+  @UiField CheckBox reviewed;
+  @UiField Element filePath;
+
+  @UiField InlineHyperlink prev;
+  @UiField InlineHyperlink up;
+  @UiField InlineHyperlink next;
 
   private final KeyCommandSet keys;
   private final PatchSet.Id patchSetId;
   private final String path;
 
-  NavLinks2(KeyCommandSet keys, PatchSet.Id patchSetId, String path) {
+  Header(KeyCommandSet keys, PatchSet.Id patchSetId, String path) {
     initWidget(uiBinder.createAndBindUi(this));
     this.keys = keys;
     this.patchSetId = patchSetId;
     this.path = path;
-    upLink.setTargetHistoryToken(PageLinks.toChange2(
+
+    SafeHtml.setInnerHTML(filePath, formatPath(path));
+    up.setTargetHistoryToken(PageLinks.toChange2(
         patchSetId.getParentKey(),
         String.valueOf(patchSetId.get())));
   }
 
+  private static SafeHtml formatPath(String path) {
+    SafeHtmlBuilder b = new SafeHtmlBuilder();
+    if (Patch.COMMIT_MSG.equals(path)) {
+      return b.append(Util.C.commitMessage());
+    }
+
+    int s = path.lastIndexOf('/') + 1;
+    b.append(path.substring(0, s));
+    b.openElement("b");
+    b.append(path.substring(s));
+    b.closeElement("b");
+    return b;
+  }
+
   @Override
   protected void onLoad() {
     ChangeApi.revision(patchSetId).view("files").get(
@@ -75,14 +102,35 @@
             index = i;
           }
         }
-        setupNav('[', PatchUtil.C.previousFileHelp(),
+        setupNav(prev, '[', PatchUtil.C.previousFileHelp(),
             index == 0 ? null : files.get(index - 1));
-        setupNav(']', PatchUtil.C.nextFileHelp(),
+        setupNav(next, ']', PatchUtil.C.nextFileHelp(),
             index == files.length() - 1 ? null : files.get(index + 1));
       }
     });
   }
 
+  void setReviewed(boolean r) {
+    reviewed.setValue(r, true);
+  }
+
+  boolean isReviewed() {
+    return reviewed.getValue();
+  }
+
+  @UiHandler("reviewed")
+  void onValueChange(ValueChangeEvent<Boolean> event) {
+    RestApi api = ChangeApi.revision(patchSetId)
+        .view("files")
+        .id(path)
+        .view("reviewed");
+    if (event.getValue()) {
+      api.put(CallbackGroup.<ReviewInfo>emptyCallback());
+    } else {
+      api.delete(CallbackGroup.<ReviewInfo>emptyCallback());
+    }
+  }
+
   private String url(FileInfo info) {
     Change.Id c = patchSetId.getParentKey();
     StringBuilder p = new StringBuilder();
@@ -92,21 +140,11 @@
     return p.toString();
   }
 
-  private void setupNav(int key, String help, FileInfo info) {
+  private void setupNav(InlineHyperlink link, int key, String help, FileInfo info) {
     if (info != null) {
       final String url = url(info);
-      String fileName = getFileNameOnly(info.path());
-      if (key == '[') {
-        prevLink.setTargetHistoryToken(url);
-        prevLink.setHTML(new SafeHtmlBuilder()
-            .append(SafeHtml.asis(Util.C.prevPatchLinkIcon()))
-            .append(fileName));
-      } else {
-        nextLink.setTargetHistoryToken(url);
-        nextLink.setHTML(new SafeHtmlBuilder()
-            .append(fileName)
-            .append(SafeHtml.asis(Util.C.nextPatchLinkIcon())));
-      }
+      link.setTargetHistoryToken(url);
+      link.setTitle(getFileName(info.path()));
       keys.add(new KeyCommand(0, key, help) {
         @Override
         public void onKeyPress(KeyPressEvent event) {
@@ -114,11 +152,12 @@
         }
       });
     } else {
+      link.getElement().getStyle().setVisibility(Visibility.HIDDEN);
       keys.add(new UpToChangeCommand2(patchSetId, 0, key));
     }
   }
 
-  private static String getFileNameOnly(String path) {
+  private static String getFileName(String path) {
     String fileName = Patch.COMMIT_MSG.equals(path)
         ? Util.C.commitMessage()
         : path;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
new file mode 100644
index 0000000..a23cbfb
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:x='urn:import:com.google.gerrit.client.ui'>
+  <ui:style>
+  .header {
+    position: relative;
+  }
+  .reviewed input {
+    margin: 0;
+    padding: 0;
+    vertical-align: bottom;
+  }
+  .path {
+  }
+  .navigation {
+    position: absolute;
+    top: 0;
+    right: 15px;
+  }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.header}'>
+    <g:CheckBox ui:field='reviewed'
+        styleName='{style.reviewed}'
+        title='Mark file as reviewed (Shortcut: r)'>
+      <ui:attribute name='title'/>
+    </g:CheckBox>
+    <span ui:field='filePath' class='{style.path}'/>
+
+    <div class='{style.navigation}'>
+      <x:InlineHyperlink ui:field='prev'>&#x21e6;</x:InlineHyperlink>
+      <x:InlineHyperlink ui:field='up' title='Up to change (Shortcut: u)'>
+        <ui:attribute name='title'/>
+        &#x21e7;
+      </x:InlineHyperlink>
+      <x:InlineHyperlink ui:field='next'>&#x21e8;</x:InlineHyperlink>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NavLinks2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NavLinks2.ui.xml
deleted file mode 100644
index f825979..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NavLinks2.ui.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2013 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-<ui:UiBinder
-    xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'
-    xmlns:ge='urn:import:com.google.gerrit.client.ui'>
-  <ui:style>
-  .table {
-    table-layout: fixed;
-    width: 100%;
-  }
-  .prev {
-    text-align: left;
-  }
-  .up {
-    text-align: center;
-  }
-  .next {
-    text-align: right;
-  }
-  </ui:style>
-  <g:HTMLPanel>
-    <table ui:field='table' class='{style.table}'>
-      <tr>
-        <td class='{style.prev}'>
-          <ge:InlineHyperlink ui:field='prevLink'/>
-        </td>
-        <td class='{style.up}'>
-          <ge:InlineHyperlink ui:field='upLink'>
-            <ui:msg>&#x21e7;Up to change</ui:msg>
-          </ge:InlineHyperlink>
-        </td>
-        <td class='{style.next}'>
-          <ge:InlineHyperlink ui:field='nextLink'/>
-        </td>
-      </tr>
-    </table>
-  </g:HTMLPanel>
-</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
index 2f923c8..6298296 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
@@ -36,6 +36,7 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 /** An HtmlPanel for displaying a published comment */
@@ -53,6 +54,7 @@
   private DraftBox replyBox;
 
   @UiField Style style;
+  @UiField Widget header;
   @UiField Element name;
   @UiField Element summary;
   @UiField Element date;
@@ -81,7 +83,7 @@
     }
 
     initWidget(uiBinder.createAndBindUi(this));
-    addDomHandler(new ClickHandler() {
+    header.addDomHandler(new ClickHandler() {
       @Override
       public void onClick(ClickEvent event) {
         setOpen(!isOpen());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
index 50d67f0..f3beda1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
@@ -48,11 +48,11 @@
       addStyleNames='{style.closed}'>
     <c:AvatarImage ui:field='avatar' styleName='{style.avatar}'/>
     <div class='{res.style.contents}'>
-      <div class='{res.style.header}'>
+      <g:HTMLPanel ui:field='header' styleName='{res.style.header}'>
         <div ui:field='name' class='{style.name}'/>
         <div ui:field='summary' class='{res.style.summary}'/>
         <div ui:field='date' class='{res.style.date}'/>
-      </div>
+      </g:HTMLPanel>
       <div ui:field='message' aria-hidden='true' style='display: NONE'/>
       <div ui:field='buttons' aria-hidden='true' style='display: NONE'>
         <g:Button ui:field='reply' styleName=''
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.java
deleted file mode 100644
index 60120bf..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.diff;
-
-import com.google.gerrit.client.changes.ChangeApi;
-import com.google.gerrit.client.changes.ReviewInfo;
-import com.google.gerrit.client.changes.Util;
-import com.google.gerrit.client.patches.PatchUtil;
-import com.google.gerrit.client.rpc.CallbackGroup;
-import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-
-class ReviewedPanel extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, ReviewedPanel> {}
-  private static UiBinder<HTMLPanel, ReviewedPanel> uiBinder =
-      GWT.create(Binder.class);
-
-  @UiField
-  Element fileName;
-
-  @UiField
-  CheckBox checkBox;
-
-  @UiField
-  Anchor nextLink;
-
-  private PatchSet.Id patchId;
-  private String fileId;
-
-  ReviewedPanel(PatchSet.Id id, String path) {
-    initWidget(uiBinder.createAndBindUi(this));
-    patchId = id;
-    fileId = path;
-    fileName.setInnerText(path);
-    nextLink.setHTML(PatchUtil.C.next() + Util.C.nextPatchLinkIcon());
-  }
-
-  void setReviewed(boolean reviewed) {
-    checkBox.setValue(reviewed, true);
-  }
-
-  boolean isReviewed() {
-    return checkBox.getValue();
-  }
-
-  @UiHandler("checkBox")
-  void onValueChange(ValueChangeEvent<Boolean> event) {
-    RestApi api = ChangeApi.revision(patchId)
-        .view("files")
-        .id(fileId)
-        .view("reviewed");
-    if (event.getValue()) {
-      api.put(CallbackGroup.<ReviewInfo>emptyCallback());
-    } else {
-      api.delete(CallbackGroup.<ReviewInfo>emptyCallback());
-    }
-  }
-
-  // TODO: Implement this to go to the next file in the patchset.
-  void onNext(ClickEvent e) {
-    setReviewed(true);
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.ui.xml
deleted file mode 100644
index f2b6725..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.ui.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
-<!--
-Copyright (C) 2013 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'
-    xmlns:d='urn:import:com.google.gerrit.client.diff'>
-  <ui:style>
-    .reviewedPanel, .table {
-      width: 100%;
-    }
-    .fileName {
-      font-size: larger;
-      font-weight: bold;
-    }
-    .reviewed {
-      width: 20%;
-      text-align: right;
-    }
-  </ui:style>
-  <g:HTMLPanel styleName='{style.reviewedPanel}'>
-    <table class='{style.table}'>
-    <tr>
-      <td ui:field='fileName' class='{style.fileName}' />
-      <td class='{style.reviewed}'>
-        <g:CheckBox ui:field='checkBox'><ui:msg>Reviewed &amp; </ui:msg></g:CheckBox>
-        <g:Anchor ui:field='nextLink' href='javascript:;'/>
-      </td>
-    </tr>
-    </table>
-  </g:HTMLPanel>
-</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index c953df8..3ad82f8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -56,7 +56,7 @@
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
@@ -85,17 +85,14 @@
 import java.util.Map;
 
 public class SideBySide2 extends Screen {
-  interface Binder extends UiBinder<HTMLPanel, SideBySide2> {}
+  interface Binder extends UiBinder<FlowPanel, SideBySide2> {}
   private static Binder uiBinder = GWT.create(Binder.class);
 
   private static final JsArrayString EMPTY =
       JavaScriptObject.createArray().cast();
 
   @UiField(provided = true)
-  ReviewedPanel reviewed;
-
-  @UiField(provided = true)
-  NavLinks2 navLinks;
+  Header header;
 
   @UiField(provided = true)
   DiffTable diffTable;
@@ -139,9 +136,8 @@
     this.handlers = new ArrayList<HandlerRegistration>(6);
     // TODO: Re-implement necessary GlobalKey bindings.
     addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
-    reviewed = new ReviewedPanel(revision, path);
     keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
-    add(navLinks = new NavLinks2(keysNavigation, revision, path));
+    add(header = new Header(keysNavigation, revision, path));
     add(diffTable = new DiffTable(this, path));
     add(uiBinder.createAndBindUi(this));
   }
@@ -466,11 +462,12 @@
   }
 
   private DraftBox addNewDraft(CodeMirror cm, int line) {
-    return addDraftBox(CommentInfo.createLine(
+    return addDraftBox(CommentInfo.createRange(
         path,
         getSideFromCm(cm),
         line + 1,
         null,
+        null,
         null));
   }
 
@@ -478,8 +475,8 @@
     if (!replyTo.has_line()) {
       return CommentInfo.createFile(path, replyTo.side(), replyTo.id(), null);
     } else {
-      return CommentInfo.createLine(path, replyTo.side(), replyTo.line(),
-          replyTo.id(), null);
+      return CommentInfo.createRange(path, replyTo.side(), replyTo.line(),
+          replyTo.id(), null, null);
     }
   }
 
@@ -972,7 +969,7 @@
   private Runnable toggleReviewed() {
     return new Runnable() {
      public void run() {
-       reviewed.setReviewed(!reviewed.isReviewed());
+       header.setReviewed(!header.isReviewed());
      }
     };
   }
@@ -1057,8 +1054,10 @@
     if (cmA == null) {
       return;
     }
-    int h = Gerrit.getHeaderFooterHeight() + reviewed.getOffsetHeight() +
-        navLinks.getOffsetHeight() + diffTable.getHeaderHeight() + 10; // Estimate
+    int h = Gerrit.getHeaderFooterHeight()
+        + header.getOffsetHeight()
+        + diffTable.getHeaderHeight()
+        + 10; // Estimate
     cmA.setHeight(Window.getClientHeight() - h);
     cmA.refresh();
     cmB.setHeight(Window.getClientHeight() - h);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
index 8719ecd..1bc707a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
@@ -17,9 +17,8 @@
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'
     xmlns:d='urn:import:com.google.gerrit.client.diff'>
-  <g:HTMLPanel>
-    <d:ReviewedPanel ui:field='reviewed'/>
-    <d:NavLinks2 ui:field='navLinks'/>
+  <g:FlowPanel>
+    <d:Header ui:field='header'/>
     <d:DiffTable ui:field='diffTable'/>
-  </g:HTMLPanel>
+  </g:FlowPanel>
 </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
index 9a10c23..d862965 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.ui;
 
+import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.projects.BranchInfo;
 import com.google.gerrit.client.projects.ProjectApi;
@@ -21,6 +22,8 @@
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.FocusWidget;
 import com.google.gwt.user.client.ui.SuggestBox;
 import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
@@ -59,9 +62,15 @@
       }
     });
 
-    newBranch.setWidth("70ex");
+    newBranch.setWidth("100%");
+    DOM.setStyleAttribute(newBranch.getElement(), "box-sizing", "border-box");
     message.setCharacterWidth(70);
-    panel.insert(newBranch, 0);
+
+    final FlowPanel mwrap = new FlowPanel();
+    mwrap.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
+    mwrap.add(newBranch);
+
+    panel.insert(mwrap, 0);
     panel.insert(new SmallHeading(Util.C.headingCherryPickBranch()), 0);
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
index 263703e..1095b66 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentedActionDialog.java
@@ -67,7 +67,7 @@
     });
 
     cancelButton = new Button(Util.C.commentedActionButtonCancel());
-    DOM.setStyleAttribute(cancelButton.getElement(), "marginLeft", "300px");
+    DOM.setStyleAttribute(cancelButton.getElement(), "float", "right");
     cancelButton.addClickHandler(new ClickHandler() {
       @Override
       public void onClick(final ClickEvent event) {
@@ -82,6 +82,7 @@
     buttonPanel = new FlowPanel();
     buttonPanel.add(sendButton);
     buttonPanel.add(cancelButton);
+    DOM.setStyleAttribute(buttonPanel.getElement(), "margin-top", "4px");
 
     panel = new FlowPanel();
     panel.add(new SmallHeading(heading));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
index c72969e..2c7451a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
@@ -21,7 +21,7 @@
 
 public class FancyFlexTableImpl {
   public void resetHtml(final MyFlexTable myTable, final SafeHtml body) {
-    SafeHtml.set(getBodyElement(myTable), body);
+    SafeHtml.setInnerHTML(getBodyElement(myTable), body);
   }
 
   protected static native Element getBodyElement(HTMLTable myTable)
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 f2fee0b..a4ba520 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
@@ -23,8 +23,8 @@
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
 
@@ -46,6 +46,7 @@
 import com.google.common.net.HttpHeaders;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.audit.HttpAuditEvent;
+import com.google.gerrit.extensions.annotations.CapabilityScope;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsCreate;
@@ -199,17 +200,17 @@
 
       List<IdString> path = splitPath(req);
       RestCollection<RestResource, RestResource> rc = members.get();
-      checkAccessAnnotations(rc.getClass());
+      checkAccessAnnotations(null, rc.getClass());
 
       RestResource rsrc = TopLevelResource.INSTANCE;
-      RestView<RestResource> view = null;
+      ViewData viewData = new ViewData(null, null);
       if (path.isEmpty()) {
         if ("GET".equals(req.getMethod())) {
-          view = rc.list();
+          viewData = new ViewData(null, rc.list());
         } else if (rc instanceof AcceptsPost && "POST".equals(req.getMethod())) {
           @SuppressWarnings("unchecked")
           AcceptsPost<RestResource> ac = (AcceptsPost<RestResource>) rc;
-          view = ac.post(rsrc);
+          viewData = new ViewData(null, ac.post(rsrc));
         } else {
           throw new MethodNotAllowedException();
         }
@@ -227,30 +228,30 @@
                   || "PUT".equals(req.getMethod()))) {
             @SuppressWarnings("unchecked")
             AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) rc;
-            view = ac.create(rsrc, id);
+            viewData = new ViewData(null, ac.create(rsrc, id));
             status = SC_CREATED;
           } else {
             throw e;
           }
         }
-        if (view == null) {
-          view = view(rc, req.getMethod(), path);
+        if (viewData.view == null) {
+          viewData = view(rc, req.getMethod(), path);
         }
       }
-      checkAccessAnnotations(view.getClass());
+      checkAccessAnnotations(viewData);
 
-      while (view instanceof RestCollection<?,?>) {
+      while (viewData.view instanceof RestCollection<?,?>) {
         @SuppressWarnings("unchecked")
         RestCollection<RestResource, RestResource> c =
-            (RestCollection<RestResource, RestResource>) view;
+            (RestCollection<RestResource, RestResource>) viewData.view;
 
         if (path.isEmpty()) {
           if ("GET".equals(req.getMethod())) {
-            view = c.list();
+            viewData = new ViewData(null, c.list());
           } else if (c instanceof AcceptsPost && "POST".equals(req.getMethod())) {
             @SuppressWarnings("unchecked")
             AcceptsPost<RestResource> ac = (AcceptsPost<RestResource>) c;
-            view = ac.post(rsrc);
+            viewData = new ViewData(null, ac.post(rsrc));
           } else {
             throw new MethodNotAllowedException();
           }
@@ -260,7 +261,7 @@
           try {
             rsrc = c.parse(rsrc, id);
             checkPreconditions(req, rsrc);
-            view = null;
+            viewData = new ViewData(null, null);
           } catch (ResourceNotFoundException e) {
             if (c instanceof AcceptsCreate
                 && path.isEmpty()
@@ -268,17 +269,17 @@
                     || "PUT".equals(req.getMethod()))) {
               @SuppressWarnings("unchecked")
               AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) c;
-              view = ac.create(rsrc, id);
+              viewData = new ViewData(null, ac.create(rsrc, id));
               status = SC_CREATED;
             } else {
               throw e;
             }
           }
-          if (view == null) {
-            view = view(c, req.getMethod(), path);
+          if (viewData.view == null) {
+            viewData = view(c, req.getMethod(), path);
           }
         }
-        checkAccessAnnotations(view.getClass());
+        checkAccessAnnotations(viewData);
       }
 
       if (notModified(req, rsrc)) {
@@ -288,19 +289,19 @@
 
       Multimap<String, String> config = LinkedHashMultimap.create();
       ParameterParser.splitQueryString(req.getQueryString(), config, params);
-      if (!globals.paramParser.get().parse(view, params, req, res)) {
+      if (!globals.paramParser.get().parse(viewData.view, params, req, res)) {
         return;
       }
 
-      if (view instanceof RestModifyView<?, ?>) {
+      if (viewData.view instanceof RestModifyView<?, ?>) {
         @SuppressWarnings("unchecked")
         RestModifyView<RestResource, Object> m =
-            (RestModifyView<RestResource, Object>) view;
+            (RestModifyView<RestResource, Object>) viewData.view;
 
         inputRequestBody = parseRequest(req, inputType(m));
         result = m.apply(rsrc, inputRequestBody);
-      } else if (view instanceof RestReadView<?>) {
-        result = ((RestReadView<RestResource>) view).apply(rsrc);
+      } else if (viewData.view instanceof RestReadView<?>) {
+        result = ((RestReadView<RestResource>) viewData.view).apply(rsrc);
       } else {
         throw new ResourceNotFoundException();
       }
@@ -766,7 +767,7 @@
     return gz.setContentType(src.getContentType());
   }
 
-  private RestView<RestResource> view(
+  private ViewData view(
       RestCollection<RestResource, RestResource> rc,
       String method, List<IdString> path) throws ResourceNotFoundException,
       MethodNotAllowedException, AmbiguousViewException {
@@ -786,7 +787,7 @@
       RestView<RestResource> view =
           views.get(p.get(0), method + "." + p.get(1));
       if (view != null) {
-        return view;
+        return new ViewData(p.get(0), view);
       }
       throw new ResourceNotFoundException(projection);
     }
@@ -794,7 +795,7 @@
     String name = method + "." + p.get(0);
     RestView<RestResource> core = views.get("gerrit", name);
     if (core != null) {
-      return core;
+      return new ViewData(null, core);
     }
 
     Map<String, RestView<RestResource>> r = Maps.newTreeMap();
@@ -806,7 +807,9 @@
     }
 
     if (r.size() == 1) {
-      return Iterables.getFirst(r.values(), null);
+      Map.Entry<String, RestView<RestResource>> entry =
+          Iterables.getOnlyElement(r.entrySet());
+      return new ViewData(entry.getKey(), entry.getValue());
     } else if (r.isEmpty()) {
       throw new ResourceNotFoundException(projection);
     } else {
@@ -862,16 +865,35 @@
     return !("GET".equals(method) || "HEAD".equals(method));
   }
 
-  private void checkAccessAnnotations(Class<? extends Object> clazz)
+  private void checkAccessAnnotations(ViewData viewData) throws AuthException {
+    checkAccessAnnotations(viewData.pluginName, viewData.view.getClass());
+  }
+
+  private void checkAccessAnnotations(String pluginName, Class<?> clazz)
       throws AuthException {
     RequiresCapability rc = clazz.getAnnotation(RequiresCapability.class);
     if (rc != null) {
       CurrentUser user = globals.currentUser.get();
       CapabilityControl ctl = user.getCapabilities();
-      if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
+      String capability = rc.value();
+
+     if (pluginName != null && !"gerrit".equals(pluginName)
+         && (rc.scope() == CapabilityScope.PLUGIN
+          || rc.scope() == CapabilityScope.CONTEXT)) {
+        capability = String.format("%s-%s", pluginName, rc.value());
+      } else if (rc.scope() == CapabilityScope.PLUGIN) {
+        log.error(String.format(
+            "Class %s uses @%s(scope=%s), but is not within a plugin",
+            clazz.getName(),
+            RequiresCapability.class.getSimpleName(),
+            CapabilityScope.PLUGIN.name()));
+        throw new AuthException("cannot check capability");
+      }
+
+      if (!ctl.canPerform(capability) && !ctl.canAdministrateServer()) {
         throw new AuthException(String.format(
             "Capability %s is required to access this resource",
-            rc.value()));
+            capability));
       }
     }
   }
@@ -988,4 +1010,14 @@
       super(message);
     }
   }
+
+  private static class ViewData {
+    String pluginName;
+    RestView<RestResource> view;
+
+    ViewData(String pluginName, RestView<RestResource> view) {
+      this.pluginName = pluginName;
+      this.view = view;
+    }
+  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
index 18ab5ff5..66b5ec1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
@@ -84,12 +84,18 @@
             throw new IllegalStateException("Parent comment must be on same side");
           }
         }
+        if (comment.getRange() != null
+            && comment.getLine() != comment.getRange().getEndLine()) {
+            throw new IllegalStateException(
+              "Range endLine must be on the same line as the comment");
+        }
 
         final PatchLineComment nc =
             new PatchLineComment(new PatchLineComment.Key(patchKey, ChangeUtil
                 .messageUUID(db)), comment.getLine(), me, comment.getParentUuid());
         nc.setSide(comment.getSide());
         nc.setMessage(comment.getMessage());
+        nc.setRange(comment.getRange());
         db.patchComments().insert(Collections.singleton(nc));
         db.commit();
         return nc;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java
new file mode 100644
index 0000000..b0f3879
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java
@@ -0,0 +1,90 @@
+// 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.reviewdb.client;
+
+import com.google.gwtorm.client.Column;
+
+public class CommentRange {
+
+  @Column(id = 1)
+  protected int startLine;
+
+  @Column(id = 2)
+  protected int startCharacter;
+
+  @Column(id = 3)
+  protected int endLine;
+
+  @Column(id = 4)
+  protected int endCharacter;
+
+  protected CommentRange() {
+  }
+
+  public CommentRange(int sl, int sc, int el, int ec) {
+    startLine = sl;
+    startCharacter = sc;
+    endLine = el;
+    endCharacter = ec;
+  }
+
+  public int getStartLine() {
+    return startLine;
+  }
+
+  public int getStartCharacter() {
+    return startCharacter;
+  }
+
+  public int getEndLine() {
+    return endLine;
+  }
+
+  public int getEndCharacter() {
+    return endCharacter;
+  }
+
+  public void setStartLine(int sl) {
+    startLine = sl;
+  }
+
+  public void setStartCharacter(int sc) {
+    startCharacter = sc;
+  }
+
+  public void setEndLine(int el) {
+    endLine = el;
+  }
+
+  public void setEndCharacter(int ec) {
+    endCharacter = ec;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof CommentRange) {
+      CommentRange other = (CommentRange) obj;
+      return startLine == other.startLine && startCharacter == other.startCharacter &&
+          endLine == other.endLine && endCharacter == other.endCharacter;
+    }
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return "Range[startLine=" + startLine + ", startCharacter=" + startCharacter
+        + ", endLine=" + endLine + ", endCharacter=" + endCharacter + "]";
+  }
+}
\ No newline at end of file
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
index af35e52f..0119c3e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
@@ -117,6 +117,9 @@
   @Column(id = 8, length = 40, notNull = false)
   protected String parentUuid;
 
+  @Column(id = 9, notNull = false)
+  protected CommentRange range;
+
   protected PatchLineComment() {
   }
 
@@ -189,4 +192,12 @@
   public void setParentUuid(String inReplyTo) {
     parentUuid = inReplyTo;
   }
+
+  public void setRange(CommentRange r) {
+    range = r;
+  }
+
+  public CommentRange getRange() {
+    return range;
+  }
 }
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 01e8002..cafc540 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
@@ -139,12 +139,6 @@
     return canPerform(GlobalCapability.ACCESS_DATABASE);
   }
 
-  /** @return true if the user can force replication to any configured destination. */
-  public boolean canStartReplication() {
-    return canPerform(GlobalCapability.START_REPLICATION)
-        || canAdministrateServer();
-  }
-
   /** @return true if the user can stream Gerrit events. */
   public boolean canStreamEvents() {
     return canPerform(GlobalCapability.STREAM_EVENTS)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 54f1980..615d09e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -23,7 +23,6 @@
 import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
 import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
 import static com.google.gerrit.common.data.GlobalCapability.RUN_GC;
-import static com.google.gerrit.common.data.GlobalCapability.START_REPLICATION;
 import static com.google.gerrit.common.data.GlobalCapability.STREAM_EVENTS;
 import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
 import static com.google.gerrit.common.data.GlobalCapability.VIEW_CONNECTIONS;
@@ -34,6 +33,8 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -68,10 +69,13 @@
   private Set<String> query;
 
   private final Provider<CurrentUser> self;
+  private final DynamicMap<CapabilityDefinition> pluginCapabilities;
 
   @Inject
-  GetCapabilities(Provider<CurrentUser> self) {
+  GetCapabilities(Provider<CurrentUser> self,
+      DynamicMap<CapabilityDefinition> pluginCapabilities) {
     this.self = self;
+    this.pluginCapabilities = pluginCapabilities;
   }
 
   @Override
@@ -93,6 +97,14 @@
         }
       }
     }
+    for (String pluginName : pluginCapabilities.plugins()) {
+      for (String capability : pluginCapabilities.byPlugin(pluginName).keySet()) {
+        String name = String.format("%s-%s", pluginName, capability);
+        if (want(name) && cc.canPerform(name)) {
+          have.put(name, true);
+        }
+      }
+    }
 
     have.put(CREATE_ACCOUNT, cc.canCreateAccount());
     have.put(CREATE_GROUP, cc.canCreateGroup());
@@ -104,7 +116,6 @@
     have.put(VIEW_CONNECTIONS, cc.canViewConnections());
     have.put(VIEW_QUEUE, cc.canViewQueue());
     have.put(RUN_GC, cc.canRunGC());
-    have.put(START_REPLICATION, cc.canStartReplication());
     have.put(STREAM_EVENTS, cc.canStreamEvents());
     have.put(ACCESS_DATABASE, cc.canAccessDatabase());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 0151dde..7d0ad24 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -197,13 +197,11 @@
     if (!schema.groupMemberQueryList.isEmpty()) {
       final HashMap<String, String> params = new HashMap<String, String>();
 
-      if (schema.groupNeedsAccount) {
-        if (account == null) {
-          account = findAccount(schema, ctx, username);
-        }
-        for (String name : schema.groupMemberQueryList.get(0).getParameters()) {
-          params.put(name, account.get(name));
-        }
+      if (account == null) {
+        account = findAccount(schema, ctx, username);
+      }
+      for (String name : schema.groupMemberQueryList.get(0).getParameters()) {
+        params.put(name, account.get(name));
       }
 
       params.put(LdapRealm.USERNAME, username);
@@ -286,7 +284,6 @@
     final String accountMemberField;
     final List<LdapQuery> accountQueryList;
 
-    boolean groupNeedsAccount;
     final List<String> groupBases;
     final SearchScope groupScope;
     final ParameterizedString groupPattern;
@@ -321,10 +318,7 @@
           }
 
           for (final String name : groupMemberQuery.getParameters()) {
-            if (!LdapRealm.USERNAME.equals(name)) {
-              groupNeedsAccount = true;
-              accountAtts.add(name);
-            }
+            accountAtts.add(name);
           }
 
           groupMemberQueryList.add(groupMemberQuery);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
index db5baeb..3c1b0d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
@@ -57,7 +57,7 @@
 
     @Override
     String groupMemberPattern() {
-      return "(memberUid=${username})";
+      return "(|(memberUid=${username})(gidNumber=${gidNumber}))";
     }
 
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
index c0942b2..fe372f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.server.account.AccountInfo;
 
 import java.sql.Timestamp;
@@ -33,6 +34,7 @@
   String message;
   Timestamp updated;
   AccountInfo author;
+  CommentRange range;
 
   CommentInfo(PatchLineComment c, AccountInfo.Loader accountLoader) {
     id = Url.encode(c.getKey().get());
@@ -46,6 +48,7 @@
     inReplyTo = Url.encode(c.getParentUuid());
     message = Strings.emptyToNull(c.getMessage());
     updated = c.getWrittenOn();
+    range = c.getRange();
     if (accountLoader != null) {
       author = accountLoader.get(c.getAuthor());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
index 9ef1cfe..1b7bbe7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.PutDraft.Input;
@@ -51,18 +50,22 @@
       throw new BadRequestException("message must be non-empty");
     } else if (in.line != null && in.line <= 0) {
       throw new BadRequestException("line must be > 0");
+    } else if (in.line != null && in.range != null && in.line != in.range.getEndLine()) {
+      throw new BadRequestException("range endLine must be on the same line as the comment");
     }
 
+    int line = in.line != null
+        ? in.line
+        : in.range != null ? in.range.getEndLine() : 0;
+
     PatchLineComment c = new PatchLineComment(
         new PatchLineComment.Key(
             new Patch.Key(rsrc.getPatchSet().getId(), in.path),
             ChangeUtil.messageUUID(db.get())),
-        in.line != null ? in.line : 0,
-        rsrc.getAccountId(),
-        Url.decode(in.inReplyTo));
-    c.setStatus(Status.DRAFT);
+        line, rsrc.getAccountId(), Url.decode(in.inReplyTo));
     c.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
     c.setMessage(in.message.trim());
+    c.setRange(in.range);
     db.get().patchComments().insert(Collections.singleton(c));
     return Response.created(new CommentInfo(c, null));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index d929a89..5e2c797 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -151,6 +151,11 @@
     return this;
   }
 
+  public PatchSet.Id getPatchSetId() {
+    init();
+    return patchSet.getId();
+  }
+
   public PatchSetInserter setMessage(String message) throws OrmException {
     changeMessage = new ChangeMessage(new ChangeMessage.Key(change.getId(),
         ChangeUtil.messageUUID(db)), user.getAccountId(), patchSet.getId());
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 b9ca124..91c6abe 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
@@ -38,6 +38,7 @@
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
@@ -116,6 +117,7 @@
     int line;
     String inReplyTo;
     String message;
+    CommentRange range;
   }
 
   static class Output {
@@ -365,6 +367,7 @@
         e.setWrittenOn(timestamp);
         e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
         e.setMessage(c.message);
+        e.setRange(c.range);
         (create ? ins : upd).add(e);
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
index c6fc4fc..005d493 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.PutDraft.Input;
 import com.google.gwtorm.server.OrmException;
@@ -41,6 +42,7 @@
     Integer line;
     String inReplyTo;
     Timestamp updated; // Accepted but ignored.
+    CommentRange range;
 
     @DefaultInput
     String message;
@@ -67,6 +69,8 @@
       throw new BadRequestException("id must match URL");
     } else if (in.line != null && in.line < 0) {
       throw new BadRequestException("line must be >= 0");
+    } else if (in.line != null && in.range != null && in.line != in.range.getEndLine()) {
+      throw new BadRequestException("range endLine must be on the same line as the comment");
     }
 
     if (in.path != null
@@ -92,13 +96,14 @@
     if (in.side != null) {
       e.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
     }
-    if (in.line != null) {
-      e.setLine(in.line);
-    }
     if (in.inReplyTo != null) {
       e.setParentUuid(Url.decode(in.inReplyTo));
     }
     e.setMessage(in.message.trim());
+    if (in.range != null || in.line != null) {
+      e.setRange(in.range);
+      e.setLine(in.range != null ? in.range.getEndLine() : in.line);
+    }
     e.updated();
     return e;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index 40deb0c..dceecbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -293,7 +293,7 @@
     final ChangeControl changeControl =
         changeControlFactory.validateFor(change.getId(), uploader);
 
-    PatchSetInserter patchSetinserter = patchSetInserterFactory
+    PatchSetInserter patchSetInserter = patchSetInserterFactory
         .create(git, revWalk, changeControl.getRefControl(), uploader, change, rebasedCommit)
         .setCopyLabels(true)
         .setValidatePolicy(validate)
@@ -301,13 +301,15 @@
         .setSendMail(sendMail)
         .setRunHooks(runHooks);
 
+    final PatchSet.Id newPatchSetId = patchSetInserter.getPatchSetId();
     final ChangeMessage cmsg =
         new ChangeMessage(new ChangeMessage.Key(change.getId(),
             ChangeUtil.messageUUID(db)), uploader.getAccountId(), patchSetId);
-    cmsg.setMessage("Patch Set " + change.currentPatchSetId().get()
+
+    cmsg.setMessage("Patch Set " + newPatchSetId.get()
         + ": Patch Set " + patchSetId.get() + " was rebased");
 
-    Change newChange = patchSetinserter
+    Change newChange = patchSetInserter
         .setMessage(cmsg)
         .insert();
 
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
index c0a014c..2e54f3e 100644
--- 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
@@ -34,7 +34,6 @@
   public String queryLimit;
   public String runAs;
   public String runGC;
-  public String startReplication;
   public String streamEvents;
   public String viewCaches;
   public String viewConnections;
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 581b067..66ee72b 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
@@ -19,6 +19,7 @@
 import com.google.common.cache.Cache;
 import com.google.gerrit.audit.AuditModule;
 import com.google.gerrit.common.ChangeListener;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
@@ -249,6 +250,7 @@
     bind(GitReferenceUpdated.class);
     DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
     DynamicSet.setOf(binder(), CacheRemovalListener.class);
+    DynamicMap.mapOf(binder(), CapabilityDefinition.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
index 8b517a3..641a48f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -16,6 +16,8 @@
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.util.ServerRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
@@ -25,8 +27,10 @@
 public class GitReceivePackGroupsProvider extends GroupSetProvider {
   @Inject
   public GitReceivePackGroupsProvider(GroupBackend gb,
-      @GerritServerConfig Config config) {
-    super(gb, config, "receive", null, "allowGroup");
+      @GerritServerConfig Config config,
+      ThreadLocalRequestContext threadContext,
+      ServerRequestContext serverCtx) {
+    super(gb, config, threadContext, serverCtx, "receive", null, "allowGroup");
 
     // If no group was set, default to "registered users"
     //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
index c519902..edae46b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -16,6 +16,8 @@
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.util.ServerRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
@@ -26,8 +28,10 @@
 public class GitUploadPackGroupsProvider extends GroupSetProvider {
   @Inject
   public GitUploadPackGroupsProvider(GroupBackend gb,
-      @GerritServerConfig Config config) {
-    super(gb, config, "upload", null, "allowGroup");
+      @GerritServerConfig Config config,
+      ThreadLocalRequestContext threadContext,
+      ServerRequestContext serverCtx) {
+    super(gb, config, threadContext, serverCtx, "upload", null, "allowGroup");
 
     // If no group was set, default to "registered users" and "anonymous"
     //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
index 5fa243b..5c3ec39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -19,6 +19,9 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ServerRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -37,19 +40,26 @@
 
   @Inject
   protected GroupSetProvider(GroupBackend groupBackend,
-      @GerritServerConfig Config config, String section,
+      @GerritServerConfig Config config,
+      ThreadLocalRequestContext threadContext,
+      ServerRequestContext serverCtx, String section,
       String subsection, String name) {
-    String[] groupNames = config.getStringList(section, subsection, name);
-    ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
-    for (String n : groupNames) {
-      GroupReference g = GroupBackends.findBestSuggestion(groupBackend, n);
-      if (g == null) {
-        log.warn("Group \"{0}\" not in database, skipping.", n);
-      } else {
-        builder.add(g.getUUID());
+    RequestContext ctx = threadContext.setContext(serverCtx);
+    try {
+      String[] groupNames = config.getStringList(section, subsection, name);
+      ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
+      for (String n : groupNames) {
+        GroupReference g = GroupBackends.findBestSuggestion(groupBackend, n);
+        if (g == null) {
+          log.warn("Group \"{}\" not in database, skipping.", n);
+        } else {
+          builder.add(g.getUUID());
+        }
       }
+      groupIds = builder.build();
+    } finally {
+      threadContext.setContext(ctx);
     }
-    groupIds = builder.build();
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
index d92dfa7..8062087 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -16,21 +16,39 @@
 
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+import com.google.gerrit.extensions.registration.DynamicMap;
 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 com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import java.util.Map;
 
 /** List capabilities visible to the calling user. */
 public class ListCapabilities implements RestReadView<ConfigResource> {
+  private final DynamicMap<CapabilityDefinition> pluginCapabilities;
+
+  @Inject
+  public ListCapabilities(DynamicMap<CapabilityDefinition> pluginCapabilities) {
+    this.pluginCapabilities = pluginCapabilities;
+  }
+
   @Override
   public Map<String, CapabilityInfo> apply(ConfigResource resource)
       throws AuthException, BadRequestException, ResourceConflictException,
       IllegalArgumentException, SecurityException, IllegalAccessException,
       NoSuchFieldException {
     Map<String, CapabilityInfo> output = Maps.newTreeMap();
+    collectCoreCapabilities(output);
+    collectPluginCapabilities(output);
+    return output;
+  }
+
+  private void collectCoreCapabilities(Map<String, CapabilityInfo> output)
+      throws IllegalAccessException, NoSuchFieldException {
     Class<? extends CapabilityConstants> bundleClass =
         CapabilityConstants.get().getClass();
     CapabilityConstants c = CapabilityConstants.get();
@@ -38,7 +56,18 @@
       String name = (String) bundleClass.getField(id).get(c);
       output.put(id, new CapabilityInfo(id, name));
     }
-    return output;
+  }
+
+  private void collectPluginCapabilities(Map<String, CapabilityInfo> output) {
+    for (String pluginName : pluginCapabilities.plugins()) {
+      for (Map.Entry<String, Provider<CapabilityDefinition>> entry :
+          pluginCapabilities.byPlugin(pluginName).entrySet()) {
+        String id = String.format("%s-%s", pluginName, entry.getKey());
+        output.put(id, new CapabilityInfo(
+            id,
+            entry.getValue().get().getDescription()));
+      }
+    }
   }
 
   public static class CapabilityInfo {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
index 6622b0f..0189de3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.config;
 
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.util.ServerRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
@@ -33,7 +35,9 @@
 public class ProjectOwnerGroupsProvider extends GroupSetProvider {
   @Inject
   public ProjectOwnerGroupsProvider(GroupBackend gb,
-      @GerritServerConfig final Config config) {
-    super(gb, config, "repository", "*", "ownerGroup");
+      @GerritServerConfig final Config config,
+      ThreadLocalRequestContext context,
+      ServerRequestContext serverCtx) {
+    super(gb, config, context, serverCtx, "repository", "*", "ownerGroup");
   }
 }
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 5144dcb..a803ddd 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
@@ -533,15 +533,13 @@
 
     AccessSection capability = null;
     for (String varName : rc.getNames(CAPABILITY)) {
-      if (GlobalCapability.isCapability(varName)) {
-        if (capability == null) {
-          capability = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
-          accessSections.put(AccessSection.GLOBAL_CAPABILITIES, capability);
-        }
-        Permission perm = capability.getPermission(varName, true);
-        loadPermissionRules(rc, CAPABILITY, null, varName, groupsByName, perm,
-            GlobalCapability.hasRange(varName));
+      if (capability == null) {
+        capability = new AccessSection(AccessSection.GLOBAL_CAPABILITIES);
+        accessSections.put(AccessSection.GLOBAL_CAPABILITIES, capability);
       }
+      Permission perm = capability.getPermission(varName, true);
+      loadPermissionRules(rc, CAPABILITY, null, varName, groupsByName, perm,
+          GlobalCapability.hasRange(varName));
     }
   }
 
@@ -879,8 +877,7 @@
         rc.setStringList(CAPABILITY, null, permission.getName(), rules);
       }
       for (String varName : rc.getNames(CAPABILITY)) {
-        if (GlobalCapability.isCapability(varName)
-            && !have.contains(varName.toLowerCase())) {
+        if (!have.contains(varName.toLowerCase())) {
           rc.unset(CAPABILITY, null, varName);
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
index bb11e62..64210a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -26,6 +26,7 @@
 
 import java.lang.Thread.UncaughtExceptionHandler;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
@@ -248,6 +249,7 @@
     private final Executor executor;
     private final int taskId;
     private final AtomicBoolean running;
+    private final Date startTime;
 
     Task(Runnable runnable, RunnableScheduledFuture<V> task, Executor executor,
         int taskId) {
@@ -256,6 +258,7 @@
       this.executor = executor;
       this.taskId = taskId;
       this.running = new AtomicBoolean();
+      this.startTime = new Date();
     }
 
     public int getTaskId() {
@@ -281,6 +284,10 @@
       return State.OTHER;
     }
 
+    public Date getStartTime() {
+      return startTime;
+    }
+
     public boolean cancel(boolean mayInterruptIfRunning) {
       if (task.cancel(mayInterruptIfRunning)) {
         // Tiny abuse of running: if the task needs to know it was
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
index 5bf1cbd..e997c57 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
@@ -276,7 +276,7 @@
         }
 
         for (ObjectId id : byId.keySet()) {
-          getPathsAndIndex(walk.parseCommit(id));
+          getPathsAndIndex(id);
         }
       } finally {
         walk.release();
@@ -284,10 +284,11 @@
       return null;
     }
 
-    private void getPathsAndIndex(RevCommit bCommit) throws Exception {
-      RevTree bTree = bCommit.getTree();
-      List<ChangeData> cds = Lists.newArrayList(byId.get(bCommit));
+    private void getPathsAndIndex(ObjectId b) throws Exception {
+      List<ChangeData> cds = Lists.newArrayList(byId.get(b));
       try {
+        RevCommit bCommit = walk.parseCommit(b);
+        RevTree bTree = bCommit.getTree();
         RevTree aTree = aFor(bCommit, walk);
         DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
         try {
@@ -315,7 +316,7 @@
           df.release();
         }
       } catch (Exception e) {
-        fail("Failed to index commit " + bCommit.name(), false, e);
+        fail("Failed to index commit " + b.name(), false, e);
         for (ChangeData cd : cds) {
           fail("Failed to index change " + cd.getId(), true, null);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 716a22b..4439755 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -28,6 +28,8 @@
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.Collections;
@@ -37,6 +39,9 @@
 
 /** Send comments, after the author of them hit used Publish Comments in the UI. */
 public class CommentSender extends ReplyToChangeSender {
+  private static final Logger log = LoggerFactory
+      .getLogger(CommentSender.class);
+
   public static interface Factory {
     public CommentSender create(NotifyHandling notify, Change change);
   }
@@ -97,8 +102,7 @@
   }
 
   public String getInlineComments(int lines) {
-    StringBuilder  cmts = new StringBuilder();
-
+    StringBuilder cmts = new StringBuilder();
     final Repository repo = getRepository();
     try {
       PatchList patchList = null;
@@ -114,55 +118,34 @@
       PatchFile currentFileData = null;
       for (final PatchLineComment c : inlineComments) {
         final Patch.Key pk = c.getKey().getParentKey();
-        final int lineNbr = c.getLine();
-        final short side = c.getSide();
 
         if (!pk.equals(currentFileKey)) {
           cmts.append("....................................................\n");
           if (Patch.COMMIT_MSG.equals(pk.get())) {
             cmts.append("Commit Message\n");
           } else {
-            cmts.append("File ");
-            cmts.append(pk.get());
-            cmts.append("\n");
+            cmts.append("File ").append(pk.get()).append('\n');
           }
           currentFileKey = pk;
 
           if (patchList != null) {
             try {
               currentFileData =
-                  new PatchFile(repo, patchList, pk.getFileName());
+                  new PatchFile(repo, patchList, pk.get());
             } catch (IOException e) {
-              // Don't quote the line if we can't load it.
+              log.warn(String.format(
+                  "Cannot load %s from %s in %s",
+                  pk.getFileName(),
+                  patchList.getNewId().name(),
+                  projectState.getProject().getName()), e);
+              currentFileData = null;
             }
-          } else {
-            currentFileData = null;
           }
         }
 
         if (currentFileData != null) {
-          int maxLines;
-          try {
-            maxLines = currentFileData.getLineCount(side);
-          } catch (Throwable e) {
-            maxLines = lineNbr;
-          }
-
-          final int startLine = Math.max(1, lineNbr - lines + 1);
-          final int stopLine = Math.min(maxLines, lineNbr + lines);
-
-          for (int line = startLine; line <= lineNbr; ++line) {
-            appendFileLine(cmts, currentFileData, side, line);
-          }
-
-          cmts.append(c.getMessage().trim());
-          cmts.append("\n");
-
-          for (int line = lineNbr + 1; line < stopLine; ++line) {
-            appendFileLine(cmts, currentFileData, side, line);
-          }
+          appendComment(cmts, lines, currentFileData, c);
         }
-
         cmts.append("\n\n");
       }
     } finally {
@@ -173,6 +156,30 @@
     return cmts.toString();
   }
 
+  private void appendComment(StringBuilder out, int contextLines,
+      PatchFile currentFileData, PatchLineComment comment) {
+    int lineNbr = comment.getLine();
+    short side = comment.getSide();
+    int maxLines;
+    try {
+      maxLines = currentFileData.getLineCount(side);
+    } catch (Throwable e) {
+      maxLines = lineNbr;
+    }
+
+    final int startLine = Math.max(1, lineNbr - contextLines + 1);
+    final int stopLine = Math.min(maxLines, lineNbr + contextLines);
+
+    for (int line = startLine; line <= lineNbr; ++line) {
+      appendFileLine(out, currentFileData, side, line);
+    }
+    out.append(comment.getMessage().trim()).append('\n');
+
+    for (int line = lineNbr + 1; line < stopLine; ++line) {
+      appendFileLine(out, currentFileData, side, line);
+    }
+  }
+
   private void appendFileLine(StringBuilder cmts, PatchFile fileData, short side, int line) {
     cmts.append("Line " + line);
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index ac59f65..fba13cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
 /** A version of the database schema. */
 public abstract class SchemaVersion {
   /** The current schema version. */
-  public static final Class<Schema_79> C = Schema_79.class;
+  public static final Class<Schema_81> C = Schema_81.class;
 
   public static class Module extends AbstractModule {
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_80.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_80.java
new file mode 100644
index 0000000..2cf8d2a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_80.java
@@ -0,0 +1,26 @@
+// 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.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_80 extends SchemaVersion {
+
+  @Inject
+  Schema_80(Provider<Schema_79> prior) {
+    super(prior);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_81.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_81.java
new file mode 100644
index 0000000..bc3b390
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_81.java
@@ -0,0 +1,159 @@
+// 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.schema;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.sql.SQLException;
+
+public class Schema_81 extends SchemaVersion {
+
+  private final File pluginsDir;
+  private final GitRepositoryManager mgr;
+  private final AllProjectsName allProjects;
+  private final PersonIdent serverUser;
+
+  @Inject
+  Schema_81(Provider<Schema_80> prior, SitePaths sitePaths,
+      AllProjectsName allProjects, GitRepositoryManager mgr,
+      @GerritPersonIdent PersonIdent serverUser) {
+    super(prior);
+    this.pluginsDir = sitePaths.plugins_dir;
+    this.mgr = mgr;
+    this.allProjects = allProjects;
+    this.serverUser = serverUser;
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
+      SQLException {
+    try {
+      migrateStartReplicationCapability(db, scanForReplicationPlugin());
+    } catch (RepositoryNotFoundException e) {
+      throw new OrmException(e);
+    } catch (SQLException e) {
+      throw new OrmException(e);
+    } catch (IOException e) {
+      throw new OrmException(e);
+    } catch (ConfigInvalidException e) {
+      throw new OrmException(e);
+    }
+  }
+
+  private File[] scanForReplicationPlugin() {
+    File[] matches = null;
+    if (pluginsDir != null && pluginsDir.exists()) {
+      matches = pluginsDir.listFiles(new FileFilter() {
+        @Override
+        public boolean accept(File pathname) {
+          String n = pathname.getName();
+          return (n.endsWith(".jar") || n.endsWith(".jar.disabled"))
+              && pathname.isFile() && n.indexOf("replication") >= 0;
+        }
+      });
+    }
+    return matches;
+  }
+
+  private void migrateStartReplicationCapability(ReviewDb db, File[] matches)
+      throws SQLException, RepositoryNotFoundException, IOException,
+      ConfigInvalidException {
+    Description d = new Description();
+    if (matches == null || matches.length == 0) {
+      d.what = Description.Action.REMOVE;
+    } else {
+      d.what = Description.Action.RENAME;
+      d.prefix = nameOf(matches[0]);
+    }
+    migrateStartReplicationCapability(db, d);
+  }
+
+  private void migrateStartReplicationCapability(ReviewDb db, Description d)
+      throws SQLException, RepositoryNotFoundException, IOException,
+      ConfigInvalidException {
+    Repository git = mgr.openRepository(allProjects);
+    try {
+      MetaDataUpdate md =
+          new MetaDataUpdate(GitReferenceUpdated.DISABLED, allProjects, git);
+      md.getCommitBuilder().setAuthor(serverUser);
+      md.getCommitBuilder().setCommitter(serverUser);
+      ProjectConfig config = ProjectConfig.read(md);
+      AccessSection capabilities =
+          config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES);
+      Permission startReplication =
+          capabilities.getPermission("startReplication");
+      if (startReplication == null) {
+        return;
+      }
+      String msg = null;
+      switch (d.what) {
+        case REMOVE:
+          capabilities.remove(startReplication);
+          msg = "Remove startReplication capability, plugin not installed\n";
+          break;
+        case RENAME:
+          capabilities.remove(startReplication);
+          Permission pluginStartReplication =
+              capabilities.getPermission(
+                  String.format("%s-startReplication", d.prefix), true);
+          pluginStartReplication.setRules(startReplication.getRules());
+          msg = "Rename startReplication capability to match updated plugin\n";
+          break;
+      }
+      config.replace(capabilities);
+      md.setMessage(msg);
+      config.commit(md);
+    } finally {
+      git.close();
+    }
+  }
+
+  private static String nameOf(File jar) {
+    String name = jar.getName();
+    if (name.endsWith(".disabled")) {
+      name = name.substring(0, name.lastIndexOf('.'));
+    }
+    int ext = name.lastIndexOf('.');
+    return 0 < ext ? name.substring(0, ext) : name;
+  }
+
+  private static class Description {
+    private enum Action {
+      REMOVE, RENAME
+    }
+    Action what;
+    String prefix;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
new file mode 100644
index 0000000..6730e30
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ServerRequestContext.java
@@ -0,0 +1,48 @@
+// 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.util;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.InternalUser;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+import com.google.inject.Inject;
+
+/** RequestContext with an InternalUser making the internals visible. */
+public class ServerRequestContext implements RequestContext {
+  private final InternalUser user;
+
+  @Inject
+  ServerRequestContext(InternalUser.Factory userFactory) {
+    this.user = userFactory.create();
+  }
+
+  @Override
+  public CurrentUser getCurrentUser() {
+    return user;
+  }
+
+  @Override
+  public Provider<ReviewDb> getReviewDbProvider() {
+    return new Provider<ReviewDb>() {
+      @Override
+      public ReviewDb get() {
+        throw new ProvisionException(
+            "Automatic ReviewDb only available in request scope");
+      }
+    };
+  }
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
index 7d14ac2..4b89544 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
@@ -15,6 +15,7 @@
 package gerrit;
 
 import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelValue;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.project.ProjectState;
@@ -43,6 +44,8 @@
  * </ul>
  */
 class PRED_get_legacy_label_types_1 extends Predicate.P1 {
+  private static final SymbolTerm NONE = SymbolTerm.intern("none");
+
   PRED_get_legacy_label_types_1(Term a1, Operation n) {
     arg1 = a1;
     cont = n;
@@ -75,10 +78,12 @@
       "label_type", 4);
 
   static Term export(LabelType type) {
+    LabelValue min = type.getMin();
+    LabelValue max = type.getMax();
     return new StructureTerm(symLabelType,
         SymbolTerm.intern(type.getName()),
         SymbolTerm.intern(type.getFunctionName()),
-        new IntegerTerm(type.getMin().getValue()),
-        new IntegerTerm(type.getMax().getValue()));
+        min != null ? new IntegerTerm(min.getValue()) : NONE,
+        max != null ? new IntegerTerm(max.getValue()) : NONE);
   }
 }
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
index d19fd22..a1b966f7 100644
--- 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
@@ -10,7 +10,6 @@
 queryLimit = Query Limit
 runAs = Run As
 runGC = Run Garbage Collection
-startReplication = Start Replication
 streamEvents = Stream Events
 viewCaches = View Caches
 viewConnections = View Connections
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index 6a9a93f..f1d986d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountInfo;
@@ -111,13 +112,13 @@
     long timeBase = System.currentTimeMillis();
     plc1 = newPatchLineComment(psId1, "Comment1", null,
         "FileOne.txt", Side.REVISION, 1, account1, timeBase,
-        "First Comment");
+        "First Comment", new CommentRange(1, 2, 3, 4));
     plc2 = newPatchLineComment(psId1, "Comment2", "Comment1",
         "FileOne.txt", Side.REVISION, 1, account2, timeBase + 1000,
-        "Reply to First Comment");
+        "Reply to First Comment",  new CommentRange(1, 2, 3, 4));
     plc3 = newPatchLineComment(psId1, "Comment3", "Comment1",
         "FileOne.txt", Side.PARENT, 1, account1, timeBase + 2000,
-        "First Parent Comment");
+        "First Parent Comment",  new CommentRange(1, 2, 3, 4));
 
     expect(plca.publishedByPatchSet(psId1))
         .andAnswer(results(plc1, plc2, plc3)).anyTimes();
@@ -206,16 +207,18 @@
     assertEquals(plc.getSide() == 0 ? Side.PARENT : Side.REVISION,
         Objects.firstNonNull(ci.side, Side.REVISION));
     assertEquals(plc.getWrittenOn(), ci.updated);
+    assertEquals(plc.getRange(), ci.range);
   }
 
   private static PatchLineComment newPatchLineComment(PatchSet.Id psId,
       String uuid, String inReplyToUuid, String filename, Side side, int line,
-      Account.Id authorId, long millis, String message) {
+      Account.Id authorId, long millis, String message, CommentRange range) {
     Patch.Key p = new Patch.Key(psId, filename);
     PatchLineComment.Key id = new PatchLineComment.Key(p, uuid);
     PatchLineComment plc =
         new PatchLineComment(id, line, authorId, inReplyToUuid);
     plc.setMessage(message);
+    plc.setRange(range);
     plc.setSide(side == Side.PARENT ? (short) 0 : (short) 1);
     plc.setStatus(Status.PUBLISHED);
     plc.setWrittenOn(new Timestamp(millis));
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
index f97ca55..992502f 100644
--- 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
@@ -19,21 +19,55 @@
 import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Map;
 
 public class ListCapabilitiesTest {
+  private Injector injector;
+
+  @Before
+  public void setUp() throws Exception {
+    AbstractModule mod = new AbstractModule() {
+      @Override
+      protected void configure() {
+        DynamicMap.mapOf(binder(), CapabilityDefinition.class);
+        bind(CapabilityDefinition.class)
+          .annotatedWith(Exports.named("printHello"))
+          .toInstance(new CapabilityDefinition() {
+            @Override
+            public String getDescription() {
+              return "Print Hello";
+            }
+          });
+      }
+    };
+    injector = Guice.createInjector(mod);
+  }
+
   @Test
   public void testList() throws Exception {
     Map<String, CapabilityInfo> m =
-        new ListCapabilities().apply(new ConfigResource());
+        injector.getInstance(ListCapabilities.class)
+            .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);
     }
+
+    String pluginCapability = "gerrit-printHello";
+    assertTrue("contains " + pluginCapability, m.containsKey(pluginCapability));
+    assertEquals(pluginCapability, m.get(pluginCapability).id);
+    assertEquals("Print Hello", m.get(pluginCapability).name);
   }
 }
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 93a3ef7..7a8610d 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -16,6 +16,7 @@
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:jsch',
+    '//lib:jsr305',
     '//lib/commons:codec',
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 98b740c..0cf48d8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.sshd;
 
 import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.NameKey;
 import com.google.gerrit.server.CurrentUser;
@@ -53,6 +54,8 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.annotation.Nullable;
+
 public abstract class BaseCommand implements Command {
   private static final Logger log = LoggerFactory.getLogger(BaseCommand.class);
   public static final String ENC = "UTF-8";
@@ -90,6 +93,11 @@
   @Inject
   private Provider<SshScope.Context> contextProvider;
 
+  /** Commands declared by a plugin can be scoped by the plugin name. */
+  @Inject(optional = true)
+  @PluginName
+  private String pluginName;
+
   /** The task, as scheduled on a worker thread. */
   private final AtomicReference<Future<?>> task;
 
@@ -119,6 +127,11 @@
     this.exit = callback;
   }
 
+  @Nullable
+  String getPluginName() {
+    return pluginName;
+  }
+
   String getName() {
     return commandName;
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
index cfcee6a..42c0fd7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
@@ -27,5 +27,9 @@
 @Retention(RUNTIME)
 public @interface CommandMetaData {
   String name();
+  String description() default "";
+
+  /** @deprecated use description intead. */
+  @Deprecated
   String descr() default "";
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
index e7e8a44..c64f9d8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
 import com.google.inject.AbstractModule;
 import com.google.inject.binder.LinkedBindingBuilder;
 
@@ -74,7 +76,7 @@
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
-    bind(Commands.key(parent, meta.name(), meta.descr())).to(clazz);
+    bind(Commands.key(parent, meta.name(), description(meta))).to(clazz);
   }
 
   /**
@@ -93,7 +95,14 @@
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
-    bind(Commands.key(parent, name, meta.descr())).to(clazz);
+    bind(Commands.key(parent, name, description(meta))).to(clazz);
+  }
+
+  @SuppressWarnings("deprecation")
+  private static String description(CommandMetaData meta) {
+    return Objects.firstNonNull(
+        Strings.emptyToNull(meta.description()),
+        meta.descr());
   }
 
   /**
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index 455b732..0fa5d25 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.extensions.annotations.CapabilityScope;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityControl;
@@ -28,6 +29,8 @@
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.kohsuke.args4j.Argument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.StringWriter;
@@ -40,6 +43,9 @@
  * Command that dispatches to a subcommand from its command table.
  */
 final class DispatchCommand extends BaseCommand {
+  private static final Logger log = LoggerFactory
+      .getLogger(DispatchCommand.class);
+
   interface Factory {
     DispatchCommand create(Map<String, CommandProvider> map);
   }
@@ -113,15 +119,36 @@
     }
   }
 
-  private void checkRequiresCapability(Command cmd) throws UnloggedFailure {
+  private void checkRequiresCapability(Command cmd)
+      throws UnloggedFailure {
     RequiresCapability rc = cmd.getClass().getAnnotation(RequiresCapability.class);
     if (rc != null) {
       CurrentUser user = currentUser.get();
       CapabilityControl ctl = user.getCapabilities();
-      if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
+      String capability = rc.value();
+
+      if (cmd instanceof BaseCommand) {
+        String pluginName = ((BaseCommand) cmd).getPluginName();
+        if (pluginName != null && !"gerrit".equals(pluginName)
+            && (rc.scope() == CapabilityScope.PLUGIN
+             || rc.scope() == CapabilityScope.CONTEXT)) {
+          capability = String.format("%s-%s", pluginName, rc.value());
+        } else if (rc.scope() == CapabilityScope.PLUGIN) {
+          log.error(String.format(
+              "Class %s uses @%s(scope=%s), but is not within a plugin",
+              cmd.getClass().getName(),
+              RequiresCapability.class.getSimpleName(),
+              CapabilityScope.PLUGIN.name()));
+          throw new UnloggedFailure(
+              BaseCommand.STATUS_NOT_ADMIN,
+              "fatal: cannot check capability");
+        }
+      }
+
+      if (!ctl.canPerform(capability) && !ctl.canAdministrateServer()) {
         String msg = String.format(
             "fatal: %s does not have \"%s\" capability.",
-            user.getUserName(), rc.value());
+            user.getUserName(), capability);
         throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
       }
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
index 15229dc..0ff3a01 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
@@ -28,7 +28,7 @@
 /** Opens a query processor. */
 @AdminHighPriorityCommand
 @RequiresCapability(GlobalCapability.ACCESS_DATABASE)
-@CommandMetaData(name = "gsql", descr = "Administrative interface to active database")
+@CommandMetaData(name = "gsql", description = "Administrative interface to active database")
 final class AdminQueryShell extends SshCommand {
   @Inject
   private QueryShell.Factory factory;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index e83963a..c68dc26 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -50,7 +50,7 @@
 import java.util.Set;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "set-project-parent", descr = "Change the project permissions are inherited from")
+@CommandMetaData(name = "set-project-parent", description = "Change the project permissions are inherited from")
 final class AdminSetParent extends SshCommand {
   private static final Logger log = LoggerFactory.getLogger(AdminSetParent.class);
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index 0268bc0..dc22a29 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -33,7 +33,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@CommandMetaData(name = "ban-commit", descr = "Ban a commit from a project's repository")
+@CommandMetaData(name = "ban-commit", description = "Ban a commit from a project's repository")
 public class BanCommitCommand extends SshCommand {
   @Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit")
   private String reason;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index 6c4b41c..aef1560 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -39,7 +39,7 @@
 
 /** Create a new user account. **/
 @RequiresCapability(GlobalCapability.CREATE_ACCOUNT)
-@CommandMetaData(name = "create-account", descr = "Create a new batch/role account")
+@CommandMetaData(name = "create-account", description = "Create a new batch/role account")
 final class CreateAccountCommand extends SshCommand {
   @Option(name = "--group", aliases = {"-g"}, metaVar = "GROUP", usage = "groups to add account to")
   private List<AccountGroup.Id> groups = new ArrayList<AccountGroup.Id>();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
index 660460a..c652641 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -38,7 +38,7 @@
  * Optionally, puts an initial set of user in the newly created group.
  */
 @RequiresCapability(GlobalCapability.CREATE_GROUP)
-@CommandMetaData(name = "create-group", descr = "Create a new account group")
+@CommandMetaData(name = "create-group", description = "Create a new account group")
 final class CreateGroupCommand extends SshCommand {
   @Option(name = "--owner", aliases = {"-o"}, metaVar = "GROUP", usage = "owning group, if not specified the group will be self-owning")
   private AccountGroup.Id ownerGroupId;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 89bb973..24d7689 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -36,7 +36,7 @@
 
 /** Create a new project. **/
 @RequiresCapability(GlobalCapability.CREATE_PROJECT)
-@CommandMetaData(name = "create-project", descr = "Create a new project and associated Git repository")
+@CommandMetaData(name = "create-project", description = "Create a new project and associated Git repository")
 final class CreateProjectCommand extends SshCommand {
   @Option(name = "--name", aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created (deprecated option)")
   void setProjectNameFromOption(String name) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 4b78cfc..6c07dddb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -31,7 +31,7 @@
 
 /** Causes the caches to purge all entries and reload. */
 @RequiresCapability(GlobalCapability.FLUSH_CACHES)
-@CommandMetaData(name = "flush-caches", descr = "Flush some/all server caches from memory")
+@CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory")
 final class FlushCaches extends CacheCommand {
   private static final String WEB_SESSIONS = "web_sessions";
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index c561153..346bea7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -37,7 +37,7 @@
 
 /** Runs the Git garbage collection. */
 @RequiresCapability(GlobalCapability.RUN_GC)
-@CommandMetaData(name = "gc", descr = "Run Git garbage collection")
+@CommandMetaData(name = "gc", description = "Run Git garbage collection")
 public class GarbageCollectionCommand extends BaseCommand {
 
   @Option(name = "--all", usage = "runs the Git garbage collection for all projects")
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index e5b4203..aadb1d9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -37,7 +37,7 @@
 
 import java.io.PrintWriter;
 
-@CommandMetaData(name = "ls-groups", descr = "List groups visible to the caller")
+@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller")
 public class ListGroupsCommand extends BaseCommand {
   @Inject
   private MyListGroups impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index c5bda3c..b7dd380 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -39,7 +39,7 @@
 /**
  * Implements a command that allows the user to see the members of a group.
  */
-@CommandMetaData(name = "ls-members", descr = "Lists the members of a given group")
+@CommandMetaData(name = "ls-members", description = "Lists the members of a given group")
 public class ListMembersCommand extends BaseCommand {
   @Inject
   ListMembersCommandImpl impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index ab70395..8bcae4b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -23,7 +23,7 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "ls-projects", descr = "List projects visible to the caller")
+@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller")
 final class ListProjectsCommand extends BaseCommand {
   @Inject
   private ListProjects impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index 58abf95..1806749 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -40,7 +40,7 @@
 import java.util.Map;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "ls-user-refs", descr = "List refs visible to a specific user")
+@CommandMetaData(name = "ls-user-refs", description = "List refs visible to a specific user")
 public class LsUserRefs extends SshCommand {
   @Inject
   private AccountResolver accountResolver;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
index 709e337..47c2d68 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
@@ -28,7 +28,7 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "enable", descr = "Enable plugins")
+@CommandMetaData(name = "enable", description = "Enable plugins")
 final class PluginEnableCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin(s) to enable")
   List<String> names;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index fc036fe..70d09ee 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -35,7 +35,7 @@
 import java.net.URL;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "install", descr = "Install/Add a plugin")
+@CommandMetaData(name = "install", description = "Install/Add a plugin")
 final class PluginInstallCommand extends SshCommand {
   @Option(name = "--name", aliases = {"-n"}, usage = "install under name")
   private String name;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index ab6c978..7e44641 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -26,7 +26,7 @@
 import java.io.IOException;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "ls", descr = "List the installed plugins")
+@CommandMetaData(name = "ls", description = "List the installed plugins")
 final class PluginLsCommand extends BaseCommand {
   @Inject
   private ListPlugins impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
index 85ade03..3ed1011 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
@@ -28,7 +28,7 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "reload", descr = "Reload/Restart plugins")
+@CommandMetaData(name = "reload", description = "Reload/Restart plugins")
 final class PluginReloadCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", usage = "plugins to reload/restart")
   private List<String> names;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
index 96adb8fe..0ae11af 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
@@ -27,7 +27,7 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "remove", descr = "Disable plugins")
+@CommandMetaData(name = "remove", description = "Disable plugins")
 final class PluginRemoveCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin to remove")
   List<String> names;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index 2d17876..185bb67 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -24,7 +24,7 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "query", descr = "Query the change database")
+@CommandMetaData(name = "query", description = "Query the change database")
 class Query extends SshCommand {
   @Inject
   private QueryProcessor processor;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index f9d5ac1..b3aad6f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -42,7 +42,7 @@
 import java.util.Set;
 
 /** Receives change upload over SSH using the Git receive-pack protocol. */
-@CommandMetaData(name = "receive-pack", descr = "Standard Git server side command for client side git push")
+@CommandMetaData(name = "receive-pack", description = "Standard Git server side command for client side git push")
 final class Receive extends AbstractGitCommand {
   private static final Logger log = LoggerFactory.getLogger(Receive.class);
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
index f3c1bb3..38535a4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
@@ -25,7 +25,7 @@
 
 import org.kohsuke.args4j.Argument;
 
-@CommandMetaData(name = "rename-group", descr = "Rename an account group")
+@CommandMetaData(name = "rename-group", description = "Rename an account group")
 public class RenameGroupCommand extends SshCommand {
   @Argument(index = 0, required = true, metaVar = "GROUP", usage = "name of the group to be renamed")
   private String groupName;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index b48320f..714a6ad 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -63,7 +63,7 @@
 import java.util.Map;
 import java.util.Set;
 
-@CommandMetaData(name = "review", descr = "Verify, approve and/or submit one or more patch sets")
+@CommandMetaData(name = "review", description = "Verify, approve and/or submit one or more patch sets")
 public class ReviewCommand extends SshCommand {
   private static final Logger log =
       LoggerFactory.getLogger(ReviewCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index d515aab..5736bb6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -55,7 +55,7 @@
 import java.util.List;
 
 /** Set a user's account settings. **/
-@CommandMetaData(name = "set-account", descr = "Change an account's settings")
+@CommandMetaData(name = "set-account", description = "Change an account's settings")
 final class SetAccountCommand extends BaseCommand {
 
   @Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id")
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
index cd2710f..48c37b8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -43,7 +43,7 @@
 import java.io.UnsupportedEncodingException;
 import java.util.List;
 
-@CommandMetaData(name = "set-members", descr = "Modifies members of specific group or number of groups")
+@CommandMetaData(name = "set-members", description = "Modifies members of specific group or number of groups")
 public class SetMembersCommand extends SshCommand {
 
   @Option(name = "--add", aliases = {"-a"}, metaVar = "USER", usage = "users that should be added as group member")
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index d512fd5..b45fb3a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -38,7 +38,7 @@
 import java.io.IOException;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "set-project", descr = "Change a project's settings")
+@CommandMetaData(name = "set-project", description = "Change a project's settings")
 final class SetProjectCommand extends SshCommand {
   private static final Logger log = LoggerFactory
       .getLogger(SetProjectCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
index 8e9bcac..6dc79ff 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -45,7 +45,7 @@
 import java.util.List;
 import java.util.Set;
 
-@CommandMetaData(name = "set-reviewers", descr = "Add or remove reviewers on a change")
+@CommandMetaData(name = "set-reviewers", description = "Add or remove reviewers on a change")
 public class SetReviewersCommand extends SshCommand {
   private static final Logger log =
       LoggerFactory.getLogger(SetReviewersCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index ae5dc56..7929ef6 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -52,7 +52,7 @@
 
 /** Show the current cache states. */
 @RequiresCapability(GlobalCapability.VIEW_CACHES)
-@CommandMetaData(name = "show-caches", descr = "Display current cache statistics")
+@CommandMetaData(name = "show-caches", description = "Display current cache statistics")
 final class ShowCaches extends CacheCommand {
   private static volatile long serverStarted;
 
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 f5abf4b..7b308c1 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
@@ -44,7 +44,7 @@
 
 /** Show the current SSH connections. */
 @RequiresCapability(GlobalCapability.VIEW_CONNECTIONS)
-@CommandMetaData(name = "show-connections", descr = "Display active client SSH connections")
+@CommandMetaData(name = "show-connections", description = "Display active client SSH connections")
 final class ShowConnections extends SshCommand {
   @Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
   private boolean numeric;
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 88973fc..a1b062c 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
@@ -40,7 +40,7 @@
 
 /** Display the current work queue. */
 @AdminHighPriorityCommand
-@CommandMetaData(name = "show-queue", descr = "Display the background work queues, including replication")
+@CommandMetaData(name = "show-queue", description = "Display the background work queues, including replication")
 final class ShowQueue extends SshCommand {
   @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
   private boolean wide;
@@ -94,10 +94,10 @@
       }
     });
 
-    taskNameWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 8 - 4;
+    taskNameWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4;
 
-    stdout.print(String.format("%-8s %-12s %-8s %s\n", //
-        "Task", "State", "", "Command"));
+    stdout.print(String.format("%-8s %-12s %-12s %-4s %s\n", //
+        "Task", "State", "StartTime", "", "Command"));
     stdout.print("----------------------------------------------"
         + "--------------------------------\n");
 
@@ -149,10 +149,12 @@
         }
       }
 
+      String startTime = startTime(task.getStartTime());
+
       // Shows information about tasks depending on the user rights
       if (viewAll || (!hasCustomizedPrint && regularUserCanSee)) {
-        stdout.print(String.format("%8s %-12s %-8s %s\n", //
-            id(task.getTaskId()), start, "", format(task)));
+        stdout.print(String.format("%8s %-12s %-12s %-4s %s\n", //
+            id(task.getTaskId()), start, startTime, "", format(task)));
       } else if (regularUserCanSee) {
         if (remoteName == null) {
           remoteName = projectName.get();
@@ -160,8 +162,8 @@
           remoteName = remoteName + "/" + projectName;
         }
 
-        stdout.print(String.format("%8s %-12s %-8s %s\n", //
-            id(task.getTaskId()), start, "", remoteName));
+        stdout.print(String.format("%8s %-12s %-4s %s\n", //
+            id(task.getTaskId()), start, startTime, "", remoteName));
       }
     }
     stdout.print("----------------------------------------------"
@@ -180,7 +182,15 @@
 
   private static String time(final long now, final long delay) {
     final Date when = new Date(now + delay);
-    if (delay < 24 * 60 * 60 * 1000L) {
+    return format(when, delay);
+  }
+
+  private static String startTime(final Date when) {
+    return format(when, System.currentTimeMillis() - when.getTime());
+  }
+
+  private static String format(final Date when, final long timeFromNow) {
+    if (timeFromNow < 24 * 60 * 60 * 1000L) {
       return new SimpleDateFormat("HH:mm:ss.SSS").format(when);
     }
     return new SimpleDateFormat("MMM-dd HH:mm").format(when);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
index 99d4baa..fd4a9ec 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -36,7 +36,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 
 @RequiresCapability(GlobalCapability.STREAM_EVENTS)
-@CommandMetaData(name = "stream-events", descr = "Monitor events occurring in real time")
+@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time")
 final class StreamEvents extends BaseCommand {
   /** Maximum number of events that may be queued up for each connection. */
   private static final int MAX_EVENTS = 128;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java
index 6335160..b957a7a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java
@@ -23,7 +23,7 @@
 import com.google.inject.Provider;
 
 /** Command that allows testing of prolog submit-rules in a live instance. */
-@CommandMetaData(name = "rule", descr = "Test prolog submit rules")
+@CommandMetaData(name = "rule", description = "Test prolog submit rules")
 final class TestSubmitRuleCommand extends BaseTestPrologCommand {
   @Inject
   private Provider<TestSubmitRule> view;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java
index 326ff46..2e7f0df 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java
@@ -23,7 +23,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-@CommandMetaData(name = "type", descr = "Test prolog submit type")
+@CommandMetaData(name = "type", description = "Test prolog submit type")
 final class TestSubmitTypeCommand extends BaseTestPrologCommand {
   @Inject
   private Provider<TestSubmitType> view;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
index 2066cc2..19888c8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
@@ -18,7 +18,7 @@
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 
-@CommandMetaData(name = "version", descr = "Display gerrit version")
+@CommandMetaData(name = "version", description = "Display gerrit version")
 final class VersionCommand extends SshCommand {
 
   @Override
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index e53a6fc..dd41f52 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -63,6 +63,7 @@
 import com.google.inject.Module;
 import com.google.inject.Provider;
 import com.google.inject.name.Names;
+import com.google.inject.servlet.GuiceFilter;
 import com.google.inject.servlet.GuiceServletContextListener;
 import com.google.inject.spi.Message;
 
@@ -71,16 +72,24 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
 import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.sql.DataSource;
 
 /** Configures the web application environment for Gerrit Code Review. */
-public class WebAppInitializer extends GuiceServletContextListener {
+public class WebAppInitializer extends GuiceServletContextListener
+    implements Filter {
   private static final Logger log =
       LoggerFactory.getLogger(WebAppInitializer.class);
 
@@ -91,6 +100,13 @@
   private Injector webInjector;
   private Injector sshInjector;
   private LifecycleManager manager;
+  private GuiceFilter filter;
+
+  @Override
+  public void doFilter(ServletRequest req, ServletResponse res,
+      FilterChain chain) throws IOException, ServletException {
+    filter.doFilter(req, res, chain);
+  }
 
   private synchronized void init() {
     if (manager == null) {
@@ -143,6 +159,7 @@
           .setHttpServletRequest(
               webInjector.getProvider(HttpServletRequest.class));
 
+      filter = webInjector.getInstance(GuiceFilter.class);
       manager = new LifecycleManager();
       manager.add(dbInjector);
       manager.add(cfgInjector);
@@ -303,18 +320,17 @@
   }
 
   @Override
-  public void contextInitialized(final ServletContextEvent event) {
-    super.contextInitialized(event);
+  public void init(FilterConfig cfg) throws ServletException {
+    contextInitialized(new ServletContextEvent(cfg.getServletContext()));
     init();
     manager.start();
   }
 
   @Override
-  public void contextDestroyed(final ServletContextEvent event) {
+  public void destroy() {
     if (manager != null) {
       manager.stop();
       manager = null;
     }
-    super.contextDestroyed(event);
   }
 }
diff --git a/gerrit-war/src/main/webapp/WEB-INF/web.xml b/gerrit-war/src/main/webapp/WEB-INF/web.xml
index 205341c..386eb07 100644
--- a/gerrit-war/src/main/webapp/WEB-INF/web.xml
+++ b/gerrit-war/src/main/webapp/WEB-INF/web.xml
@@ -8,14 +8,10 @@
 
   <filter>
     <filter-name>guiceFilter</filter-name>
-    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
+    <filter-class>com.google.gerrit.httpd.WebAppInitializer</filter-class>
   </filter>
   <filter-mapping>
     <filter-name>guiceFilter</filter-name>
     <url-pattern>/*</url-pattern>
   </filter-mapping>
-
-  <listener>
-    <listener-class>com.google.gerrit.httpd.WebAppInitializer</listener-class>
-  </listener>
 </web-app>
diff --git a/lib/BUCK b/lib/BUCK
index 18d13cd..44193e7 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -22,9 +22,9 @@
 
 maven_jar(
   name = 'gwtorm',
-  id = 'gwtorm:gwtorm:1.6',
-  bin_sha1 = '61bcb92f438524260429149910b5261d48812419',
-  src_sha1 = '2624f9d6a750a8aa8f9a5d4b5062b70cd12d1ae7',
+  id = 'gwtorm:gwtorm:1.7',
+  bin_sha1 = 'ee3b316a023f1422dd4b6654a3d51d0e5690809c',
+  src_sha1 = 'a145bde4cc87a4ff4cec283880e2a03be32cc868',
   license = 'Apache2.0',
   repository = GERRIT,
 )
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
new file mode 160000
index 0000000..0da2b05
--- /dev/null
+++ b/plugins/cookbook-plugin
@@ -0,0 +1 @@
+Subproject commit 0da2b05c13948dba6a92346538a844a20ee353cc
diff --git a/plugins/helloworld b/plugins/helloworld
deleted file mode 160000
index 6e3c3c8..0000000
--- a/plugins/helloworld
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6e3c3c8a54e9e0e2c5b2fb9205bbbe3112ea55bb
diff --git a/plugins/replication b/plugins/replication
index fefda22..7ec017f 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit fefda225f42c7d5995f791034704eb4989915972
+Subproject commit 7ec017fbac3f34fffc7af175d0dec181d18c33f2
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 36d87e5..1fac69b 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 36d87e549a4294c1697d68a0160383153b8f7891
+Subproject commit 1fac69b085cf67706a3a124df366d55e1976cd21