Merge "Better email wording when updating changes the user does not own"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6a78d9f..b0e64a8 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5152,6 +5152,8 @@
 Number of inserted lines.
 |`deletions`          ||
 Number of deleted lines.
+|`unresolved_comment_count`  |optional|
+Number of unresolved comments. Not set if the current change index doesn't have the data.
 |`_number`            ||The legacy numeric ID of the change.
 |`owner`              ||
 The owner of the change as an link:rest-api-accounts.html#account-info[
diff --git a/Documentation/user-search-groups.txt b/Documentation/user-search-groups.txt
index 3d8c51c..fccad65 100644
--- a/Documentation/user-search-groups.txt
+++ b/Documentation/user-search-groups.txt
@@ -49,9 +49,10 @@
 Matches groups that have the name 'NAME' (case-insensitive).
 
 [[owner]]
-owner:'UUID'::
+owner:'OWNER'::
 +
-Matches groups that are owned by a group that has the UUID 'UUID'.
+Matches groups that are owned by the group whose name best matches
+'OWNER' or that has the UUID 'OWNER'.
 
 [[uuid]]
 uuid:'UUID'::
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index f1f1654..2b5702e 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -278,6 +278,10 @@
 +
 True if the change has inline edit created by the current user.
 
+has:unresolved::
++
+True if the change has unresolved comments.
+
 [[is]]
 [[is-starred]]
 is:starred::
@@ -417,6 +421,16 @@
 only applies to the top-level status; individual label statuses can be
 searched link:#labels[by label].
 
+[[unresolved]]
+unresolved:'RELATION''NUMBER'::
++
+True if the number of unresolved comments satisfies the given relation for the given number.
++
+For example, unresolved:>0 will be true for any change which has at least one unresolved
+comment while unresolved:0 will be true for any change which has all comments resolved.
++
+Valid relations are >=, >, <=, <, or no relation, which will match if the number of unresolved
+comments is exactly equal.
 
 == Argument Quoting
 
diff --git a/WORKSPACE b/WORKSPACE
index 986b4c2..1ffe850 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -696,10 +696,18 @@
     sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0",
 )
 
+TRUTH_VERS = "0.31"
+
 maven_jar(
     name = "truth",
-    artifact = "com.google.truth:truth:0.30",
-    sha1 = "9d591b5a66eda81f0b88cf1c748ab8853d99b18b",
+    artifact = "com.google.truth:truth:" + TRUTH_VERS,
+    sha1 = "1a926b0cb2879fd32efbb3716ee8bab040f4218b",
+)
+
+maven_jar(
+    name = "truth-java8-extension",
+    artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
+    sha1 = "a7e80e631f2bf4ecc2b99ad1e33059eb0dcc6ea0",
 )
 
 maven_jar(
@@ -1098,8 +1106,8 @@
 bower_archive(
     name = "web-component-tester",
     package = "web-component-tester",
-    sha1 = "54556000c33d9ed7949aa546c1b4a1531491a5f0",
-    version = "4.2.2",
+    sha1 = "a4a9bc7815a22d143e8f8593e37b3c2028b8c20f",
+    version = "5.0.0",
 )
 
 # Bower component transitive dependencies.
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index 69f132b..db5a300 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -41,6 +41,7 @@
         "//gerrit-server:testutil",
         "//gerrit-server/src/main/prolog:common",
         "//lib:truth",
+        "//lib:truth-java8-extension",
         "//lib/auto:auto-value",
         "//lib/httpcomponents:fluent-hc",
         "//lib/httpcomponents:httpclient",
diff --git a/gerrit-acceptance-tests/BUILD b/gerrit-acceptance-tests/BUILD
index e7a6222..3154b1f 100644
--- a/gerrit-acceptance-tests/BUILD
+++ b/gerrit-acceptance-tests/BUILD
@@ -1,5 +1,6 @@
 java_library(
     name = "lib",
+    srcs = ["src/test/java/com/google/gerrit/acceptance/Dummy.java"],
     testonly = 1,
     visibility = ["//visibility:public"],
     exports = [
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/Dummy.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/Dummy.java
new file mode 100644
index 0000000..fb4783b
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/Dummy.java
@@ -0,0 +1,17 @@
+// Copyright (C) 2017 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.acceptance;
+
+public class Dummy {}
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 998abbf..df82e21 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
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance.api.change;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
@@ -31,9 +32,11 @@
 import static com.google.gerrit.server.project.Util.value;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
 import static org.junit.Assert.fail;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -57,6 +60,7 @@
 import com.google.gerrit.extensions.api.changes.RebaseInput;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.client.ChangeKind;
@@ -114,6 +118,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Constants;
@@ -945,17 +951,41 @@
   }
 
   @Test
-  public void implicitlyCcOnNonVotingReview() throws Exception {
+  public void implicitlyCcOnNonVotingReviewPgStyle() throws Exception {
     PushOneCommit.Result r = createChange();
     setApiUser(user);
-    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(new ReviewInput());
+    assertThat(getReviewerState(r.getChangeId(), user.id)).isEmpty();
 
-    ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    // If we're not reading from NoteDb, then the CCed user will be returned
-    // in the REVIEWER state.
-    ReviewerState state = notesMigration.readChanges() ? CC : REVIEWER;
-    assertThat(c.reviewers.get(state).stream().map(ai -> ai._accountId).collect(toList()))
-        .containsExactly(user.id.get());
+    // Exact request format made by PG UI at ddc6b7160fe416fed9e7e3180489d44c82fd64f8.
+    ReviewInput in = new ReviewInput();
+    in.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
+    in.labels = ImmutableMap.of();
+    in.message = "comment";
+    in.reviewers = ImmutableList.of();
+    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
+
+    // If we're not reading from NoteDb, then the CCed user will be returned in the REVIEWER state.
+    assertThat(getReviewerState(r.getChangeId(), user.id))
+        .hasValue(notesMigration.readChanges() ? CC : REVIEWER);
+  }
+
+  @Test
+  public void implicitlyCcOnNonVotingReviewGwtStyle() throws Exception {
+    PushOneCommit.Result r = createChange();
+    setApiUser(user);
+    assertThat(getReviewerState(r.getChangeId(), user.id)).isEmpty();
+
+    // Exact request format made by GWT UI at ddc6b7160fe416fed9e7e3180489d44c82fd64f8.
+    ReviewInput in = new ReviewInput();
+    in.labels = ImmutableMap.of("Code-Review", (short) 0);
+    in.strictLabels = true;
+    in.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
+    in.message = "comment";
+    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
+
+    // If we're not reading from NoteDb, then the CCed user will be returned in the REVIEWER state.
+    assertThat(getReviewerState(r.getChangeId(), user.id))
+        .hasValue(notesMigration.readChanges() ? CC : REVIEWER);
   }
 
   @Test
@@ -2290,6 +2320,20 @@
     return changeResourceFactory.create(ctls.get(0));
   }
 
+  private Optional<ReviewerState> getReviewerState(String changeId, Account.Id accountId)
+      throws Exception {
+    ChangeInfo c = gApi.changes().id(changeId).get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
+    Set<ReviewerState> states =
+        c.reviewers
+            .entrySet()
+            .stream()
+            .filter(e -> e.getValue().stream().anyMatch(a -> a._accountId == accountId.get()))
+            .map(e -> e.getKey())
+            .collect(toSet());
+    assertThat(states.size()).named(states.toString()).isAtMost(1);
+    return states.stream().findFirst();
+  }
+
   private void setChangeStatus(Change.Id id, Change.Status newStatus) throws Exception {
     try (BatchUpdate batchUpdate =
         updateFactory.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index 283f14b..faa21cf 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -17,14 +17,17 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
+import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static com.google.gerrit.extensions.common.RobotCommentInfoSubject.assertThatList;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
 import com.google.gerrit.extensions.client.Comment;
+import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.FixReplacementInfo;
 import com.google.gerrit.extensions.common.FixSuggestionInfo;
 import com.google.gerrit.extensions.common.RobotCommentInfo;
@@ -364,6 +367,31 @@
     gApi.changes().id(changeId).current().review(reviewInput);
   }
 
+  @Test
+  public void queryChangesWithUnresolvedCommentCount() throws Exception {
+    assume().that(notesMigration.enabled()).isTrue();
+
+    PushOneCommit.Result r1 = createChange();
+    PushOneCommit.Result r2 =
+        pushFactory
+            .create(
+                db, admin.getIdent(), testRepo, SUBJECT, FILE_NAME, "new content", r1.getChangeId())
+            .to("refs/for/master");
+
+    addRobotComment(r2.getChangeId(), createRobotCommentInputWithMandatoryFields());
+
+    AcceptanceTestRequestScope.Context ctx = disableDb();
+    try {
+      ChangeInfo result = Iterables.getOnlyElement(query(r2.getChangeId()));
+      // currently, we create all robot comments as 'resolved' by default.
+      // if we allow users to resolve a robot comment, then this test should
+      // be modified.
+      assertThat(result.unresolvedCommentCount).isEqualTo(0);
+    } finally {
+      enableDb(ctx);
+    }
+  }
+
   private RobotCommentInput createRobotCommentInputWithMandatoryFields() {
     RobotCommentInput in = new RobotCommentInput();
     in.robotId = "happyRobot";
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 72146ab..a4b2209 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -866,6 +866,16 @@
 
   @Test
   public void pushAFewChanges() throws Exception {
+    testPushAFewChanges();
+  }
+
+  @Test
+  public void pushAFewChangesWithCreateNewChangeForAllNotInTarget() throws Exception {
+    enableCreateNewChangeForAllNotInTarget();
+    testPushAFewChanges();
+  }
+
+  private void testPushAFewChanges() throws Exception {
     int n = 10;
     String r = "refs/for/master";
     ObjectId initialHead = testRepo.getRepository().resolve("HEAD");
@@ -991,11 +1001,75 @@
     pushForReviewRejected(testRepo, "invalid Change-Id line format in commit message footer");
   }
 
+  @Test
+  public void pushCommitWithSameChangeIdAsPredecessorChange() throws Exception {
+    PushOneCommit push =
+        pushFactory.create(
+            db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "a.txt", "content");
+    PushOneCommit.Result r = push.to("refs/for/master");
+    r.assertOkStatus();
+    RevCommit commitChange1 = r.getCommit();
+
+    createCommit(testRepo, commitChange1.getFullMessage());
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+
+    ProjectConfig config = projectCache.checkedGet(project).getConfig();
+    config.getProject().setRequireChangeID(InheritableBoolean.FALSE);
+    saveProjectConfig(project, config);
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+  }
+
+  @Test
+  public void pushTwoCommitWithSameChangeId() throws Exception {
+    RevCommit commitChange1 = createCommitWithChangeId(testRepo, "some change");
+
+    createCommit(testRepo, commitChange1.getFullMessage());
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+
+    ProjectConfig config = projectCache.checkedGet(project).getConfig();
+    config.getProject().setRequireChangeID(InheritableBoolean.FALSE);
+    saveProjectConfig(project, config);
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+  }
+
   private static RevCommit createCommit(TestRepository<?> testRepo, String message)
       throws Exception {
     return testRepo.branch("HEAD").commit().message(message).add("a.txt", "content").create();
   }
 
+  private static RevCommit createCommitWithChangeId(TestRepository<?> testRepo, String message)
+      throws Exception {
+    RevCommit c =
+        testRepo
+            .branch("HEAD")
+            .commit()
+            .message(message)
+            .insertChangeId()
+            .add("a.txt", "content")
+            .create();
+    return testRepo.getRevWalk().parseCommit(c);
+  }
+
   @Test
   public void cantAutoCloseChangeAlreadyMergedToBranch() throws Exception {
     PushOneCommit.Result r1 = createChange();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 2fa0f4d..7a84e6d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.extensions.api.changes.DraftInput;
@@ -31,6 +32,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
@@ -401,7 +403,7 @@
     addComment(r1, "nit: trailing whitespace");
     Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
     assertThat(result.get(FILE_NAME)).hasSize(2);
-    addComment(r1, "nit: trailing whitespace", true);
+    addComment(r1, "nit: trailing whitespace", true, false);
     result = getPublishedComments(changeId, revId);
     assertThat(result.get(FILE_NAME)).hasSize(2);
 
@@ -411,7 +413,7 @@
             .to("refs/for/master");
     changeId = r2.getChangeId();
     revId = r2.getCommit().getName();
-    addComment(r2, "nit: trailing whitespace", true);
+    addComment(r2, "nit: trailing whitespace", true, false);
     result = getPublishedComments(changeId, revId);
     assertThat(result.get(FILE_NAME)).hasSize(1);
   }
@@ -694,6 +696,30 @@
     assertThat(drafts.get(0).tag).isEqualTo("tag2");
   }
 
+  @Test
+  public void queryChangesWithUnresolvedCommentCount() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+
+    addComment(r1, "comment 1", false, true);
+    addComment(r1, "nit: trailing whitespace", false, null);
+
+    PushOneCommit.Result r2 =
+        pushFactory
+            .create(
+                db, admin.getIdent(), testRepo, SUBJECT, FILE_NAME, "new cntent", r1.getChangeId())
+            .to("refs/for/master");
+
+    addComment(r2, "typo: content", false, false);
+
+    AcceptanceTestRequestScope.Context ctx = disableDb();
+    try {
+      ChangeInfo result = Iterables.getOnlyElement(query(r2.getChangeId()));
+      assertThat(result.unresolvedCommentCount).isEqualTo(1);
+    } finally {
+      enableDb(ctx);
+    }
+  }
+
   private static String extractComments(String msg) {
     // Extract lines between start "....." and end "-- ".
     Pattern p = Pattern.compile(".*[.]{5}\n+(.*)\\n+-- \n.*", Pattern.DOTALL);
@@ -709,15 +735,17 @@
   }
 
   private void addComment(PushOneCommit.Result r, String message) throws Exception {
-    addComment(r, message, false);
+    addComment(r, message, false, false);
   }
 
-  private void addComment(PushOneCommit.Result r, String message, boolean omitDuplicateComments)
+  private void addComment(
+      PushOneCommit.Result r, String message, boolean omitDuplicateComments, Boolean unresolved)
       throws Exception {
     CommentInput c = new CommentInput();
     c.line = 1;
     c.message = message;
     c.path = FILE_NAME;
+    c.unresolved = unresolved;
     ReviewInput in = newInput(c);
     in.omitDuplicateComments = omitDuplicateComments;
     gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index d423a2d..6b10edb9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -67,7 +67,7 @@
 
     Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
     assertThat(messages).hasSize(3);
-    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\nTest Message");
+    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n\nTest Message");
     assertThat(Iterables.getLast(messages).tag).isEqualTo("mailMessageId=some id");
   }
 
@@ -96,7 +96,7 @@
     // Assert messages
     Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
     assertThat(messages).hasSize(3);
-    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n(1 comment)");
+    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n\n(1 comment)");
     assertThat(Iterables.getLast(messages).tag).isEqualTo("mailMessageId=some id");
 
     // Assert comment
@@ -132,7 +132,7 @@
     // Assert messages
     Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
     assertThat(messages).hasSize(3);
-    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n(1 comment)");
+    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n\n(1 comment)");
     assertThat(Iterables.getLast(messages).tag).isEqualTo("mailMessageId=some id");
 
     // Assert comment
diff --git a/gerrit-acceptance-tests/tests.bzl b/gerrit-acceptance-tests/tests.bzl
index 1fd47ae..3b1a4f4 100644
--- a/gerrit-acceptance-tests/tests.bzl
+++ b/gerrit-acceptance-tests/tests.bzl
@@ -1,10 +1,5 @@
 load("//tools/bzl:junit.bzl", "junit_tests")
 
-BOUNCYCASTLE = [
-    "//lib/bouncycastle:bcpkix-without-neverlink",
-    "//lib/bouncycastle:bcpg-without-neverlink",
-]
-
 def acceptance_tests(
     group,
     deps = [],
@@ -13,8 +8,10 @@
     **kwargs):
   junit_tests(
     name = group,
-    deps = deps + BOUNCYCASTLE + [
+    deps = deps + [
       '//gerrit-acceptance-tests:lib',
+      "//lib/bouncycastle:bcpkix",
+      "//lib/bouncycastle:bcpg",
     ],
     tags = labels + [
       'acceptance',
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
deleted file mode 100644
index f716058..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2008 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.common.data;
-
-import com.google.gerrit.common.audit.Audit;
-import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.RemoteJsonService;
-import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.RpcImpl.Version;
-import java.util.List;
-import java.util.Set;
-
-@RpcImpl(version = Version.V2_0)
-public interface AccountSecurity extends RemoteJsonService {
-  @SignInRequired
-  void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
-
-  @Audit
-  @SignInRequired
-  void deleteExternalIds(
-      Set<AccountExternalId.Key> keys, AsyncCallback<Set<AccountExternalId.Key>> callback);
-}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index dc2668d..83bb3af 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -353,6 +353,7 @@
           ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
           ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
           cd);
+      decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
 
       if (source.get(ChangeField.REF_STATE.getName()) != null) {
         JsonArray refStates = source.get(ChangeField.REF_STATE.getName()).getAsJsonArray();
@@ -381,5 +382,13 @@
           opts,
           out);
     }
+
+    private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) {
+      JsonElement count = doc.get(fieldName);
+      if (count == null) {
+        return;
+      }
+      out.setUnresolvedCommentCount(count.getAsInt());
+    }
   }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 7061f31..3803714 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -43,6 +43,7 @@
   public Boolean submittable;
   public Integer insertions;
   public Integer deletions;
+  public Integer unresolvedCommentCount;
 
   public int _number;
 
diff --git a/gerrit-gpg/BUILD b/gerrit-gpg/BUILD
index dcaf442..1d949d1 100644
--- a/gerrit-gpg/BUILD
+++ b/gerrit-gpg/BUILD
@@ -33,8 +33,8 @@
     visibility = ["//visibility:public"],
     deps = DEPS + [
         ":gpg",
-        "//lib/bouncycastle:bcpg-without-neverlink",
-        "//lib/bouncycastle:bcprov-without-neverlink",
+        "//lib/bouncycastle:bcpg",
+        "//lib/bouncycastle:bcprov",
     ],
 )
 
