Merge "Remove example mail for announcing a new Gerrit release"
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index d4f4973..84613c2 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -331,7 +331,11 @@
   make -C ReleaseNotes
 ----
 
-* Upload html files to the storage bucket via `https://cloud.google.com/console` (manual via web browser)
+* Upload the html files manually via web browser to the
+link:https://console.developers.google.com/project/164060093628/storage/gerrit-documentation/[
+gerrit-documentation] storage bucket. The `gerrit-documentation`
+storage bucket is accessible via the
+link:https://cloud.google.com/console[Google Developers Console].
 ** Documentation html files must be extracted from `buck-out/gen/Documentation/html.zip`
 * Update Google Code project links
 ** Go to http://code.google.com/p/gerrit/admin
diff --git a/Documentation/images/user-review-ui-change-screen-replying.png b/Documentation/images/user-review-ui-change-screen-replying.png
index 0fd5e80..0ae85ab 100644
--- a/Documentation/images/user-review-ui-change-screen-replying.png
+++ b/Documentation/images/user-review-ui-change-screen-replying.png
Binary files differ
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 63e5499..7a01b99 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1554,6 +1554,79 @@
   }
 ----
 
+[[get-related-changes]]
+=== Get Related Changes
+--
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/related'
+--
+
+Retrieves related changes of a revision.  Related changes are changes that either
+depend on, or are dependencies of the revision.
+
+.Request
+----
+  GET /changes/gerrit~master~I5e4fc08ce34d33c090c9e0bf320de1b17309f774/revisions/b1cb4caa6be46d12b94c25aa68aebabcbb3f53fe/related HTTP/1.0
+----
+
+As result a link:#related-changes-info[RelatedChangesInfo] entity is returned
+describing the related changes.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "changes": [
+      {
+        "change_id": "Ic62ae3103fca2214904dbf2faf4c861b5f0ae9b5",
+        "commit": {
+          "commit": "78847477532e386f5a2185a4e8c90b2509e354e3",
+          "parents": [
+            {
+              "commit": "bb499510bbcdbc9164d96b0dbabb4aa45f59a87e"
+            }
+          ],
+          "author": {
+            "name": "David Ostrovsky",
+            "email": "david@ostrovsky.org",
+            "date": "2014-07-12 15:04:24.000000000",
+            "tz": 120
+          },
+          "subject": "Remove Solr"
+        },
+        "_change_number": 58478,
+        "_revision_number": 2,
+        "_current_revision_number": 2
+      },
+      {
+        "change_id": "I5e4fc08ce34d33c090c9e0bf320de1b17309f774",
+        "commit": {
+          "commit": "b1cb4caa6be46d12b94c25aa68aebabcbb3f53fe",
+          "parents": [
+            {
+              "commit": "d898f12a9b7a92eb37e7a80636195a1b06417aad"
+            }
+          ],
+          "author": {
+            "name": "David Pursehouse",
+            "email": "david.pursehouse@sonymobile.com",
+            "date": "2014-06-24 02:01:28.000000000",
+            "tz": 540
+          },
+          "subject": "Add support for secondary index with Elasticsearch"
+        },
+        "_change_number": 58081,
+        "_revision_number": 10,
+        "_current_revision_number": 10
+      }
+    ]
+  }
+----
+
+
 [[set-review]]
 === Set Review
 --
@@ -2861,6 +2934,37 @@
 Only set on either the last or the first change that is returned.
 |==================================
 
+[[related-changes-info]]
+=== RelatedChangesInfo
+The `RelatedChangesInfo` entity contains information about related
+changes.
+
+[options="header",width="50%",cols="1,6"]
+|===========================
+|Field Name                |Description
+|`changes`                 |A list of
+link:#related-change-and-commit-info[RelatedChangeAndCommitInfo] entities
+describing the related changes. Sorted by git commit order, newest to
+oldest. Empty if there are no related changes.
+|===========================
+
+[[related-change-and-commit-info]]
+=== RelatedChangeAndCommitInfo
+
+The `RelatedChangeAndCommitInfo` entity contains information about
+a related change and commit.
+
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name                ||Description
+|`change_id`               |optional|The Change-Id of the change.
+|`commit`                  ||The commit as a
+link:#commit-info[CommitInfo] entity.
+|`_change_number`          |optional|The change number.
+|`_revision_number`        |optional|The revision number.
+|`_current_revision_number`|optional|The current revision number.
+|===========================
+
 [[change-message-info]]
 === ChangeMessageInfo
 The `ChangeMessageInfo` entity contains information about a message
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 81ceb06..1c3967d 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -602,11 +602,6 @@
 
 The `Post` button publishes the comments and the votes.
 
-The `send email` checkbox controls whether the reply should trigger
-email notifications for other users. Deselecting the checkbox means
-that there will be no email notification about the change update to the
-change author, the reviewers or any other user.
-
 image::images/user-review-ui-change-screen-replying.png[width=800, link="images/user-review-ui-change-screen-replying.png"]
 
 If a user can approve a label that is still required, a quick approve
