Merge "Update Polymer components"
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 1b3b9d9..f681fd5 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -81,7 +81,6 @@
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -532,7 +531,7 @@
 
   private Context newRequestContext(TestAccount account) {
     return atrScope.newContext(reviewDbProvider, new SshSession(server, account),
-        identifiedUserFactory.create(Providers.of(db), account.getId()));
+        identifiedUserFactory.create(account.getId()));
   }
 
   protected Context setApiUser(TestAccount account) {
@@ -717,7 +716,7 @@
   }
 
   protected IdentifiedUser user(TestAccount testAccount) {
-    return identifiedUserFactory.create(Providers.of(db), testAccount.getId());
+    return identifiedUserFactory.create(testAccount.getId());
   }
 
   protected RevisionResource parseCurrentRevisionResource(String changeId)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index dfa0336c..5d038c4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -629,7 +629,7 @@
         .votes();
 
     assertThat(m).hasSize(1);
-    assertThat(m).containsEntry("Code-Review", new Short((short)2));
+    assertThat(m).containsEntry("Code-Review", Short.valueOf((short)2));
 
     setApiUser(user);
     gApi.changes()
@@ -643,7 +643,7 @@
         .votes();
 
     assertThat(m).hasSize(1);
-    assertThat(m).containsEntry("Code-Review", new Short((short)-1));
+    assertThat(m).containsEntry("Code-Review", Short.valueOf((short)-1));
   }
 
   @Test
@@ -681,7 +681,7 @@
       // When NoteDb is disabled there is a dummy 0 approval on the change so
       // that the user is still returned as CC when all votes of that user have
       // been deleted.
-      assertThat(m).containsEntry("Code-Review", new Short((short)0));
+      assertThat(m).containsEntry("Code-Review", Short.valueOf((short)0));
     }
 
     ChangeInfo c = gApi.changes()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
index cfe04a2..3e2b9a6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -38,7 +38,6 @@
 import com.google.gerrit.server.project.Util;
 import com.google.gerrit.testutil.DisabledReviewDb;
 import com.google.inject.Inject;
-import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -272,7 +271,7 @@
     AcceptanceTestRequestScope.Context ctx = disableDb();
     try (Repository repo = repoManager.openRepository(project)) {
       ProjectControl ctl = projectControlFactory.controlFor(project,
-          identifiedUserFactory.create(Providers.of(db), user.getId()));
+          identifiedUserFactory.create(user.getId()));
       VisibleRefFilter filter = new VisibleRefFilter(
           tagCache, changeCache, repo, ctl, new DisabledReviewDb(), true);
       Map<String, Ref> all = repo.getAllRefs();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
index b8f0ec9..6781ef1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.Util;
@@ -56,6 +57,18 @@
   @Test
   @TestProjectInput(cloneAs = "user")
   public void updateProjectConfig() throws Exception {
+    String id = testUpdateProjectConfig();
+    assertThat(gApi.changes().id(id).get().revisions).hasSize(1);
+  }
+
+  @Test
+  @TestProjectInput(cloneAs = "user", submitType = SubmitType.CHERRY_PICK)
+  public void updateProjectConfigWithCherryPick() throws Exception {
+    String id = testUpdateProjectConfig();
+    assertThat(gApi.changes().id(id).get().revisions).hasSize(2);
+  }
+
+  private String testUpdateProjectConfig() throws Exception {
     Config cfg = readProjectConfig();
     assertThat(cfg.getString("project", null, "description")).isNull();
     String desc = "new project description";
@@ -74,6 +87,11 @@
     fetchRefsMetaConfig();
     assertThat(readProjectConfig().getString("project", null, "description"))
         .isEqualTo(desc);
+    String changeRev = gApi.changes().id(id).get().currentRevision;
+    String branchRev = gApi.projects().name(project.get())
+        .branch("refs/meta/config").get().revision;
+    assertThat(changeRev).isEqualTo(branchRev);
+    return id;
   }
 
   @Test
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 7933665..9b83c5a 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -75,6 +75,7 @@
     '//lib/guice:javax-inject',
     '//lib/guice:guice_library',
     '//lib/guice:guice-assistedinject',
+    '//gerrit-common:annotations',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index 903bf2e..fe6d719 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -169,7 +169,7 @@
     if (extId == null) {
       return CheckResult.bad("Key is not associated with any users");
     }