@@ -53,7 +53,7 @@
         "//gerrit-server:testutil",
         "//lib:truth",
         "//lib/jgit/org.eclipse.jgit.junit:junit",
-        "//lib/bouncycastle:bcpg-without-neverlink",
-        "//lib/bouncycastle:bcprov-without-neverlink",
+        "//lib/bouncycastle:bcpg",
+        "//lib/bouncycastle:bcprov",
     ],
 )
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index cbd12ea..165d0ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -118,6 +118,7 @@
     suggestions.add("has:edit");
     suggestions.add("has:star");
     suggestions.add("has:stars");
+    suggestions.add("has:unresolved");
     suggestions.add("star:");
 
     suggestions.add("is:");
@@ -148,6 +149,8 @@
     suggestions.add("delta:");
     suggestions.add("size:");
 
+    suggestions.add("unresolved:");
+
     if (Gerrit.isNoteDbEnabled()) {
       suggestions.add("hashtag:");
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java
index 8d5cd68..4ac0716 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java
@@ -57,8 +57,6 @@
       // Describe a mailto address as just its email address,
       // which is already shown in the email address field.
       return "";
-    } else if (isScheme("https://www.google.com/accounts/o8/id")) {
-      return OpenIdUtil.C.nameGoogle();
     } else if (isScheme(OpenIdUrls.URL_LAUNCHPAD)) {
       return OpenIdUtil.C.nameLaunchpad();
     } else if (isScheme(OpenIdUrls.URL_YAHOO)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index afa0de6..37813af 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -88,7 +88,7 @@
         new ValueChangeHandler<String>() {
           @Override
           public void onValueChange(ValueChangeEvent<String> event) {
-            if (!Util.C.addPermission().equals(event.getValue())) {
+            if (!AdminConstants.I.addPermission().equals(event.getValue())) {
               onAddPermission(event.getValue());
             }
           }
@@ -113,14 +113,13 @@
     isDeleted = true;
 
     if (name.isVisible() && RefConfigSection.isValid(name.getValue())) {
-      deletedName.setInnerText(Util.M.deletedReference(name.getValue()));
-
+      deletedName.setInnerText(AdminMessages.I.deletedReference(name.getValue()));
     } else {
-      String name = Util.C.sectionNames().get(value.getName());
+      String name = AdminConstants.I.sectionNames().get(value.getName());
       if (name == null) {
         name = value.getName();
       }
-      deletedName.setInnerText(Util.M.deletedSection(name));
+      deletedName.setInnerText(AdminMessages.I.deletedSection(name));
     }
 
     normal.getStyle().setDisplay(Display.NONE);
@@ -181,18 +180,18 @@
     if (RefConfigSection.isValid(value.getName())) {
       name.setVisible(true);
       name.setIgnoreEditorValue(false);
-      sectionType.setInnerText(Util.C.sectionTypeReference());
+      sectionType.setInnerText(AdminConstants.I.sectionTypeReference());
 
     } else {
       name.setVisible(false);
       name.setIgnoreEditorValue(true);
 
-      String name = Util.C.sectionNames().get(value.getName());
+      String name = AdminConstants.I.sectionNames().get(value.getName());
       if (name != null) {
         sectionType.setInnerText(name);
         sectionName.getStyle().setDisplay(Display.NONE);
       } else {
-        sectionType.setInnerText(Util.C.sectionTypeSection());
+        sectionType.setInnerText(AdminConstants.I.sectionTypeSection());
         sectionName.setInnerText(value.getName());
         sectionName.getStyle().clearDisplay();
       }
@@ -223,7 +222,7 @@
       for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
         addPermission(Permission.forLabelAs(t.getName()), perms);
       }
-      for (String varName : Util.C.permissionNames().keySet()) {
+      for (String varName : AdminConstants.I.permissionNames().keySet()) {
         addPermission(varName, perms);
       }
     }
@@ -231,8 +230,8 @@
       addContainer.getStyle().setDisplay(Display.NONE);
     } else {
       addContainer.getStyle().setDisplay(Display.BLOCK);
-      perms.add(0, Util.C.addPermission());
-      permissionSelector.setValue(Util.C.addPermission());
+      perms.add(0, AdminConstants.I.addPermission());
+      permissionSelector.setValue(AdminConstants.I.addPermission());
       permissionSelector.setAcceptableValues(perms);
     }
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
index 16a51e1..4bcd8d4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupAuditLogScreen.java
@@ -44,7 +44,7 @@
   @Override
   protected void onInitUI() {
     super.onInitUI();
-    add(new SmallHeading(Util.C.headingAuditLog()));
+    add(new SmallHeading(AdminConstants.I.headingAuditLog()));
     auditEventTable = new AuditEventTable();
     add(auditEventTable);
   }
@@ -63,10 +63,10 @@
 
   private class AuditEventTable extends FancyFlexTable<GroupAuditEventInfo> {
     AuditEventTable() {
-      table.setText(0, 1, Util.C.columnDate());
-      table.setText(0, 2, Util.C.columnType());
-      table.setText(0, 3, Util.C.columnMember());
-      table.setText(0, 4, Util.C.columnByUser());
+      table.setText(0, 1, AdminConstants.I.columnDate());
+      table.setText(0, 2, AdminConstants.I.columnType());
+      table.setText(0, 3, AdminConstants.I.columnMember());
+      table.setText(0, 4, AdminConstants.I.columnByUser());
 
       FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
@@ -95,11 +95,11 @@
       switch (auditEvent.type()) {
         case ADD_USER:
         case ADD_GROUP:
-          table.setText(row, 2, Util.C.typeAdded());
+          table.setText(row, 2, AdminConstants.I.typeAdded());
           break;
         case REMOVE_USER:
         case REMOVE_GROUP:
-          table.setText(row, 2, Util.C.typeRemoved());
+          table.setText(row, 2, AdminConstants.I.typeRemoved());
           break;
       }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
index b1d9873..4d1ad22 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -72,7 +72,7 @@
   private void initUUID() {
     final VerticalPanel groupUUIDPanel = new VerticalPanel();
     groupUUIDPanel.setStyleName(Gerrit.RESOURCES.css().groupUUIDPanel());
-    groupUUIDPanel.add(new SmallHeading(Util.C.headingGroupUUID()));
+    groupUUIDPanel.add(new SmallHeading(AdminConstants.I.headingGroupUUID()));
     groupUUIDLabel = new CopyableLabel("");
     groupUUIDPanel.add(groupUUIDLabel);
     add(groupUUIDPanel);
@@ -86,7 +86,7 @@
     groupNameTxt.setVisibleLength(60);
     groupNamePanel.add(groupNameTxt);
 
-    saveName = new Button(Util.C.buttonRenameGroup());
+    saveName = new Button(AdminConstants.I.buttonRenameGroup());
     saveName.setEnabled(false);
     saveName.addClickHandler(
         new ClickHandler() {
@@ -100,7 +100,7 @@
                   @Override
                   public void onSuccess(final com.google.gerrit.client.VoidResult result) {
                     saveName.setEnabled(false);
-                    setPageTitle(Util.M.group(newName));
+                    setPageTitle(AdminMessages.I.group(newName));
                     groupNameTxt.setText(newName);
                     if (getGroupUUID().equals(getOwnerGroupUUID())) {
                       ownerTxt.setText(newName);
@@ -116,7 +116,7 @@
   private void initOwner() {
     final VerticalPanel ownerPanel = new VerticalPanel();
     ownerPanel.setStyleName(Gerrit.RESOURCES.css().groupOwnerPanel());
-    ownerPanel.add(new SmallHeading(Util.C.headingOwner()));
+    ownerPanel.add(new SmallHeading(AdminConstants.I.headingOwner()));
 
     final AccountGroupSuggestOracle accountGroupOracle = new AccountGroupSuggestOracle();
     ownerTxt = new RemoteSuggestBox(accountGroupOracle);
@@ -124,7 +124,7 @@
     ownerTxt.setVisibleLength(60);
     ownerPanel.add(ownerTxt);
 
-    saveOwner = new Button(Util.C.buttonChangeGroupOwner());
+    saveOwner = new Button(AdminConstants.I.buttonChangeGroupOwner());
     saveOwner.setEnabled(false);
     saveOwner.addClickHandler(
         new ClickHandler() {
@@ -154,14 +154,14 @@
   private void initDescription() {
     final VerticalPanel vp = new VerticalPanel();
     vp.setStyleName(Gerrit.RESOURCES.css().groupDescriptionPanel());
-    vp.add(new SmallHeading(Util.C.headingDescription()));
+    vp.add(new SmallHeading(AdminConstants.I.headingDescription()));
 
     descTxt = new NpTextArea();
     descTxt.setVisibleLines(6);
     descTxt.setCharacterWidth(60);
     vp.add(descTxt);
 
-    saveDesc = new Button(Util.C.buttonSaveDescription());
+    saveDesc = new Button(AdminConstants.I.buttonSaveDescription());
     saveDesc.setEnabled(false);
     saveDesc.addClickHandler(
         new ClickHandler() {
@@ -188,13 +188,13 @@
 
     final VerticalPanel vp = new VerticalPanel();
     vp.setStyleName(Gerrit.RESOURCES.css().groupOptionsPanel());
-    vp.add(new SmallHeading(Util.C.headingGroupOptions()));
+    vp.add(new SmallHeading(AdminConstants.I.headingGroupOptions()));
 
-    visibleToAllCheckBox = new CheckBox(Util.C.isVisibleToAll());
+    visibleToAllCheckBox = new CheckBox(AdminConstants.I.isVisibleToAll());
     vp.add(visibleToAllCheckBox);
     groupOptionsPanel.add(vp);
 
-    saveGroupOptions = new Button(Util.C.buttonSaveGroupOptions());
+    saveGroupOptions = new Button(AdminConstants.I.buttonSaveGroupOptions());
     saveGroupOptions.setEnabled(false);
     saveGroupOptions.addClickHandler(
         new ClickHandler() {
@@ -226,7 +226,7 @@
     ownerTxt.setText(
         group.owner() != null
             ? group.owner()
-            : Util.M.deletedReference(group.getOwnerUUID().get()));
+            : AdminMessages.I.deletedReference(group.getOwnerUUID().get()));
     descTxt.setText(group.description());
     visibleToAllCheckBox.setValue(group.options().isVisibleToAll());
     setMembersTabVisible(AccountGroup.isInternalGroup(group.getGroupUUID()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index 7aab6c3..51b4979 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -81,7 +81,9 @@
   private void initMemberList() {
     addMemberBox =
         new AddMemberBox(
-            Util.C.buttonAddGroupMember(), Util.C.defaultAccountName(), new AccountSuggestOracle());
+            AdminConstants.I.buttonAddGroupMember(),
+            AdminConstants.I.defaultAccountName(),
+            new AccountSuggestOracle());
 
     addMemberBox.addClickHandler(
         new ClickHandler() {
@@ -94,7 +96,7 @@
     members = new MemberTable();
     members.addStyleName(Gerrit.RESOURCES.css().groupMembersTable());
 
-    delMember = new Button(Util.C.buttonDeleteGroupMembers());
+    delMember = new Button(AdminConstants.I.buttonDeleteGroupMembers());
     delMember.addClickHandler(
         new ClickHandler() {
           @Override
@@ -104,7 +106,7 @@
         });
 
     memberPanel = new FlowPanel();
-    memberPanel.add(new SmallHeading(Util.C.headingMembers()));
+    memberPanel.add(new SmallHeading(AdminConstants.I.headingMembers()));
     memberPanel.add(addMemberBox);
     memberPanel.add(members);
     memberPanel.add(delMember);
@@ -115,8 +117,8 @@
     accountGroupSuggestOracle = new AccountGroupSuggestOracle();
     addIncludeBox =
         new AddMemberBox(
-            Util.C.buttonAddIncludedGroup(),
-            Util.C.defaultAccountGroupName(),
+            AdminConstants.I.buttonAddIncludedGroup(),
+            AdminConstants.I.defaultAccountGroupName(),
             accountGroupSuggestOracle);
 
     addIncludeBox.addClickHandler(
@@ -130,7 +132,7 @@
     includes = new IncludeTable();
     includes.addStyleName(Gerrit.RESOURCES.css().groupIncludesTable());
 
-    delInclude = new Button(Util.C.buttonDeleteIncludedGroup());
+    delInclude = new Button(AdminConstants.I.buttonDeleteIncludedGroup());
     delInclude.addClickHandler(
         new ClickHandler() {
           @Override
@@ -140,7 +142,7 @@
         });
 
     includePanel = new FlowPanel();
-    includePanel.add(new SmallHeading(Util.C.headingIncludedGroups()));
+    includePanel.add(new SmallHeading(AdminConstants.I.headingIncludedGroups()));
     includePanel.add(addIncludeBox);
     includePanel.add(includes);
     includePanel.add(delInclude);
@@ -150,7 +152,7 @@
   private void initNoMembersInfo() {
     noMembersInfo = new FlowPanel();
     noMembersInfo.setVisible(false);
-    noMembersInfo.add(new SmallHeading(Util.C.noMembersInfo()));
+    noMembersInfo.add(new SmallHeading(AdminConstants.I.noMembersInfo()));
     add(noMembersInfo);
   }
 
@@ -231,8 +233,8 @@
     private boolean enabled = true;
 
     MemberTable() {
-      table.setText(0, 2, Util.C.columnMember());
-      table.setText(0, 3, Util.C.columnEmailAddress());
+      table.setText(0, 2, AdminConstants.I.columnMember());
+      table.setText(0, 3, AdminConstants.I.columnEmailAddress());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
@@ -341,8 +343,8 @@
     private boolean enabled = true;
 
     IncludeTable() {
-      table.setText(0, 2, Util.C.columnGroupName());
-      table.setText(0, 3, Util.C.columnGroupDescription());
+      table.setText(0, 2, AdminConstants.I.columnGroupName());
+      table.setText(0, 3, AdminConstants.I.columnGroupDescription());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
index e655a2d..29b7677 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
@@ -40,9 +40,9 @@
     this.membersTabToken = getTabToken(token, MEMBERS);
     this.auditLogTabToken = getTabToken(token, AUDIT_LOG);
 
-    link(Util.C.groupTabGeneral(), getTabToken(token, INFO));
+    link(AdminConstants.I.groupTabGeneral(), getTabToken(token, INFO));
     link(
-        Util.C.groupTabMembers(),
+        AdminConstants.I.groupTabMembers(),
         membersTabToken,
         AccountGroup.isInternalGroup(group.getGroupUUID()));
   }
@@ -57,7 +57,7 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    setPageTitle(Util.M.group(group.name()));
+    setPageTitle(AdminMessages.I.group(group.name()));
     display();
     GroupApi.isGroupOwner(
         group.name(),
@@ -66,7 +66,7 @@
           public void onSuccess(Boolean result) {
             if (result) {
               link(
-                  Util.C.groupTabAuditLog(),
+                  AdminConstants.I.groupTabAuditLog(),
                   auditLogTabToken,
                   AccountGroup.isInternalGroup(group.getGroupUUID()));
               setToken(token);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 39676a1..b8da3a7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -14,10 +14,13 @@
 
 package com.google.gerrit.client.admin;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.i18n.client.Constants;
 import java.util.Map;
 
 public interface AdminConstants extends Constants {
+  AdminConstants I = GWT.create(AdminConstants.class);
+
   String defaultAccountName();
 
   String defaultAccountGroupName();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index d1c7b1d1..0c2f6fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -14,9 +14,12 @@
 
 package com.google.gerrit.client.admin;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.i18n.client.Messages;
 
 public interface AdminMessages extends Messages {
+  AdminMessages I = GWT.create(AdminMessages.class);
+
   String group(String name);
 
   String label(String name);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
index 6ac4a68..2e5bbb5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
@@ -31,8 +31,8 @@
     b.setEnabled(false);
     new CreateChangeDialog(new Project.NameKey(project)) {
       {
-        sendButton.setText(Util.C.buttonCreate());
-        message.setText(Util.C.buttonCreateDescription());
+        sendButton.setText(AdminConstants.I.buttonCreate());
+        message.setText(AdminConstants.I.buttonCreateDescription());
       }
 
       @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
index 262c1f1..457e179 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
@@ -70,14 +70,14 @@
   @Override
   protected void onInitUI() {
     super.onInitUI();
-    setPageTitle(Util.C.createGroupTitle());
+    setPageTitle(AdminConstants.I.createGroupTitle());
     addCreateGroupPanel();
   }
 
   private void addCreateGroupPanel() {
     VerticalPanel addPanel = new VerticalPanel();
     addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
-    addPanel.add(new SmallHeading(Util.C.headingCreateGroup()));
+    addPanel.add(new SmallHeading(AdminConstants.I.headingCreateGroup()));
 
     addTxt =
         new NpTextBox() {
@@ -112,7 +112,7 @@
         });
     addPanel.add(addTxt);
 
-    addNew = new Button(Util.C.buttonCreateGroup());
+    addNew = new Button(AdminConstants.I.buttonCreateGroup());
     addNew.setEnabled(false);
     addNew.addClickHandler(
         new ClickHandler() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
index 5e93679..dd46c5c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
@@ -94,7 +94,7 @@
   @Override
   protected void onInitUI() {
     super.onInitUI();
-    setPageTitle(Util.C.createProjectTitle());
+    setPageTitle(AdminConstants.I.createProjectTitle());
     addCreateProjectPanel();
 
     /* popup */
@@ -108,7 +108,7 @@
             }
           }
         };
-    projectsPopup.initPopup(Util.C.projects(), PageLinks.ADMIN_PROJECTS);
+    projectsPopup.initPopup(AdminConstants.I.projects(), PageLinks.ADMIN_PROJECTS);
   }
 
   private void addCreateProjectPanel() {
@@ -121,8 +121,8 @@
 
     addGrid(fp);
 
-    emptyCommit = new CheckBox(Util.C.checkBoxEmptyCommit());
-    permissionsOnly = new CheckBox(Util.C.checkBoxPermissionsOnly());
+    emptyCommit = new CheckBox(AdminConstants.I.checkBoxEmptyCommit());
+    permissionsOnly = new CheckBox(AdminConstants.I.checkBoxPermissionsOnly());
     fp.add(emptyCommit);
     fp.add(permissionsOnly);
     fp.add(create);
@@ -168,7 +168,7 @@
   }
 
   private void initCreateButton() {
-    create = new Button(Util.C.buttonCreateProject());
+    create = new Button(AdminConstants.I.buttonCreateProject());
     create.setEnabled(false);
     create.addClickHandler(
         new ClickHandler() {
@@ -178,7 +178,7 @@
           }
         });
 
-    browse = new Button(Util.C.buttonBrowseProjects());
+    browse = new Button(AdminConstants.I.buttonBrowseProjects());
     browse.addClickHandler(
         new ClickHandler() {
           @Override
@@ -207,7 +207,7 @@
     suggestedParentsTab =
         new ProjectsTable() {
           {
-            table.setText(0, 1, Util.C.parentSuggestions());
+            table.setText(0, 1, AdminConstants.I.parentSuggestions());
           }
 
           @Override
@@ -246,9 +246,9 @@
   private void addGrid(final VerticalPanel fp) {
     grid = new Grid(2, 3);
     grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
-    grid.setText(0, 0, Util.C.columnProjectName() + ":");
+    grid.setText(0, 0, AdminConstants.I.columnProjectName() + ":");
     grid.setWidget(0, 1, project);
-    grid.setText(1, 0, Util.C.headingParentProjectName() + ":");
+    grid.setText(1, 0, AdminConstants.I.headingParentProjectName() + ":");
     grid.setWidget(1, 1, parent);
     grid.setWidget(1, 2, browse);
     fp.add(grid);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
index fdbf3d9..d28e9bb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
@@ -31,7 +31,7 @@
         project,
         RefNames.REFS_CONFIG,
         null,
-        Util.C.editConfigMessage(),
+        AdminConstants.I.editConfigMessage(),
         null,
         new GerritCallback<ChangeInfo>() {
           @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index 3faa067..b37a680 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -95,7 +95,7 @@
   @Override
   protected void onInitUI() {
     super.onInitUI();
-    setPageTitle(Util.C.groupListTitle());
+    setPageTitle(AdminConstants.I.groupListTitle());
     initPageHeader();
 
     prev = PagingHyperlink.createPrev();
@@ -117,7 +117,7 @@
   private void initPageHeader() {
     final HorizontalPanel hp = new HorizontalPanel();
     hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
-    final Label filterLabel = new Label(Util.C.projectFilter());
+    final Label filterLabel = new Label(AdminConstants.I.projectFilter());
     filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
     hp.add(filterLabel);
     filterTxt = new NpTextBox();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
index 9b8f50b..0f5bf22 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.client.admin;
 
-import static com.google.gerrit.client.admin.Util.C;
-
 import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.groups.GroupList;
@@ -46,12 +44,12 @@
   }
 
   public GroupTable(final String pointerId) {
-    super(C.groupItemHelp());
+    super(AdminConstants.I.groupItemHelp());
     setSavePointerId(pointerId);
 
-    table.setText(0, 1, C.columnGroupName());
-    table.setText(0, 2, C.columnGroupDescription());
-    table.setText(0, 3, C.columnGroupVisibleToAll());
+    table.setText(0, 1, AdminConstants.I.columnGroupName());
+    table.setText(0, 2, AdminConstants.I.columnGroupDescription());
+    table.setText(0, 3, AdminConstants.I.columnGroupVisibleToAll());
     table.addClickHandler(
         new ClickHandler() {
           @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
index c6a36b0..ef02bd0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -25,7 +25,7 @@
 
   static {
     permissions = new HashMap<>();
-    for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
+    for (Map.Entry<String, String> e : AdminConstants.I.permissionNames().entrySet()) {
       permissions.put(e.getKey(), e.getValue());
       permissions.put(e.getKey().toLowerCase(), e.getValue());
     }
@@ -40,9 +40,9 @@
   @Override
   public String render(String varName) {
     if (Permission.isLabelAs(varName)) {
-      return Util.M.labelAs(Permission.extractLabel(varName));
+      return AdminMessages.I.labelAs(Permission.extractLabel(varName));
     } else if (Permission.isLabel(varName)) {
-      return Util.M.label(Permission.extractLabel(varName));
+      return AdminMessages.I.label(Permission.extractLabel(varName));
     }
 
     String desc = permissions.get(varName);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
index dcd214c..8a70f2e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
@@ -63,10 +63,10 @@
 
   private static class PluginTable extends FancyFlexTable<PluginInfo> {
     PluginTable() {
-      table.setText(0, 1, Util.C.columnPluginName());
-      table.setText(0, 2, Util.C.columnPluginSettings());
-      table.setText(0, 3, Util.C.columnPluginVersion());
-      table.setText(0, 4, Util.C.columnPluginStatus());
+      table.setText(0, 1, AdminConstants.I.columnPluginName());
+      table.setText(0, 2, AdminConstants.I.columnPluginSettings());
+      table.setText(0, 3, AdminConstants.I.columnPluginVersion());
+      table.setText(0, 4, AdminConstants.I.columnPluginStatus());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
@@ -99,13 +99,16 @@
           InlineHyperlink adminScreenLink = new InlineHyperlink();
           adminScreenLink.setHTML(new ImageResourceRenderer().render(Gerrit.RESOURCES.gear()));
           adminScreenLink.setTargetHistoryToken("/x/" + plugin.name() + "/settings");
-          adminScreenLink.setTitle(Util.C.pluginSettingsToolTip());
+          adminScreenLink.setTitle(AdminConstants.I.pluginSettingsToolTip());
           table.setWidget(row, 2, adminScreenLink);
         }
       }
 
       table.setText(row, 3, plugin.version());
-      table.setText(row, 4, plugin.disabled() ? Util.C.pluginDisabled() : Util.C.pluginEnabled());
+      table.setText(
+          row,
+          4,
+          plugin.disabled() ? AdminConstants.I.pluginDisabled() : AdminConstants.I.pluginEnabled());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
index dabcb45..0909fe1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
@@ -25,7 +25,7 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    setPageTitle(Util.C.plugins());
+    setPageTitle(AdminConstants.I.plugins());
     display();
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index ded0b059..0398e9d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -184,7 +184,7 @@
     final ProjectAccess access = driver.flush();
 
     if (driver.hasErrors()) {
-      Window.alert(Util.C.errorsMustBeFixed());
+      Window.alert(AdminConstants.I.errorsMustBeFixed());
       return;
     }
 
@@ -264,7 +264,7 @@
     final ProjectAccess access = driver.flush();
 
     if (driver.hasErrors()) {
-      Window.alert(Util.C.errorsMustBeFixed());
+      Window.alert(AdminConstants.I.errorsMustBeFixed());
       return;
     }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 729b43f..887e4b8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -133,7 +133,7 @@
 
     nameTxtBox = new HintTextBox();
     nameTxtBox.setVisibleLength(texBoxLength);
-    nameTxtBox.setHintText(Util.C.defaultBranchName());
+    nameTxtBox.setHintText(AdminConstants.I.defaultBranchName());
     nameTxtBox.addKeyPressHandler(
         new KeyPressHandler() {
           @Override
@@ -143,12 +143,12 @@
             }
           }
         });
-    addGrid.setText(0, 0, Util.C.columnBranchName() + ":");
+    addGrid.setText(0, 0, AdminConstants.I.columnBranchName() + ":");
     addGrid.setWidget(0, 1, nameTxtBox);
 
     irevTxtBox = new HintTextBox();
     irevTxtBox.setVisibleLength(texBoxLength);
-    irevTxtBox.setHintText(Util.C.defaultRevisionSpec());
+    irevTxtBox.setHintText(AdminConstants.I.defaultRevisionSpec());
     irevTxtBox.addKeyPressHandler(
         new KeyPressHandler() {
           @Override
@@ -158,10 +158,10 @@
             }
           }
         });
-    addGrid.setText(1, 0, Util.C.initialRevision() + ":");
+    addGrid.setText(1, 0, AdminConstants.I.initialRevision() + ":");
     addGrid.setWidget(1, 1, irevTxtBox);
 
-    addBranch = new Button(Util.C.buttonAddBranch());
+    addBranch = new Button(AdminConstants.I.buttonAddBranch());
     addBranch.addClickHandler(
         new ClickHandler() {
           @Override
@@ -174,7 +174,7 @@
 
     branchTable = new BranchesTable();
 
-    delBranch = new Button(Util.C.buttonDeleteBranch());
+    delBranch = new Button(AdminConstants.I.buttonDeleteBranch());
     delBranch.setStyleName(Gerrit.RESOURCES.css().branchTableDeleteButton());
     delBranch.addClickHandler(
         new ClickHandler() {
@@ -197,7 +197,7 @@
     parseToken();
     HorizontalPanel hp = new HorizontalPanel();
     hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
-    Label filterLabel = new Label(Util.C.projectFilter());
+    Label filterLabel = new Label(AdminConstants.I.projectFilter());
     filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
     hp.add(filterLabel);
     filterTxt = new NpTextBox();
@@ -301,8 +301,8 @@
 
     BranchesTable() {
       table.setWidth("");
-      table.setText(0, 2, Util.C.columnBranchName());
-      table.setText(0, 3, Util.C.columnBranchRevision());
+      table.setText(0, 2, AdminConstants.I.columnBranchName());
+      table.setText(0, 3, AdminConstants.I.columnBranchRevision());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
@@ -499,11 +499,11 @@
       input.setValue(headRevision);
       input.setVisible(false);
       final Button save = new Button();
-      save.setText(Util.C.saveHeadButton());
+      save.setText(AdminConstants.I.saveHeadButton());
       save.setVisible(false);
       save.setEnabled(false);
       final Button cancel = new Button();
-      cancel.setText(Util.C.cancelHeadButton());
+      cancel.setText(AdminConstants.I.cancelHeadButton());
       cancel.setVisible(false);
 
       OnEditEnabler e = new OnEditEnabler(save);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index e2203d5..3645fb9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -108,7 +108,7 @@
     super.onInitUI();
 
     Resources.I.style().ensureInjected();
-    saveProject = new Button(Util.C.buttonSaveChanges());
+    saveProject = new Button(AdminConstants.I.buttonSaveChanges());
     saveProject.setStyleName("");
     saveProject.addClickHandler(
         new ClickHandler() {
@@ -203,7 +203,7 @@
 
   private void initDescription() {
     final VerticalPanel vp = new VerticalPanel();
-    vp.add(new SmallHeading(Util.C.headingDescription()));
+    vp.add(new SmallHeading(AdminConstants.I.headingDescription()));
 
     descTxt = new NpTextArea();
     descTxt.setVisibleLines(6);
@@ -216,14 +216,14 @@
   }
 
   private void initProjectOptions() {
-    grid.addHeader(new SmallHeading(Util.C.headingProjectOptions()));
+    grid.addHeader(new SmallHeading(AdminConstants.I.headingProjectOptions()));
 
     state = new ListBox();
     for (ProjectState stateValue : ProjectState.values()) {
       state.addItem(Util.toLongString(stateValue), stateValue.name());
     }
     saveEnabler.listenTo(state);
-    grid.add(Util.C.headingProjectState(), state);
+    grid.add(AdminConstants.I.headingProjectState(), state);
 
     submitType = new ListBox();
     for (final SubmitType type : SubmitType.values()) {
@@ -237,32 +237,32 @@
           }
         });
     saveEnabler.listenTo(submitType);
-    grid.add(Util.C.headingProjectSubmitType(), submitType);
+    grid.add(AdminConstants.I.headingProjectSubmitType(), submitType);
 
     contentMerge = newInheritedBooleanBox();
     saveEnabler.listenTo(contentMerge);
-    grid.add(Util.C.useContentMerge(), contentMerge);
+    grid.add(AdminConstants.I.useContentMerge(), contentMerge);
 
     newChangeForAllNotInTarget = newInheritedBooleanBox();
     saveEnabler.listenTo(newChangeForAllNotInTarget);
-    grid.add(Util.C.createNewChangeForAllNotInTarget(), newChangeForAllNotInTarget);
+    grid.add(AdminConstants.I.createNewChangeForAllNotInTarget(), newChangeForAllNotInTarget);
 
     requireChangeID = newInheritedBooleanBox();
     saveEnabler.listenTo(requireChangeID);
-    grid.addHtml(Util.C.requireChangeID(), requireChangeID);
+    grid.addHtml(AdminConstants.I.requireChangeID(), requireChangeID);
 
     if (Gerrit.info().receive().enableSignedPush()) {
       enableSignedPush = newInheritedBooleanBox();
       saveEnabler.listenTo(enableSignedPush);
-      grid.add(Util.C.enableSignedPush(), enableSignedPush);
+      grid.add(AdminConstants.I.enableSignedPush(), enableSignedPush);
       requireSignedPush = newInheritedBooleanBox();
       saveEnabler.listenTo(requireSignedPush);
-      grid.add(Util.C.requireSignedPush(), requireSignedPush);
+      grid.add(AdminConstants.I.requireSignedPush(), requireSignedPush);
     }
 
     rejectImplicitMerges = newInheritedBooleanBox();
     saveEnabler.listenTo(rejectImplicitMerges);
-    grid.addHtml(Util.C.rejectImplicitMerges(), rejectImplicitMerges);
+    grid.addHtml(AdminConstants.I.rejectImplicitMerges(), rejectImplicitMerges);
 
     maxObjectSizeLimit = new NpTextBox();
     saveEnabler.listenTo(maxObjectSizeLimit);
@@ -272,7 +272,7 @@
     HorizontalPanel p = new HorizontalPanel();
     p.add(maxObjectSizeLimit);
     p.add(effectiveMaxObjectSizeLimit);
-    grid.addHtml(Util.C.headingMaxObjectSizeLimit(), p);
+    grid.addHtml(AdminConstants.I.headingMaxObjectSizeLimit(), p);
   }
 
   private static ListBox newInheritedBooleanBox() {
@@ -301,17 +301,17 @@
   }
 
   private void initAgreements() {
-    grid.addHeader(new SmallHeading(Util.C.headingAgreements()));
+    grid.addHeader(new SmallHeading(AdminConstants.I.headingAgreements()));
 
     contributorAgreements = newInheritedBooleanBox();
     if (Gerrit.info().auth().useContributorAgreements()) {
       saveEnabler.listenTo(contributorAgreements);
-      grid.add(Util.C.useContributorAgreements(), contributorAgreements);
+      grid.add(AdminConstants.I.useContributorAgreements(), contributorAgreements);
     }
 
     signedOffBy = newInheritedBooleanBox();
     saveEnabler.listenTo(signedOffBy);
-    grid.addHtml(Util.C.useSignedOffBy(), signedOffBy);
+    grid.addHtml(AdminConstants.I.useSignedOffBy(), signedOffBy);
   }
 
   private void setSubmitType(final SubmitType newSubmitType) {
@@ -401,9 +401,9 @@
     if (result.maxObjectSizeLimit().inheritedValue() != null) {
       effectiveMaxObjectSizeLimit.setVisible(true);
       effectiveMaxObjectSizeLimit.setText(
-          Util.M.effectiveMaxObjectSizeLimit(result.maxObjectSizeLimit().value()));
+          AdminMessages.I.effectiveMaxObjectSizeLimit(result.maxObjectSizeLimit().value()));
       effectiveMaxObjectSizeLimit.setTitle(
-          Util.M.globalMaxObjectSizeLimit(result.maxObjectSizeLimit().inheritedValue()));
+          AdminMessages.I.globalMaxObjectSizeLimit(result.maxObjectSizeLimit().inheritedValue()));
     } else {
       effectiveMaxObjectSizeLimit.setVisible(false);
     }
@@ -421,7 +421,7 @@
       Map<String, HasEnabled> widgetMap = new HashMap<>();
       pluginConfigWidgets.put(pluginName, widgetMap);
       LabeledWidgetsGrid g = new LabeledWidgetsGrid();
-      g.addHeader(new SmallHeading(Util.M.pluginProjectOptionsTitle(pluginName)));
+      g.addHeader(new SmallHeading(AdminMessages.I.pluginProjectOptionsTitle(pluginName)));
       pluginOptionsPanel.add(g);
       NativeMap<ConfigParameterInfo> pluginConfig = info.pluginConfig(pluginName);
       pluginConfig.copyKeysIntoChildren("name");
@@ -460,7 +460,8 @@
     NpTextBox textBox = param.type().equals("STRING") ? new NpTextBox() : new NpIntTextBox();
     if (param.inheritable()) {
       textBox.setValue(param.configuredValue());
-      Label inheritedLabel = new Label(Util.M.pluginProjectInheritedValue(param.inheritedValue()));
+      Label inheritedLabel =
+          new Label(AdminMessages.I.pluginProjectInheritedValue(param.inheritedValue()));
       inheritedLabel.setStyleName(Gerrit.RESOURCES.css().pluginProjectConfigInheritedValue());
       HorizontalPanel p = new HorizontalPanel();
       p.add(textBox);
@@ -500,7 +501,7 @@
     }
     ListBox listBox = new ListBox();
     if (param.inheritable()) {
-      listBox.addItem(Util.M.pluginProjectInheritedListValue(param.inheritedValue()));
+      listBox.addItem(AdminMessages.I.pluginProjectInheritedListValue(param.inheritedValue()));
       if (param.configuredValue() == null) {
         listBox.setSelectedIndex(0);
       }
@@ -532,7 +533,7 @@
         // since the listBox is disabled the inherited value cannot be
         // seen and we have to display it explicitly
         Label inheritedLabel =
-            new Label(Util.M.pluginProjectInheritedValue(param.inheritedValue()));
+            new Label(AdminMessages.I.pluginProjectInheritedValue(param.inheritedValue()));
         inheritedLabel.setStyleName(Gerrit.RESOURCES.css().pluginProjectConfigInheritedValue());
         HorizontalPanel p = new HorizontalPanel();
         p.add(listBox);
@@ -599,11 +600,11 @@
       return;
     }
     actions.copyKeysIntoChildren("id");
-    actionsGrid.addHeader(new SmallHeading(Util.C.headingProjectCommands()));
+    actionsGrid.addHeader(new SmallHeading(AdminConstants.I.headingProjectCommands()));
     FlowPanel actionsPanel = new FlowPanel();
     actionsPanel.setStyleName(Gerrit.RESOURCES.css().projectActions());
     actionsPanel.setVisible(true);
-    actionsGrid.add(Util.C.headingCommands(), actionsPanel);
+    actionsGrid.add(AdminConstants.I.headingCommands(), actionsPanel);
 
     for (String id : actions.keySet()) {
       actionsPanel.add(new ActionButton(getProjectKey(), actions.get(id)));
@@ -621,9 +622,9 @@
   }
 
   private Button createChangeAction() {
-    final Button createChange = new Button(Util.C.buttonCreateChange());
+    final Button createChange = new Button(AdminConstants.I.buttonCreateChange());
     createChange.setStyleName("");
-    createChange.setTitle(Util.C.buttonCreateChangeDescription());
+    createChange.setTitle(AdminConstants.I.buttonCreateChangeDescription());
     createChange.addClickHandler(
         new ClickHandler() {
           @Override
@@ -635,9 +636,9 @@
   }
 
   private Button createEditConfigAction() {
-    final Button editConfig = new Button(Util.C.buttonEditConfig());
+    final Button editConfig = new Button(AdminConstants.I.buttonEditConfig());
     editConfig.setStyleName("");
-    editConfig.setTitle(Util.C.buttonEditConfigDescription());
+    editConfig.setTitle(AdminConstants.I.buttonEditConfigDescription());
     editConfig.addClickHandler(
         new ClickHandler() {
           @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index 63455b5..604a0dc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -71,7 +71,7 @@
   @Override
   protected void onInitUI() {
     super.onInitUI();
-    setPageTitle(Util.C.projectListTitle());
+    setPageTitle(AdminConstants.I.projectListTitle());
     initPageHeader();
 
     prev = PagingHyperlink.createPrev();
@@ -85,7 +85,7 @@
           @Override
           protected void initColumnHeaders() {
             super.initColumnHeaders();
-            table.setText(0, ProjectsTable.C_REPO_BROWSER, Util.C.projectRepoBrowser());
+            table.setText(0, ProjectsTable.C_REPO_BROWSER, AdminConstants.I.projectRepoBrowser());
             table
                 .getFlexCellFormatter()
                 .addStyleName(0, ProjectsTable.C_REPO_BROWSER, Gerrit.RESOURCES.css().dataHeader());
@@ -162,7 +162,7 @@
   private void initPageHeader() {
     final HorizontalPanel hp = new HorizontalPanel();
     hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
-    final Label filterLabel = new Label(Util.C.projectFilter());
+    final Label filterLabel = new Label(AdminConstants.I.projectFilter());
     filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
     hp.add(filterLabel);
     filterTxt = new NpTextBox();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
index a63dae4..3328163 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
@@ -49,7 +49,7 @@
   protected void onInitUI() {
     super.onInitUI();
     if (name != null) {
-      setPageTitle(Util.M.project(name.get()));
+      setPageTitle(AdminMessages.I.project(name.get()));
     }
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
index 41f89f9..e64f569 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
@@ -83,7 +83,7 @@
     parseToken();
     HorizontalPanel hp = new HorizontalPanel();
     hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
-    Label filterLabel = new Label(Util.C.projectFilter());
+    Label filterLabel = new Label(AdminConstants.I.projectFilter());
     filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
     hp.add(filterLabel);
     filterTxt = new NpTextBox();
@@ -109,8 +109,8 @@
 
     TagsTable() {
       table.setWidth("");
-      table.setText(0, 1, Util.C.columnTagName());
-      table.setText(0, 2, Util.C.columnBranchRevision());
+      table.setText(0, 1, AdminConstants.I.columnTagName());
+      table.setText(0, 2, AdminConstants.I.columnBranchRevision());
 
       FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
index a979ba7..f1180cc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/RefPatternBox.java
@@ -45,11 +45,11 @@
           String ref = text.toString();
 
           if (ref.isEmpty()) {
-            throw new ParseException(Util.C.refErrorEmpty(), 0);
+            throw new ParseException(AdminConstants.I.refErrorEmpty(), 0);
           }
 
           if (ref.charAt(0) == '/') {
-            throw new ParseException(Util.C.refErrorBeginSlash(), 0);
+            throw new ParseException(AdminConstants.I.refErrorBeginSlash(), 0);
           }
 
           if (ref.charAt(0) == '^') {
@@ -64,15 +64,15 @@
             final char c = ref.charAt(i);
 
             if (c == '/' && 0 < i && ref.charAt(i - 1) == '/') {
-              throw new ParseException(Util.C.refErrorDoubleSlash(), i);
+              throw new ParseException(AdminConstants.I.refErrorDoubleSlash(), i);
             }
 
             if (c == ' ') {
-              throw new ParseException(Util.C.refErrorNoSpace(), i);
+              throw new ParseException(AdminConstants.I.refErrorNoSpace(), i);
             }
 
             if (c < ' ') {
-              throw new ParseException(Util.C.refErrorPrintable(), i);
+              throw new ParseException(AdminConstants.I.refErrorPrintable(), i);
             }
           }
           return ref;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 3a46203..f08cdd8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -21,8 +21,6 @@
 import com.google.gwtjsonrpc.client.JsonUtil;
 
 public class Util {
-  public static final AdminConstants C = GWT.create(AdminConstants.class);
-  public static final AdminMessages M = GWT.create(AdminMessages.class);
   public static final ProjectAdminService PROJECT_SVC;
 
   static {
@@ -38,17 +36,17 @@
     }
     switch (type) {
       case FAST_FORWARD_ONLY:
-        return C.projectSubmitType_FAST_FORWARD_ONLY();
+        return AdminConstants.I.projectSubmitType_FAST_FORWARD_ONLY();
       case MERGE_IF_NECESSARY:
-        return C.projectSubmitType_MERGE_IF_NECESSARY();
+        return AdminConstants.I.projectSubmitType_MERGE_IF_NECESSARY();
       case REBASE_IF_NECESSARY:
-        return C.projectSubmitType_REBASE_IF_NECESSARY();
+        return AdminConstants.I.projectSubmitType_REBASE_IF_NECESSARY();
       case REBASE_ALWAYS:
-        return C.projectSubmitType_REBASE_ALWAYS();
+        return AdminConstants.I.projectSubmitType_REBASE_ALWAYS();
       case MERGE_ALWAYS:
-        return C.projectSubmitType_MERGE_ALWAYS();
+        return AdminConstants.I.projectSubmitType_MERGE_ALWAYS();
       case CHERRY_PICK:
-        return C.projectSubmitType_CHERRY_PICK();
+        return AdminConstants.I.projectSubmitType_CHERRY_PICK();
       default:
         return type.name();
     }
@@ -60,11 +58,11 @@
     }
     switch (type) {
       case ACTIVE:
-        return C.projectState_ACTIVE();
+        return AdminConstants.I.projectState_ACTIVE();
       case READ_ONLY:
-        return C.projectState_READ_ONLY();
+        return AdminConstants.I.projectState_READ_ONLY();
       case HIDDEN:
-        return C.projectState_HIDDEN();
+        return AdminConstants.I.projectState_HIDDEN();
       default:
         return type.name();
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java
index 2c21b74..a0eaef7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java
@@ -17,8 +17,6 @@
 import com.google.gwt.i18n.client.Constants;
 
 public interface OpenIdConstants extends Constants {
-  String nameGoogle();
-
   String nameLaunchpad();
 
   String nameYahoo();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties
index 08ddf38..d6e8de6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties
@@ -1,3 +1,2 @@
-nameGoogle = Google Account
 nameLaunchpad = Launchpad ID
 nameYahoo = Yahoo! ID
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
index 5e7110d..8609774 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client.change;
 
-import com.google.gerrit.client.admin.Util;
+import com.google.gerrit.client.admin.AdminConstants;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.info.GroupBaseInfo;
@@ -75,7 +75,8 @@
         this.displayString = replacementString;
       } else {
         this.replacementString = reviewer.group().name();
-        this.displayString = replacementString + " (" + Util.C.suggestedGroupLabel() + ")";
+        this.displayString =
+            replacementString + " (" + AdminConstants.I.suggestedGroupLabel() + ")";
       }
     }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java
index e4ad903..7f0ef68 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java
@@ -15,16 +15,16 @@
 package com.google.gerrit.client.ui;
 
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.admin.Util;
+import com.google.gerrit.client.admin.AdminConstants;
 
 public class PagingHyperlink extends Hyperlink {
 
   public static PagingHyperlink createPrev() {
-    return new PagingHyperlink(Util.C.pagedListPrev());
+    return new PagingHyperlink(AdminConstants.I.pagedListPrev());
   }
 
   public static PagingHyperlink createNext() {
-    return new PagingHyperlink(Util.C.pagedListNext());
+    return new PagingHyperlink(AdminConstants.I.pagedListNext());
   }
 
   private PagingHyperlink(String text) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
index 8e6ee42..cace84b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
@@ -112,7 +112,8 @@
   private void createWidgets(final String popupText, final String currentPageLink) {
     filterPanel = new HorizontalPanel();
     filterPanel.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
-    final Label filterLabel = new Label(com.google.gerrit.client.admin.Util.C.projectFilter());
+    final Label filterLabel =
+        new Label(com.google.gerrit.client.admin.AdminConstants.I.projectFilter());
     filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
     filterPanel.add(filterLabel);
     filterTxt = new NpTextBox();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectSearchLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectSearchLink.java
index b383a83..f0e06a0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectSearchLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectSearchLink.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.client.ui;
 
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.admin.Util;
+import com.google.gerrit.client.admin.AdminConstants;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.user.client.DOM;
@@ -25,7 +25,7 @@
 
   public ProjectSearchLink(Project.NameKey projectName) {
     super(" ", PageLinks.toProjectDefaultDashboard(projectName));
-    setTitle(Util.C.projectListQueryLink());
+    setTitle(AdminConstants.I.projectListQueryLink());
     final Image image = new Image(Gerrit.RESOURCES.queryIcon());
     image.setStyleName(Gerrit.RESOURCES.css().queryIcon());
     DOM.insertBefore(getElement(), image.getElement(), DOM.getFirstChild(getElement()));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
index 4398c78..9aab920 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.httpd.rpc;
 
-import com.google.gerrit.httpd.rpc.account.AccountModule;
 import com.google.gerrit.httpd.rpc.project.ProjectModule;
 
 /** Registers servlets to answer RPCs from client UI. */
@@ -27,7 +26,6 @@
   protected void configureServlets() {
     rpc(SystemInfoServiceImpl.class);
 
-    install(new AccountModule());
     install(new ProjectModule());
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
deleted file mode 100644
index 120b582..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.account;
-
-import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.httpd.rpc.RpcServletModule;
-import com.google.gerrit.httpd.rpc.UiRpcModule;
-
-public class AccountModule extends RpcServletModule {
-  public AccountModule() {
-    super(UiRpcModule.PREFIX);
-  }
-
-  @Override
-  protected void configureServlets() {
-    install(
-        new FactoryModule() {
-          @Override
-          protected void configure() {
-            factory(DeleteExternalIds.Factory.class);
-            factory(ExternalIdDetailFactory.Factory.class);
-          }
-        });
-    rpc(AccountSecurityImpl.class);
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
deleted file mode 100644
index 5a6fdfa..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.account;
-
-import com.google.gerrit.common.data.AccountSecurity;
-import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.util.List;
-import java.util.Set;
-
-class AccountSecurityImpl extends BaseServiceImplementation implements AccountSecurity {
-  private final DeleteExternalIds.Factory deleteExternalIdsFactory;
-  private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
-
-  @Inject
-  AccountSecurityImpl(
-      final Provider<ReviewDb> schema,
-      final Provider<CurrentUser> currentUser,
-      final DeleteExternalIds.Factory deleteExternalIdsFactory,
-      final ExternalIdDetailFactory.Factory externalIdDetailFactory) {
-    super(schema, currentUser);
-    this.deleteExternalIdsFactory = deleteExternalIdsFactory;
-    this.externalIdDetailFactory = externalIdDetailFactory;
-  }
-
-  @Override
-  public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
-    externalIdDetailFactory.create().to(callback);
-  }
-
-  @Override
-  public void deleteExternalIds(
-      final Set<AccountExternalId.Key> keys,
-      final AsyncCallback<Set<AccountExternalId.Key>> callback) {
-    deleteExternalIdsFactory.create(keys).to(callback);
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java
deleted file mode 100644
index ddfed8b..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.account;
-
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-class DeleteExternalIds extends Handler<Set<AccountExternalId.Key>> {
-  interface Factory {
-    DeleteExternalIds create(Set<AccountExternalId.Key> keys);
-  }
-
-  private final ReviewDb db;
-  private final IdentifiedUser user;
-  private final ExternalIdDetailFactory detailFactory;
-  private final AccountByEmailCache byEmailCache;
-  private final AccountCache accountCache;
-
-  private final Set<AccountExternalId.Key> keys;
-
-  @Inject
-  DeleteExternalIds(
-      final ReviewDb db,
-      final IdentifiedUser user,
-      final ExternalIdDetailFactory detailFactory,
-      final AccountByEmailCache byEmailCache,
-      final AccountCache accountCache,
-      @Assisted final Set<AccountExternalId.Key> keys) {
-    this.db = db;
-    this.user = user;
-    this.detailFactory = detailFactory;
-    this.byEmailCache = byEmailCache;
-    this.accountCache = accountCache;
-
-    this.keys = keys;
-  }
-
-  @Override
-  public Set<AccountExternalId.Key> call() throws OrmException, IOException {
-    final Map<AccountExternalId.Key, AccountExternalId> have = have();
-
-    List<AccountExternalId> toDelete = new ArrayList<>();
-    for (AccountExternalId.Key k : keys) {
-      final AccountExternalId id = have.get(k);
-      if (id != null && id.canDelete()) {
-        toDelete.add(id);
-      }
-    }
-
-    if (!toDelete.isEmpty()) {
-      db.accountExternalIds().delete(toDelete);
-      accountCache.evict(user.getAccountId());
-      for (AccountExternalId e : toDelete) {
-        byEmailCache.evict(e.getEmailAddress());
-      }
-    }
-
-    return toKeySet(toDelete);
-  }
-
-  private Map<AccountExternalId.Key, AccountExternalId> have() throws OrmException {
-    Map<AccountExternalId.Key, AccountExternalId> r;
-
-    r = new HashMap<>();
-    for (AccountExternalId i : detailFactory.call()) {
-      r.put(i.getKey(), i);
-    }
-    return r;
-  }
-
-  private Set<AccountExternalId.Key> toKeySet(List<AccountExternalId> toDelete) {
-    Set<AccountExternalId.Key> r = new HashSet<>();
-    for (AccountExternalId i : toDelete) {
-      r.add(i.getKey());
-    }
-    return r;
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java
deleted file mode 100644
index 80b7b9b..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.account;
-
-import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
-
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.httpd.WebSession;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.AuthConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import java.util.Collections;
-import java.util.List;
-
-class ExternalIdDetailFactory extends Handler<List<AccountExternalId>> {
-  interface Factory {
-    ExternalIdDetailFactory create();
-  }
-
-  private final ReviewDb db;
-  private final IdentifiedUser user;
-  private final AuthConfig authConfig;
-  private final DynamicItem<WebSession> session;
-
-  @Inject
-  ExternalIdDetailFactory(
-      final ReviewDb db,
-      final IdentifiedUser user,
-      final AuthConfig authConfig,
-      final DynamicItem<WebSession> session) {
-    this.db = db;
-    this.user = user;
-    this.authConfig = authConfig;
-    this.session = session;
-  }
-
-  @Override
-  public List<AccountExternalId> call() throws OrmException {
-    final AccountExternalId.Key last = session.get().getLastLoginExternalId();
-    final List<AccountExternalId> ids =
-        db.accountExternalIds().byAccount(user.getAccountId()).toList();
-
-    for (final AccountExternalId e : ids) {
-      e.setTrusted(authConfig.isIdentityTrustable(Collections.singleton(e)));
-
-      // The identity can be deleted only if its not the one used to
-      // establish this web session, and if only if an identity was
-      // actually used to establish this web session.
-      //
-      if (e.isScheme(SCHEME_USERNAME)) {
-        e.setCanDelete(false);
-      } else {
-        e.setCanDelete(last != null && !last.equals(e.getKey()));
-      }
-    }
-    return ids;
-  }
-}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 5477b31..ba5780e 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -127,6 +127,8 @@
       ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName();
   private static final String SUBMIT_RECORD_STRICT_FIELD =
       ChangeField.STORED_SUBMIT_RECORD_STRICT.getName();
+  private static final String UNRESOLVED_COMMENT_COUNT_FIELD =
+      ChangeField.UNRESOLVED_COMMENT_COUNT.getName();
 
   static Term idTerm(ChangeData cd) {
     return QueryBuilder.intTerm(LEGACY_ID.getName(), cd.getId().get());
@@ -467,6 +469,8 @@
     if (fields.contains(REF_STATE_PATTERN_FIELD)) {
       decodeRefStatePatterns(doc, cd);
     }
+
+    decodeUnresolvedCommentCount(doc, cd);
     return cd;
   }
 
@@ -568,6 +572,14 @@
     cd.setRefStatePatterns(copyAsBytes(doc.get(REF_STATE_PATTERN_FIELD)));
   }
 
+  private void decodeUnresolvedCommentCount(
+      ListMultimap<String, IndexableField> doc, ChangeData cd) {
+    IndexableField f = Iterables.getFirst(doc.get(UNRESOLVED_COMMENT_COUNT_FIELD), null);
+    if (f != null && f.numericValue() != null) {
+      cd.setUnresolvedCommentCount(f.numericValue().intValue());
+    }
+  }
+
   private static <T> List<T> decodeProtos(
       ListMultimap<String, IndexableField> doc, String fieldName, ProtobufCodec<T> codec) {
     Collection<IndexableField> fields = doc.get(fieldName);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index 3a43412..ec30e4f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -131,9 +131,5 @@
     boolean def = flags.cfg.getBoolean(RECEIVE, ENABLE_SIGNED_PUSH, false);
     boolean enable = ui.yesno(def, "Enable signed push support");
     receive.set("enableSignedPush", Boolean.toString(enable));
-    if (enable) {
-      libraries.bouncyCastleProvider.downloadRequired();
-      libraries.bouncyCastlePGP.downloadRequired();
-    }
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index d49a267..d6e682d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -79,12 +79,6 @@
     port = ui.readInt(port, "Listen on port");
     sshd.set("listenAddress", SocketUtil.format(hostname, port));
 
-    if (exists(site.ssh_rsa) || exists(site.ssh_dsa)) {
-      libraries.bouncyCastleSSL.downloadRequired();
-    } else if (!exists(site.ssh_key)) {
-      libraries.bouncyCastleSSL.downloadOptional();
-    }
-
     generateSshHostKeys();
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index de87ba9..526f172 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -40,9 +40,6 @@
   private final List<String> skippedDownloads;
   private final boolean skipAllDownloads;
 
-  /* final */ LibraryDownloader bouncyCastlePGP;
-  /* final */ LibraryDownloader bouncyCastleProvider;
-  /* final */ LibraryDownloader bouncyCastleSSL;
   /* final */ LibraryDownloader db2Driver;
   /* final */ LibraryDownloader db2DriverLicense;
   /* final */ LibraryDownloader hanaDriver;
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
index 3a0d7e5..fa0cf2e 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
@@ -12,30 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-# Version should match lib/bouncycastle/BUCK
-[library "bouncyCastleProvider"]
-  name = Bouncy Castle Crypto Provider v156
-  url = https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.56/bcprov-jdk15on-1.56.jar
-  sha1 = a153c6f9744a3e9dd6feab5e210e1c9861362ec7
-  remove = bcprov-.*[.]jar
-
-# Version should match lib/bouncycastle/BUCK
-[library "bouncyCastleSSL"]
-  name = Bouncy Castle Crypto SSL v156
-  url = https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.56/bcpkix-jdk15on-1.56.jar
-  sha1 = 4648af70268b6fdb24674fb1fd7c1fcc73db1231
-  needs = bouncyCastleProvider
-  remove = bcpkix-.*[.]jar
-
-# Version should match lib/bouncycastle/BUCK
-[library "bouncyCastlePGP"]
-  name = Bouncy Castle Crypto OpenPGP v156
-  url = https://repo1.maven.org/maven2/org/bouncycastle/bcpg-jdk15on/1.56/bcpg-jdk15on-1.56.jar
-  sha1 = 9c3f2e7072c8cc1152079b5c25291a9f462631f1
-  needs = bouncyCastleProvider
-  remove = bcpg-.*[.]jar
-
 [library "mysqlDriver"]
   name = MySQL Connector/J 5.1.40
   url = https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.40/mysql-connector-java-5.1.40.jar
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
index 6585650..e05d3de 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
@@ -45,7 +45,6 @@
             Collections.<String>emptyList(),
             false);
 
-    assertNotNull(lib.bouncyCastleProvider);
     assertNotNull(lib.mysqlDriver);
 
     verify(ui);
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index 17496e8..9e9c640 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -214,7 +214,7 @@
         ["src/test/java/**/*.java"],
         exclude = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE + QUERY_TESTS,
     ),
-    resources = glob(["src/test/resources/com/google/gerrit/server/mail/*"]),
+    resources = glob(["src/test/resources/com/google/gerrit/server/**/*"]),
     visibility = ["//visibility:public"],
     deps = TESTUTIL_DEPS + [
         ":testutil",
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 be719c5..3b765ef 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
@@ -477,6 +477,7 @@
     out.created = in.getCreatedOn();
     out.updated = in.getLastUpdatedOn();
     out._number = in.getId().get();
+    out.unresolvedCommentCount = cd.unresolvedCommentCount();
 
     if (user.isIdentifiedUser()) {
       Collection<String> stars = cd.stars(user.getAccountId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 790c241..d0c0e29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -244,7 +244,10 @@
         batchUpdateFactory.create(
             db.get(), revision.getChange().getProject(), revision.getUser(), ts)) {
       Account.Id id = bu.getUser().getAccountId();
-      boolean ccOrReviewer = input.labels != null && !input.labels.isEmpty();
+      boolean ccOrReviewer = false;
+      if (input.labels != null && !input.labels.isEmpty()) {
+        ccOrReviewer = input.labels.values().stream().filter(v -> v != 0).findFirst().isPresent();
+      }
 
       if (!ccOrReviewer) {
         // Check if user was already CCed or reviewing prior to this review.
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 1206156..e4bc86f 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
@@ -658,12 +658,14 @@
     }
 
     // Update superproject gitlinks if required.
-    try (MergeOpRepoManager orm = ormProvider.get()) {
-      orm.setContext(db, TimeUtil.nowTs(), user, receiveId);
-      SubmoduleOp op = subOpFactory.create(branches, orm);
-      op.updateSuperProjects();
-    } catch (SubmoduleException e) {
-      logError("Can't update the superprojects", e);
+    if (!branches.isEmpty()) {
+      try (MergeOpRepoManager orm = ormProvider.get()) {
+        orm.setContext(db, TimeUtil.nowTs(), user, receiveId);
+        SubmoduleOp op = subOpFactory.create(branches, orm);
+        op.updateSuperProjects();
+      } catch (SubmoduleException e) {
+        logError("Can't update the superprojects", e);
+      }
     }
 
     closeProgress.end();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index 4747e1a..e2ca3dd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -547,6 +547,16 @@
         }
       };
 
+  /** Number of unresolved comments of the change. */
+  public static final FieldDef<ChangeData, Integer> UNRESOLVED_COMMENT_COUNT =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_UNRESOLVED_COMMENT_COUNT, FieldType.INTEGER_RANGE, true) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args) throws OrmException {
+          return input.unresolvedCommentCount();
+        }
+      };
+
   /** Whether the change is mergeable. */
   public static final FieldDef<ChangeData, String> MERGEABLE =
       new FieldDef.Single<ChangeData, String>(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 76a9bc9..a00dfe2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -85,7 +85,9 @@
   static final Schema<ChangeData> V36 =
       schema(V35, ChangeField.REF_STATE, ChangeField.REF_STATE_PATTERN);
 
-  static final Schema<ChangeData> V37 = schema(V36);
+  @Deprecated static final Schema<ChangeData> V37 = schema(V36);
+
+  static final Schema<ChangeData> V38 = schema(V37, ChangeField.UNRESOLVED_COMMENT_COUNT);
 
   public static final String NAME = "changes";
   public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
index 163d414..f282c2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
@@ -91,7 +91,7 @@
           && !e.className().startsWith("gmail")) {
         // This is a comment typed by the user
         // Replace non-breaking spaces and trim string
-        String content = e.ownText() .replace('\u00a0', ' ').trim();
+        String content = e.ownText().replace('\u00a0', ' ').trim();
         if (!Strings.isNullOrEmpty(content)) {
           if (lastEncounteredComment == null && lastEncounteredFileName == null) {
             // Remove quotation line, email signature and
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 54b30c7..599278f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -231,12 +231,13 @@
 
       String changeMsg = "Patch Set " + psId.get() + ":";
       if (parsedComments.get(0).type == MailComment.CommentType.CHANGE_MESSAGE) {
+        // Add a blank line after Patch Set to follow the default format
         if (parsedComments.size() > 1) {
-          changeMsg += "\n" + numComments(parsedComments.size() - 1);
+          changeMsg += "\n\n" + numComments(parsedComments.size() - 1);
         }
-        changeMsg += "\n" + parsedComments.get(0).message;
+        changeMsg += "\n\n" + parsedComments.get(0).message;
       } else {
-        changeMsg += "\n" + numComments(parsedComments.size());
+        changeMsg += "\n\n" + numComments(parsedComments.size());
       }
 
       changeMessage = ChangeMessagesUtil.newMessage(ctx, changeMsg, tag);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 17e43f2..84af7be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -41,6 +41,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RobotComment;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -83,6 +84,7 @@
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -334,6 +336,7 @@
   private Map<Integer, List<String>> files;
   private Map<Integer, Optional<DiffSummary>> diffSummaries;
   private Collection<Comment> publishedComments;
+  private Collection<RobotComment> robotComments;
   private CurrentUser visibleTo;
   private ChangeControl changeControl;
   private List<ChangeMessage> messages;
@@ -351,6 +354,7 @@
   private List<ReviewerStatusUpdate> reviewerUpdates;
   private PersonIdent author;
   private PersonIdent committer;
+  private Integer unresolvedCommentCount;
 
   private ImmutableList<byte[]> refStates;
   private ImmutableList<byte[]> refStatePatterns;
@@ -977,6 +981,34 @@
     return publishedComments;
   }
 
+  public Collection<RobotComment> robotComments() throws OrmException {
+    if (robotComments == null) {
+      if (!lazyLoad) {
+        return Collections.emptyList();
+      }
+      robotComments = commentsUtil.robotCommentsByChange(notes());
+    }
+    return robotComments;
+  }
+
+  public Integer unresolvedCommentCount() throws OrmException {
+    if (unresolvedCommentCount == null) {
+      if (!lazyLoad) {
+        return null;
+      }
+      Long count =
+          Stream.concat(publishedComments().stream(), robotComments().stream())
+              .filter(c -> (c.unresolved == Boolean.TRUE))
+              .count();
+      unresolvedCommentCount = count.intValue();
+    }
+    return unresolvedCommentCount;
+  }
+
+  public void setUnresolvedCommentCount(Integer count) {
+    this.unresolvedCommentCount = count;
+  }
+
   public List<ChangeMessage> messages() throws OrmException {
     if (messages == null) {
       if (!lazyLoad) {
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 2af5cd8..aa220e1 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
@@ -159,6 +159,7 @@
   public static final String FIELD_STATUS = "status";
   public static final String FIELD_SUBMISSIONID = "submissionid";
   public static final String FIELD_TR = "tr";
+  public static final String FIELD_UNRESOLVED_COMMENT_COUNT = "unresolved";
   public static final String FIELD_VISIBLETO = "visibleto";
   public static final String FIELD_WATCHEDBY = "watchedby";
 
@@ -513,6 +514,10 @@
       return new EditByPredicate(self());
     }
 
+    if ("unresolved".equalsIgnoreCase(value)) {
+      return new IsUnresolvedPredicate();
+    }
+
     // for plugins the value will be operandName_pluginName
     String[] names = value.split("_");
     if (names.length == 2) {
@@ -677,7 +682,7 @@
     // label:CodeReview=1,jsmith or
     // label:CodeReview=1,group=android_approvers or
     // label:CodeReview=1,android_approvers
-    //  user/groups without a label will first attempt to match user
+    // user/groups without a label will first attempt to match user
     // Special case: votes by owners can be tracked with ",owner":
     // label:Code-Review+2,owner
     // label:Code-Review+2,user=owner
@@ -1056,6 +1061,11 @@
     return new SubmittablePredicate(status);
   }
 
+  @Operator
+  public Predicate<ChangeData> unresolved(String value) throws QueryParseException {
+    return new IsUnresolvedPredicate(value);
+  }
+
   @Override
   protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
     if (query.startsWith("refs/")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
new file mode 100644
index 0000000..17a6347
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 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.query.change;
+
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+
+public class IsUnresolvedPredicate extends IntegerRangeChangePredicate {
+  IsUnresolvedPredicate() throws QueryParseException {
+    this(">0");
+  }
+
+  IsUnresolvedPredicate(String value) throws QueryParseException {
+    super(ChangeField.UNRESOLVED_COMMENT_COUNT, value);
+  }
+
+  @Override
+  protected Integer getValueInt(ChangeData changeData) throws OrmException {
+    return ChangeField.UNRESOLVED_COMMENT_COUNT.get(changeData, null);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
index d0751a9..650024c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
@@ -14,30 +14,14 @@
 
 package com.google.gerrit.server.query.group;
 
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.group.GroupField;
 import com.google.gerrit.server.query.Predicate;
-import java.util.List;
 import java.util.Locale;
 
 public class GroupPredicates {
-  public static Predicate<AccountGroup> defaultPredicate(String query) {
-    // Adapt the capacity of this list when adding more default predicates.
-    List<Predicate<AccountGroup>> preds = Lists.newArrayListWithCapacity(5);
-    preds.add(uuid(new AccountGroup.UUID(query)));
-    preds.add(name(query));
-    preds.add(inname(query));
-    if (!Strings.isNullOrEmpty(query)) {
-      preds.add(description(query));
-    }
-    preds.add(owner(query));
-    return Predicate.or(preds);
-  }
-
   public static Predicate<AccountGroup> uuid(AccountGroup.UUID uuid) {
     return new GroupPredicate(GroupField.UUID, GroupQueryBuilder.FIELD_UUID, uuid.get());
   }
@@ -57,8 +41,9 @@
         GroupField.NAME, GroupQueryBuilder.FIELD_NAME, name.toLowerCase(Locale.US));
   }
 
-  public static Predicate<AccountGroup> owner(String owner) {
-    return new GroupPredicate(GroupField.OWNER_UUID, GroupQueryBuilder.FIELD_OWNER, owner);
+  public static Predicate<AccountGroup> owner(AccountGroup.UUID ownerUuid) {
+    return new GroupPredicate(
+        GroupField.OWNER_UUID, GroupQueryBuilder.FIELD_OWNER, ownerUuid.get());
   }
 
   public static Predicate<AccountGroup> isVisibleToAll() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
index f9093e1..3197ab7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
@@ -15,13 +15,19 @@
 package com.google.gerrit.server.query.group;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.query.LimitPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryBuilder;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
+import java.util.List;
 
 /** Parses a query string meant to be applied to group objects. */
 public class GroupQueryBuilder extends QueryBuilder<AccountGroup> {
@@ -35,9 +41,23 @@
   private static final QueryBuilder.Definition<AccountGroup, GroupQueryBuilder> mydef =
       new QueryBuilder.Definition<>(GroupQueryBuilder.class);
 
+  public static class Arguments {
+    final GroupCache groupCache;
+    final GroupBackend groupBackend;
+
+    @Inject
+    Arguments(GroupCache groupCache, GroupBackend groupBackend) {
+      this.groupCache = groupCache;
+      this.groupBackend = groupBackend;
+    }
+  }
+
+  private final Arguments args;
+
   @Inject
-  GroupQueryBuilder() {
+  GroupQueryBuilder(Arguments args) {
     super(mydef);
+    this.args = args;
   }
 
   @Operator
@@ -68,8 +88,16 @@
   }
 
   @Operator
-  public Predicate<AccountGroup> owner(String owner) {
-    return GroupPredicates.owner(owner);
+  public Predicate<AccountGroup> owner(String owner) throws QueryParseException {
+    AccountGroup group = args.groupCache.get(new AccountGroup.UUID(owner));
+    if (group != null) {
+      return GroupPredicates.owner(group.getGroupUUID());
+    }
+    GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, owner);
+    if (g == null) {
+      throw error("Group " + owner + " not found");
+    }
+    return GroupPredicates.owner(g.getUUID());
   }
 
   @Operator
@@ -81,8 +109,21 @@
   }
 
   @Override
-  protected Predicate<AccountGroup> defaultField(String query) {
-    return GroupPredicates.defaultPredicate(query);
+  protected Predicate<AccountGroup> defaultField(String query) throws QueryParseException {
+    // Adapt the capacity of this list when adding more default predicates.
+    List<Predicate<AccountGroup>> preds = Lists.newArrayListWithCapacity(5);
+    preds.add(uuid(query));
+    preds.add(name(query));
+    preds.add(inname(query));
+    if (!Strings.isNullOrEmpty(query)) {
+      preds.add(description(query));
+    }
+    try {
+      preds.add(owner(query));
+    } catch (QueryParseException e) {
+      // Skip.
+    }
+    return Predicate.or(preds);
   }
 
   @Operator
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/ValidatorTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java
rename to gerrit-server/src/test/java/com/google/gerrit/server/mail/send/ValidatorTest.java
index 0620b24..23b8d5a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/ValidatorTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assert_;
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 bf3e618..1b9fc61 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
@@ -1549,6 +1549,37 @@
   }
 
   @Test
+  public void byUnresolved() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    Change change1 = insert(repo, newChange(repo));
+    Change change2 = insert(repo, newChange(repo));
+    Change change3 = insert(repo, newChange(repo));
+
+    // Change1 has one resolved comment (unresolvedcount = 0)
+    // Change2 has one unresolved comment (unresolvedcount = 1)
+    // Change3 has one resolved comment and one unresolved comment (unresolvedcount = 1)
+    addComment(change1.getChangeId(), "comment 1", false);
+    addComment(change2.getChangeId(), "comment 2", true);
+    addComment(change3.getChangeId(), "comment 3", false);
+    addComment(change3.getChangeId(), "comment 4", true);
+
+    assertQuery("has:unresolved", change3, change2);
+
+    assertQuery("unresolved:0", change1);
+    List<ChangeInfo> changeInfos = assertQuery("unresolved:>=0", change3, change2, change1);
+    assertThat(changeInfos.get(0).unresolvedCommentCount).isEqualTo(1); // Change3
+    assertThat(changeInfos.get(1).unresolvedCommentCount).isEqualTo(1); // Change2
+    assertThat(changeInfos.get(2).unresolvedCommentCount).isEqualTo(0); // Change1
+    assertQuery("unresolved:>0", change3, change2);
+
+    assertQuery("unresolved:<1", change1);
+    assertQuery("unresolved:<=1", change3, change2, change1);
+    assertQuery("unresolved:1", change3, change2);
+    assertQuery("unresolved:>1");
+    assertQuery("unresolved:>=1", change3, change2);
+  }
+
+  @Test
   public void byCommitsOnBranchNotMerged() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     int n = 10;
@@ -1595,6 +1626,7 @@
     cd.changedLines();
     cd.reviewedBy();
     cd.reviewers();
+    cd.unresolvedCommentCount();
 
     // TODO(dborowitz): Swap out GitRepositoryManager somehow? Will probably be
     // necessary for NoteDb anyway.
@@ -1932,4 +1964,16 @@
   protected static long lastUpdatedMs(Change c) {
     return c.getLastUpdatedOn().getTime();
   }
+
+  private void addComment(int changeId, String message, Boolean unresolved) throws Exception {
+    ReviewInput input = new ReviewInput();
+    ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
+    comment.line = 1;
+    comment.message = message;
+    comment.unresolved = unresolved;
+    input.comments =
+        ImmutableMap.<String, List<ReviewInput.CommentInput>>of(
+            Patch.COMMIT_MSG, ImmutableList.<ReviewInput.CommentInput>of(comment));
+    gApi.changes().id(changeId).current().review(input);
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index 2813c03..a0e5ee0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -216,14 +216,15 @@
 
   @Test
   public void byOwner() throws Exception {
-    assertQuery("owner:non-existing");
-
     GroupInfo ownerGroup = createGroup(name("owner-group"));
     GroupInfo group = createGroupWithOwner(name("group"), ownerGroup);
     createGroup(name("group2"));
 
+    assertQuery("owner:" + group.id);
+
     // ownerGroup owns itself
     assertQuery("owner:" + ownerGroup.id, group, ownerGroup);
+    assertQuery("owner:" + ownerGroup.name, group, ownerGroup);
   }
 
   @Test
diff --git a/gerrit-server/src/test/resources/com/google/gerrit/server/mail/tlds-alpha-by-domain.txt b/gerrit-server/src/test/resources/com/google/gerrit/server/mail/send/tlds-alpha-by-domain.txt
similarity index 100%
rename from gerrit-server/src/test/resources/com/google/gerrit/server/mail/tlds-alpha-by-domain.txt
rename to gerrit-server/src/test/resources/com/google/gerrit/server/mail/send/tlds-alpha-by-domain.txt
diff --git a/lib/BUILD b/lib/BUILD
index ca0fec3..fe1933c 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -231,6 +231,17 @@
 )
 
 java_library(
+    name = "truth-java8-extension",
+    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+    visibility = ["//visibility:public"],
+    exports = [
+        ":guava",
+        ":truth",
+        "@truth-java8-extension//jar",
+    ],
+)
+
+java_library(
     name = "javassist",
     data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
     visibility = ["//visibility:public"],
diff --git a/lib/bouncycastle/BUILD b/lib/bouncycastle/BUILD
index 4ec7fa0..efea418 100644
--- a/lib/bouncycastle/BUILD
+++ b/lib/bouncycastle/BUILD
@@ -1,44 +1,21 @@
-java_library(
-    name = "bcprov",
-    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = ["@bcprov//jar"],
-)
 
 java_library(
-    name = "bcprov-without-neverlink",
-    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+    name = "bcprov",
+    data = ["//lib:LICENSE-bouncycastle"],
     visibility = ["//visibility:public"],
     exports = ["@bcprov//jar"],
 )
 
 java_library(
     name = "bcpg",
-    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = ["@bcpg//jar"],
-)
-
-java_library(
-    name = "bcpg-without-neverlink",
-    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+    data = ["//lib:LICENSE-bouncycastle"],
     visibility = ["//visibility:public"],
     exports = ["@bcpg//jar"],
 )
 
 java_library(
     name = "bcpkix",
-    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = ["@bcpkix//jar"],
-)
-
-java_library(
-    name = "bcpkix-without-neverlink",
-    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+    data = ["//lib:LICENSE-bouncycastle"],
     visibility = ["//visibility:public"],
     exports = ["@bcpkix//jar"],
 )
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index aaa8b81..67cc7c0 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -75,8 +75,8 @@
   bower_archive(
     name = "mocha",
     package = "mocha",
-    version = "2.5.3",
-    sha1 = "22ef0d1f43ba5e2241369c501ac648f00c0440c0")
+    version = "3.2.0",
+    sha1 = "b77f23f7ad1f1363501bcae96f0f4f47745dad0f")
   bower_archive(
     name = "neon-animation",
     package = "neon-animation",
@@ -105,5 +105,5 @@
   bower_archive(
     name = "webcomponentsjs",
     package = "webcomponentsjs",
-    version = "0.7.22",
-    sha1 = "8ba97a4a279ec6973a19b171c462a7b5cf454fb9")
+    version = "0.7.23",
+    sha1 = "3d62269e614175573b0a0f3039aab05d40f0a763")
diff --git a/plugins/hooks b/plugins/hooks
index 3128bc6..7156fc2 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 3128bc65ab7ee453d853b04ae7b86307f72eed40
+Subproject commit 7156fc2b350307fd8212591e29eb6b4662f9d17d
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index b094e1c..7b0eeb9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -69,11 +69,11 @@
     });
 
     suite('test show change number preference enabled', function() {
-      return setup(function() {
-         return stubRestAPI({legacycid_in_change_table: true,
-            time_format: 'HHMM_12',
-            change_table: [],
-          }).then(function() {
+      setup(function() {
+        return stubRestAPI({legacycid_in_change_table: true,
+           time_format: 'HHMM_12',
+           change_table: [],
+        }).then(function() {
           element = fixture('basic');
           return element._loadPreferences();
         });
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index ab3a98a..c320fa1 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -104,10 +104,12 @@
     </div>
     <gr-overlay id="overlay" with-backdrop>
       <gr-confirm-rebase-dialog id="confirmRebase"
-          rebase-on-current="[[rebaseOnCurrent]]"
           class="confirmDialog"
           on-confirm="_handleRebaseConfirm"
           on-cancel="_handleConfirmDialogCancel"
+          branch="[[change.branch]]"
+          has-parent="[[hasParent]]"
+          rebase-on-current="[[revisionActions.rebase.rebaseOnCurrent]]"
           hidden></gr-confirm-rebase-dialog>
       <gr-confirm-cherrypick-dialog id="confirmCherrypick"
           class="confirmDialog"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index aa90a09..8a4a5e9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -129,8 +129,8 @@
       changeNum: String,
       changeStatus: String,
       commitNum: String,
+      hasParent: Boolean,
       patchNum: String,
-      rebaseOnCurrent: Boolean,
       commitMessage: {
         type: String,
         value: '',
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 6bd98ff..5314b09 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -314,8 +314,8 @@
                 on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
             <gr-change-actions id="actions"
                 change="[[_change]]"
+                has-parent="[[hasParent]]"
                 actions="[[_change.actions]]"
-                rebase-on-current="[[_rebaseOnCurrent]]"
                 revision-actions="[[_currentRevisionActions]]"
                 change-num="[[_changeNum]]"
                 change-status="[[_change.status]]"
@@ -369,6 +369,7 @@
             <div class="relatedChanges">
               <gr-related-changes-list id="relatedChanges"
                   change="[[_change]]"
+                  has-parent="{{hasParent}}"
                   patch-num="[[_computeLatestPatchNum(_allPatchSets)]]">
               </gr-related-changes-list>
             </div>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 7c8d92e..7cfaf76 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -56,6 +56,7 @@
         value: function() { return {}; },
       },
       backPage: String,
+      hasParent: Boolean,
       serverConfig: Object,
       keyEventTarget: {
         type: Object,
@@ -829,7 +830,8 @@
 
     _updateRebaseAction: function(revisionActions) {
       if (revisionActions && revisionActions.rebase) {
-        this._rebaseOnCurrent = !!revisionActions.rebase.enabled;
+        revisionActions.rebase.rebaseOnCurrent =
+            !!revisionActions.rebase.enabled;
         revisionActions.rebase.enabled = true;
       }
       return revisionActions;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index e36a55b..a416aa2 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -219,17 +219,18 @@
       assert.equal(element._updateRebaseAction(currentRevisionActions),
         currentRevisionActions);
 
-      assert.isTrue(element._rebaseOnCurrent);
+      assert.isTrue(currentRevisionActions.rebase.enabled);
+      assert.isTrue(currentRevisionActions.rebase.rebaseOnCurrent);
 
-      var newRevisionActions = currentRevisionActions
-      delete newRevisionActions.rebase.enabled;
+      delete currentRevisionActions.rebase.enabled;
 
       // When rebase is not enabled initially, rebaseOnCurrent should be set to
       // false.
-      assert.equal(element._updateRebaseAction(newRevisionActions),
+      assert.equal(element._updateRebaseAction(currentRevisionActions),
         currentRevisionActions);
 
-      assert.isFalse(element._rebaseOnCurrent);
+      assert.isTrue(currentRevisionActions.rebase.enabled);
+      assert.isFalse(currentRevisionActions.rebase.rebaseOnCurrent);
     });
 
     test('_reload is called when an approved label is removed', function() {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index 6cf6797..47fbee0 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -31,6 +31,8 @@
 
     properties: {
       base: String,
+      branch: String,
+      hasParent: Boolean,
       clearParent: {
         type: Boolean,
         value: false,
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 553e1dd..937c124 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -265,6 +265,7 @@
             [[_computeDraftsString(drafts, patchRange.patchNum, file.__path)]]
           </span>
           [[_computeCommentsString(comments, patchRange.patchNum, file.__path)]]
+          [[_computeUnresolvedString(comments, drafts, patchRange.patchNum, file.__path)]]
         </div>
         <div class="comments mobile">
           <span class="drafts">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 1fd8909..9c1d8b5 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -295,16 +295,60 @@
       return commentCount ? commentCount + 'c' : '';
     },
 
+    _getCommentsForPath: function(comments, patchNum, path) {
+      return (comments[path] || []).filter(function(c) {
+        return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
+      });
+    },
+
     _computeCountString: function(comments, patchNum, path, opt_noun) {
       if (!comments) { return ''; }
 
-      var patchComments = (comments[path] || []).filter(function(c) {
-        return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
-      });
+      var patchComments = this._getCommentsForPath(comments, patchNum, path);
       var num = patchComments.length;
       if (num === 0) { return ''; }
       if (!opt_noun) { return num; }
-      return num + ' ' + opt_noun + (num > 1 ? 's' : '');
+      var output = num + ' ' + opt_noun + (num > 1 ? 's' : '');
+      return output;
+    },
+
+    /**
+     * Computes a string counting the number of unresolved comment threads in a
+     * given file and path.
+     *
+     * @param {Object} comments
+     * @param {Object} drafts
+     * @param {number} patchNum
+     * @param {string} path
+     * @return {string}
+     */
+    _computeUnresolvedString: function(comments, drafts, patchNum, path) {
+      comments = this._getCommentsForPath(comments, patchNum, path);
+      drafts = this._getCommentsForPath(drafts, patchNum, path);
+      comments = comments.concat(drafts);
+
+      // Create an object where every comment ID is the key of an unresolved
+      // comment.
+
+      var idMap = comments.reduce(function(acc, comment) {
+        if (comment.unresolved) {
+          acc[comment.id] = true;
+        }
+        return acc;
+      }, {});
+
+      // Set false for the comments that are marked as parents.
+      comments.forEach(function(comment) {
+        idMap[comment.in_reply_to] = false;
+      });
+
+      // The unresolved comments are the comments that still have true.
+      var unresolvedLeaves = Object.keys(idMap).filter(function(key) {
+        return idMap[key];
+      });
+
+      return unresolvedLeaves.length === 0 ?
+          '' : '(' + unresolvedLeaves.length + ' unresolved)';
     },
 
     _computeReviewed: function(file, _reviewed) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 1fb9b60..4720aa9 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -362,14 +362,50 @@
     test('comment filtering', function() {
       var comments = {
         '/COMMIT_MSG': [
-          {patch_set: 1, message: 'Done'},
-          {patch_set: 1, message: 'oh hay'},
-          {patch_set: 2, message: 'hello'},
+          {patch_set: 1, message: 'Done', updated: '2017-02-08 16:40:49'},
+          {patch_set: 1, message: 'oh hay', updated: '2017-02-09 16:40:49'},
+          {patch_set: 2, message: 'hello', updated: '2017-02-10 16:40:49'},
         ],
         'myfile.txt': [
-          {patch_set: 1, message: 'good news!'},
-          {patch_set: 2, message: 'wat!?'},
-          {patch_set: 2, message: 'hi'},
+          {patch_set: 1, message: 'good news!', updated: '2017-02-08 16:40:49'},
+          {patch_set: 2, message: 'wat!?', updated: '2017-02-09 16:40:49'},
+          {patch_set: 2, message: 'hi', updated: '2017-02-10 16:40:49'},
+        ],
+        'unresolved.file': [
+          {
+            patch_set: 2,
+            message: 'wat!?',
+            updated: '2017-02-09 16:40:49',
+            id: '1',
+            unresolved: true,
+          },
+          {
+            patch_set: 2,
+            message: 'hi',
+            updated: '2017-02-10 16:40:49',
+            id: '2',
+            in_reply_to: '1',
+            unresolved: false,
+          },
+          {
+            patch_set: 2,
+            message: 'good news!',
+            updated: '2017-02-08 16:40:49',
+            id: '3',
+            unresolved: true,
+          },
+        ],
+      };
+      var drafts = {
+        'unresolved.file': [
+          {
+            patch_set: 2,
+            message: 'hi',
+            updated: '2017-02-11 16:40:49',
+            id: '4',
+            in_reply_to: '3',
+            unresolved: false,
+          },
         ],
       };
       assert.equal(
@@ -420,6 +456,16 @@
       assert.equal(
           element._computeCountString(comments, '2',
           'file_added_in_rev2.txt', 'comment'), '');
+      assert.equal(element._computeCountString(comments, '2',
+          'unresolved.file', 'comment'), '3 comments');
+      assert.equal(
+          element._computeUnresolvedString(comments, [], 2, 'myfile.txt'), '');
+      assert.equal(
+          element._computeUnresolvedString(comments, [], 2, 'unresolved.file'),
+          '(1 unresolved)');
+      assert.equal(
+          element._computeUnresolvedString(comments, drafts, 2,
+          'unresolved.file'), '');
     });
 
     test('computed properties', function() {
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 4c6ff36..d22cfd5 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -256,7 +256,7 @@
         id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
       }];
       element.messages = messages;
-      element.comments = comments
+      element.comments = comments;
       flushAsynchronousOperations();
       var messageEls = Polymer.dom(element.root).querySelectorAll('gr-message');
       assert.equal(messageEls.length, 1);
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index c066b17..61c19d9 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -19,7 +19,12 @@
 
     properties: {
       change: Object,
+      hasParent: {
+        type: Boolean,
+        notify: true,
+      },
       patchNum: String,
+      parentChange: Object,
       hidden: {
         type: Boolean,
         value: false,
@@ -56,6 +61,10 @@
       var promises = [
         this._getRelatedChanges().then(function(response) {
           this._relatedResponse = response;
+
+          this.hasParent = this._calculateHasParent(this.change.change_id,
+            response.changes);
+
         }.bind(this)),
         this._getSubmittedTogether().then(function(response) {
           this._submittedTogether = response;
@@ -88,6 +97,20 @@
       }.bind(this));
     },
 
+    /**
+     * Determines whether or not the given change has a parent change. If there
+     * is a relation chain, and the change id is not the last item of the
+     * relation chain, there is a parent.
+     * @param  {Number} currentChangeId
+     * @param  {Array} relatedChanges
+     * @return {Boolean}
+     */
+    _calculateHasParent: function(currentChangeId, relatedChanges) {
+      return relatedChanges.length > 0 &&
+          relatedChanges[relatedChanges.length - 1].change_id !==
+          currentChangeId;
+    },
+
     _getRelatedChanges: function() {
       return this.$.restAPI.getRelatedChanges(this.change._number,
           this.patchNum);
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index 21903d2..98cb570d 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -238,7 +238,9 @@
         element = fixture('basic');
 
         sandbox.stub(element, '_getRelatedChanges',
-            function() { return Promise.resolve(); });
+            function() {
+              return Promise.resolve({changes: []});
+            });
         sandbox.stub(element, '_getSubmittedTogether',
             function() { return Promise.resolve(); });
         sandbox.stub(element, '_getCherryPicks',
@@ -250,6 +252,7 @@
       test('request conflicts if open and mergeable', function() {
         element.patchNum = 7;
         element.change = {
+          change_id: 123,
           status: 'NEW',
           mergeable: true,
         };
@@ -260,6 +263,7 @@
       test('does not request conflicts if closed and mergeable', function() {
         element.patchNum = 7;
         element.change = {
+          change_id: 123,
           status: 'MERGED',
           mergeable: true,
         };
@@ -270,6 +274,7 @@
       test('does not request conflicts if open and not mergeable', function() {
         element.patchNum = 7;
         element.change = {
+          change_id: 123,
           status: 'NEW',
           mergeable: false,
         };
@@ -281,6 +286,7 @@
           function() {
         element.patchNum = 7;
         element.change = {
+          change_id: 123,
           status: 'MERGED',
           mergeable: false,
         };
@@ -288,5 +294,22 @@
         assert.isFalse(conflictsStub.called);
       });
     });
+
+    test('_calculateHasParent', function() {
+      var changeId = 123;
+      var relatedChanges = [];
+
+      assert.equal(element._calculateHasParent(changeId, relatedChanges),
+          false);
+
+      relatedChanges.push({change_id: 123});
+      assert.equal(element._calculateHasParent(changeId, relatedChanges),
+          false);
+
+      relatedChanges.push({change_id: 234});
+      assert.equal(element._calculateHasParent(changeId, relatedChanges),
+          true);
+
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.html b/polygerrit-ui/app/elements/core/gr-router/gr-router.html
index 4ad2a37..bd79419 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.html
@@ -17,5 +17,7 @@
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-reporting/gr-reporting.html">
 
-<script src="../../../bower_components/page/page.js"></script>
-<script src="gr-router.js"></script>
+<dom-module id="gr-router">
+  <script src="../../../bower_components/page/page.js"></script>
+  <script src="gr-router.js"></script>
+</dom-module>
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 b439fc8..d8f9c32 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -19,15 +19,27 @@
   var app = document.querySelector('#app');
   if (!app) {
     console.log('No gr-app found (running tests)');
-    return;
   }
 
-  window.addEventListener('WebComponentsReady', function() {
-    var restAPI = document.createElement('gr-rest-api-interface');
-    var reporting = document.createElement('gr-reporting');
+  var _reporting;
+  function getReporting() {
+    if (!_reporting) {
+      _reporting = document.createElement('gr-reporting');
+    }
+    return _reporting;
+  }
 
-    reporting.timeEnd('WebComponentsReady');
-    reporting.pageLoaded();
+  document.onload = function() {
+    getReporting().pageLoaded();
+  };
+
+  window.addEventListener('WebComponentsReady', function() {
+    getReporting().timeEnd('WebComponentsReady');
+  });
+
+  function startRouter() {
+    var restAPI = document.createElement('gr-rest-api-interface');
+    var reporting = getReporting();
 
     // Middleware
     page(function(ctx, next) {
@@ -216,5 +228,13 @@
     });
 
     page.start();
+  }
+
+  Polymer({
+    is: 'gr-router',
+    start: function() {
+      if (!app) { return; }
+      startRouter();
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index c27c57e..d62dbf8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -340,7 +340,7 @@
     var threadGroupEl =
         document.createElement('gr-diff-comment-thread-group');
     threadGroupEl.changeNum = changeNum;
-    threadGroupEl.patchNum = patchNum;
+    threadGroupEl.patchForNewThreads = patchNum;
     threadGroupEl.path = path;
     threadGroupEl.side = side;
     threadGroupEl.projectConfig = projectConfig;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index 4665bdf..e5fe1d7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -241,7 +241,7 @@
 
       function checkThreadGroupProps(threadGroupEl, patchNum, side, comments) {
         assert.equal(threadGroupEl.changeNum, '42');
-        assert.equal(threadGroupEl.patchNum, patchNum);
+        assert.equal(threadGroupEl.patchForNewThreads, patchNum);
         assert.equal(threadGroupEl.path, '/path/to/foo');
         assert.equal(threadGroupEl.side, side);
         assert.deepEqual(threadGroupEl.projectConfig, {foo: 'bar'});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
index adbb1a4..95de61f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
@@ -28,14 +28,14 @@
         margin-top: .2em;
       }
     </style>
-    <template is="dom-repeat" items="[[_threadGroups]]"
+    <template is="dom-repeat" items="[[_threads]]"
         as="thread">
       <gr-diff-comment-thread
           comments="[[thread.comments]]"
           comment-side="[[thread.commentSide]]"
           change-num="[[changeNum]]"
           location-range="[[thread.locationRange]]"
-          patch-num="[[patchNum]]"
+          patch-num="[[thread.patchNum]]"
           path="[[path]]"
           side="[[side]]"
           project-config="[[projectConfig]]"></gr-diff-comment-thread>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
index b9fc9f7..ee4c5b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
@@ -23,14 +23,14 @@
         type: Array,
         value: function() { return []; },
       },
-      patchNum: String,
+      patchForNewThreads: String,
       projectConfig: Object,
       range: Object,
       side: {
         type: String,
         value: 'REVISION',
       },
-      _threadGroups: {
+      _threads: {
         type: Array,
         value: function() { return []; },
       },
@@ -40,17 +40,18 @@
       '_commentsChanged(comments.*)',
     ],
 
-    addNewThread: function(locationRange, commentSide) {
-      this.push('_threadGroups', {
+    addNewThread: function(locationRange) {
+      this.push('_threads', {
         comments: [],
         locationRange: locationRange,
+        patchNum: this.patchForNewThreads,
       });
     },
 
     removeThread: function(locationRange) {
-      for (var i = 0; i < this._threadGroups.length; i++) {
-        if (this._threadGroups[i].locationRange === locationRange) {
-          this.splice('_threadGroups', i, 1);
+      for (var i = 0; i < this._threads.length; i++) {
+        if (this._threads[i].locationRange === locationRange) {
+          this.splice('_threads', i, 1);
           return;
         }
       }
@@ -68,7 +69,7 @@
     },
 
     _commentsChanged: function() {
-      this._threadGroups = this._getThreadGroups(this.comments);
+      this._threads = this._getThreadGroups(this.comments);
     },
 
     _sortByDate: function(threadGroups) {
@@ -95,6 +96,16 @@
           comment.__commentSide;
     },
 
+    /**
+     * Determines what the patchNum of a thread should be. Use patchNum from
+     * comment if it exists, otherwise the property of the thread group.
+     * This is needed for switching between side-by-side and unified views when
+     * there are unsaved drafts.
+     */
+    _getPatchNum: function(comment) {
+      return comment.patchNum || this.patchForNewThreads;
+    },
+
     _getThreadGroups: function(comments) {
       var threadGroups = {};
 
@@ -114,6 +125,7 @@
             comments: [comment],
             locationRange: locationRange,
             commentSide: comment.__commentSide,
+            patchNum: this._getPatchNum(comment),
           };
         }
       }.bind(this));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
index 79e58ee..bc08bab 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
@@ -49,6 +49,7 @@
     });
 
     test('_getThreadGroups', function() {
+      element.patchForNewThreads = 3;
       var comments = [
         {
           id: 'sallys_confession',
@@ -79,12 +80,14 @@
               __commentSide: 'left',
             }],
           locationRange: 'line-left',
+          patchNum: 3
         },
       ];
 
       assert.deepEqual(element._getThreadGroups(comments),
           expectedThreadGroups);
 
+      // Patch num should get inherited from comment rather
       comments.push({
           id: 'betsys_confession',
           message: 'i like you, jack',
@@ -113,6 +116,7 @@
               updated: '2015-12-24 15:00:20.396000000',
               __commentSide: 'left',
             }],
+          patchNum: 3,
           locationRange: 'line-left',
         },
         {
@@ -130,6 +134,7 @@
             },
             __commentSide: 'left',
           }],
+          patchNum: 3,
           locationRange: 'range-1-1-1-2-left',
         },
       ];
@@ -218,21 +223,33 @@
 
     test('addNewThread', function() {
       var locationRange = 'range-1-2-3-4';
-      element._threadGroups = [{locationRange: 'line'}];
+      element._threads = [{locationRange: 'line'}];
       element.addNewThread(locationRange);
-      assert(element._threadGroups.length, 2);
+      assert(element._threads.length, 2);
+    });
+
+    test('_getPatchNum', function() {
+      element.patchForNewThreads = 3;
+      var comment = {
+        id: 'sallys_confession',
+        message: 'i like you, jack',
+        updated: '2015-12-23 15:00:20.396000000',
+      };
+      assert.equal(element._getPatchNum(comment), 3);
+      comment.patchNum = 4;
+      assert.equal(element._getPatchNum(comment), 4);
     });
 
     test('removeThread', function() {
       var locationRange = 'range-1-2-3-4';
-      element._threadGroups = [
+      element._threads = [
         {locationRange: 'range-1-2-3-4', comments: []},
         {locationRange: 'line', comments: []}
       ];
       flushAsynchronousOperations();
       element.removeThread(locationRange);
       flushAsynchronousOperations();
-      assert(element._threadGroups.length, 1);
+      assert(element._threads.length, 1);
     });
   });
 </script>
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 fc130c7..c088b1a 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
@@ -271,6 +271,7 @@
         __draftID: Math.random().toString(36),
         __date: new Date(),
         path: this.path,
+        patchNum: this.patchNum,
         side: this.side,
         __commentSide: this.commentSide,
       };
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index 424cef6..e0ae9ff 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -532,8 +532,10 @@
 
     test('_newDraft', function() {
       element.commentSide = 'left';
+      element.patchNum = 3;
       var draft = element._newDraft();
       assert.equal(draft.__commentSide, 'left');
+      assert.equal(draft.patchNum, 3);
     });
 
     test('new comment gets created', function() {
@@ -541,8 +543,8 @@
       element.addOrEditDraft(1);
       assert.equal(element.comments.length, 1);
       // Mock a submitted comment.
-      element.comments[0].__draft = false;
       element.comments[0].id = element.comments[0].__draftID;
+      element.comments[0].__draft = false;
       element.addOrEditDraft(1);
       assert.equal(element.comments.length, 2);
     });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index eea27db..8d54b2a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -458,9 +458,10 @@
 
       promises.push(this._getChangeDetail(this._changeNum));
 
-      Promise.all(promises)
-          .then(function() { return this.$.diff.reload(); }.bind(this))
-          .then(function() { this._loading = false; }.bind(this));
+      Promise.all(promises).then(function() {
+        this._loading = false;
+        this.$.diff.reload();
+      }.bind(this));
 
       this._loadCommentMap().then(function(commentMap) {
         this._commentMap = commentMap;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index d2cdd07..117ff24 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -236,15 +236,18 @@
 
         sandbox.stub(element.$.diffBuilder, 'createCommentThreadGroup',
             function() {
-          return document.createElement('gr-diff-comment-thread-group');
+          var threadGroup =
+              document.createElement('gr-diff-comment-thread-group');
+          threadGroup.patchForNewThreads = 1;
+          return threadGroup;
         });
 
         // No thread groups.
         assert.isNotOk(element._getThreadGroupForLine(contentEl));
 
         // A thread group gets created.
-        assert.isOk(element._getOrCreateThreadAtLineRange(contentEl, patchNum,
-            commentSide, side));
+        assert.isOk(element._getOrCreateThreadAtLineRange(contentEl,
+            patchNum, commentSide, side));
 
         // Try to fetch a thread with a different range.
         range = {
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 6499f08..f7f50d4 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -164,6 +164,7 @@
     <gr-error-manager></gr-error-manager>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-reporting id="reporting"></gr-reporting>
+    <gr-router id="router"></gr-router>
   </template>
-  <script src="gr-app.js"></script>
+  <script src="gr-app.js" crossorigin="anonymous"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index f38bb91..82c71be 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -14,6 +14,13 @@
 (function() {
   'use strict';
 
+  // Eagerly render Polymer components when backgrounded. (Skips
+  // requestAnimationFrame.)
+  // @see https://github.com/Polymer/polymer/issues/3851
+  // TODO: Reassess after Polymer 2.0 upgrade.
+  // @see Issue 4699
+  Polymer.RenderStatus._makeReady();
+
   Polymer({
     is: 'gr-app',
 
@@ -67,6 +74,7 @@
     },
 
     attached: function() {
+      this.$.router.start();
       this.$.restAPI.getAccount().then(function(account) {
         this._account = account;
       }.bind(this));
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index e675de2..dcda26c 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -42,9 +42,6 @@
 
       stub('gr-rest-api-interface', {
         getAccount: function() { return Promise.resolve(account); },
-        getAccountHttpPassword: function() {
-          return Promise.resolve(password);
-        },
       });
 
       element = fixture('basic');
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index fbfdcc3..0d381cb 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -123,7 +123,6 @@
         getAccountEmails: function() { return Promise.resolve(); },
         getConfig: function() { return Promise.resolve(config); },
         getAccountGroups: function() { return Promise.resolve([]); },
-        getAccountHttpPassword: function() { return Promise.resolve(''); },
       });
       element = fixture('basic');
 
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 4d64a77..5c2ad5c 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -513,6 +513,8 @@
               function(revisionActions) {
                 // The rebase button on change screen is always enabled.
                 if (revisionActions.rebase) {
+                  revisionActions.rebase.rebaseOnCurrent =
+                      !!revisionActions.rebase.enabled;
                   revisionActions.rebase.enabled = true;
                 }
                 return revisionActions;
@@ -968,11 +970,6 @@
           '/topic', {topic: topic});
     },
 
-    getAccountHttpPassword: function(opt_errFn) {
-      return this._fetchSharedCacheURL('/accounts/self/password.http',
-          opt_errFn);
-    },
-
     deleteAccountHttpPassword: function() {
       return this.send('DELETE', '/accounts/self/password.http');
     },
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 6af8938..7a0dc37 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -383,20 +383,37 @@
           ]);
     });
 
-    test('rebase always enabled', function(done) {
+    suite('rebase action', function() {
       var resolveFetchJSON;
-      sandbox.stub(element, 'fetchJSON').returns(
-          new Promise(function(resolve) {
-            resolveFetchJSON = resolve;
-          }));
-      element.getChangeRevisionActions('42', '1337').then(
+      setup(function() {
+        sandbox.stub(element, 'fetchJSON').returns(
+            new Promise(function(resolve) {
+              resolveFetchJSON = resolve;
+            }));
+      });
+
+      test('no rebase on current', function(done) {
+        element.getChangeRevisionActions('42', '1337').then(
           function(response) {
             assert.isTrue(response.rebase.enabled);
+            assert.isFalse(response.rebase.rebaseOnCurrent);
             done();
           });
-      resolveFetchJSON({rebase: {}});
+        resolveFetchJSON({rebase: {}});
+      });
+
+      test('rebase on current', function(done) {
+        element.getChangeRevisionActions('42', '1337').then(
+          function(response) {
+            assert.isTrue(response.rebase.enabled);
+            assert.isTrue(response.rebase.rebaseOnCurrent);
+            done();
+          });
+        resolveFetchJSON({rebase: {enabled: true}});
+      });
     });
 
+
     test('server error', function(done) {
       var getResponseObjectStub = sandbox.stub(element, 'getResponseObject');
       sandbox.stub(window, 'fetch', function() {
diff --git a/polygerrit-ui/app/index.html b/polygerrit-ui/app/index.html
index cd66b21..db9a1c5 100644
--- a/polygerrit-ui/app/index.html
+++ b/polygerrit-ui/app/index.html
@@ -29,7 +29,7 @@
 <link rel="stylesheet" href="/styles/fonts.css">
 <link rel="stylesheet" href="/styles/main.css">
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<link rel="preload" href="/elements/gr-app.js" as="script">
+<link rel="preload" href="/elements/gr-app.js" as="script" crossorigin="anonymous">
 <link rel="import" href="/elements/gr-app.html">
 
 <body unresolved>
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 6e5faf0..de82af8 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -30,6 +30,7 @@
     resources = resources,
     deps = provided_deps + deps + GWT_PLUGIN_DEPS_NEVERLINK + PLUGIN_DEPS_NEVERLINK,
     visibility = ['//visibility:public'],
+    **kwargs
   )
 
   static_jars = []
@@ -56,6 +57,7 @@
       resources = list(set(srcs + resources)),
       runtime_deps = deps + GWT_PLUGIN_DEPS,
       visibility = ['//visibility:public'],
+      **kwargs
     )
     genrule2(
       name = '%s-static' % name,