diff --git a/ReleaseNotes/ReleaseNotes-2.9.txt b/ReleaseNotes/ReleaseNotes-2.9.txt
index 64195a5..ff307b2 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.txt
@@ -30,9 +30,59 @@
   java -jar gerrit.war reindex --recheck-mergeable -d site_path
 ----
 
-*WARNING* Support for query via the SQL index is removed. The usage of
+*WARNING:* Support for query via the SQL index is removed. The usage of
 a secondary index is now mandatory.
 
+*WARNING:* The new change screen only displays download commands if the
+`download-commands` core plugin or any other plugin providing download
+commands is installed. The `download-commands` plugin provides the
+standard download schemes and commands. It is packaged together with
+Gerrit and can be installed, or upgraded, during the
+link:https://gerrit-review.googlesource.com/Documentation/pgm-init.html[
+site initialization]:
+
+.Installing the plugin for the first time
+- Batch init:
++
+By default the batch init does *not* install any core plugin. To
+install the `download-commands` plugin during batch init, specify the
+'--install-plugin download-commands' option:
++
+----
+  $ java -jar gerrit-2.9.war init -d site --batch --install-plugin download-commands
+----
+
+- Interactive init:
++
+There is a question whether the `download-commands` plugin should be
+installed. To install the plugin the question must be answered with `y`:
++
+----
+  Install plugin download-commands version v2.9 [y/N]? y
+----
+
+.Upgrading the plugin
+Pay attention that the `download-commands` plugin from Gerrit 2.8 is
+*not* compatible with Gerrit 2.9 and must be upgraded:
+
+- Batch init:
++
+With the batch init it is *not* possible to upgrade core plugins.
+
+- Interactive init:
++
+The interactive init asks whether the plugin should be upgraded:
++
+----
+  Install plugin download-commands version v2.9 [y/N]? y
+  version v2.8.6.1 is already installed, overwrite it [y/N]? y
+----
+
+- Manual upgrade:
++
+The plugin can be upgraded manually by copying the new plugin jar into
+the site's `plugins` folder.
+
 *WARNING:* Upgrading to 2.9.x requires the server be first upgraded to 2.1.7 (or
 a later 2.1.x version), and then to 2.9.x.  If you are upgrading from 2.2.x.x or
 later, you may ignore this warning and upgrade directly to 2.9.x.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 9653ffa..e18ac17 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -17,6 +17,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -145,10 +146,67 @@
     assertEquals(2, orig.get().messages.size());
 
     assertTrue(cherry.get().subject.contains(in.message));
-    cherry.current()
-        .review(ReviewInput.approve());
-    cherry.current()
-        .submit();
+    cherry.current().review(ReviewInput.approve());
+    cherry.current().submit();
+  }
+
+  @Test
+  public void cherryPickIdenticalTree() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    CherryPickInput in = new CherryPickInput();
+    in.destination = "foo";
+    in.message = "it goes to stable branch";
+    gApi.projects()
+        .name(project.get())
+        .branch(in.destination)
+        .create(new BranchInput());
+    ChangeApi orig = gApi.changes()
+        .id("p~master~" + r.getChangeId());
+
+    assertEquals(1, orig.get().messages.size());
+    ChangeApi cherry = orig.revision(r.getCommit().name())
+        .cherryPick(in);
+    assertEquals(2, orig.get().messages.size());
+
+    assertTrue(cherry.get().subject.contains(in.message));
+    cherry.current().review(ReviewInput.approve());
+    cherry.current().submit();
+
+    try {
+      orig.revision(r.getCommit().name()).cherryPick(in);
+      fail("Cherry-pick identical tree error expected");
+    } catch (RestApiException e) {
+      assertEquals("Cherry pick failed: identical tree", e.getMessage());
+    }
+  }
+
+  @Test
+  public void cherryPickConflict() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    CherryPickInput in = new CherryPickInput();
+    in.destination = "foo";
+    in.message = "it goes to stable branch";
+    gApi.projects()
+        .name(project.get())
+        .branch(in.destination)
+        .create(new BranchInput());
+
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+            PushOneCommit.FILE_NAME, "another content");
+    push.to(git, "refs/heads/foo");
+
+    ChangeApi orig = gApi.changes().id("p~master~" + r.getChangeId());
+    assertEquals(1, orig.get().messages.size());
+
+    try {
+      orig.revision(r.getCommit().name()).cherryPick(in);
+      fail("Cherry-pick merge conflict error expected");
+    } catch (RestApiException e) {
+      assertEquals("Cherry pick failed: merge conflict", e.getMessage());
+    }
   }
 
   @Test
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index d689c2d..5b96ba0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -336,7 +336,15 @@
     } else if (token.startsWith("/")) {
       token = token.substring(1);
     }