-    IdentifiedUser user = userFactory.create(db, extId.getAccountId());
+    IdentifiedUser user = userFactory.create(extId.getAccountId());
     Set<String> allowedUserIds = getAllowedUserIds(user);
     if (allowedUserIds.isEmpty()) {
       return CheckResult.bad("No identities found for user");
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index ebe8105..749360c 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -151,12 +151,12 @@
   private IdentifiedUser addUser(String name) throws Exception {
     AuthRequest req = AuthRequest.forUser(name);
     Account.Id id = accountManager.authenticate(req).getAccountId();
-    return userFactory.create(Providers.of(db), id);
+    return userFactory.create(id);
   }
 
   private IdentifiedUser reloadUser() {
     accountCache.evict(userId);
-    user = userFactory.create(Providers.of(db), userId);
+    user = userFactory.create(userId);
     return user;
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
index 20dd883..1d198ec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
@@ -31,10 +31,11 @@
  */
 abstract class CommentGroup extends Composite {
 
+  final DisplaySide side;
+  final int line;
+
   private final CommentManager manager;
   private final CodeMirror cm;
-  private final DisplaySide side;
-  private final int line;
   private final FlowPanel comments;
   private LineWidget lineWidget;
   private Timer resizeTimer;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
index 216fbda..ae6d3c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
@@ -21,6 +21,8 @@
 
 import net.codemirror.lib.CodeMirror;
 
+import java.util.PriorityQueue;
+
 /**
  * LineWidget attached to a CodeMirror container.
  *
@@ -28,14 +30,15 @@
  * The group tracks all comment boxes on that same line, and also includes an
  * empty padding element to keep subsequent lines vertically aligned.
  */
-class SideBySideCommentGroup extends CommentGroup {
+class SideBySideCommentGroup extends CommentGroup
+    implements Comparable<SideBySideCommentGroup> {
   static void pair(SideBySideCommentGroup a, SideBySideCommentGroup b) {
-    a.peer = b;
-    b.peer = a;
+    a.peers.add(b);
+    b.peers.add(a);
   }
 
   private final Element padding;
-  private SideBySideCommentGroup peer;
+  private final PriorityQueue<SideBySideCommentGroup> peers;
 
   SideBySideCommentGroup(SideBySideCommentManager manager, CodeMirror cm, DisplaySide side,
       int line) {
@@ -45,29 +48,42 @@
     padding.setClassName(SideBySideTable.style.padding());
     SideBySideChunkManager.focusOnClick(padding, cm.side());
     getElement().appendChild(padding);
+    peers = new PriorityQueue<>();
   }
 
   SideBySideCommentGroup getPeer() {
-    return peer;
+    return peers.peek();
   }
 
   @Override
   void remove(DraftBox box) {
     super.remove(box);
 
-    if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
-      resize();
-    } else {
+    if (getBoxCount() == 0 && peers.size() == 1
+        && peers.peek().peers.size() > 1) {
+      SideBySideCommentGroup peer = peers.peek();
+      peer.peers.remove(this);
       detach();
-      peer.detach();
+      if (peer.getBoxCount() == 0 && peer.peers.size() == 1
+          && peer.peers.peek().getBoxCount() == 0) {
+        peer.detach();
+      } else {
+        peer.resize();
+      }
+    } else {
+      resize();
     }
   }
 
   @Override
   void init(DiffTable parent) {
-    if (getLineWidget() == null && peer.getLineWidget() == null) {
-      this.attach(parent);
-      peer.attach(parent);
+    if (getLineWidget() == null) {
+      attach(parent);
+    }
+    for (CommentGroup peer : peers) {
+      if (peer.getLineWidget() == null) {
+        peer.attach(parent);
+      }
     }
   }
 
@@ -76,20 +92,20 @@
     getLineWidget().onRedraw(new Runnable() {
       @Override
       public void run() {
-        if (canComputeHeight() && peer.canComputeHeight()) {
+        if (canComputeHeight() && peers.peek().canComputeHeight()) {
           if (getResizeTimer() != null) {
             getResizeTimer().cancel();
             setResizeTimer(null);
           }
-          adjustPadding(SideBySideCommentGroup.this, peer);
+          adjustPadding(SideBySideCommentGroup.this, peers.peek());
         } else if (getResizeTimer() == null) {
           setResizeTimer(new Timer() {
             @Override
             public void run() {
-              if (canComputeHeight() && peer.canComputeHeight()) {
+              if (canComputeHeight() && peers.peek().canComputeHeight()) {
                 cancel();
                 setResizeTimer(null);
-                adjustPadding(SideBySideCommentGroup.this, peer);
+                adjustPadding(SideBySideCommentGroup.this, peers.peek());
               }
             }
           });
@@ -102,7 +118,7 @@
   @Override
   void resize() {
     if (getLineWidget() != null) {
-      adjustPadding(this, peer);
+      adjustPadding(this, peers.peek());
     }
   }
 
@@ -117,6 +133,16 @@
   private static void adjustPadding(SideBySideCommentGroup a, SideBySideCommentGroup b) {
     int apx = a.computeHeight();
     int bpx = b.computeHeight();
+    for (SideBySideCommentGroup otherPeer : a.peers) {
+      if (otherPeer != b) {
+        bpx += otherPeer.computeHeight();
+      }
+    }
+    for (SideBySideCommentGroup otherPeer : b.peers) {
+      if (otherPeer != a) {
+        apx += otherPeer.computeHeight();
+      }
+    }
     int h = Math.max(apx, bpx);
     a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX);
     b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX);
@@ -125,4 +151,14 @@
     a.updateSelection();
     b.updateSelection();
   }
+
+  @Override
+  public int compareTo(SideBySideCommentGroup o) {
+    if (side == o.side) {
+      return line - o.line;
+    } else {
+      throw new IllegalStateException(
+          "Cannot compare SideBySideCommentGroup with different sides");
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
index a2af3a1c..2b83b71 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
@@ -23,6 +23,7 @@
 import net.codemirror.lib.TextMarker.FromTo;
 
 import java.util.Collection;
+import java.util.Map;
 import java.util.SortedMap;
 
 /** Tracks comment widgets for {@link SideBySide}. */
@@ -94,36 +95,33 @@
 
   @Override
   CommentGroup group(DisplaySide side, int line) {
-    SideBySideCommentGroup w = (SideBySideCommentGroup) map(side).get(line);
-    if (w != null) {
-      return w;
+    CommentGroup existing = map(side).get(line);
+    if (existing != null) {
+      return existing;
     }
 
-    int lineA;
-    int lineB;
-    if (line == 0) {
-      lineA = lineB = 0;
-    } else if (side == DisplaySide.A) {
-      lineA = line;
-      lineB = host.lineOnOther(side, line - 1).getLine() + 1;
+    SideBySideCommentGroup newGroup = newGroup(side, line);
+    Map<Integer, CommentGroup> map =
+        side == DisplaySide.A ? sideA : sideB;
+    Map<Integer, CommentGroup> otherMap =
+        side == DisplaySide.A ? sideB : sideA;
+    map.put(line, newGroup);
+    int otherLine = host.lineOnOther(side, line - 1).getLine() + 1;
+    existing = map(side.otherSide()).get(otherLine);
+    CommentGroup otherGroup;
+    if (existing != null) {
+      otherGroup = existing;
     } else {
-      lineA = host.lineOnOther(side, line - 1).getLine() + 1;
-      lineB = line;
+      otherGroup = newGroup(side.otherSide(), otherLine);
+      otherMap.put(otherLine, otherGroup);
     }
-
-    SideBySideCommentGroup a = newGroup(DisplaySide.A, lineA);
-    SideBySideCommentGroup b = newGroup(DisplaySide.B, lineB);
-    SideBySideCommentGroup.pair(a, b);
-
-    sideA.put(lineA, a);
-    sideB.put(lineB, b);
+    SideBySideCommentGroup.pair(newGroup, (SideBySideCommentGroup) otherGroup);
 
     if (isAttached()) {
-      a.init(host.getDiffTable());
-      b.handleRedraw();
+      newGroup.init(host.getDiffTable());
+      otherGroup.handleRedraw();
     }
-
-    return side == DisplaySide.A ? a : b;
+    return newGroup;
   }
 
   private SideBySideCommentGroup newGroup(DisplaySide side, int line) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 0493df3..c7f4c4a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.CapabilityControl;
@@ -90,29 +89,20 @@
       this.disableReverseDnsLookup = disableReverseDnsLookup;
     }
 
-    public IdentifiedUser create(final Account.Id id) {
+    public IdentifiedUser create(Account.Id id) {
       return create((SocketAddress) null, id);
     }
 
-    public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
-      return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
-          authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
-          groupBackend, disableReverseDnsLookup, null, db, id, null);
-    }
-
     public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
-      return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
-          authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
-          groupBackend, disableReverseDnsLookup, Providers.of(remotePeer), null,
-          id, null);
+      return runAs(remotePeer, id, null);
     }
 
-    public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
+    public IdentifiedUser runAs(SocketAddress remotePeer, Account.Id id,
         @Nullable CurrentUser caller) {
       return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
           authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
-          groupBackend, disableReverseDnsLookup, Providers.of(remotePeer), null,
-          id, caller);
+          groupBackend, disableReverseDnsLookup, Providers.of(remotePeer), id,
+          caller);
     }
   }
 
@@ -133,23 +123,20 @@
     private final AccountCache accountCache;
     private final GroupBackend groupBackend;
     private final Boolean disableReverseDnsLookup;
-
     private final Provider<SocketAddress> remotePeerProvider;
-    private final Provider<ReviewDb> dbProvider;
 
     @Inject
     RequestFactory(
         CapabilityControl.Factory capabilityControlFactory,
         @Nullable StarredChangesUtil starredChangesUtil,
-        final AuthConfig authConfig,
+        AuthConfig authConfig,
         Realm realm,
-        @AnonymousCowardName final String anonymousCowardName,
-        @CanonicalWebUrl final Provider<String> canonicalUrl,
-        final AccountCache accountCache,
-        final GroupBackend groupBackend,
-        @DisableReverseDnsLookup final Boolean disableReverseDnsLookup,
-        @RemotePeer final Provider<SocketAddress> remotePeerProvider,
-        final Provider<ReviewDb> dbProvider) {
+        @AnonymousCowardName String anonymousCowardName,
+        @CanonicalWebUrl Provider<String> canonicalUrl,
+        AccountCache accountCache,
+        GroupBackend groupBackend,
+        @DisableReverseDnsLookup Boolean disableReverseDnsLookup,
+        @RemotePeer Provider<SocketAddress> remotePeerProvider) {
       this.capabilityControlFactory = capabilityControlFactory;
       this.starredChangesUtil = starredChangesUtil;
       this.authConfig = authConfig;
@@ -160,21 +147,19 @@
       this.groupBackend = groupBackend;
       this.disableReverseDnsLookup = disableReverseDnsLookup;
       this.remotePeerProvider = remotePeerProvider;
-      this.dbProvider = dbProvider;
     }
 
     public IdentifiedUser create(Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
           authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
-          groupBackend, disableReverseDnsLookup, remotePeerProvider, dbProvider,
-          id, null);
+          groupBackend, disableReverseDnsLookup, remotePeerProvider, id, null);
     }
 
     public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
       return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
           authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
-          groupBackend, disableReverseDnsLookup, remotePeerProvider, dbProvider,
-          id, caller);
+          groupBackend, disableReverseDnsLookup, remotePeerProvider, id,
+          caller);
     }
   }
 
@@ -196,12 +181,7 @@
   private final Set<String> validEmails =
       Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER);
 
-  @Nullable
   private final Provider<SocketAddress> remotePeerProvider;
-
-  @Nullable
-  private final Provider<ReviewDb> dbProvider;
-
   private final Account.Id accountId;
 
   private AccountState state;
@@ -224,7 +204,6 @@
       final GroupBackend groupBackend,
       final Boolean disableReverseDnsLookup,
       @Nullable final Provider<SocketAddress> remotePeerProvider,
-      @Nullable final Provider<ReviewDb> dbProvider,
       final Account.Id id,
       @Nullable CurrentUser realUser) {
     super(capabilityControlFactory);
@@ -237,7 +216,6 @@
     this.anonymousCowardName = anonymousCowardName;
     this.disableReverseDnsLookup = disableReverseDnsLookup;
     this.remotePeerProvider = remotePeerProvider;
-    this.dbProvider = dbProvider;
     this.accountId = id;
     this.realUser = realUser != null ? realUser : this;
   }
