Merge "intro-user.txt: Fix another typo"
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 1af8f7d..d5e2d9e 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -93,6 +93,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.Groups;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.index.change.ChangeIndex;
@@ -254,6 +255,7 @@
   @Inject private InProcessProtocol inProcessProtocol;
   @Inject private Provider<AnonymousUser> anonymousUser;
   @Inject private SchemaFactory<ReviewDb> reviewDbProvider;
+  @Inject private Groups groups;
 
   private List<Repository> toClose;
 
@@ -344,6 +346,8 @@
     Transport.register(inProcessProtocol);
     toClose = Collections.synchronizedList(new ArrayList<Repository>());
 
+    db = reviewDbProvider.open();
+
     // All groups which were added during the server start (e.g. in SchemaCreator) aren't contained
     // in the instance of the group index which is available here and in tests. There are two
     // reasons:
@@ -353,7 +357,7 @@
     // later on. As test indexes are non-permanent, closing an instance and opening another one
     // removes all indexed data.
     // As a workaround, we simply reindex all available groups here.
-    for (AccountGroup group : groupCache.all()) {
+    for (AccountGroup group : groups.getAll(db).collect(toList())) {
       groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey());
     }
 
@@ -367,8 +371,6 @@
     adminRestSession = new RestSession(server, admin);
     userRestSession = new RestSession(server, user);
 