-    return selfRedirect("/login/" + token);
+
+    UrlBuilder builder = new UrlBuilder();
+    builder.setProtocol(Location.getProtocol());
+    builder.setHost(Location.getHost());
+    String port = Location.getPort();
+    if (port != null && !port.isEmpty()) {
+      builder.setPort(Integer.parseInt(port));
+    }
+    return builder.buildString() + ("/login/" + URL.encodePathSegment("#/" + token));
   }
 
   public static String selfRedirect(String suffix) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index 5b8135c..ddd650e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -99,7 +99,6 @@
   @UiField Element labelsParent;
   @UiField Grid labelsTable;
   @UiField Button post;
-  @UiField CheckBox email;
   @UiField Button cancel;
   @UiField ScrollPanel commentsPanel;
   @UiField FlowPanel comments;
@@ -188,15 +187,6 @@
     }
   }
 
-  @UiHandler("email")
-  void onEmail(ValueChangeEvent<Boolean> e) {
-    if (e.getValue()) {
-      in.notify(ReviewInput.NotifyHandling.ALL);
-    } else {
-      in.notify(ReviewInput.NotifyHandling.NONE);
-    }
-  }
-
   @UiHandler("post")
   void onPost(ClickEvent e) {
     postReview();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
index 1b122c5..3c927fc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
@@ -68,7 +68,7 @@
           styleName='{res.style.button}'>
         <ui:attribute name='title'/>
         <div>Post</div>
-      </g:Button> and <g:CheckBox ui:field='email' value='true'>send email</g:CheckBox></ui:msg>
+      </g:Button></ui:msg>
 
       <g:Button ui:field='cancel'
           title='Close reply form (Shortcut: Esc)'
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
index 1be4e44..044c18c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/LoginUrlToken.java
@@ -14,20 +14,22 @@
 
 package com.google.gerrit.httpd;
 
+import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.restapi.Url;
 
 import javax.servlet.http.HttpServletRequest;
 
 public class LoginUrlToken {
+  private static final String DEFAULT_TOKEN = '#' + PageLinks.MINE;
 
-  public static String getToken(HttpServletRequest req) {
-    String token = req.getPathInfo();
-    if (Strings.isNullOrEmpty(token)) {
-      token = PageLinks.MINE;
-    } else if (!token.startsWith("/")) {
-      token = "/" + token;
+  public static String getToken(final HttpServletRequest req){
+    String encodedToken = req.getPathInfo();
+    if (Strings.isNullOrEmpty(encodedToken)) {
+      return DEFAULT_TOKEN;
+    } else {
+      return CharMatcher.is('/').trimLeadingFrom(Url.decode(encodedToken));
     }
-    return token;
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 42f0aaf..c18a3b7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.httpd.HtmlDomUtil;
+import com.google.gerrit.httpd.LoginUrlToken;
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.httpd.template.SiteHeaderFooter;
 import com.google.gerrit.reviewdb.client.Account;
@@ -132,11 +133,12 @@
         }
         rdr.append("gwt.codesvr=").append(req.getParameter("gwt.codesvr"));
       }
-      rdr.append('#');
+
       if (res.isNew()) {
-        rdr.append(PageLinks.REGISTER);
+        rdr.append('#' + PageLinks.REGISTER);
+      } else {
+        rdr.append(LoginUrlToken.getToken(req));
       }
-      rdr.append(PageLinks.MINE);
       rsp.sendRedirect(rdr.toString());
 
     } else {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index ffa2dddc..734addb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -80,10 +80,6 @@
   protected void doGet(final HttpServletRequest req,
       final HttpServletResponse rsp) throws ServletException, IOException {
     final String token = LoginUrlToken.getToken(req);
-    if ("/logout".equals(token) || "/signout".equals(token)) {
-      req.getRequestDispatcher("/logout").forward(req, rsp);
-      return;
-    }
 
     CacheHeaders.setNotCacheable(rsp);
     final String user = authFilter.getRemoteUser(req);
@@ -131,9 +127,8 @@
       rdr.append(authConfig.getRegisterPageUrl());
     } else {
       rdr.append(urlProvider.get(req));
-      rdr.append('#');
       if (arsp.isNew() && !token.startsWith(PageLinks.REGISTER + "/")) {
-        rdr.append(PageLinks.REGISTER);
+        rdr.append('#' + PageLinks.REGISTER);
       }
       rdr.append(token);
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java
index 5542d15..8652ef0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertLoginServlet.java
@@ -53,7 +53,6 @@
       final HttpServletResponse rsp) throws IOException {
     final StringBuilder rdr = new StringBuilder();
     rdr.append(urlProvider.get());
-    rdr.append('#');
     rdr.append(LoginUrlToken.getToken(req));
 
     CacheHeaders.setNotCacheable(rsp);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
index 2f3ffba..2deb2eb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
@@ -73,10 +73,7 @@
       @Nullable String errorMessage) throws IOException {
     String self = req.getRequestURI();
     String cancel = Objects.firstNonNull(urlProvider.get(req), "/");
-    String token = LoginUrlToken.getToken(req);
-    if (!token.equals("/")) {
-      cancel += "#" + token;
-    }
+    cancel += LoginUrlToken.getToken(req);
 
     Document doc = headers.parse(LdapLoginServlet.class, "LoginForm.html");
     HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName());
@@ -144,7 +141,6 @@
 
     StringBuilder dest = new StringBuilder();
     dest.append(urlProvider.get(req));
-    dest.append('#');
     dest.append(LoginUrlToken.getToken(req));
 
     CacheHeaders.setNotCacheable(res);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 2902335..147fe04 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -387,7 +387,7 @@
         // Allow anonymous users a chance to login.
         // Avoid leaking information by not distinguishing between
         // project not existing and no access rights.
-        rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        rsp.sendRedirect(getLoginRedirectUrl(req));
       }
       return;
     }
@@ -408,6 +408,16 @@
     }
   }
 