@@ -386,14 +364,11 @@
     user = user + "|" + "account-" + ua.getId().toString();
 
     String host = null;
-    if (remotePeerProvider != null) {
-      final SocketAddress remotePeer = remotePeerProvider.get();
-      if (remotePeer instanceof InetSocketAddress) {
-        final InetSocketAddress sa = (InetSocketAddress) remotePeer;
-        final InetAddress in = sa.getAddress();
-
-        host = in != null ? getHost(in) : sa.getHostName();
-      }
+    SocketAddress remotePeer = remotePeerProvider.get();
+    if (remotePeer instanceof InetSocketAddress) {
+      InetSocketAddress sa = (InetSocketAddress) remotePeer;
+      InetAddress in = sa.getAddress();
+      host = in != null ? getHost(in) : sa.getHostName();
     }
     if (host == null || host.isEmpty()) {
       host = "unknown";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index eaf51e4..568f816 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -967,7 +967,7 @@
       if (in.getPushCertificate() != null) {
         out.pushCertificate = gpgApi.checkPushCertificate(
             in.getPushCertificate(),
-            userFactory.create(db, in.getUploader()));
+            userFactory.create(in.getUploader()));
       } else {
         out.pushCertificate = new PushCertificateInfo();
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
index 8cbdf14..40df57c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -64,8 +64,7 @@
       return new VisibilityControl() {
         @Override
         public boolean isVisibleTo(Account.Id account) throws OrmException {
-          IdentifiedUser who =
-              identifiedUserFactory.create(dbProvider, account);
+          IdentifiedUser who = identifiedUserFactory.create(account);
           // we can't use changeControl directly as it won't suggest reviewers
           // to drafts
           return rsrc.getControl().forUser(who).isRefVisible();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
index d236682..f19c0aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
@@ -112,8 +112,7 @@
   @Override
   public CurrentUser getUser() {
     if (submitter != null) {
-      return identifiedUserFactory.create(
-          getReviewDbProvider(), submitter).getRealUser();
+      return identifiedUserFactory.create(submitter).getRealUser();
     }
     throw new OutOfScopeException("No user on email thread");
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index b6cd992..b52844d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -937,7 +937,7 @@
           case UPDATE_NONFASTFORWARD:
             try {
               ProjectConfig cfg = new ProjectConfig(project.getNameKey());
-              cfg.load(repo, cmd.getNewId());
+              cfg.load(rp.getRevWalk(), cmd.getNewId());
               if (!cfg.getValidationErrors().isEmpty()) {
                 addError("Invalid project configuration:");
                 for (ValidationError err : cfg.getValidationErrors()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index ecba568..dc927a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -137,12 +137,36 @@
    */
   public void load(Repository db, ObjectId id) throws IOException,
       ConfigInvalidException {
-    reader = db.newObjectReader();
+    try (RevWalk walk = new RevWalk(db)) {
+      load(walk, id);
+    }
+  }
+
+  /**
+   * Load a specific version from an open walk.
+   * <p>
+   * This method is primarily useful for applying updates to a specific revision
+   * that was shown to an end-user in the user interface. If there are conflicts
+   * with another user's concurrent changes, these will be automatically
+   * detected at commit time.
+   * <p>
+   * The caller retains ownership of the walk and is responsible for closing
+   * it. However, this instance does not hold a reference to the walk or the
+   * repository after the call completes, allowing the application to retain
+   * this object for long periods of time.
+   *
+   * @param walk open walk to access to access.
+   * @param id revision to load.
+   * @throws IOException
+   * @throws ConfigInvalidException
+   */
+  public void load(RevWalk walk, ObjectId id) throws IOException,
+     ConfigInvalidException {
+    this.reader = walk.getObjectReader();
     try {
       revision = id != null ? new RevWalk(reader).parseCommit(id) : null;
       onLoad();
     } finally {
-      reader.close();
       reader = null;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 01ae0b8..5e70b91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -145,7 +145,7 @@
       logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
       try {
         ProjectConfig cfg = new ProjectConfig(getProject());
-        cfg.load(ctx.getRepository(), commit);
+        cfg.load(ctx.getRevWalk(), commit);
       } catch (Exception e) {
         throw new IntegrationException("Submit would store invalid"
             + " project configuration " + commit.name() + " for "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 374b2e9..8a80bfe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -174,8 +174,7 @@
 
   private boolean add(Watchers matching, AccountProjectWatch w, NotifyType type)
       throws OrmException {
-    IdentifiedUser user =
-        args.identifiedUserFactory.create(args.db, w.getAccountId());
+    IdentifiedUser user = args.identifiedUserFactory.create(w.getAccountId());
 
     try {
       if (filterMatch(user, w.getFilter())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 07c8c72..d92c2b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -301,7 +301,7 @@
       } catch (ProvisionException e) {
         // Doesn't match current user, continue.
       }
-      return asUser(userFactory.create(db, otherId));
+      return asUser(userFactory.create(otherId));
     }
 
     IdentifiedUser getIdentifiedUser() throws QueryParseException {
@@ -736,7 +736,7 @@
     if (!m.isEmpty()) {
       List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
       for (Account.Id id : m) {
-        return visibleto(args.userFactory.create(args.db, id));
+        return visibleto(args.userFactory.create(id));
       }
       return Predicate.or(p);
     }
@@ -791,7 +791,7 @@
     if (g == null) {
       throw error("Group " + group + " not found");
     }
-    return new OwnerinPredicate(args.db, args.userFactory, g.getUUID());
+    return new OwnerinPredicate(args.userFactory, g.getUUID());
   }
 
   @Operator
@@ -818,7 +818,7 @@
     if (g == null) {
       throw error("Group " + group + " not found");
     }
-    return new ReviewerinPredicate(args.db, args.userFactory, g.getUUID());
+    return new ReviewerinPredicate(args.userFactory, g.getUUID());
   }
 
   @Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index ff9c853..b01fdbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -112,7 +112,7 @@
     if (psVal == expVal) {
       // Double check the value is still permitted for the user.
       //
-      IdentifiedUser reviewer = userFactory.create(dbProvider, approver);
+      IdentifiedUser reviewer = userFactory.create(approver);
       try {
         ChangeControl cc =
             ccFactory.controlFor(dbProvider.get(), change, reviewer);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
index a0c1235..467e4c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
@@ -16,21 +16,17 @@
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.query.OperatorPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class OwnerinPredicate extends OperatorPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final IdentifiedUser.GenericFactory userFactory;
   private final AccountGroup.UUID uuid;
 
-  OwnerinPredicate(Provider<ReviewDb> dbProvider,
-    IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+  OwnerinPredicate(IdentifiedUser.GenericFactory userFactory,
+    AccountGroup.UUID uuid) {
     super(ChangeQueryBuilder.FIELD_OWNERIN, uuid.toString());
-    this.dbProvider = dbProvider;
     this.userFactory = userFactory;
     this.uuid = uuid;
   }
@@ -45,8 +41,7 @@
     if (change == null) {
       return false;
     }
-    final IdentifiedUser owner = userFactory.create(dbProvider,
-      change.getOwner());
+    final IdentifiedUser owner = userFactory.create(change.getOwner());
     return owner.getEffectiveGroups().contains(uuid);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
index a29ac62..eb93451 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
@@ -16,21 +16,17 @@
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.query.OperatorPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class ReviewerinPredicate extends OperatorPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final IdentifiedUser.GenericFactory userFactory;
   private final AccountGroup.UUID uuid;
 
-  ReviewerinPredicate(Provider<ReviewDb> dbProvider,
-    IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+  ReviewerinPredicate(IdentifiedUser.GenericFactory userFactory,
+    AccountGroup.UUID uuid) {
     super(ChangeQueryBuilder.FIELD_REVIEWERIN, uuid.toString());
-    this.dbProvider = dbProvider;
     this.userFactory = userFactory;
     this.uuid = uuid;
   }
@@ -42,7 +38,7 @@
   @Override
   public boolean match(final ChangeData object) throws OrmException {
     for (Account.Id accountId : object.reviewers().values()) {
-      IdentifiedUser reviewer = userFactory.create(dbProvider, accountId);
+      IdentifiedUser reviewer = userFactory.create(accountId);
       if (reviewer.getEffectiveGroups().contains(uuid)) {
         return true;
       }
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
index 3ee8d82..87c7138 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
@@ -17,12 +17,10 @@
 import static com.googlecode.prolog_cafe.lang.SymbolTerm.intern;
 
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.util.Providers;
 
 import com.googlecode.prolog_cafe.exceptions.IllegalTypeException;
 import com.googlecode.prolog_cafe.exceptions.PInstantiationException;
@@ -90,14 +88,8 @@
       Account.Id accountId = new Account.Id(((IntegerTerm) idTerm).intValue());
       user = cache.get(accountId);
       if (user == null) {
-        ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
         IdentifiedUser.GenericFactory userFactory = userFactory(engine);
-        IdentifiedUser who;
-        if (db != null) {
-          who = userFactory.create(Providers.of(db), accountId);
-        } else {
-          who = userFactory.create(accountId);
-        }
+        IdentifiedUser who = userFactory.create(accountId);
         cache.put(accountId, who);
         user = who;
       }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
index 6b6528e..aa23e50 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -90,7 +90,7 @@
     schemaCreator.create(db);
     userId = accountManager.authenticate(AuthRequest.forUser("user"))
         .getAccountId();
-    user = userFactory.create(Providers.of(db), userId);
+    user = userFactory.create(userId);
 
     requestContext.setContext(new RequestContext() {
       @Override
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
index f29a351..e3c382a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
@@ -79,7 +79,7 @@
     schemaCreator.create(db);
     Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user"))
         .getAccountId();
-    user = userFactory.create(Providers.of(db), userId);
+    user = userFactory.create(userId);
 
     Project.NameKey name = new Project.NameKey("project");
     InMemoryRepository inMemoryRepo = repoManager.createRepository(name);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 031eb0a..dad134b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -156,13 +156,13 @@
     Account userAccount = db.accounts().get(userId);
     userAccount.setPreferredEmail("user@example.com");
     db.accounts().update(ImmutableList.of(userAccount));
-    user = userFactory.create(Providers.of(db), userId);
+    user = userFactory.create(userId);
     requestContext.setContext(newRequestContext(userAccount.getId()));
   }
 
   protected RequestContext newRequestContext(Account.Id requestUserId) {
     final CurrentUser requestUser =
-        userFactory.create(Providers.of(db), requestUserId);
+        userFactory.create(requestUserId);
     return new RequestContext() {
       @Override
       public CurrentUser getUser() {
@@ -571,7 +571,7 @@
         .reviewer(user.getAccountId().toString())
         .votes();
     assertThat(m).hasSize(1);
-    assertThat(m).containsEntry("Code-Review", new Short((short)1));
+    assertThat(m).containsEntry("Code-Review", Short.valueOf((short) 1));
 
     Map<Integer, Change> changes = new LinkedHashMap<>(5);
     changes.put(2, reviewPlus2Change);
@@ -1540,7 +1540,7 @@
     Project.NameKey project = new Project.NameKey(
         repo.getRepository().getDescription().getRepositoryName());
     Account.Id ownerId = owner != null ? owner : userId;
-    IdentifiedUser user = userFactory.create(Providers.of(db), ownerId);
+    IdentifiedUser user = userFactory.create(ownerId);
     try (BatchUpdate bu =
         updateFactory.create(db, project, user, TimeUtil.nowTs())) {
       bu.insertChange(ins);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
index bbad2be..fde3a66 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
@@ -63,19 +63,19 @@
     for (String name : chain(command)) {
       CommandProvider p = map.get(name);
       if (p == null) {
-        throw new UnloggedFailure(1, getName() + ": not found");
+        throw die(getName() + ": not found");
       }
 
       Command cmd = p.getProvider().get();
       if (!(cmd instanceof DispatchCommand)) {
-        throw new UnloggedFailure(1, getName() + ": not found");
+        throw die(getName() + ": not found");
       }
       map = ((DispatchCommand) cmd).getMap();
     }
 
     CommandProvider p = map.get(command.value());
     if (p == null) {
-      throw new UnloggedFailure(1, getName() + ": not found");
+      throw die(getName() + ": not found");
     }
 
     Command cmd = p.getProvider().get();
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 f296ef3..2873c37 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
@@ -377,6 +377,14 @@
     return new UnloggedFailure(1, "fatal: " + why.getMessage(), why);
   }
 
+  protected void writeError(String type, String msg) {
+    try {
+      err.write((type + ": " + msg + "\n").getBytes(ENC));
+    } catch (IOException e) {
+      // Ignored
+    }
+  }
+
   public void checkExclusivity(final Object arg1, final String arg1name,
       final Object arg2, final String arg2name) throws UnloggedFailure {
     if (arg1 != null && arg2 != null) {
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 8873be9..f2911dc 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
@@ -73,7 +73,7 @@
       if (Strings.isNullOrEmpty(commandName)) {
         StringWriter msg = new StringWriter();
         msg.write(usage());
-        throw new UnloggedFailure(1, msg.toString());
+        throw die(msg.toString());
       }
 
       final CommandProvider p = commands.get(commandName);
@@ -81,7 +81,7 @@
         String msg =
             (getName().isEmpty() ? "Gerrit Code Review" : getName()) + ": "
                 + commandName + ": not found";
-        throw new UnloggedFailure(1, msg);
+        throw die(msg);
       }
 
       final Command cmd = p.getProvider().get();
@@ -96,7 +96,7 @@
         bc.setArguments(args.toArray(new String[args.size()]));
 
       } else if (!args.isEmpty()) {
-        throw new UnloggedFailure(1, commandName + " does not take arguments");
+        throw die(commandName + " does not take arguments");
       }
 
       provideStateTo(cmd);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
index 4308db9..24bd8c2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
@@ -115,10 +115,9 @@
     if (caller instanceof PeerDaemonUser) {
       // OK.
     } else if (!enableRunAs) {
-      throw new UnloggedFailure(1,
-          "fatal: suexec disabled by auth.enableRunAs = false");
+      throw die("suexec disabled by auth.enableRunAs = false");
     } else if (!caller.getCapabilities().canRunAs()) {
-      throw new UnloggedFailure(1, "fatal: suexec not permitted");
+      throw die("suexec not permitted");
     }
   }
 
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 850026b..237d844 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
@@ -47,7 +47,7 @@
     try {
       checkPermission();
     } catch (PermissionDeniedException err) {
-      throw new UnloggedFailure("fatal: " + err.getMessage());
+      throw die(err.getMessage());
     }
 
     QueryShell shell = factory.create(in, out);
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 b4594d4..eb0d7b2 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
@@ -84,12 +84,11 @@
   @Override
   protected void run() throws Failure {
     if (oldParent == null && children.isEmpty()) {
-      throw new UnloggedFailure(1, "fatal: child projects have to be specified as " +
-                                   "arguments or the --children-of option has to be set");
+      throw die("child projects have to be specified as " +
+          "arguments or the --children-of option has to be set");
     }
     if (oldParent == null && !excludedChildren.isEmpty()) {
-      throw new UnloggedFailure(1, "fatal: --exclude can only be used together " +
-                                   "with --children-of");
+      throw die("--exclude can only be used together with --children-of");
     }
 
     final StringBuilder err = new StringBuilder();
@@ -164,7 +163,7 @@
       while (err.charAt(err.length() - 1) == '\n') {
         err.setLength(err.length() - 1);
       }
-      throw new UnloggedFailure(1, err.toString());
+      throw die(err.toString());
     }
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
index cc393ce..12f69ed 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
@@ -48,7 +48,7 @@
             docResult.url));
       }
     } catch (DocQueryException dqe) {
-      throw new UnloggedFailure(1, "fatal: " + dqe.getMessage());
+      throw die(dqe);
     }
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
index 301bc0e..9f31ddc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
@@ -76,7 +76,7 @@
       OutputFormat.JSON.newGson().toJson(result, stdout);
       stdout.print('\n');
     } catch (Exception e) {
-      throw new UnloggedFailure("Processing of prolog script failed: " + e);
+      throw die("Processing of prolog script failed: " + e);
     }
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
index b1d09e9..d3ec69c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
@@ -48,7 +48,7 @@
       gApi.projects().name(project.getProject().getNameKey().get())
           .branch(name).create(in);
     } catch (RestApiException e) {
-      throw new UnloggedFailure(1, "fatal: " + e.getMessage(), e);
+      throw die(e);
     }
   }
 }
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 3ad5156..4ebafb8 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
@@ -134,7 +134,7 @@
     try {
       if (!suggestParent) {
         if (projectName == null) {
-          throw new UnloggedFailure(1, "fatal: Project name is required.");
+          throw die("Project name is required.");
         }
 
         ProjectInput input = new ProjectInput();
@@ -176,7 +176,7 @@
         }
       }
     } catch (RestApiException | NoSuchProjectException err) {
-      throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
+      throw die(err);
     }
   }
 
@@ -188,7 +188,7 @@
       String[] s = pluginConfigValue.split("=");
       String[] s2 = s[0].split("\\.");
       if (s.length != 2 || s2.length != 2) {
-        throw new UnloggedFailure(1, "Invalid plugin config value '"
+        throw die("Invalid plugin config value '"
             + pluginConfigValue
             + "', expected format '<plugin-name>.<parameter-name>=<value>'"
             + " or '<plugin-name>.<parameter-name>=<value1,value2,...>'");
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 fcf365c..1f03225 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
@@ -60,14 +60,14 @@
     try {
       if (list) {
         if (all || caches.size() > 0) {
-          throw error("error: cannot use --list with --all or --cache");
+          throw die("cannot use --list with --all or --cache");
         }
         doList();
         return;
       }
 
       if (all && caches.size() > 0) {
-        throw error("error: cannot combine --all and --cache");
+        throw die("cannot combine --all and --cache");
       } else if (!all && caches.size() == 1 && caches.contains("all")) {
         caches.clear();
         all = true;
@@ -87,10 +87,6 @@
     }
   }
 
-  private static UnloggedFailure error(String msg) {
-    return new UnloggedFailure(1, msg);
-  }
-
   @SuppressWarnings("unchecked")
   private void doList() {
     for (String name : (List<String>) listCaches
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 a3fbcb2..520d194 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
@@ -68,11 +68,10 @@
 
   private void verifyCommandLine() throws UnloggedFailure {
     if (!all && projects.isEmpty()) {
-      throw new UnloggedFailure(1,
-          "needs projects as command arguments or --all option");
+      throw die("needs projects as command arguments or --all option");
     }
     if (all && !projects.isEmpty()) {
-      throw new UnloggedFailure(1,
+      throw die(
           "either specify projects as command arguments or use --all option");
     }
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
index c508b1d..4991700 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
-
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.lucene.LuceneVersionManager;
@@ -28,8 +26,7 @@
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
 @CommandMetaData(name = "activate",
-  description = "Activate the latest index version available",
-  runsAt = MASTER)
+  description = "Activate the latest index version available")
 public class IndexActivateCommand extends SshCommand {
 
   @Argument(index = 0, required = true, metaVar = "INDEX",
@@ -48,8 +45,7 @@
         stdout.println("Not activating index, already using latest version");
       }
     } catch (ReindexerAlreadyRunningException e) {
-      throw new UnloggedFailure("Failed to activate latest index: "
-          + e.getMessage());
+      throw die("Failed to activate latest index: " + e.getMessage());
     }
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
index c2c565f..73e9f33 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
-
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.lucene.LuceneVersionManager;
@@ -27,8 +25,7 @@
 import org.kohsuke.args4j.Argument;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "start", description = "Start the online reindexer",
-  runsAt = MASTER)
+@CommandMetaData(name = "start", description = "Start the online reindexer")
 public class IndexStartCommand extends SshCommand {
 
   @Argument(index = 0, required = true, metaVar = "INDEX",
@@ -47,7 +44,7 @@
         stdout.println("Nothing to reindex, index is already the latest version");
       }
     } catch (ReindexerAlreadyRunningException e) {
-      throw new UnloggedFailure("Failed to start reindexer: " + e.getMessage());
+      throw die("Failed to start reindexer: " + e.getMessage());
     }
   }
 }
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 bd97286..2e11ef9 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
@@ -49,7 +49,7 @@
   @Override
   public void run() throws Exception {
     if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
-      throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
+      throw die("--user and --project options are not compatible.");
     }
     impl.display(stdout);
   }
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 134a719..d81c153 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
@@ -34,10 +34,10 @@
     if (!impl.getFormat().isJson()) {
       List<String> showBranch = impl.getShowBranch();
       if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {
-        throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
+        throw die("--tree and --show-branch options are not compatible.");
       }
       if (impl.isShowTree() && impl.isShowDescription()) {
-        throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
+        throw die("--tree and --description options are not compatible.");
       }
     }
     impl.display(out);
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 a70d581..1ac347f 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
@@ -109,11 +109,10 @@
             + projectControl.getProject().getNameKey(), e);
       }
     } catch (RepositoryNotFoundException e) {
-      throw new UnloggedFailure("fatal: '"
-          + projectControl.getProject().getNameKey() + "': not a git archive");
+      throw die("'" + projectControl.getProject().getNameKey()
+          + "': not a git archive");
     } catch (IOException e) {
-      throw new UnloggedFailure("fatal: Error opening: '"
-          + projectControl.getProject().getNameKey());
+      throw die("Error opening: '" + projectControl.getProject().getNameKey());
     }
   }
 }
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 f65a0c9..8bde743 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
@@ -102,7 +102,7 @@
     super.parseCommandLine();
     if (processor.getIncludeFiles() &&
         !(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
-      throw new UnloggedFailure(1, "--files option needs --patch-sets or --current-patch-set");
+      throw die("--files option needs --patch-sets or --current-patch-set");
     }
   }
 
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 0b12aa6..011cb91 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
@@ -83,7 +83,7 @@
 
     Capable r = receive.canUpload();
     if (r != Capable.OK) {
-      throw new UnloggedFailure(1, "\nfatal: " + r.getMessage());
+      throw die(r.getMessage());
     }
 
     verifyProjectVisible("reviewer", reviewerId);
@@ -165,7 +165,7 @@
     for (final Account.Id id : who) {
       final IdentifiedUser user = identifiedUserFactory.create(id);
       if (!projectControl.forUser(user).isVisible()) {
-        throw new UnloggedFailure(1, type + " "
+        throw die(type + " "
             + user.getAccount().getFullName() + " cannot access the project");
       }
     }
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 dad8672..3c5d5a3 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
@@ -153,68 +153,68 @@
   protected void run() throws UnloggedFailure {
     if (abandonChange) {
       if (restoreChange) {
-        throw error("abandon and restore actions are mutually exclusive");
+        throw die("abandon and restore actions are mutually exclusive");
       }
       if (submitChange) {
-        throw error("abandon and submit actions are mutually exclusive");
+        throw die("abandon and submit actions are mutually exclusive");
       }
       if (publishPatchSet) {
-        throw error("abandon and publish actions are mutually exclusive");
+        throw die("abandon and publish actions are mutually exclusive");
       }
       if (deleteDraftPatchSet) {
-        throw error("abandon and delete actions are mutually exclusive");
+        throw die("abandon and delete actions are mutually exclusive");
       }
       if (rebaseChange) {
-        throw error("abandon and rebase actions are mutually exclusive");
+        throw die("abandon and rebase actions are mutually exclusive");
       }
     }
     if (publishPatchSet) {
       if (restoreChange) {
-        throw error("publish and restore actions are mutually exclusive");
+        throw die("publish and restore actions are mutually exclusive");
       }
       if (submitChange) {
-        throw error("publish and submit actions are mutually exclusive");
+        throw die("publish and submit actions are mutually exclusive");
       }
       if (deleteDraftPatchSet) {
-        throw error("publish and delete actions are mutually exclusive");
+        throw die("publish and delete actions are mutually exclusive");
       }
     }
     if (json) {
       if (restoreChange) {
-        throw error("json and restore actions are mutually exclusive");
+        throw die("json and restore actions are mutually exclusive");
       }
       if (submitChange) {
-        throw error("json and submit actions are mutually exclusive");
+        throw die("json and submit actions are mutually exclusive");
       }
       if (deleteDraftPatchSet) {
-        throw error("json and delete actions are mutually exclusive");
+        throw die("json and delete actions are mutually exclusive");
       }
       if (publishPatchSet) {
-        throw error("json and publish actions are mutually exclusive");
+        throw die("json and publish actions are mutually exclusive");
       }
       if (abandonChange) {
-        throw error("json and abandon actions are mutually exclusive");
+        throw die("json and abandon actions are mutually exclusive");
       }
       if (changeComment != null) {
-        throw error("json and message are mutually exclusive");
+        throw die("json and message are mutually exclusive");
       }
       if (rebaseChange) {
-        throw error("json and rebase actions are mutually exclusive");
+        throw die("json and rebase actions are mutually exclusive");
       }
       if (changeTag != null) {
-        throw error("json and tag actions are mutually exclusive");
+        throw die("json and tag actions are mutually exclusive");
       }
     }
     if (rebaseChange) {
       if (deleteDraftPatchSet) {
-        throw error("rebase and delete actions are mutually exclusive");
+        throw die("rebase and delete actions are mutually exclusive");
       }
       if (submitChange) {
-        throw error("rebase and submit actions are mutually exclusive");
+        throw die("rebase and submit actions are mutually exclusive");
       }
     }
     if (deleteDraftPatchSet && submitChange) {
-      throw error("delete and submit actions are mutually exclusive");
+      throw die("delete and submit actions are mutually exclusive");
     }
 
     boolean ok = true;
@@ -232,20 +232,21 @@
         }
       } catch (RestApiException | UnloggedFailure e) {
         ok = false;
-        writeError("error: " + e.getMessage() + "\n");
+        writeError("error", e.getMessage() + "\n");
       } catch (NoSuchChangeException e) {
         ok = false;
-        writeError("no such change " + patchSet.getId().getParentKey().get());
+        writeError("error",
+            "no such change " + patchSet.getId().getParentKey().get());
       } catch (Exception e) {
         ok = false;
-        writeError("fatal: internal server error while reviewing "
+        writeError("fatal", "internal server error while reviewing "
             + patchSet.getId() + "\n");
         log.error("internal error while reviewing " + patchSet.getId(), e);
       }
     }
 
     if (!ok) {
-      throw error("one or more reviews failed; review output above");
+      throw die("one or more reviews failed; review output above");
     }
   }
 
@@ -262,8 +263,8 @@
       return OutputFormat.JSON.newGson().
           fromJson(CharStreams.toString(r), ReviewInput.class);
     } catch (IOException | JsonSyntaxException e) {
-      writeError(e.getMessage() + '\n');
-      throw error("internal error while reading review input");
+      writeError("error", e.getMessage() + '\n');
+      throw die("internal error while reading review input");
     }
   }
 
@@ -321,7 +322,7 @@
         revisionApi(patchSet).delete();
       }
     } catch (IllegalStateException | RestApiException e) {
-      throw error(e.getMessage());
+      throw die(e);
     }
   }
 
@@ -342,7 +343,7 @@
     try {
       allProjectsControl = projectControlFactory.controlFor(allProjects);
     } catch (NoSuchProjectException e) {
-      throw new UnloggedFailure("missing " + allProjects.get());
+      throw die("missing " + allProjects.get());
     }
 
     for (LabelType type : allProjectsControl.getLabelTypes().getLabelTypes()) {
@@ -360,16 +361,4 @@
 
     super.parseCommandLine();
   }
-
-  private void writeError(final String msg) {
-    try {
-      err.write(msg.getBytes(ENC));
-    } catch (IOException e) {
-      // Ignored
-    }
-  }
-
-  private static UnloggedFailure error(final String msg) {
-    return new UnloggedFailure(1, msg);
-  }
 }
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 6e03ac1..535f79a 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
@@ -145,16 +145,14 @@
 
   private void validate() throws UnloggedFailure {
     if (active && inactive) {
-      throw new UnloggedFailure(1,
-          "--active and --inactive options are mutually exclusive.");
+      throw die("--active and --inactive options are mutually exclusive.");
     }
     if (clearHttpPassword && !Strings.isNullOrEmpty(httpPassword)) {
-      throw new UnloggedFailure(1,
-          "--http-password and --clear-http-password options are mutually " +
-          "exclusive.");
+      throw die("--http-password and --clear-http-password options are "
+          + "mutually exclusive.");
     }
     if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) {
-      throw new UnloggedFailure(1, "Only one option may use the stdin");
+      throw die("Only one option may use the stdin");
     }
     if (deleteSshKeys.contains("ALL")) {
       deleteSshKeys = Collections.singletonList("ALL");
@@ -163,8 +161,7 @@
       deleteEmails = Collections.singletonList("ALL");
     }
     if (deleteEmails.contains(preferredEmail)) {
-      throw new UnloggedFailure(1,
-          "--preferred-email and --delete-email options are mutually " +
+      throw die("--preferred-email and --delete-email options are mutually " +
           "exclusive for the same email address.");
     }
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
index b1d1605..4fef018 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
@@ -49,7 +49,7 @@
     try {
       setHead.apply(new ProjectResource(project), input);
     } catch (UnprocessableEntityException e) {
-      throw new UnloggedFailure("fatal: " + e.getMessage());
+      throw die(e);
     }
   }
 }
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 589fbf0..6328fb4 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
@@ -155,7 +155,7 @@
       md.setMessage("Project settings updated");
       config.commit(md);
     } catch (RepositoryNotFoundException notFound) {
-      err.append("error: Project ").append(name).append(" not found\n");
+      err.append("Project ").append(name).append(" not found\n");
     } catch (IOException | ConfigInvalidException e) {
       final String msg = "Cannot update project " + name;
       log.error(msg, e);
@@ -167,7 +167,7 @@
       while (err.charAt(err.length() - 1) == '\n') {
         err.setLength(err.length() - 1);
       }
-      throw new UnloggedFailure(1, err.toString());
+      throw die(err.toString());
     }
   }
 }
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 63cb54f..bf4aae7 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
@@ -39,7 +39,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -63,7 +62,7 @@
     toRemove.add(who);
   }
 