-    db = reviewDbProvider.open();
-
     testRequiresSsh = classDesc.useSshAnnotation() || methodDesc.useSshAnnotation();
     if (testRequiresSsh
         && SshMode.useSsh()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
index eb4df15..305a2b0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -30,7 +30,7 @@
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.extensions.api.groups.GroupApi;
 import com.google.gerrit.extensions.api.groups.GroupInput;
-import com.google.gerrit.extensions.api.groups.Groups;
+import com.google.gerrit.extensions.api.groups.Groups.ListRequest;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.GroupAuditEventInfo;
 import com.google.gerrit.extensions.common.GroupAuditEventInfo.GroupMemberAuditEventInfo;
@@ -46,6 +46,7 @@
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.group.Groups;
 import com.google.gerrit.server.group.GroupsUpdate;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.ServerInitiated;
@@ -63,6 +64,7 @@
 @NoHttpd
 public class GroupsIT extends AbstractDaemonTest {
   @Inject @ServerInitiated private Provider<GroupsUpdate> groupsUpdateProvider;
+  @Inject private Groups groups;
 
   @Test
   public void systemGroupCanBeRetrievedFromIndex() throws Exception {
@@ -481,7 +483,7 @@
   @Test
   public void listAllGroups() throws Exception {
     List<String> expectedGroups =
-        groupCache.all().stream().map(a -> a.getName()).sorted().collect(toList());
+        groups.getAll(db).map(a -> a.getName()).sorted().collect(toList());
     assertThat(expectedGroups.size()).isAtLeast(2);
     assertThat(gApi.groups().list().getAsMap().keySet())
         .containsExactlyElementsIn(expectedGroups)
@@ -692,7 +694,7 @@
     groupsUpdateProvider.get().updateGroup(db, groupUuid, group -> group.setCreatedOn(null));
   }
 
-  private void assertBadRequest(Groups.ListRequest req) throws Exception {
+  private void assertBadRequest(ListRequest req) throws Exception {
     try {
       req.get();
       fail("Expected BadRequestException");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
index 82bcce3..d985426 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.collect.ImmutableList;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.group.InternalGroup;
 import java.io.IOException;
@@ -49,9 +48,6 @@
    */
   Optional<InternalGroup> get(AccountGroup.UUID groupUuid);
 
-  /** @return sorted list of groups. */
-  ImmutableList<AccountGroup> all();
-
   /** Notify the cache that a new group was constructed. */
   void onCreateGroup(AccountGroup group) throws IOException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
index edbc2d8..81996ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -14,11 +14,8 @@
 
 package com.google.gerrit.server.account;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
-
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableList;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.CacheModule;
@@ -26,7 +23,6 @@
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.query.group.InternalGroupQuery;
-import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Module;
@@ -71,24 +67,18 @@
   private final LoadingCache<AccountGroup.Id, Optional<InternalGroup>> byId;
   private final LoadingCache<String, Optional<InternalGroup>> byName;
   private final LoadingCache<String, Optional<InternalGroup>> byUUID;
-  private final SchemaFactory<ReviewDb> schema;
   private final Provider<GroupIndexer> indexer;
-  private final Groups groups;
 
   @Inject
   GroupCacheImpl(
       @Named(BYID_NAME) LoadingCache<AccountGroup.Id, Optional<InternalGroup>> byId,
       @Named(BYNAME_NAME) LoadingCache<String, Optional<InternalGroup>> byName,
       @Named(BYUUID_NAME) LoadingCache<String, Optional<InternalGroup>> byUUID,
-      SchemaFactory<ReviewDb> schema,
-      Provider<GroupIndexer> indexer,
-      Groups groups) {
+      Provider<GroupIndexer> indexer) {
     this.byId = byId;
     this.byName = byName;
     this.byUUID = byUUID;
-    this.schema = schema;
     this.indexer = indexer;
-    this.groups = groups;
   }
 
   @Override
@@ -152,16 +142,6 @@
   }
 
   @Override
-  public ImmutableList<AccountGroup> all() {
-    try (ReviewDb db = schema.open()) {
-      return groups.getAll(db).collect(toImmutableList());
-    } catch (OrmException e) {
-      log.warn("Cannot list internal groups", e);
-      return ImmutableList.of();
-    }
-  }
-
-  @Override
   public void onCreateGroup(AccountGroup group) throws IOException {
     indexer.get().index(group.getGroupUUID());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
index e471d57c..60e6297 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -19,12 +19,17 @@
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.group.Groups;
 import com.google.gerrit.server.group.InternalGroupDescription;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.Collection;
+import java.util.Collections;
 import org.eclipse.jgit.lib.ObjectId;
 
 /** Implementation of GroupBackend for the internal group system. */
@@ -32,15 +37,21 @@
 public class InternalGroupBackend implements GroupBackend {
   private final GroupControl.Factory groupControlFactory;
   private final GroupCache groupCache;
+  private final Groups groups;
+  private final Provider<ReviewDb> db;
   private final IncludingGroupMembership.Factory groupMembershipFactory;
 
   @Inject
   InternalGroupBackend(
       GroupControl.Factory groupControlFactory,
       GroupCache groupCache,
+      Groups groups,
+      Provider<ReviewDb> db,
       IncludingGroupMembership.Factory groupMembershipFactory) {
     this.groupControlFactory = groupControlFactory;
     this.groupCache = groupCache;
+    this.groups = groups;
+    this.db = db;
     this.groupMembershipFactory = groupMembershipFactory;
   }
 
@@ -61,16 +72,19 @@
 
   @Override
   public Collection<GroupReference> suggest(String name, ProjectState project) {
-    return groupCache
-        .all()
-        .stream()
-        .filter(
-            group ->
-                // startsWithIgnoreCase && isVisible
-                group.getName().regionMatches(true, 0, name, 0, name.length())
-                    && groupControlFactory.controlFor(group).isVisible())
-        .map(GroupReference::forGroup)
-        .collect(toList());
+    try {
+      return groups
+          .getAll(db.get())
+          .filter(
+              group ->
+                  // startsWithIgnoreCase && isVisible
+                  group.getName().regionMatches(true, 0, name, 0, name.length())
+                      && groupControlFactory.controlFor(group).isVisible())
+          .map(GroupReference::forGroup)
+          .collect(toList());
+    } catch (OrmException e) {
+      return Collections.emptyList();
+    }
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 970269b..0f9cc89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.GetGroups;
@@ -75,6 +76,8 @@
   private final GetGroups accountGetGroups;
   private final GroupJson json;
   private final GroupBackend groupBackend;
+  private final Groups groups;
+  private final Provider<ReviewDb> db;
 
   private EnumSet<ListGroupsOption> options = EnumSet.noneOf(ListGroupsOption.class);
   private boolean visibleToAll;
@@ -215,7 +218,9 @@
       final IdentifiedUser.GenericFactory userFactory,
       final GetGroups accountGetGroups,
       GroupJson json,
-      GroupBackend groupBackend) {
+      GroupBackend groupBackend,
+      Groups groups,
+      Provider<ReviewDb> db) {
     this.groupCache = groupCache;
     this.groupControlFactory = groupControlFactory;
     this.genericGroupControlFactory = genericGroupControlFactory;
@@ -224,6 +229,8 @@
     this.accountGetGroups = accountGetGroups;
     this.json = json;
     this.groupBackend = groupBackend;
+    this.groups = groups;
+    this.db = db;
   }
 
   public void setOptions(EnumSet<ListGroupsOption> options) {
@@ -379,11 +386,14 @@
   }
 
   private ImmutableList<GroupDescription.Internal> getAllExistingInternalGroups() {
-    return groupCache
-        .all()
-        .stream()
-        .map(GroupDescriptions::forAccountGroup)
-        .collect(toImmutableList());
+    try {
+      return groups
+          .getAll(db.get())
+          .map(GroupDescriptions::forAccountGroup)
+          .collect(toImmutableList());
+    } catch (OrmException e) {
+      return ImmutableList.of();
+    }
   }
 
   private List<GroupDescription.Internal> filterGroups(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
index f0421a5..151b43d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.group;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -24,16 +25,18 @@
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.StartupCheck;
 import com.google.gerrit.server.StartupException;
 import com.google.gerrit.server.account.AbstractGroupBackend;
-import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -184,12 +187,14 @@
 
   public static class NameCheck implements StartupCheck {
     private final Config cfg;
-    private final GroupCache groupCache;
+    private final Groups groups;
+    private final Provider<ReviewDb> db;
 
     @Inject
-    NameCheck(@GerritServerConfig Config cfg, GroupCache groupCache) {
+    NameCheck(@GerritServerConfig Config cfg, Groups groups, Provider<ReviewDb> db) {
       this.cfg = cfg;
-      this.groupCache = groupCache;
+      this.groups = groups;
+      this.db = db;
     }
 
     @Override
@@ -206,7 +211,13 @@
       if (configuredNames.isEmpty()) {
         return;
       }
-      for (AccountGroup g : groupCache.all()) {
+      List<AccountGroup> allGroups;
+      try {
+        allGroups = groups.getAll(db.get()).collect(toList());
+      } catch (OrmException e) {
+        return;
+      }
+      for (AccountGroup g : allGroups) {
         String name = g.getName().toLowerCase(Locale.US);
         if (byLowerCaseConfiguredName.keySet().contains(name)) {
           AccountGroup.UUID uuidSystemGroup = byLowerCaseConfiguredName.get(name);
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 595559d..cbaf605 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -267,7 +267,7 @@
                 <span class$="[[label.className]]">
                   <gr-label
                       has-tooltip
-                      title="[[_computeValueTooltip(label.value, labelName)]]"
+                      title="[[_computeValueTooltip(change, label.value, labelName)]]"
                       class="labelValue">
                     [[label.value]]
                   </gr-label>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index d117d17..b5b909a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -158,9 +158,11 @@
       return result;
     },
 
-    _computeValueTooltip(score, labelName) {
-      const values = this.change.labels[labelName].values;
-      return values[score];
+    _computeValueTooltip(change, score, labelName) {
+      if (!change.labels[labelName] ||
+          !change.labels[labelName].values ||
+          !change.labels[labelName].values[score]) { return ''; }
+      return change.labels[labelName].values[score];
     },
 
     _handleTopicChanged(e, topic) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 2c3330f..634e21c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -276,6 +276,32 @@
           element._computeShowUploaderHide(change), 'hideDisplay');
     });
 
+    test('_computeValueTooltip', () => {
+      // Existing label.
+      const change = {labels: {'Foo-bar': {values: {0: 'Baz'}}}};
+      let score = '0';
+      let labelName = 'Foo-bar';
+      let actual = element._computeValueTooltip(change, score, labelName);
+      assert.equal(actual, 'Baz');
+
+      // Non-extsistent label.
+      labelName = 'xyz';
+      actual = element._computeValueTooltip(change, score, labelName);
+      assert.equal(actual, '');
+
+      // Non-extsistent score.
+      score = '2';
+      actual = element._computeValueTooltip(change, score, labelName);
+      assert.equal(actual, '');
+
+      // No values on label.
+      labelName = 'abcd';
+      score = '0';
+      change.labels.abcd = {};
+      actual = element._computeValueTooltip(change, score, labelName);
+      assert.equal(actual, '');
+    });
+
     suite('Topic removal', () => {
       let change;
       setup(() => {
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
index 2ebf7c7..401aaf8 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
@@ -15,9 +15,9 @@
 -->
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 
-<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
 <link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
 <link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 
@@ -112,7 +112,7 @@
             items="[[_relatedResponse.changes]]"
             as="related">
           <div class$="rightIndent [[_computeChangeContainerClass(change, related)]]">
-            <a href$="[[_computeChangeURL(related._change_number, related._revision_number)]]"
+            <a href$="[[_computeChangeURL(related._change_number, related.project, related._revision_number)]]"
                 class$="[[_computeLinkClass(related)]]"
                 title$="[[related.commit.subject]]">
               [[related.commit.subject]]
@@ -127,7 +127,7 @@
         <h4>Submitted together</h4>
         <template is="dom-repeat" items="[[_submittedTogether]]" as="change">
           <div>
-            <a href$="[[_computeChangeURL(change._number)]]"
+            <a href$="[[_computeChangeURL(change._number, change.project)]]"
                 class$="[[_computeLinkClass(change)]]"
                 title$="[[change.project]]: [[change.branch]]: [[change.subject]]">
               [[change.project]]: [[change.branch]]: [[change.subject]]
@@ -143,7 +143,7 @@
         <h4>Same topic</h4>
         <template is="dom-repeat" items="[[_sameTopic]]" as="change">
           <div>
-            <a href$="[[_computeChangeURL(change._number)]]"
+            <a href$="[[_computeChangeURL(change._number, change.project)]]"
                 class$="[[_computeLinkClass(change)]]"
                 title$="[[change.project]]: [[change.branch]]: [[change.subject]]">
               [[change.project]]: [[change.branch]]: [[change.subject]]
@@ -155,7 +155,7 @@
         <h4>Merge conflicts</h4>
         <template is="dom-repeat" items="[[_conflicts]]" as="change">
           <div>
-            <a href$="[[_computeChangeURL(change._number)]]"
+            <a href$="[[_computeChangeURL(change._number, change.project)]]"
                 class$="[[_computeLinkClass(change)]]"
                 title$="[[change.subject]]">
               [[change.subject]]
@@ -167,7 +167,7 @@
         <h4>Cherry picks</h4>
         <template is="dom-repeat" items="[[_cherryPicks]]" as="change">
           <div>
-            <a href$="[[_computeChangeURL(change._number)]]"
+            <a href$="[[_computeChangeURL(change._number, change.project)]]"
                 class$="[[_computeLinkClass(change)]]"
                 title$="[[change.branch]]: [[change.subject]]">
               [[change.branch]]: [[change.subject]]
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 f330581..39c736d 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
@@ -64,7 +64,6 @@
     },
 
     behaviors: [
-      Gerrit.BaseUrlBehavior,
       Gerrit.PatchSetBehavior,
       Gerrit.RESTClientBehavior,
     ],
@@ -164,12 +163,14 @@
       return this.$.restAPI.getChangesWithSameTopic(this.change.topic);
     },
 
-    _computeChangeURL(changeNum, patchNum) {
-      let urlStr = this.getBaseUrl() + '/c/' + changeNum;
-      if (patchNum != null) {
-        urlStr += '/' + patchNum;
-      }
-      return urlStr;
+    /**
+     * @param {number} changeNum
+     * @param {string} project
+     * @param {number=} opt_patchNum
+     * @return {string}
+     */
+    _computeChangeURL(changeNum, project, opt_patchNum) {
+      return Gerrit.Nav.getUrlForChangeById(changeNum, project, opt_patchNum);
     },
 
     _computeChangeContainerClass(currentChange, relatedChange) {
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 66d6b02..df4391e 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
@@ -357,5 +357,11 @@
       assert.isFalse(element.hidden);
       assert.isTrue(updateHandler.called);
     });
+
+    test('_computeChangeURL uses Gerrit.Nav', () => {
+      const getUrlStub = sandbox.stub(Gerrit.Nav, 'getUrlForChangeById');
+      element._computeChangeURL(123, 'abc/def', 12);
+      assert.isTrue(getUrlStub.called);
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index a056cf1..0ba84f4 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -189,6 +189,21 @@
       },
 
       /**
+       * @param {number} changeNum
+       * @param {string} project The name of the project.
+       * @param {number=} opt_patchNum
+       * @return {string}
+       */
+      getUrlForChangeById(changeNum, project, opt_patchNum) {
+        return this._getUrlFor({
+          view: Gerrit.Nav.View.CHANGE,
+          changeNum,
+          project,
+          patchNum: opt_patchNum,
+        });
+      },
+
+      /**
        * @param {!Object} change The change object.
        * @param {number=} opt_patchNum
        * @param {number|string=} opt_basePatchNum The string 'PARENT' can be
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 3a724bb..211e328 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -19,7 +19,7 @@
     DASHBOARD: '/dashboard/(.*)',
     ADMIN_PLACEHOLDER: '/admin/(.*)',
     AGREEMENTS: /^\/settings\/(agreements|new-agreement)/,
-    REGISTER: /^\/register(\/.*)?/,
+    REGISTER: /^\/register(\/.*)?$/,
 
     // Pattern for login and logout URLs intended to be passed-through. May
     // include a return URL.
@@ -961,7 +961,11 @@
 
     _handleRegisterRoute(ctx) {
       this._setParams({justRegistered: true});
-      const path = ctx.params[0] || '/';
+      let path = ctx.params[0] || '/';
+
+      // Prevent redirect looping.
+      if (path.startsWith('/register')) { path = '/'; }
+
       if (path[0] !== '/') { return; }
       this._redirect(this.getBaseUrl() + path);
     },
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index c294b22..8c9f3d8 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -478,6 +478,32 @@
             '/c/test/+/42#foo');
       });
 
+      suite('_handleRegisterRoute', () => {
+        test('happy path', () => {
+          const ctx = {params: ['/foo/bar']};
+          element._handleRegisterRoute(ctx);
+          assert.isTrue(redirectStub.calledWithExactly('/foo/bar'));
+          assert.isTrue(setParamsStub.calledOnce);
+          assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
+        });
+
+        test('no param', () => {
+          const ctx = {params: ['']};
+          element._handleRegisterRoute(ctx);
+          assert.isTrue(redirectStub.calledWithExactly('/'));
+          assert.isTrue(setParamsStub.calledOnce);
+          assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
+        });
+
+        test('prevent redirect', () => {
+          const ctx = {params: ['/register']};
+          element._handleRegisterRoute(ctx);
+          assert.isTrue(redirectStub.calledWithExactly('/'));
+          assert.isTrue(setParamsStub.calledOnce);
+          assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
+        });
+      });
+
       suite('_handleRootRoute', () => {
         test('closes for closeAfterLogin', () => {
           const data = {querystring: 'closeAfterLogin', canonicalPath: ''};
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
index 1bd345e..1c5e4ae 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
@@ -69,14 +69,14 @@
         </section>
         <section>
           <div class="title">Preferred Email</div>
-          <gr-select id="email">
-            <select disabled="[[_saving]]">
-              <option value="[[_account.email]]">[[_account.email]]</option>
-              <template is="dom-repeat" items="[[_account.secondary_emails]]">
-                <option value="[[item]]">[[item]]</option>
-              </template>
-            </select>
-          </gr-select>
+          <select
+              id="email"
+              disabled="[[_saving]]">
+            <option value="[[_account.email]]">[[_account.email]]</option>
+            <template is="dom-repeat" items="[[_account.secondary_emails]]">
+              <option value="[[item]]">[[item]]</option>
+            </template>
+          </select>
         </section>
       </main>
       <footer>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
index bf8995e..858c3ae 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
@@ -144,5 +144,13 @@
         assert.equal(account.name, 'entered name');
       }).then(done);
     });
+
+    test('email select properly populated', done => {
+      element._account = {email: 'foo', secondary_emails: ['bar', 'baz']};
+      flush(() => {
+        assert.equal(element.$.email.value, 'foo');
+        done();
+      });
+    });
   });
 </script>