Merge "Remove target=_self from commentlinks"
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 28ecc97..10a2ff3 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1728,6 +1728,10 @@
 
 Retrieves the external ids of a user account.
 
+Only external ids belonging to the caller may be requested. Users that have
+link:access-control.html#capability_modifyAccount[Modify Account] can request
+external ids that belong to other accounts.
+
 .Request
 ----
   GET /a/accounts/self/external.ids HTTP/1.0
@@ -1761,7 +1765,9 @@
 Delete a list of external ids for a user account. The target external ids must
 be provided as a list in the request body.
 
-Only external ids belonging to the caller may be deleted.
+Only external ids belonging to the caller may be deleted. Users that have
+link:access-control.html#capability_modifyAccount[Modify Account] can delete
+external ids that belong to other accounts.
 
 .Request
 ----
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 3b243b0..59af694 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6266,7 +6266,7 @@
 |`tag`         |optional, drafts only|
 Value of the `tag` field. Only allowed on link:#create-draft[draft comment] +
 inputs; for published comments, use the `tag` field in +
-link#review-input[ReviewInput]. Votes/comments that contain `tag` with
+link:#review-input[ReviewInput]. Votes/comments that contain `tag` with
 'autogenerated:' prefix can be filtered out in the web UI.
 |`unresolved`        |optional|
 Whether or not the comment must be addressed by the user. This value will
diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
index 43470af..b6375c8 100644
--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -186,12 +186,6 @@
     if (urlParameterMap.containsKey("ce")) {
       data.put("polyfillCE", "true");
     }