-  @Argument(index = 0, required = true, multiValued = true, metaVar = "COMMIT", usage = "changes to modify")
+  @Argument(index = 0, required = true, multiValued = true, metaVar = "CHANGE", usage = "changes to modify")
   void addChange(String token) {
     try {
       addChangeImpl(token);
@@ -113,7 +112,7 @@
     }
 
     if (!ok) {
-      throw error("fatal: one or more updates failed; review output above");
+      throw die("one or more updates failed; review output above");
     }
   }
 
@@ -171,7 +170,7 @@
     }
     switch (toAdd.size()) {
       case 0:
-        throw error("\"" + id + "\" no such change");
+        throw die("\"" + id + "\" no such change");
 
       case 1:
         ChangeControl ctl = toAdd.get(0);
@@ -179,7 +178,7 @@
         break;
 
       default:
-        throw error("\"" + id + "\" matches multiple changes");
+        throw die("\"" + id + "\" matches multiple changes");
     }
   }
 
@@ -191,16 +190,4 @@
       return true;
     }
   }
-
-  private void writeError(String type, String msg) {
-    try {
-      err.write((type + ": " + msg + "\n").getBytes(ENC));
-    } catch (IOException e) {
-      // Ignored
-    }
-  }
-
-  private static UnloggedFailure error(String msg) {
-    return new UnloggedFailure(1, msg);
-  }
 }
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 2da1b6d..9e6630a 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
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Supplier;
@@ -48,8 +47,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 
 @RequiresCapability(GlobalCapability.STREAM_EVENTS)