+  private static String getLoginRedirectUrl(HttpServletRequest req) {
+    String loginUrl = req.getContextPath() + "/login/";
+    String token = req.getRequestURI().substring(1);
+    String queryString = req.getQueryString();
+    if (queryString != null && !queryString.isEmpty()) {
+      token = token.concat("?" + queryString);
+    }
+    return (loginUrl + Url.encode(token));
+  }
+
   private static Map<String, String> getParameters(HttpServletRequest req) {
     final Map<String, String> params = new HashMap<>();
     for (final String pair : req.getQueryString().split("[&;]")) {
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index 5276828..92ee908 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -221,10 +221,7 @@
       boolean link, @Nullable String errorMessage) throws IOException {
     String self = req.getRequestURI();
     String cancel = Objects.firstNonNull(urlProvider != null ? urlProvider.get() : "/", "/");
-    String token = LoginUrlToken.getToken(req);
-    if (!token.equals("/")) {
-      cancel += "#" + token;
-    }
+    cancel += LoginUrlToken.getToken(req);
 
     Document doc = header.parse(LoginForm.class, "LoginForm.html");
     HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName());
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 5c77ae9..1681bc2 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.auth.openid.OpenIdUrls;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.httpd.CanonicalWebUrl;
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.reviewdb.client.Account;
@@ -483,11 +484,10 @@
 
     final StringBuilder rdr = new StringBuilder();
     rdr.append(urlProvider.get(req));
-    rdr.append('#');
     if (isNew && !token.startsWith(PageLinks.REGISTER + "/")) {
-      rdr.append(PageLinks.REGISTER);
+      rdr.append('#' + PageLinks.REGISTER);
     }
-    rdr.append(token);
+    rdr.append(Url.decode(token));
     rsp.sendRedirect(rdr.toString());
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
index 559c168..d0a89e7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
@@ -170,6 +170,24 @@
     return nv;
   }
 
+  public String passwordForKey(String key, String password) {
+    String ov = getSecure(password);
+    if (ov != null) {
+      // If the password is already stored, try to reuse it
+      // rather than prompting for a whole new one.
+      //
+      if (ui.isBatch() || !ui.yesno(false, "Change %s", key)) {
+        return ov;
+      }
+    }
+
+    final String nv = ui.password("%s", key);
+    if (!eq(ov, nv)) {
+      setSecure(password, nv);
+    }
+    return nv;
+  }
+
   public String getSecure(String name) {
     return flags.sec.getString(section, subsection, name);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index d034a10..889ae1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -91,7 +91,7 @@
       return json.format(cherryPickedChangeId);
     } catch (InvalidChangeOperationException e) {
       throw new BadRequestException(e.getMessage());
-    } catch (MergeException  e) {
+    } catch (MergeException e) {
       throw new ResourceConflictException(e.getMessage());
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index b1f6d8c..158f569 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -27,7 +27,9 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeConflictException;
 import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
@@ -153,14 +155,12 @@
           cherryPickCommit =
               mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
                   commitToCherryPick, committerIdent, commitMessage, revWalk);
+        } catch (MergeIdenticalTreeException | MergeConflictException e) {
+          throw new MergeException("Cherry pick failed: " + e.getMessage());
         } finally {
           oi.release();
         }
 