-    if (urlParameterMap.containsKey("sd")) {
-      data.put("polyfillSD", "true");
-    }
-    if (urlParameterMap.containsKey("sc")) {
-      data.put("polyfillSC", "true");
-    }
     if (urlParameterMap.containsKey("gf")) {
       data.put("useGoogleFonts", "true");
     }
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index b32da89..89ebdc1 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -328,10 +328,10 @@
     try (TraceContext traceContext = enableTracing(req, res)) {
       List<IdString> path = splitPath(req);
 
-      RequestInfo requestInfo = createRequestInfo(traceContext, requestUri(req), path);
-      globals.requestListeners.runEach(l -> l.onRequest(requestInfo));
-
       try (PerThreadCache ignored = PerThreadCache.create()) {
+        RequestInfo requestInfo = createRequestInfo(traceContext, requestUri(req), path);
+        globals.requestListeners.runEach(l -> l.onRequest(requestInfo));
+
         // It's important that the PerformanceLogContext is closed before the response is sent to
         // the client. Only this way it is ensured that the invocation of the PerformanceLogger
         // plugins happens before the client sees the response. This is needed for being able to
@@ -1702,9 +1702,9 @@
     if (rootCollection instanceof ProjectsCollection) {
       requestInfo.project(Project.nameKey(resourceId));
     } else if (rootCollection instanceof ChangesCollection) {
-      ChangeNotes changeNotes = globals.changeFinder.findOne(resourceId);
-      if (changeNotes != null) {
-        requestInfo.project(changeNotes.getProjectName());
+      Optional<ChangeNotes> changeNotes = globals.changeFinder.findOne(resourceId);
+      if (changeNotes.isPresent()) {
+        requestInfo.project(changeNotes.get().getProjectName());
       }
     }
     return requestInfo.build();
diff --git a/java/com/google/gerrit/server/account/CreateGroupArgs.java b/java/com/google/gerrit/server/account/CreateGroupArgs.java
index 2a764cc..ba58c3f 100644
--- a/java/com/google/gerrit/server/account/CreateGroupArgs.java
+++ b/java/com/google/gerrit/server/account/CreateGroupArgs.java
@@ -35,10 +35,6 @@
   }
 
   public void setGroupName(String n) {
-    groupName = n != null ? AccountGroup.nameKey(n) : null;
-  }
-
-  public void setGroupName(AccountGroup.NameKey n) {
-    groupName = n;
+    groupName = n != null ? AccountGroup.nameKey(n.trim()) : null;
   }
 }
diff --git a/java/com/google/gerrit/server/change/ChangeFinder.java b/java/com/google/gerrit/server/change/ChangeFinder.java
index da3650d..f2b6dd6 100644
--- a/java/com/google/gerrit/server/change/ChangeFinder.java
+++ b/java/com/google/gerrit/server/change/ChangeFinder.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
@@ -54,6 +55,8 @@
 
 @Singleton
 public class ChangeFinder {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   private static final String CACHE_NAME = "changeid_project";
 
   public static Module module() {
@@ -108,12 +111,12 @@
     this.allowedIdTypes = ImmutableSet.copyOf(configuredChangeIdTypes);
   }
 
-  public ChangeNotes findOne(String id) {
+  public Optional<ChangeNotes> findOne(String id) {
     List<ChangeNotes> ctls = find(id);
     if (ctls.size() != 1) {
-      return null;
+      return Optional.empty();
     }
-    return ctls.get(0);
+    return Optional.of(ctls.get(0));
   }
 
   /**
@@ -211,12 +214,12 @@
     }
   }
 
-  public ChangeNotes findOne(Change.Id id) {
+  public Optional<ChangeNotes> findOne(Change.Id id) {
     List<ChangeNotes> notes = find(id);
     if (notes.size() != 1) {
-      throw new NoSuchChangeException(id);
+      return Optional.empty();
     }
-    return notes.get(0);
+    return Optional.of(notes.get(0));
   }
 
   public List<ChangeNotes> find(Change.Id id) {
@@ -239,7 +242,11 @@
     List<ChangeNotes> notes = new ArrayList<>(cds.size());
     if (!indexConfig.separateChangeSubIndexes()) {
       for (ChangeData cd : cds) {
-        notes.add(cd.notes());
+        try {
+          notes.add(cd.notes());
+        } catch (NoSuchChangeException e) {
+          logger.atWarning().log("Change %s seen in index, but missing in NoteDb", e.getMessage());
+        }
       }
       return notes;
     }
@@ -253,7 +260,11 @@
     Set<Change.Id> seen = Sets.newHashSetWithExpectedSize(cds.size());
     for (ChangeData cd : cds) {
       if (seen.add(cd.getId())) {
-        notes.add(cd.notes());
+        try {
+          notes.add(cd.notes());
+        } catch (NoSuchChangeException e) {
+          logger.atWarning().log("Change %s seen in index, but missing in NoteDb", e.getMessage());
+        }
       }
     }
     return notes;
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 42a8310..4a95b2e 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -41,6 +41,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.ProvisionException;
+import java.util.Optional;
 
 /** Parses a query string meant to be applied to account objects. */
 public class AccountQueryBuilder extends QueryBuilder<AccountState, AccountQueryBuilder> {
@@ -115,20 +116,23 @@
   @Operator
   public Predicate<AccountState> cansee(String change)
       throws QueryParseException, PermissionBackendException {
-    ChangeNotes changeNotes = args.changeFinder.findOne(change);
-    if (changeNotes == null) {
+    Optional<ChangeNotes> changeNotes = args.changeFinder.findOne(change);
+    if (!changeNotes.isPresent()) {
       throw error(String.format("change %s not found", change));
     }
 
     try {
-      args.permissionBackend.user(args.getUser()).change(changeNotes).check(ChangePermission.READ);
+      args.permissionBackend
+          .user(args.getUser())
+          .change(changeNotes.get())
+          .check(ChangePermission.READ);
     } catch (AuthException e) {
       String msg = String.format("change %s not found", change);
       logger.atSevere().withCause(e).log(msg);
       throw error(msg);
     }
 
-    return AccountPredicates.cansee(args, changeNotes);
+    return AccountPredicates.cansee(args, changeNotes.get());
   }
 
   @Operator
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index f8610db..7401657 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -703,7 +703,7 @@
   }
 
   @Operator
-  public Predicate<ChangeData> branch(String name) {
+  public Predicate<ChangeData> branch(String name) throws QueryParseException {
     if (name.startsWith("^")) {
       return ref("^" + RefNames.fullName(name.substring(1)));
     }
@@ -732,7 +732,7 @@
   }
 
   @Operator
-  public Predicate<ChangeData> ref(String ref) {
+  public Predicate<ChangeData> ref(String ref) throws QueryParseException {
     if (ref.startsWith("^")) {
       return new RegexRefPredicate(ref);
     }
diff --git a/java/com/google/gerrit/server/query/change/RegexRefPredicate.java b/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
index 6d404b4..b2dba72 100644
--- a/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
@@ -22,7 +23,7 @@
 public class RegexRefPredicate extends ChangeRegexPredicate {
   protected final RunAutomaton pattern;
 
-  public RegexRefPredicate(String re) {
+  public RegexRefPredicate(String re) throws QueryParseException {
     super(ChangeField.REF, re);
 
     if (re.startsWith("^")) {
@@ -33,7 +34,11 @@
       re = re.substring(0, re.length() - 1);
     }
 
-    this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
+    try {
+      this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
+    } catch (IllegalArgumentException e) {
+      throw new QueryParseException(String.format("invalid regular expression: %s", re), e);
+    }
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java b/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java
index 80f0bb1..445a5d6 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java
@@ -73,7 +73,7 @@
   public Response<?> apply(AccountResource resource, List<String> extIds)
       throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
     if (!self.get().hasSameAccountId(resource.getUser())) {
-      permissionBackend.currentUser().check(GlobalPermission.ACCESS_DATABASE);
+      permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
     }
 
     if (extIds == null || extIds.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/account/GetExternalIds.java b/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
index c5b4454..a3c48b9 100644
--- a/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
+++ b/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
@@ -67,7 +67,7 @@
   public Response<List<AccountExternalIdInfo>> apply(AccountResource resource)
       throws RestApiException, IOException, PermissionBackendException {
     if (!self.get().hasSameAccountId(resource.getUser())) {
-      permissionBackend.currentUser().check(GlobalPermission.ACCESS_DATABASE);
+      permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
     }
 
     Collection<ExternalId> ids = externalIds.byAccount(resource.getUser().getAccountId());
diff --git a/java/com/google/gerrit/sshd/commands/PatchSetParser.java b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
index f804c2d..4ebf15e 100644
--- a/java/com/google/gerrit/sshd/commands/PatchSetParser.java
+++ b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
@@ -22,7 +22,6 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -32,6 +31,7 @@
 import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 @Singleton
 public class PatchSetParser {
@@ -126,12 +126,11 @@
     if (projectState != null) {
       return notesFactory.create(projectState.getNameKey(), changeId);
     }
-    try {
-      ChangeNotes notes = changeFinder.findOne(changeId);
-      return notesFactory.create(notes.getProjectName(), changeId);
-    } catch (NoSuchChangeException e) {
-      throw error("\"" + changeId + "\" no such change", e);
+    Optional<ChangeNotes> notes = changeFinder.findOne(changeId);
+    if (!notes.isPresent()) {
+      throw error("\"" + changeId + "\" no such change");
     }
+    return notesFactory.create(notes.get().getProjectName(), changeId);
   }
 
   private static boolean inProject(Change change, ProjectState projectState) {
diff --git a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
index bf5acee..52bff58 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static java.util.stream.Collectors.toList;
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 
@@ -31,6 +32,7 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.server.restapi.change.QueryChanges;
 import com.google.inject.Inject;
@@ -225,6 +227,26 @@
     assertThat(queryChanges.apply(TopLevelResource.INSTANCE).statusCode()).isEqualTo(SC_OK);
   }
 
+  @Test
+  public void defaultQueryCannotBeParsedDueToInvalidRegEx() throws Exception {
+    QueryChanges queryChanges = queryChangesProvider.get();
+    queryChanges.addQuery("^[A");
+    BadRequestException e =
+        assertThrows(
+            BadRequestException.class, () -> queryChanges.apply(TopLevelResource.INSTANCE));
+    assertThat(e).hasMessageThat().contains("no viable alternative at character '['");
+  }
+
+  @Test
+  public void defaultQueryWithInvalidQuotedRegEx() throws Exception {
+    QueryChanges queryChanges = queryChangesProvider.get();
+    queryChanges.addQuery("\"^[A\"");
+    BadRequestException e =
+        assertThrows(
+            BadRequestException.class, () -> queryChanges.apply(TopLevelResource.INSTANCE));
+    assertThat(e).hasMessageThat().isEqualTo("invalid regular expression: [A");
+  }
+
   private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
     for (ChangeInfo info : results) {
       assertThat(info._moreChanges).isNull();
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 17a0ea6..30eaf22 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -363,6 +363,13 @@
   }
 
   @Test
+  public void createGroupNameIsTrimmed() throws Exception {
+    String newGroupName = name("newGroup");
+    GroupInfo g = gApi.groups().create(" " + newGroupName + " ").get();
+    assertGroupInfo(group(newGroupName), g);
+  }
+
+  @Test
   public void createDuplicateInternalGroupCaseSensitiveName_Conflict() throws Exception {
     String dupGroupName = name("dupGroup");
     gApi.groups().create(dupGroupName);
@@ -1349,17 +1356,6 @@
   }
 
   @Test
-  public void groupNamesWithLeadingAndTrailingWhitespace() throws Exception {
-    for (String leading : ImmutableList.of("", " ", "  ")) {
-      for (String trailing : ImmutableList.of("", " ", "  ")) {
-        String name = leading + name("group") + trailing;
-        GroupInfo g = gApi.groups().create(name).get();
-        assertThat(g.name).isEqualTo(name);
-      }
-    }
-  }
-
-  @Test
   @Sandboxed
   public void groupsOfUserCanBeListedInSlaveMode() throws Exception {
     GroupInput groupInput = new GroupInput();
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index 6e16435..53e871f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -132,14 +132,14 @@
     AuthException thrown =
         assertThrows(
             AuthException.class, () -> gApi.accounts().id(admin.id().get()).getExternalIds());
-    assertThat(thrown).hasMessageThat().contains("access database not permitted");
+    assertThat(thrown).hasMessageThat().contains("modify account not permitted");
   }
 
   @Test
-  public void getExternalIdsOfOtherUserWithAccessDatabase() throws Exception {
+  public void getExternalIdsOfOtherUserWithModifyAccount() throws Exception {
     projectOperations
         .allProjectsForUpdate()
-        .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+        .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
         .update();
 
     Collection<ExternalId> expectedIds = getAccountState(admin.id()).externalIds();
@@ -193,7 +193,7 @@
                 gApi.accounts()
                     .id(admin.id().get())
                     .deleteExternalIds(extIds.stream().map(e -> e.identity).collect(toList())));
-    assertThat(thrown).hasMessageThat().contains("access database not permitted");
+    assertThat(thrown).hasMessageThat().contains("modify account not permitted");
   }
 
   @Test
@@ -213,10 +213,10 @@
   }
 
   @Test
-  public void deleteExternalIdsOfOtherUserWithAccessDatabase() throws Exception {
+  public void deleteExternalIdsOfOtherUserWithModifyAccount() throws Exception {
     projectOperations
         .allProjectsForUpdate()
-        .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+        .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
         .update();
 
     List<AccountExternalIdInfo> externalIds = gApi.accounts().self().getExternalIds();
@@ -464,7 +464,7 @@
   public void readExternalIdsWhenInvalidExternalIdsExist() throws Exception {
     projectOperations
         .allProjectsForUpdate()
-        .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+        .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
         .update();
     requestScopeOperations.resetCurrentApiUser();
 
diff --git a/plugins/replication b/plugins/replication
index 8be6db9..718d621 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 8be6db961b2c6c3a32e0b3db2f04475ca2c4c520
+Subproject commit 718d6210721cbfc38bbac6a3d976636fe17e4b72
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 51fabe8..ff6a093 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
@@ -255,6 +255,9 @@
       gr-messages-list {
         display: block;
       }
+      gr-thread-list {
+        min-height: 250px;
+      }
       #includedInOverlay {
         width: 65em;
       }
@@ -617,7 +620,7 @@
               change="[[_change]]"
               change-num="[[_changeNum]]"
               logged-in="[[_loggedIn]]"
-              comment-tab="[[_currentView]]"
+              tab="[[_findings_tab_name]]"
               hide-toggle-buttons
               on-thread-list-modified="_handleReloadDiffComments"></gr-thread-list>
         </template>
@@ -672,7 +675,6 @@
               threads="[[_commentThreads]]"
               change="[[_change]]"
               change-num="[[_changeNum]]"
-              comment-tab="[[_currentView]]"
               logged-in="[[_loggedIn]]"
               only-show-robot-comments-with-human-reply
               on-thread-list-modified="_handleReloadDiffComments"></gr-thread-list>
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 8fc61b7..53e852f 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
@@ -36,6 +36,7 @@
 <link rel="import" href="../../shared/gr-select/gr-select.html">
 <link rel="import" href="../../shared/gr-count-string-formatter/gr-count-string-formatter.html">
 <link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
+<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
 <link rel="import" href="../gr-file-list-constants.html">
 
 <dom-module id="gr-file-list">
@@ -115,14 +116,10 @@
       .path {
         cursor: pointer;
         flex: 1;
-        text-decoration: none;
         /* Wrap it into multiple lines if too long. */
         white-space: normal;
         word-break: break-word;
       }
-      .path:hover :first-child {
-        text-decoration: underline;
-      }
       .oldPath {
         color: var(--deemphasized-text-color);
       }
@@ -245,6 +242,26 @@
         display: inline-block;
         margin: -2px 0;
         padding: var(--spacing-s) 0;
+        text-decoration: none;
+      }
+      .pathLink:hover {
+        text-decoration: underline;
+      }
+
+      /** copy on file path **/
+      .pathLink gr-copy-clipboard,
+      .oldPath gr-copy-clipboard {
+        display: inline-block;
+        visibility: hidden;
+        vertical-align: bottom;
+        text-decoration: none;
+        --gr-button: {
+          padding: 0px;
+        }
+      }
+      .pathLink:hover gr-copy-clipboard,
+      .oldPath:hover gr-copy-clipboard {
+        visibility: visible;
       }
 
       /** small screen breakpoint: 768px */
@@ -273,7 +290,7 @@
         }
         .expanded .fullFileName,
         .truncatedFileName {
-          display: block;
+          display: inline;
         }
         .expanded .truncatedFileName,
         .fullFileName {
@@ -330,11 +347,18 @@
                     class="truncatedFileName">
                   [[computeTruncatedPath(file.__path)]]
                 </span>
+                <gr-copy-clipboard
+                  hide-input
+                  text="[[file.__path]]"></gr-copy-clipboard>
               </a>
-              <div class="oldPath" hidden$="[[!file.old_path]]" hidden
-                  title$="[[file.old_path]]">
-                [[file.old_path]]
-              </div>
+              <template is="dom-if" if="[[file.old_path]]">
+                <div class="oldPath" title$="[[file.old_path]]">
+                  [[file.old_path]]
+                  <gr-copy-clipboard
+                    hide-input
+                    text="[[file.old_path]]"></gr-copy-clipboard>
+                </div>
+              </template>
             </span>
             <div class="comments desktop">
               <span class="drafts">
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index b807b37..f7ef7e6 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -36,7 +36,6 @@
     </style>
     <style include="shared-styles">
       :host {
-        border-bottom: 1px solid var(--border-color);
         display: block;
         position: relative;
         cursor: pointer;
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 6154ca5..60ec6b0 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -64,6 +64,9 @@
         align-items: center;
         display: flex;
       }
+      gr-message:not(:last-of-type) {
+        border-bottom: 1px solid var(--border-color);
+      }
       gr-message:nth-child(2n) {
         background-color: var(--background-color-secondary);
       }
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
index eacc0d0..f04a39a 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
@@ -25,7 +25,6 @@
     <style include="shared-styles">
       #threads {
         display: block;
-        min-height: 20rem;
         padding: var(--spacing-l);
       }
       gr-comment-thread {
@@ -76,7 +75,7 @@
     </template>
     <div id="threads">
       <template is="dom-if" if="[[!threads.length]]">
-        [[_computeNoThreadsMessage(commentTab)]]
+        [[_computeNoThreadsMessage(tab)]]
       </template>
       <template
           is="dom-repeat"
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index d8c7b61..e99367e 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -27,12 +27,7 @@
     + 'for this change.';
   const NO_ROBOT_COMMENTS_THREADS_MESSAGE = 'There are no findings for this ' +
     'patchset.';
-
-  const CommentTabs = {
-    CHANGE_LOG: 0,
-    COMMENT_THREADS: 1,
-    ROBOT_COMMENTS: 2,
-  };
+  const FINDINGS_TAB_NAME = '__gerrit_internal_findings';
 
   class GrThreadList extends Polymer.GestureEventListeners(
       Polymer.LegacyElementMixin(
@@ -73,7 +68,10 @@
           type: Boolean,
           value: false,
         },
-        commentTab: Number,
+        tab: {
+          type: String,
+          value: '',
+        },
       };
     }
 
@@ -83,8 +81,8 @@
       return loggedIn ? 'show' : '';
     }
 
-    _computeNoThreadsMessage(commentTab) {
-      if (commentTab === CommentTabs.ROBOT_COMMENTS) {
+    _computeNoThreadsMessage(tab) {
+      if (tab === FINDINGS_TAB_NAME) {
         return NO_ROBOT_COMMENTS_THREADS_MESSAGE;
       }
       return NO_THREADS_MESSAGE;
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
index 5797ff7..18ffc0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
@@ -325,6 +325,7 @@
               </div>
               <div>
                 <a
+                  tabIndex="-1"
                   on-click="_onRespectfulReadMoreClick"
                   href="https://testing.googleblog.com/2019/11/code-health-respectful-reviews-useful.html"
                   target="_blank">
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
index 2ce03e3..9be6852 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
@@ -53,7 +53,10 @@
       Polymer.dom(e).rootTarget.select();
     }
 
-    _copyToClipboard() {
+    _copyToClipboard(e) {
+      e.preventDefault();
+      e.stopPropagation();
+
       if (this.hideInput) {
         this.$.input.style.display = 'block';
       }
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
index 55eb198..05014f1 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
@@ -90,5 +90,16 @@
       flushAsynchronousOperations();
       assert.equal(getComputedStyle(element.$.input).display, 'none');
     });
+
+    test('stop events propagation', () => {
+      const divParent = document.createElement('div');
+      divParent.appendChild(element);
+      const clickStub = sinon.stub();
+      divParent.addEventListener('click', clickStub);
+      element.stopPropagation = true;
+      const copyBtn = element.shadowRoot.querySelector('.copyToClipboard');
+      MockInteractions.tap(copyBtn);
+      assert.isFalse(clickStub.called);
+    });
   });
 </script>
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index dbb47a3..212f504 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -25,8 +25,6 @@
   {@param? faviconPath: ?}
   {@param? versionInfo: ?}
   {@param? polyfillCE: ?}
-  {@param? polyfillSD: ?}
-  {@param? polyfillSC: ?}
   {@param? useGoogleFonts: ?}
   {@param? changeRequestsPath: ?}
   {@param? defaultChangeDetailHex: ?}
@@ -62,8 +60,6 @@
     {if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if}
     {if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if}
     {if $polyfillCE}if (window.customElements) window.customElements.forcePolyfill = true;{/if}
-    {if $polyfillSD}{literal}ShadyDOM = { force: true };{/literal}{/if}
-    {if $polyfillSC}{literal}ShadyCSS = { shimcssproperties: true};{/literal}{/if}
     {if $gerritInitialData}
       // INITIAL_DATA is a string that represents a JSON map. It's inlined here so that we can
       // spare calls to the API when starting up the app.