-@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time",
-  runsAt = MASTER)
+@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/lib/auto/BUCK b/lib/auto/BUCK
index c688ee4..149f2d1 100644
--- a/lib/auto/BUCK
+++ b/lib/auto/BUCK
@@ -2,12 +2,8 @@
 
 maven_jar(
   name = 'auto-value',
-  id = 'com.google.auto.value:auto-value:1.1',
-  sha1 = 'f6951c141ea3e89c0f8b01da16834880a1ebf162',
-  # Exclude un-relocated dependencies and replace with our own versions; see
-  # https://github.com/google/auto/blob/auto-value-1.1/value/pom.xml#L151
-  exclude = ['org/apache/*'],
-  deps = ['//lib:velocity'],
+  id = 'com.google.auto.value:auto-value:1.2',
+  sha1 = '6873fed014fe1de1051aae2af68ba266d2934471',
   license = 'Apache2.0',
   visibility = ['PUBLIC'],
 )
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index 263fb28..da15406 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -42,12 +42,12 @@
         word-wrap: break-word;
       }
     </style>
-    <template is="dom-repeat" items="{{_files}}" as="file">
+    <template is="dom-repeat" items="[[_files]]" as="file">
       <div class="file">
         <a href$="[[_computeFileDiffURL(file, changeNum, patchNum)]]">[[file]]</a>:
       </div>
       <template is="dom-repeat"