-        if (cherryPickCommit == null) {
-          throw new MergeException("Cherry pick failed");
-        }
-
         Change.Key changeKey;
         final List<String> idList = cherryPickCommit.getFooterLines(CHANGE_ID);
         if (!idList.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
new file mode 100644
index 0000000..02bc8dc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeConflictException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+/** Indicates that the commit cannot be merged without conflicts. */
+public class MergeConflictException extends Exception {
+  private static final long serialVersionUID = 1L;
+  public MergeConflictException(String msg) {
+    super(msg, null);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
new file mode 100644
index 0000000..109fa76
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIdenticalTreeException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+/** Indicates that the commit is already contained in destination banch. */
+public class MergeIdenticalTreeException extends Exception {
+  private static final long serialVersionUID = 1L;
+  public MergeIdenticalTreeException(String msg) {
+    super(msg, null);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index b4a1f0c..74afa60 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -173,7 +173,8 @@
   public RevCommit createCherryPickFromCommit(Repository repo,
       ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
       PersonIdent cherryPickCommitterIdent, String commitMsg, RevWalk rw)
-      throws MissingObjectException, IncorrectObjectTypeException, IOException {
+      throws MissingObjectException, IncorrectObjectTypeException, IOException,
+      MergeIdenticalTreeException, MergeConflictException {
 
     final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
 
@@ -181,7 +182,7 @@
     if (m.merge(mergeTip, originalCommit)) {
       ObjectId tree = m.getResultTreeId();
       if (tree.equals(mergeTip.getTree())) {
-        return null;
+        throw new MergeIdenticalTreeException("identical tree");
       }
 
       CommitBuilder mergeCommit = new CommitBuilder();
@@ -192,7 +193,7 @@
       mergeCommit.setMessage(commitMsg);
       return rw.parseCommit(commit(inserter, mergeCommit));
     } else {
-      return null;
+      throw new MergeConflictException("merge conflict");
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 08421ca..8568b1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -26,7 +26,9 @@
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeConflictException;
 import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.util.TimeUtil;
@@ -84,15 +86,16 @@
           // taking the delta relative to that one parent and redoing
           // that on the current merge tip.
           //
-
-          mergeTip = writeCherryPickCommit(mergeTip, n);
-
-          if (mergeTip != null) {
+          try {
+            mergeTip = writeCherryPickCommit(mergeTip, n);
             newCommits.put(mergeTip.getPatchsetId().getParentKey(), mergeTip);
-          } else {
+          } catch (MergeConflictException mce) {
             n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
+            mergeTip = null;
+          } catch (MergeIdenticalTreeException mie) {
+            n.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
+            mergeTip = null;
           }
-
         } else {
           // There are multiple parents, so this is a merge commit. We
           // don't want to cherry-pick the merge as clients can't easily
@@ -131,7 +134,8 @@
 
   private CodeReviewCommit writeCherryPickCommit(CodeReviewCommit mergeTip,
       CodeReviewCommit n) throws IOException, OrmException,
-      NoSuchChangeException {
+      NoSuchChangeException, MergeConflictException,
+      MergeIdenticalTreeException {
 
     args.rw.parseBody(n);
 
@@ -156,10 +160,6 @@
             args.inserter, mergeTip, n, cherryPickCommitterIdent,
             cherryPickCmtMsg, args.rw);
 
-    if (newCommit == null) {
-        return null;
-    }
-
     PatchSet.Id id =
         ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
     final PatchSet ps = new PatchSet(id);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index aa1ff47..0d637f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.notedb;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
 
 import com.google.gerrit.reviewdb.client.Account;
@@ -70,6 +71,12 @@
     return (IdentifiedUser) ctl.getCurrentUser();
   }
 
+  public void setPatchSetId(PatchSet.Id psId) {
+    checkArgument(psId == null
+        || psId.getParentKey().equals(getChange().getId()));
+    this.psId = psId;
+  }
+
   private void load() throws IOException {
     if (migration.write() && getRevision() == null) {
       Repository repo = repoManager.openRepository(getProjectName());
@@ -130,6 +137,11 @@
   }
 
   @Override
+  public RevCommit commit(MetaDataUpdate md) throws IOException {
+    throw new UnsupportedOperationException("use commit()");
+  }
+
+  @Override
   protected void onLoad() throws IOException, ConfigInvalidException {
     //Do nothing; just reads the current revision.
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 7da564c..b242d7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet.Id;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
@@ -102,8 +103,8 @@
       String filename1 = c1.getKey().getParentKey().get();
       String filename2 = c2.getKey().getParentKey().get();
       return ComparisonChain.start()
-          .compare(c1.getLine(), c2.getLine())
           .compare(filename1, filename2)
+          .compare(c1.getLine(), c2.getLine())
           .compare(c1.getWrittenOn(), c2.getWrittenOn())
           .result();
     }
@@ -323,7 +324,7 @@
         throws IOException, ConfigInvalidException, ParseException {
       commentNoteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
           ChangeNoteUtil.changeRefName(changeId), walk, changeId,
-          commentsForBase, commentsForPs);
+          commentsForBase, commentsForPs, Status.PUBLISHED);
     }
 
     private void parseApproval(PatchSet.Id psId, Account.Id accountId,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 7b9ef90..be3a44d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -170,12 +170,6 @@
     this.subject = subject;
   }
 
-  public void setPatchSetId(PatchSet.Id psId) {
-    checkArgument(psId == null
-        || psId.getParentKey().equals(getChange().getId()));
-    this.psId = psId;
-  }
-
   public void setChangeMessage(String changeMessage) {
     this.changeMessage = changeMessage;
   }
@@ -238,11 +232,6 @@
     return noteMap.writeTree(inserter);
   }
 
-  @Override
-  public RevCommit commit(MetaDataUpdate md) throws IOException {
-    throw new UnsupportedOperationException("use commit()");
-  }
-
   public RevCommit commit() throws IOException {
     BatchMetaDataUpdate batch = openUpdate();
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
index ec3f3a8..ede979fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -82,7 +83,8 @@
   public static NoteMap parseCommentsFromNotes(Repository repo, String refName,
       RevWalk walk, Change.Id changeId,
       Multimap<PatchSet.Id, PatchLineComment> commentsForBase,
-      Multimap<PatchSet.Id, PatchLineComment> commentsForPs)
+      Multimap<PatchSet.Id, PatchLineComment> commentsForPs,
+      Status status)
       throws IOException, ConfigInvalidException {
     Ref ref = repo.getRef(refName);
     if (ref == null) {
@@ -94,7 +96,7 @@
     for (Note note: noteMap) {
       byte[] bytes = walk.getObjectReader().open(
           note.getData(), Constants.OBJ_BLOB).getBytes();
-      List<PatchLineComment> result = parseNote(bytes, changeId);
+      List<PatchLineComment> result = parseNote(bytes, changeId, status);
       if ((result == null) || (result.isEmpty())) {
         continue;
       }
@@ -110,7 +112,7 @@
   }
 
   public static List<PatchLineComment> parseNote(byte[] note,
-      Change.Id changeId) throws ConfigInvalidException {
+      Change.Id changeId, Status status) throws ConfigInvalidException {
     List<PatchLineComment> result = Lists.newArrayList();
     int sizeOfNote = note.length;
     Charset enc = RawParseUtils.parseEncoding(note);
@@ -131,7 +133,7 @@
       String previousFileName = c == null ?
           null : c.getKey().getParentKey().getFileName();
       c = parseComment(note, curr, previousFileName, psId, revId,
-          isForBase, enc);
+          isForBase, enc, status);
       result.add(c);
     }
     return result;
@@ -150,7 +152,7 @@
 
   private static PatchLineComment parseComment(byte[] note, MutableInteger curr,
       String currentFileName, PatchSet.Id psId, RevId revId, boolean isForBase,
-      Charset enc)
+      Charset enc, Status status)
           throws ConfigInvalidException {
     Change.Id changeId = psId.getParentKey();
 
@@ -195,6 +197,7 @@
       plc.setRange(range);
     }
     plc.setRevId(revId);
+    plc.setStatus(status);
 
     curr.value = RawParseUtils.nextLF(note, curr.value + commentLength);
     curr.value = RawParseUtils.nextLF(note, curr.value);
@@ -425,14 +428,12 @@
    *            byte array.
    *            All of the comments in this list must have the same side and
    *            must share the same PatchSet.Id.
+   *            This list must not be empty because we cannot build a note
+   *            for no comments.
    * @return the note. Null if there are no comments in the list.
    */
   public byte[] buildNote(List<PatchLineComment> comments)
       throws OrmException, IOException {
-    if (comments.isEmpty()) {
-      return null;
-    }
-
     ByteArrayOutputStream buf = new ByteArrayOutputStream();
     OutputStreamWriter streamWriter = new OutputStreamWriter(buf, UTF_8);
     PrintWriter writer = new PrintWriter(streamWriter);
@@ -517,6 +518,8 @@
   public void writeCommentsToNoteMap(NoteMap noteMap,
       List<PatchLineComment> allComments, ObjectInserter inserter)
         throws OrmException, IOException {
+    checkArgument(!allComments.isEmpty(),
+        "No comments to write; to delete, use removeNoteFromNoteMap().");
     ObjectId commitOID =
         ObjectId.fromString(allComments.get(0).getRevId().get());
     Collections.sort(allComments, ChangeNotes.PatchLineCommentComparator);
@@ -524,4 +527,9 @@
     ObjectId noteId = inserter.insert(Constants.OBJ_BLOB, note, 0, note.length);
     noteMap.set(commitOID, noteId);
   }
+
+  public void removeNote(NoteMap noteMap, RevId commitId)
+      throws IOException {
+    noteMap.remove(ObjectId.fromString(commitId.get()));
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index ed65306..29d2449 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -38,6 +38,7 @@
 import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
@@ -853,8 +854,8 @@
     Timestamp time3 = TimeUtil.nowTs();
     PatchSet.Id psId = c.currentPatchSetId();
 
-    PatchLineComment comment1 = newPatchLineComment(psId, "file1", uuid,
-        range1, range1.getEndLine(), otherUser, null, time1, message1,
+    PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
+        uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
         (short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment1);
@@ -862,8 +863,8 @@
 
     update = newUpdate(c, otherUser);
     CommentRange range2 = new CommentRange(2, 1, 3, 1);
-    PatchLineComment comment2 = newPatchLineComment(psId, "file1", uuid,
-        range2, range2.getEndLine(), otherUser, null, time2, message2,
+    PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
+        uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
         (short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment2);
@@ -871,8 +872,8 @@
 
     update = newUpdate(c, otherUser);
     CommentRange range3 = new CommentRange(3, 1, 4, 1);
-    PatchLineComment comment3 = newPatchLineComment(psId, "file2", uuid,
-        range3, range3.getEndLine(), otherUser, null, time3, message3,
+    PatchLineComment comment3 = newPublishedPatchLineComment(psId, "file2",
+        uuid, range3, range3.getEndLine(), otherUser, null, time3, message3,
         (short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment3);
@@ -931,8 +932,8 @@
     Timestamp time2 = TimeUtil.nowTs();
     PatchSet.Id psId = c.currentPatchSetId();
 
-    PatchLineComment comment1 = newPatchLineComment(psId, "file1", uuid,
-        range1, range1.getEndLine(), otherUser, null, time1, message1,
+    PatchLineComment comment1 = newPublishedPatchLineComment(psId, "file1",
+        uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
         (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment1);
@@ -940,8 +941,8 @@
 
     update = newUpdate(c, otherUser);
     CommentRange range2 = new CommentRange(2, 1, 3, 1);
-    PatchLineComment comment2 = newPatchLineComment(psId, "file1", uuid,
-        range2, range2.getEndLine(), otherUser, null, time2, message2,
+    PatchLineComment comment2 = newPublishedPatchLineComment(psId, "file1",
+        uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
         (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment2);
@@ -993,7 +994,7 @@
     PatchSet.Id psId = c.currentPatchSetId();
 
     PatchLineComment commentForBase =
-        newPatchLineComment(psId, "filename", uuid,
+        newPublishedPatchLineComment(psId, "filename", uuid,
         range, range.getEndLine(), otherUser, null, now, messageForBase,
         (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
@@ -1002,7 +1003,7 @@
 
     update = newUpdate(c, otherUser);
     PatchLineComment commentForPS =
-        newPatchLineComment(psId, "filename", uuid,
+        newPublishedPatchLineComment(psId, "filename", uuid,
         range, range.getEndLine(), otherUser, null, now, messageForPS,
         (short) 1, "abcd4567abcd4567abcd4567abcd4567abcd4567");
     update.setPatchSetId(psId);
@@ -1035,17 +1036,17 @@
     ChangeUpdate update = newUpdate(c, otherUser);
     Timestamp timeForComment1 = TimeUtil.nowTs();
     Timestamp timeForComment2 = TimeUtil.nowTs();
-    PatchLineComment comment1 = newPatchLineComment(psId, filename, uuid, range,
-        range.getEndLine(), otherUser, null, timeForComment1, "comment 1", side,
-        "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    PatchLineComment comment1 = newPublishedPatchLineComment(psId, filename,
+        uuid, range, range.getEndLine(), otherUser, null, timeForComment1,
+        "comment 1", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment1);
     update.commit();
 
     update = newUpdate(c, otherUser);
-    PatchLineComment comment2 = newPatchLineComment(psId, filename, uuid, range,
-        range.getEndLine(), otherUser, null, timeForComment2, "comment 2", side,
-        "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    PatchLineComment comment2 = newPublishedPatchLineComment(psId, filename,
+        uuid, range, range.getEndLine(), otherUser, null, timeForComment2,
+        "comment 2", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment2);
     update.commit();
@@ -1081,17 +1082,17 @@
 
     ChangeUpdate update = newUpdate(c, otherUser);
     Timestamp now = TimeUtil.nowTs();
-    PatchLineComment comment1 = newPatchLineComment(psId, filename1, uuid,
-        range, range.getEndLine(), otherUser, null, now, "comment 1", side,
-        "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    PatchLineComment comment1 = newPublishedPatchLineComment(psId, filename1,
+        uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
+        side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment1);
     update.commit();
 
     update = newUpdate(c, otherUser);
-    PatchLineComment comment2 = newPatchLineComment(psId, filename2, uuid,
-        range, range.getEndLine(), otherUser, null, now, "comment 2", side,
-        "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    PatchLineComment comment2 = newPublishedPatchLineComment(psId, filename2,
+        uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
+        side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(psId);
     update.putComment(comment2);
     update.commit();
@@ -1125,9 +1126,9 @@
 
     ChangeUpdate update = newUpdate(c, otherUser);
     Timestamp now = TimeUtil.nowTs();
-    PatchLineComment comment1 = newPatchLineComment(ps1, filename, uuid,
-        range, range.getEndLine(), otherUser, null, now, "comment on ps1", side,
-        "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    PatchLineComment comment1 = newPublishedPatchLineComment(ps1, filename,
+        uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
+        side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
     update.setPatchSetId(ps1);
     update.putComment(comment1);
     update.commit();
@@ -1137,9 +1138,9 @@
 
     update = newUpdate(c, otherUser);
     now = TimeUtil.nowTs();
-    PatchLineComment comment2 = newPatchLineComment(ps2, filename, uuid,
-        range, range.getEndLine(), otherUser, null, now, "comment on ps2", side,
-        "abcd4567abcd4567abcd4567abcd4567abcd4567");
+    PatchLineComment comment2 = newPublishedPatchLineComment(ps2, filename,
+        uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
+        side, "abcd4567abcd4567abcd4567abcd4567abcd4567");
     update.setPatchSetId(ps2);
     update.putComment(comment2);
     update.commit();
@@ -1168,10 +1169,18 @@
     return TestChanges.newChange(project, changeOwner);
   }
 
-  private PatchLineComment newPatchLineComment(PatchSet.Id psId,
+  private PatchLineComment newPublishedPatchLineComment(PatchSet.Id psId,
       String filename, String UUID, CommentRange range, int line,
       IdentifiedUser commenter, String parentUUID, Timestamp t,
       String message, short side, String commitSHA1) {
+    return newPatchLineComment(psId, filename, UUID, range, line, commenter,
+        parentUUID, t, message, side, commitSHA1, Status.PUBLISHED);
+  }
+
+  private PatchLineComment newPatchLineComment(PatchSet.Id psId,
+      String filename, String UUID, CommentRange range, int line,
+      IdentifiedUser commenter, String parentUUID, Timestamp t,
+      String message, short side, String commitSHA1, Status status) {
     PatchLineComment comment = new PatchLineComment(
         new PatchLineComment.Key(
             new Patch.Key(psId, filename), UUID),
@@ -1180,6 +1189,7 @@
     comment.setMessage(message);
     comment.setRange(range);
     comment.setRevId(new RevId(commitSHA1));
+    comment.setStatus(status);
     return comment;
   }
 
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 5088424..83d75e9 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
@@ -38,7 +38,6 @@
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.apache.sshd.server.Environment;
 import org.kohsuke.args4j.Argument;
@@ -95,31 +94,31 @@
   private CreateEmail.Factory createEmailFactory;
 
   @Inject
-  private Provider<GetEmails> getEmailsProvider;
+  private GetEmails getEmails;
 
   @Inject
-  private Provider<DeleteEmail> deleteEmailProvider;
+  private DeleteEmail deleteEmail;
 
   @Inject
-  private Provider<PutName> putNameProvider;
+  private PutName putName;
 
   @Inject
-  private Provider<PutHttpPassword> putHttpPasswordProvider;
+  private PutHttpPassword putHttpPassword;
 
   @Inject
-  private Provider<PutActive> putActiveProvider;
+  private PutActive putActive;
 
   @Inject
-  private Provider<DeleteActive> deleteActiveProvider;
+  private DeleteActive deleteActive;
 
   @Inject
-  private Provider<AddSshKey> addSshKeyProvider;
+  private AddSshKey addSshKey;
 
   @Inject
-  private Provider<GetSshKeys> getSshKeysProvider;
+  private GetSshKeys getSshKeys;
 
   @Inject
-  private Provider<DeleteSshKey> deleteSshKeyProvider;
+  private DeleteSshKey deleteSshKey;
 
   private IdentifiedUser user;
   private AccountResource rsrc;
@@ -174,20 +173,20 @@
       if (fullName != null) {
         PutName.Input in = new PutName.Input();
         in.name = fullName;
-        putNameProvider.get().apply(rsrc, in);
+        putName.apply(rsrc, in);
       }
 
       if (httpPassword != null) {
         PutHttpPassword.Input in = new PutHttpPassword.Input();
         in.httpPassword = httpPassword;
-        putHttpPasswordProvider.get().apply(rsrc, in);
+        putHttpPassword.apply(rsrc, in);
       }
 
       if (active) {
-        putActiveProvider.get().apply(rsrc, null);
+        putActive.apply(rsrc, null);
       } else if (inactive) {
         try {
-          deleteActiveProvider.get().apply(rsrc, null);
+          deleteActive.apply(rsrc, null);
         } catch (ResourceNotFoundException e) {
           // user is already inactive
         }
@@ -227,13 +226,13 @@
           return sshKey.length();
         }
       };
-      addSshKeyProvider.get().apply(rsrc, in);
+      addSshKey.apply(rsrc, in);
     }
   }
 
   private void deleteSshKeys(List<String> sshKeys) throws RestApiException,
       OrmException {
-    List<SshKeyInfo> infos = getSshKeysProvider.get().apply(rsrc);
+    List<SshKeyInfo> infos = getSshKeys.apply(rsrc);
     if (sshKeys.contains("ALL")) {
       for (SshKeyInfo i : infos) {
         deleteSshKey(i);
@@ -253,7 +252,7 @@
   private void deleteSshKey(SshKeyInfo i) throws OrmException {
     AccountSshKey sshKey = new AccountSshKey(
         new AccountSshKey.Id(user.getAccountId(), i.seq), i.sshPublicKey);
-    deleteSshKeyProvider.get().apply(
+    deleteSshKey.apply(
         new AccountResource.SshKey(user, sshKey), null);
   }
 
@@ -272,14 +271,13 @@
   private void deleteEmail(String email) throws UnloggedFailure,
       RestApiException, OrmException {
     if (email.equals("ALL")) {
-      List<EmailInfo> emails = getEmailsProvider.get().apply(rsrc);
-      DeleteEmail deleteEmail = deleteEmailProvider.get();
+      List<EmailInfo> emails = getEmails.apply(rsrc);;
       for (EmailInfo e : emails) {
         deleteEmail.apply(new AccountResource.Email(user, e.email),
             new DeleteEmail.Input());
       }
     } else {
-      deleteEmailProvider.get().apply(new AccountResource.Email(user, email),
+      deleteEmail.apply(new AccountResource.Email(user, email),
           new DeleteEmail.Input());
     }
   }