-                items="[[_computeCommentsForFile(file)]]" as="comment">
+                items="[[_computeCommentsForFile(comments, file)]]" as="comment">
         <div class="container">
           <a class="lineNum"
              href$="[[_computeDiffLineURL(file, changeNum, comment.patch_set, comment)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index b40c18e..c23c373 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -19,17 +19,17 @@
 
     properties: {
       changeNum: Number,
-      comments: {
-        type: Object,
-        observer: '_commentsChanged',
-      },
+      comments: Object,
       patchNum: Number,
 
-      _files: Array,
+      _files: {
+        type: Array,
+        computed: '_computeFiles(comments)',
+      },
     },
 
-    _commentsChanged: function(value) {
-      this._files = Object.keys(value || {}).sort();
+    _computeFiles: function(comments) {
+      return Object.keys(comments || {}).sort();
     },
 
     _computeFileDiffURL: function(file, changeNum, patchNum) {
@@ -45,8 +45,8 @@
       return diffURL;
     },
 
-    _computeCommentsForFile: function(file) {
-      return this.comments[file];
+    _computeCommentsForFile: function(comments, file) {
+      return comments[file];
     },
 
     _computePatchDisplayName: function(comment) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index ae57be3..c89cb93 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -146,6 +146,10 @@
       };
       for (var label in this.permittedLabels) {
         var selectorEl = this.$$('iron-selector[data-label="' + label + '"]');
+
+        // The selector may not be present if it’s not at the latest patch set.
+        if (!selectorEl) { continue; }
+
         var selectedVal = selectorEl.selectedItem.getAttribute('data-value');
         selectedVal = parseInt(selectedVal, 10);
         obj.labels[label] = selectedVal;
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index c6d8dbc..930c8cf 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -148,7 +148,7 @@
       <div class="rightItems">
         <gr-search-bar value="{{searchQuery}}" role="search"></gr-search-bar>
         <div class="accountContainer" id="accountContainer">
-          <a class="loginButton" href$="[[_computeRelativeURL('/login')]]" on-tap="_loginTapHandler">Sign in</a>
+          <a class="loginButton" href$="[[_loginURL]]" on-tap="_loginTapHandler">Sign in</a>
           <gr-account-dropdown account="[[_account]]"></gr-account-dropdown>
         </div>
       </div>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index 186932a..6fc3cc1 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -56,6 +56,10 @@
         type: Array,
         computed: '_computeLinks(_defaultLinks, _userLinks)',
       },
+      _loginURL: {
+        type: String,
+        value: '/login',
+      },
       _userLinks: {
         type: Array,
         value: function() { return []; },
@@ -68,6 +72,18 @@
 
     attached: function() {
       this._loadAccount();
+      this.listen(window, 'location-change', '_handleLocationChange');
+    },
+
+    detached: function() {
+      this.unlisten(window, 'location-change', '_handleLocationChange');
+    },
+
+    _handleLocationChange: function(e) {
+      this._loginURL = '/login/' + encodeURIComponent(
+          window.location.pathname +
+          window.location.search +
+          window.location.hash);
     },
 
     _computeRelativeURL: function(path) {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 9145ba1..dea0d1d 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -23,6 +23,12 @@
     // Middleware
     page(function(ctx, next) {
       document.body.scrollTop = 0;
+
+      // Fire asynchronously so that the URL is changed by the time the event
+      // is processed.
+      app.async(function() {
+        app.fire('location-change');
+      }, 1);
       next();
     });
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index b827d26..aa5d8bd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -41,10 +41,6 @@
       _orderedComments: Array,
     },
 
-    get naturalHeight() {
-      return this.$.container.offsetHeight;
-    },
-
     observers: [
       '_commentsChanged(comments.splices)',
     ],
@@ -64,7 +60,11 @@
         return;
       }
 
-      this.push('comments', this._newDraft(opt_lineNum));
+      var draft = this._newDraft(opt_lineNum);
+      this.push('comments', draft);
+      this.async(function() {
+        this._commentElWithDraftID(draft.__draftID).editing = true;
+      }.bind(this), 1);
     },
 
     _getLoggedIn: function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index fab0425..6e7a68a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -129,7 +129,7 @@
           class="editMessage"
           disabled="{{disabled}}"
           rows="4"
-          bind-value="{{_editDraft}}"
+          bind-value="{{_messageText}}"
           on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
       <gr-linked-text class="message"
           pre
@@ -141,7 +141,7 @@
         <gr-button class="action done" on-tap="_handleDone">Done</gr-button>
         <gr-button class="action edit" on-tap="_handleEdit">Edit</gr-button>
         <gr-button class="action save" on-tap="_handleSave"
-            disabled$="[[_computeSaveDisabled(_editDraft)]]">Save</gr-button>
+            disabled$="[[_computeSaveDisabled(_messageText)]]">Save</gr-button>
         <gr-button class="action cancel" on-tap="_handleCancel" hidden>Cancel</gr-button>
         <div class="danger">
           <gr-button class="action discard" on-tap="_handleDiscard">Discard</gr-button>
@@ -149,7 +149,7 @@
       </div>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
-    <gr-storage id="localStorage"></gr-storage>
+    <gr-storage id="storage"></gr-storage>
   </template>
   <script src="gr-diff-comment.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index c28d0ac..525bac0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -69,25 +69,28 @@
       projectConfig: Object,
 
       _xhrPromise: Object,  // Used for testing.
-      _editDraft: {
+      _messageText: {
         type: String,
-        observer: '_editDraftChanged',
+        value: '',
+        observer: '_messageTextChanged',
       },
     },
 
-    ready: function() {
-      this._loadLocalDraft().then(function(loadedLocal) {
-        this._editDraft = (this.comment && this.comment.message) || '';
-        this.editing = !this._editDraft.length || loadedLocal;
-      }.bind(this));
-    },
+    observers: [
+      '_commentMessageChanged(comment.message)',
+      '_loadLocalDraft(changeNum, patchNum, comment)',
+    ],
 
     save: function() {
-      this.comment.message = this._editDraft;
+      this.comment.message = this._messageText;
       this.disabled = true;
 
-      this.$.localStorage.eraseDraft(this.changeNum, this.patchNum,
-          this.comment.path, this.comment.line);
+      this.$.storage.eraseDraftComment({
+        changeNum: this.changeNum,
+        patchNum: this.patchNum,
+        path: this.comment.path,
+        line: this.comment.line,
+      });
 
       this._xhrPromise = this._saveDraft(this.comment).then(function(response) {
         this.disabled = false;
@@ -147,23 +150,31 @@
       }
     },
 
-    _editDraftChanged: function(newValue, oldValue) {
-      if (this.comment && this.comment.id) { return; }
+    _commentMessageChanged: function(message) {
+      this._messageText = message || '';
+    },
+
+    _messageTextChanged: function(newValue, oldValue) {
+      if (!this.comment || (this.comment && this.comment.id)) { return; }
 
       this.debounce('store', function() {
-        var message = this._editDraft;
+        var message = this._messageText;
 
-        // If the draft has been modified to be empty, then erase the storage
-        // entry.
-        if ((!this._editDraft || !this._editDraft.length) && oldValue) {
-          this.$.localStorage.eraseDraft(this.changeNum, this.patchNum,
-              this.comment.path, this.comment.line);
-          return;
+        var commentLocation = {
+          changeNum: this.changeNum,
+          patchNum: this.patchNum,
+          path: this.comment.path,
+          line: this.comment.line,
+        };
+
+        if ((!this._messageText || !this._messageText.length) && oldValue) {
+          // If the draft has been modified to be empty, then erase the storage
+          // entry.
+          this.$.storage.eraseDraftComment(commentLocation);
+        } else {
+          this.$.storage.setDraftComment(commentLocation, message);
         }
-
-        this.$.localStorage.setDraft(this.changeNum, this.patchNum,
-            this.comment.path, this.comment.line, message);
-      }.bind(this), STORAGE_DEBOUNCE_INTERVAL);
+      }, STORAGE_DEBOUNCE_INTERVAL);
     },
 
     _handleLinkTap: function(e) {
@@ -195,7 +206,7 @@
 
     _handleEdit: function(e) {
       this._preventDefaultAndBlur(e);
-      this._editDraft = this.comment.message;
+      this._messageText = this.comment.message;
       this.editing = true;
     },
 
@@ -210,7 +221,7 @@
         this.fire('comment-discard');
         return;
       }
-      this._editDraft = this.comment.message;
+      this._messageText = this.comment.message;
       this.editing = false;
     },
 
@@ -219,8 +230,10 @@
       if (!this.comment.__draft) {
         throw Error('Cannot discard a non-draft comment.');
       }
+      this.editing = false;
       this.disabled = true;
       if (!this.comment.id) {
+        this.disabled = false;
         this.fire('comment-discard');
         return;
       }
@@ -251,28 +264,23 @@
           draft);
     },
 
-    _loadLocalDraft: function() {
-      return new Promise(function(resolve) {
-        this.async(function() {
-          // Only apply local drafts to comments that haven't been saved
-          // remotely, and haven't been given a default message already.
-          if (!this.comment || this.comment.id || this.comment.message) {
-            resolve(false);
-            return;
-          }
+    _loadLocalDraft: function(changeNum, patchNum, comment) {
+      // Only apply local drafts to comments that haven't been saved
+      // remotely, and haven't been given a default message already.
+      if (!comment || comment.id || comment.message) {
+        return;
+      }
 
-          var draft = this.$.localStorage.getDraft(this.changeNum,
-              this.patchNum, this.comment.path, this.comment.line);
+      var draft = this.$.storage.getDraftComment({
+        changeNum: changeNum,
+        patchNum: patchNum,
+        path: comment.path,
+        line: comment.line,
+      });
 
-          if (draft) {
-            this.comment.message = draft.message;
-            resolve(true);
-            return;
-          }
-
-          resolve(false);
-        }.bind(this));
-      }.bind(this));
+      if (draft) {
+        this.set('comment.message', draft.message);
+      }
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index a333e14..8ef222e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -183,11 +183,11 @@
       MockInteractions.tap(element.$$('.edit'));
       assert.isTrue(element.editing);
 
-      element._editDraft = '';
+      element._messageText = '';
       // Save should be disabled on an empty message.
       var disabled = element.$$('.save').hasAttribute('disabled');
       assert.isTrue(disabled, 'save button should be disabled.');
-      element._editDraft = '     ';
+      element._messageText = '     ';
       disabled = element.$$('.save').hasAttribute('disabled');
       assert.isTrue(disabled, 'save button should be disabled.');
 
@@ -206,7 +206,7 @@
     test('draft saving/editing', function(done) {
       element.draft = true;
       MockInteractions.tap(element.$$('.edit'));
-      element._editDraft = 'good news, everyone!';
+      element._messageText = 'good news, everyone!';
       MockInteractions.tap(element.$$('.save'));
       assert.isTrue(element.disabled,
           'Element should be disabled when creating draft.');
@@ -218,7 +218,7 @@
         assert.isFalse(element.editing);
       }).then(function() {
         MockInteractions.tap(element.$$('.edit'));
-        element._editDraft = 'You’ll be delivering a package to Chapek 9, a ' +
+        element._messageText = 'You’ll be delivering a package to Chapek 9, a ' +
             'world where humans are killed on sight.';
         MockInteractions.tap(element.$$('.save'));
         assert.isTrue(element.disabled,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 68fed9e..0e99a15 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -97,6 +97,7 @@
         color: #666;
       }
       .header {
+        align-items: center;
         display: flex;
         justify-content: space-between;
         margin: 0 var(--default-horizontal-margin) .75em;
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 684120e..affcd44 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -17,6 +17,12 @@
   Polymer({
     is: 'gr-app',
 
+    /**
+     * Fired when the URL location changes.
+     *
+     * @event location-change
+     */
+
     properties: {
       params: Object,
       keyEventTarget: {
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
index e877aec..74bfcdf 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
@@ -15,6 +15,5 @@
 -->
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <dom-module id="gr-storage">
-  <template></template>
   <script src="gr-storage.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index 79012d4..ef3a6c0 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -17,10 +17,14 @@
   // Date cutoff is one day:
   var DRAFT_MAX_AGE = 24*60*60*1000;
 
+  // Clean up old entries no more frequently than one day.
+  var CLEANUP_THROTTLE_INTERVAL = 24*60*60*1000;
+
   Polymer({
     is: 'gr-storage',
 
     properties: {
+      _lastCleanup: Number,
       _storage: {
         type: Object,
         value: function() {
@@ -29,27 +33,34 @@
       },
     },
 
-    getDraft: function(changeNum, patchNum, path, line) {
+    getDraftComment: function(location) {
       this._cleanupDrafts();
-      return this._getObject(
-          this._getDraftKey(changeNum, patchNum, path, line));
+      return this._getObject(this._getDraftKey(location));
     },
 
-    setDraft: function(changeNum, patchNum, path, line, message) {
-      var key = this._getDraftKey(changeNum, patchNum, path, line);
+    setDraftComment: function(location, message) {
+      var key = this._getDraftKey(location);
       this._setObject(key, {message: message, updated: Date.now()});
     },
 
-    eraseDraft: function(changeNum, patchNum, path, line) {
-      var key = this._getDraftKey(changeNum, patchNum, path, line);
+    eraseDraftComment: function(location) {
+      var key = this._getDraftKey(location);
       this._storage.removeItem(key);
     },
 
-    _getDraftKey: function(changeNum, patchNum, path, line) {
-      return ['draft', changeNum, patchNum, path, line].join(':');
+    _getDraftKey: function(location) {
+      return ['draft', location.changeNum, location.patchNum, location.path,
+          location.line].join(':');
     },
 
     _cleanupDrafts: function() {
+      // Throttle cleanup to the throttle interval.
+      if (this._lastCleanup &&
+          Date.now() - this._lastCleanup < CLEANUP_THROTTLE_INTERVAL) {
+        return;
+      }
+      this._lastCleanup = Date.now();
+
       var draft;
       for (var key in this._storage) {
         if (key.indexOf('draft:') === 0) {
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
index d21a6c4..4e17cb6 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -51,23 +51,29 @@
       var patchNum = 5;
       var path = 'my_source_file.js';
       var line = 123;
+      var location = {
+        changeNum: changeNum,
+        patchNum: patchNum,
+        path: path,
+        line: line,
+      };
 
       // The key is in the expected format.
-      var key = element._getDraftKey(changeNum, patchNum, path, line);
+      var key = element._getDraftKey(location);
       assert.equal(key, ['draft', changeNum, patchNum, path, line].join(':'));
 
       // There should be no draft initially.
-      var draft = element.getDraft(changeNum, patchNum, path, line);
+      var draft = element.getDraftComment(location);
       assert.isNotOk(draft);
 
       // Setting the draft stores it under the expected key.
-      element.setDraft(changeNum, patchNum, path, line, 'my comment');
+      element.setDraftComment(location, 'my comment');
       assert.isOk(storage.getItem(key));
       assert.equal(JSON.parse(storage.getItem(key)).message, 'my comment');
       assert.isOk(JSON.parse(storage.getItem(key)).updated);
 
       // Erasing the draft removes the key.
-      element.eraseDraft(changeNum, patchNum, path, line);
+      element.eraseDraftComment(location);
       assert.isNotOk(storage.getItem(key));
 
       cleanupStorage();
@@ -78,7 +84,16 @@
       var patchNum = 5;
       var path = 'my_source_file.js';
       var line = 123;
-      var key = element._getDraftKey(changeNum, patchNum, path, line);
+      var location = {
+        changeNum: changeNum,
+        patchNum: patchNum,
+        path: path,
+        line: line,
+      };
+      var key = element._getDraftKey(location);
+
+      // Make sure that the call to cleanup doesn't get throttled.
+      element._lastCleanup = 0;
 
       var cleanupSpy = sinon.spy(element, '_cleanupDrafts');
 
@@ -89,7 +104,7 @@
       }));
 
       // Getting the draft should cause it to be removed.
-      var draft = element.getDraft(changeNum, patchNum, path, line);
+      var draft = element.getDraftComment(location);
 
       assert.isTrue(cleanupSpy.called);
       assert.isNotOk(draft);