Merge "Add project specific nav to sidebar"
diff --git a/WORKSPACE b/WORKSPACE
index 5da3d522..63d10c6 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -581,10 +581,10 @@
 
 maven_jar(
     name = "blame_cache",
-    artifact = "com/google/gitiles:blame-cache:0.2-1",
+    artifact = "com/google/gitiles:blame-cache:0.2-2",
     attach_source = False,
     repository = GERRIT,
-    sha1 = "da7977e8b140b63f18054214c1d1b86ffa6896cb",
+    sha1 = "ac8693b319b3e70506fb27df1b77b598cfdcd00f",
 )
 
 # Keep this version of Soy synchronized with the version used in Gitiles.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 1c5f178..5fbf8ca 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -1174,7 +1174,6 @@
     assertThat(actual.fingerprint)
         .named(id)
         .isEqualTo(Fingerprint.toString(expected.getPublicKey().getFingerprint()));
-    @SuppressWarnings("unchecked")
     List<String> userIds = ImmutableList.copyOf(expected.getPublicKey().getUserIDs());
     assertThat(actual.userIds).named(id).containsExactlyElementsIn(userIds);
     assertThat(actual.key).named(id).startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
new file mode 100644
index 0000000..cea907d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
@@ -0,0 +1,261 @@
+// Copyright (C) 2016 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.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.createAnnotatedTag;
+import static com.google.gerrit.acceptance.GitUtil.deleteRef;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.GitUtil.updateAnnotatedTag;
+import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.ANNOTATED;
+import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.LIGHTWEIGHT;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.common.base.MoreObjects;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.reviewdb.client.RefNames;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+public abstract class AbstractPushTag extends AbstractDaemonTest {
+  enum TagType {
+    LIGHTWEIGHT(Permission.CREATE),
+    ANNOTATED(Permission.CREATE_TAG);
+
+    final String createPermission;
+
+    TagType(String createPermission) {
+      this.createPermission = createPermission;
+    }
+  }
+
+  private RevCommit initialHead;
+  private TagType tagType;
+
+  @Before
+  public void setup() throws Exception {
+    // clone with user to avoid inherited tag permissions of admin user
+    testRepo = cloneProject(project, user);
+
+    initialHead = getRemoteHead();
+    tagType = getTagType();
+  }
+
+  protected abstract TagType getTagType();
+
+  @Test
+  public void createTagForExistingCommit() throws Exception {
+    pushTagForExistingCommit(Status.REJECTED_OTHER_REASON);
+
+    allowTagCreation();
+    pushTagForExistingCommit(Status.OK);
+
+    allowPushOnRefsTags();
+    pushTagForExistingCommit(Status.OK);
+
+    removePushFromRefsTags();
+  }
+
+  @Test
+  public void createTagForNewCommit() throws Exception {
+    pushTagForNewCommit(Status.REJECTED_OTHER_REASON);
+
+    allowTagCreation();
+    pushTagForNewCommit(Status.REJECTED_OTHER_REASON);
+
+    allowPushOnRefsTags();
+    pushTagForNewCommit(Status.OK);
+
+    removePushFromRefsTags();
+  }
+
+  @Test
+  public void fastForward() throws Exception {
+    allowTagCreation();
+    String tagName = pushTagForExistingCommit(Status.OK);
+
+    fastForwardTagToExistingCommit(tagName, Status.REJECTED_OTHER_REASON);
+    fastForwardTagToNewCommit(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowTagDeletion();
+    fastForwardTagToExistingCommit(tagName, Status.REJECTED_OTHER_REASON);
+    fastForwardTagToNewCommit(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowPushOnRefsTags();
+    Status expectedStatus = tagType == ANNOTATED ? Status.REJECTED_OTHER_REASON : Status.OK;
+    fastForwardTagToExistingCommit(tagName, expectedStatus);
+    fastForwardTagToNewCommit(tagName, expectedStatus);
+
+    allowForcePushOnRefsTags();
+    fastForwardTagToExistingCommit(tagName, Status.OK);
+    fastForwardTagToNewCommit(tagName, Status.OK);
+
+    removePushFromRefsTags();
+  }
+
+  @Test
+  public void forceUpdate() throws Exception {
+    allowTagCreation();
+    String tagName = pushTagForExistingCommit(Status.OK);
+
+    forceUpdateTagToExistingCommit(tagName, Status.REJECTED_OTHER_REASON);
+    forceUpdateTagToNewCommit(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowPushOnRefsTags();
+    forceUpdateTagToExistingCommit(tagName, Status.REJECTED_OTHER_REASON);
+    forceUpdateTagToNewCommit(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowTagDeletion();
+    forceUpdateTagToExistingCommit(tagName, Status.REJECTED_OTHER_REASON);
+    forceUpdateTagToNewCommit(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowForcePushOnRefsTags();
+    forceUpdateTagToExistingCommit(tagName, Status.OK);
+    forceUpdateTagToNewCommit(tagName, Status.OK);
+
+    removePushFromRefsTags();
+  }
+
+  @Test
+  public void delete() throws Exception {
+    allowTagCreation();
+    String tagName = pushTagForExistingCommit(Status.OK);
+
+    pushTagDeletion(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowPushOnRefsTags();
+    pushTagDeletion(tagName, Status.REJECTED_OTHER_REASON);
+
+    allowForcePushOnRefsTags();
+    tagName = pushTagForExistingCommit(Status.OK);
+    pushTagDeletion(tagName, Status.OK);
+
+    removePushFromRefsTags();
+    allowTagDeletion();
+    tagName = pushTagForExistingCommit(Status.OK);
+    pushTagDeletion(tagName, Status.OK);
+  }
+
+  private String pushTagForExistingCommit(Status expectedStatus) throws Exception {
+    return pushTag(null, false, false, expectedStatus);
+  }
+
+  private String pushTagForNewCommit(Status expectedStatus) throws Exception {
+    return pushTag(null, true, false, expectedStatus);
+  }
+
+  private void fastForwardTagToExistingCommit(String tagName, Status expectedStatus)
+      throws Exception {
+    pushTag(tagName, false, false, expectedStatus);
+  }
+
+  private void fastForwardTagToNewCommit(String tagName, Status expectedStatus) throws Exception {
+    pushTag(tagName, true, false, expectedStatus);
+  }
+
+  private void forceUpdateTagToExistingCommit(String tagName, Status expectedStatus)
+      throws Exception {
+    pushTag(tagName, false, true, expectedStatus);
+  }
+
+  private void forceUpdateTagToNewCommit(String tagName, Status expectedStatus) throws Exception {
+    pushTag(tagName, true, true, expectedStatus);
+  }
+
+  private String pushTag(String tagName, boolean newCommit, boolean force, Status expectedStatus)
+      throws Exception {
+    if (force) {
+      testRepo.reset(initialHead);
+    }
+    commit(user.getIdent(), "subject");
+
+    boolean createTag = tagName == null;
+    tagName = MoreObjects.firstNonNull(tagName, "v1_" + System.nanoTime());
+    switch (tagType) {
+      case LIGHTWEIGHT:
+        break;
+      case ANNOTATED:
+        if (createTag) {
+          createAnnotatedTag(testRepo, tagName, user.getIdent());
+        } else {
+          updateAnnotatedTag(testRepo, tagName, user.getIdent());
+        }
+        break;
+      default:
+        throw new IllegalStateException("unexpected tag type: " + tagType);
+    }
+
+    if (!newCommit) {
+      grant(project, "refs/for/refs/heads/master", Permission.SUBMIT, false, REGISTERED_USERS);
+      pushHead(testRepo, "refs/for/master%submit");
+    }
+
+    String tagRef = tagRef(tagName);
+    PushResult r =
+        tagType == LIGHTWEIGHT
+            ? pushHead(testRepo, tagRef, false, force)
+            : GitUtil.pushTag(testRepo, tagName, !createTag);
+    RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
+    assertThat(refUpdate.getStatus()).named(tagType.name()).isEqualTo(expectedStatus);
+    return tagName;
+  }
+
+  private void pushTagDeletion(String tagName, Status expectedStatus) throws Exception {
+    String tagRef = tagRef(tagName);
+    PushResult r = deleteRef(testRepo, tagRef);
+    RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
+    assertThat(refUpdate.getStatus()).named(tagType.name()).isEqualTo(expectedStatus);
+  }
+
+  private void allowTagCreation() throws Exception {
+    grant(project, "refs/tags/*", tagType.createPermission, false, REGISTERED_USERS);
+  }
+
+  private void allowPushOnRefsTags() throws Exception {
+    removePushFromRefsTags();
+    grant(project, "refs/tags/*", Permission.PUSH, false, REGISTERED_USERS);
+  }
+
+  private void allowForcePushOnRefsTags() throws Exception {
+    removePushFromRefsTags();
+    grant(project, "refs/tags/*", Permission.PUSH, true, REGISTERED_USERS);
+  }
+
+  private void allowTagDeletion() throws Exception {
+    removePushFromRefsTags();
+    grant(project, "refs/tags/*", Permission.DELETE, true, REGISTERED_USERS);
+  }
+
+  private void removePushFromRefsTags() throws Exception {
+    removePermission(project, "refs/tags/*", Permission.PUSH);
+  }
+
+  private void commit(PersonIdent ident, String subject) throws Exception {
+    commitBuilder().ident(ident).message(subject + " (" + System.nanoTime() + ")").create();
+  }
+
+  private static String tagRef(String tagName) {
+    return RefNames.REFS_TAGS + tagName;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUILD
index 3266be8..fbe5d80 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUILD
@@ -6,6 +6,7 @@
     labels = ["rest"],
     deps = [
         ":project",
+        ":push_tag_util",
         ":refassert",
     ],
 )
@@ -35,3 +36,14 @@
         "//lib:truth",
     ],
 )
+
+java_library(
+    name = "push_tag_util",
+    testonly = 1,
+    srcs = [
+        "AbstractPushTag.java",
+    ],
+    deps = [
+        "//gerrit-acceptance-tests:lib",
+    ],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushAnnotatedTagIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushAnnotatedTagIT.java
new file mode 100644
index 0000000..24c8ed0
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushAnnotatedTagIT.java
@@ -0,0 +1,23 @@
+// 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.rest.project;
+
+public class PushAnnotatedTagIT extends AbstractPushTag {
+
+  @Override
+  protected TagType getTagType() {
+    return TagType.ANNOTATED;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushLightweightTagIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushLightweightTagIT.java
new file mode 100644
index 0000000..20d83a0
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushLightweightTagIT.java
@@ -0,0 +1,23 @@
+// 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.rest.project;
+
+public class PushLightweightTagIT extends AbstractPushTag {
+
+  @Override
+  protected TagType getTagType() {
+    return TagType.LIGHTWEIGHT;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushTagIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushTagIT.java
deleted file mode 100644
index 691ea8a..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/PushTagIT.java
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright (C) 2016 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.rest.project;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.acceptance.GitUtil.createAnnotatedTag;
-import static com.google.gerrit.acceptance.GitUtil.deleteRef;
-import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import static com.google.gerrit.acceptance.GitUtil.updateAnnotatedTag;
-import static com.google.gerrit.acceptance.rest.project.PushTagIT.TagType.ANNOTATED;
-import static com.google.gerrit.acceptance.rest.project.PushTagIT.TagType.LIGHTWEIGHT;
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-
-import com.google.common.base.MoreObjects;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.GitUtil;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.RefNames;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.PushResult;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
-import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
-import org.junit.Before;
-import org.junit.Test;
-
-@NoHttpd
-public class PushTagIT extends AbstractDaemonTest {
-  enum TagType {
-    LIGHTWEIGHT(Permission.CREATE),
-    ANNOTATED(Permission.CREATE_TAG);
-
-    final String createPermission;
-
-    TagType(String createPermission) {
-      this.createPermission = createPermission;
-    }
-  }
-
-  private RevCommit initialHead;
-
-  @Before
-  public void setup() throws Exception {
-    // clone with user to avoid inherited tag permissions of admin user
-    testRepo = cloneProject(project, user);
-
-    initialHead = getRemoteHead();
-  }
-
-  @Test
-  public void createTagForExistingCommit() throws Exception {
-    for (TagType tagType : TagType.values()) {
-      pushTagForExistingCommit(tagType, Status.REJECTED_OTHER_REASON);
-
-      allowTagCreation(tagType);
-      pushTagForExistingCommit(tagType, Status.OK);
-
-      allowPushOnRefsTags();
-      pushTagForExistingCommit(tagType, Status.OK);
-
-      removePushFromRefsTags();
-    }
-  }
-
-  @Test
-  public void createTagForNewCommit() throws Exception {
-    for (TagType tagType : TagType.values()) {
-      pushTagForNewCommit(tagType, Status.REJECTED_OTHER_REASON);
-
-      allowTagCreation(tagType);
-      pushTagForNewCommit(tagType, Status.REJECTED_OTHER_REASON);
-
-      allowPushOnRefsTags();
-      pushTagForNewCommit(tagType, Status.OK);
-
-      removePushFromRefsTags();
-    }
-  }
-
-  @Test
-  public void fastForward() throws Exception {
-    for (TagType tagType : TagType.values()) {
-      allowTagCreation(tagType);
-      String tagName = pushTagForExistingCommit(tagType, Status.OK);
-
-      fastForwardTagToExistingCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-      fastForwardTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-
-      allowTagDeletion();
-      fastForwardTagToExistingCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-      fastForwardTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-
-      allowPushOnRefsTags();
-      Status expectedStatus = tagType == ANNOTATED ? Status.REJECTED_OTHER_REASON : Status.OK;
-      fastForwardTagToExistingCommit(tagType, tagName, expectedStatus);
-      fastForwardTagToNewCommit(tagType, tagName, expectedStatus);
-
-      allowForcePushOnRefsTags();
-      fastForwardTagToExistingCommit(tagType, tagName, Status.OK);
-      fastForwardTagToNewCommit(tagType, tagName, Status.OK);
-
-      removePushFromRefsTags();
-    }
-  }
-
-  @Test
-  public void forceUpdate() throws Exception {
-    for (TagType tagType : TagType.values()) {
-      allowTagCreation(tagType);
-      String tagName = pushTagForExistingCommit(tagType, Status.OK);
-
-      forceUpdateTagToExistingCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-      forceUpdateTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-
-      allowPushOnRefsTags();
-      forceUpdateTagToExistingCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-      forceUpdateTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-
-      allowTagDeletion();
-      forceUpdateTagToExistingCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-      forceUpdateTagToNewCommit(tagType, tagName, Status.REJECTED_OTHER_REASON);
-
-      allowForcePushOnRefsTags();
-      forceUpdateTagToExistingCommit(tagType, tagName, Status.OK);
-      forceUpdateTagToNewCommit(tagType, tagName, Status.OK);
-
-      removePushFromRefsTags();
-    }
-  }
-
-  @Test
-  public void delete() throws Exception {
-    for (TagType tagType : TagType.values()) {
-      allowTagCreation(tagType);
-      String tagName = pushTagForExistingCommit(tagType, Status.OK);
-
-      pushTagDeletion(tagType, tagName, Status.REJECTED_OTHER_REASON);
-
-      allowPushOnRefsTags();
-      pushTagDeletion(tagType, tagName, Status.REJECTED_OTHER_REASON);
-    }
-
-    allowForcePushOnRefsTags();
-    for (TagType tagType : TagType.values()) {
-      String tagName = pushTagForExistingCommit(tagType, Status.OK);
-      pushTagDeletion(tagType, tagName, Status.OK);
-    }
-
-    removePushFromRefsTags();
-    allowTagDeletion();
-    for (TagType tagType : TagType.values()) {
-      String tagName = pushTagForExistingCommit(tagType, Status.OK);
-      pushTagDeletion(tagType, tagName, Status.OK);
-    }
-  }
-
-  private String pushTagForExistingCommit(TagType tagType, Status expectedStatus) throws Exception {
-    return pushTag(tagType, null, false, false, expectedStatus);
-  }
-
-  private String pushTagForNewCommit(TagType tagType, Status expectedStatus) throws Exception {
-    return pushTag(tagType, null, true, false, expectedStatus);
-  }
-
-  private void fastForwardTagToExistingCommit(
-      TagType tagType, String tagName, Status expectedStatus) throws Exception {
-    pushTag(tagType, tagName, false, false, expectedStatus);
-  }
-
-  private void fastForwardTagToNewCommit(TagType tagType, String tagName, Status expectedStatus)
-      throws Exception {
-    pushTag(tagType, tagName, true, false, expectedStatus);
-  }
-
-  private void forceUpdateTagToExistingCommit(
-      TagType tagType, String tagName, Status expectedStatus) throws Exception {
-    pushTag(tagType, tagName, false, true, expectedStatus);
-  }
-
-  private void forceUpdateTagToNewCommit(TagType tagType, String tagName, Status expectedStatus)
-      throws Exception {
-    pushTag(tagType, tagName, true, true, expectedStatus);
-  }
-
-  private String pushTag(
-      TagType tagType, String tagName, boolean newCommit, boolean force, Status expectedStatus)
-      throws Exception {
-    if (force) {
-      testRepo.reset(initialHead);
-    }
-    commit(user.getIdent(), "subject");
-
-    boolean createTag = tagName == null;
-    tagName = MoreObjects.firstNonNull(tagName, "v1_" + System.nanoTime());
-    switch (tagType) {
-      case LIGHTWEIGHT:
-        break;
-      case ANNOTATED:
-        if (createTag) {
-          createAnnotatedTag(testRepo, tagName, user.getIdent());
-        } else {
-          updateAnnotatedTag(testRepo, tagName, user.getIdent());
-        }
-        break;
-      default:
-        throw new IllegalStateException("unexpected tag type: " + tagType);
-    }
-
-    if (!newCommit) {
-      grant(project, "refs/for/refs/heads/master", Permission.SUBMIT, false, REGISTERED_USERS);
-      pushHead(testRepo, "refs/for/master%submit");
-    }
-
-    String tagRef = tagRef(tagName);
-    PushResult r =
-        tagType == LIGHTWEIGHT
-            ? pushHead(testRepo, tagRef, false, force)
-            : GitUtil.pushTag(testRepo, tagName, !createTag);
-    RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
-    assertThat(refUpdate.getStatus()).named(tagType.name()).isEqualTo(expectedStatus);
-    return tagName;
-  }
-
-  private void pushTagDeletion(TagType tagType, String tagName, Status expectedStatus)
-      throws Exception {
-    String tagRef = tagRef(tagName);
-    PushResult r = deleteRef(testRepo, tagRef);
-    RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
-    assertThat(refUpdate.getStatus()).named(tagType.name()).isEqualTo(expectedStatus);
-  }
-
-  private void allowTagCreation(TagType tagType) throws Exception {
-    grant(project, "refs/tags/*", tagType.createPermission, false, REGISTERED_USERS);
-  }
-
-  private void allowPushOnRefsTags() throws Exception {
-    removePushFromRefsTags();
-    grant(project, "refs/tags/*", Permission.PUSH, false, REGISTERED_USERS);
-  }
-
-  private void allowForcePushOnRefsTags() throws Exception {
-    removePushFromRefsTags();
-    grant(project, "refs/tags/*", Permission.PUSH, true, REGISTERED_USERS);
-  }
-
-  private void allowTagDeletion() throws Exception {
-    removePushFromRefsTags();
-    grant(project, "refs/tags/*", Permission.DELETE, true, REGISTERED_USERS);
-  }
-
-  private void removePushFromRefsTags() throws Exception {
-    removePermission(project, "refs/tags/*", Permission.PUSH);
-  }
-
-  private void commit(PersonIdent ident, String subject) throws Exception {
-    commitBuilder().ident(ident).message(subject + " (" + System.nanoTime() + ")").create();
-  }
-
-  private static String tagRef(String tagName) {
-    return RefNames.REFS_TAGS + tagName;
-  }
-}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index b0429cb..ffedcfb 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -176,7 +176,6 @@
 
   private boolean hasAllowedUserId(PGPPublicKey key, Set<String> allowedUserIds)
       throws PGPException {
-    @SuppressWarnings("unchecked")
     Iterator<String> userIds = key.getUserIDs();
     while (userIds.hasNext()) {
       String userId = userIds.next();
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java
index c0ab26c..70e9a24 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java
@@ -381,7 +381,6 @@
     }
 
     List<CheckResult> signerResults = new ArrayList<>();
-    @SuppressWarnings("unchecked")
     Iterator<String> userIds = key.getUserIDs();
     while (userIds.hasNext()) {
       String userId = userIds.next();
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
index 144606a..8ab5fbd 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
@@ -398,7 +398,6 @@
   }
 
   public static String keyToString(PGPPublicKey key) {
-    @SuppressWarnings("unchecked")
     Iterator<String> it = key.getUserIDs();
     return String.format(
         "%s %s(%s)",
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
index 678247e..303499e 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -219,7 +219,6 @@
     if (key != null) {
       info.id = PublicKeyStore.keyIdToString(key.getKeyID());
       info.fingerprint = Fingerprint.toString(key.getFingerprint());
-      @SuppressWarnings("unchecked")
       Iterator<String> userIds = key.getUserIDs();
       info.userIds = ImmutableList.copyOf(userIds);
 
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index 64311e0..524af50 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -374,7 +374,7 @@
 
     PGPPublicKeyRing keyRingB = keyB().getPublicKeyRing();
     PGPPublicKey keyB = keyRingB.getPublicKey();
-    keyB = PGPPublicKey.removeCertification(keyB, (String) keyB.getUserIDs().next());
+    keyB = PGPPublicKey.removeCertification(keyB, keyB.getUserIDs().next());
     keyRingB = PGPPublicKeyRing.insertPublicKey(keyRingB, keyB);
     add(keyRingB, addUser("userB"));
 
@@ -392,8 +392,7 @@
     List<ExternalId> newExtIds = new ArrayList<>(2);
     newExtIds.add(ExternalId.create(toExtIdKey(kr.getPublicKey()), id));
 
-    @SuppressWarnings("unchecked")
-    String userId = (String) Iterators.getOnlyElement(kr.getPublicKey().getUserIDs(), null);
+    String userId = Iterators.getOnlyElement(kr.getPublicKey().getUserIDs(), null);
     if (userId != null) {
       String email = PushCertificateIdent.parse(userId).getEmailAddress();
       assertThat(email).contains("@");
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java
index c9c0b18..94eff06 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java
@@ -231,7 +231,6 @@
 
   private void assertUserIds(PGPPublicKeyRing keyRing, String... expected) throws Exception {
     List<String> actual = new ArrayList<>();
-    @SuppressWarnings("unchecked")
     Iterator<String> userIds =
         store.get(keyRing.getPublicKey().getKeyID()).iterator().next().getPublicKey().getUserIDs();
     while (userIds.hasNext()) {
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java
index 420dedf..b2ef65d 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java
@@ -77,7 +77,7 @@
   }
 
   public String getFirstUserId() {
-    return (String) getPublicKey().getUserIDs().next();
+    return getPublicKey().getUserIDs().next();
   }
 
   public PGPPrivateKey getPrivateKey() throws PGPException {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index d78e592..8bbc988 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -541,26 +541,31 @@
         mode = ModeInfo.findMode(content.getContentType(), path);
       }
     }
-    mv =
-        MergeView.create(
-            editor,
-            Configuration.create()
-                .set("autoCloseBrackets", prefs.autoCloseBrackets())
-                .set("cursorBlinkRate", prefs.cursorBlinkRate())
-                .set("cursorHeight", 0.85)
-                .set("indentUnit", prefs.indentUnit())
-                .set("keyMap", prefs.keyMapType().name().toLowerCase())
-                .set("lineNumbers", prefs.hideLineNumbers())
-                .set("lineWrapping", false)
-                .set("matchBrackets", prefs.matchBrackets())
-                .set("mode", mode != null ? mode.mime() : null)
-                .set("origLeft", editContent)
-                .set("scrollbarStyle", "overlay")
-                .set("showTrailingSpace", prefs.showWhitespaceErrors())
-                .set("styleSelectedText", true)
-                .set("tabSize", prefs.tabSize())
-                .set("theme", prefs.theme().name().toLowerCase())
-                .set("value", ""));
+
+    Configuration cfg =
+        Configuration.create()
+            .set("autoCloseBrackets", prefs.autoCloseBrackets())
+            .set("cursorBlinkRate", prefs.cursorBlinkRate())
+            .set("cursorHeight", 0.85)
+            .set("indentUnit", prefs.indentUnit())
+            .set("keyMap", prefs.keyMapType().name().toLowerCase())
+            .set("lineNumbers", prefs.hideLineNumbers())
+            .set("lineWrapping", false)
+            .set("matchBrackets", prefs.matchBrackets())
+            .set("mode", mode != null ? mode.mime() : null)
+            .set("origLeft", editContent)
+            .set("scrollbarStyle", "overlay")
+            .set("showTrailingSpace", prefs.showWhitespaceErrors())
+            .set("styleSelectedText", true)
+            .set("tabSize", prefs.tabSize())
+            .set("theme", prefs.theme().name().toLowerCase())
+            .set("value", "");
+
+    if (editContent.contains("\r\n")) {
+      cfg.set("lineSeparator", "\r\n");
+    }
+
+    mv = MergeView.create(editor, cfg);
 
     cmBase = mv.leftOriginal();
     cmBase.getWrapperElement().addClassName(style.base());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index 01c5ea4..8561dce 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -30,7 +30,6 @@
 import com.google.inject.Singleton;
 import com.google.inject.servlet.ServletModule;
 import java.io.IOException;
-import java.util.List;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.regex.Matcher;
@@ -114,7 +113,7 @@
       cont.suspend(rsp);
       cont.setAttribute(TASK, task);
 
-      Future f = getExecutor().submit(task);
+      Future<?> f = getExecutor().submit(task);
       cont.addContinuationListener(new Listener(f));
     } else if (cont.isExpired()) {
       rsp.sendError(SC_SERVICE_UNAVAILABLE);
@@ -150,9 +149,9 @@
   public void destroy() {}
 
   private final class Listener implements ContinuationListener {
-    final Future future;
+    final Future<?> future;
 
-    Listener(Future future) {
+    Listener(Future<?> future) {
       this.future = future;
     }
 
@@ -163,7 +162,6 @@
     public void onTimeout(Continuation self) {
       future.cancel(true);
     }
-
   }
 
   private final class TaskThunk implements CancelableRunnable {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
index a7c04f6..26cb3e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toSet;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.CharMatcher;
@@ -267,49 +266,6 @@
     }
   }
 
-  public Set<Account.Id> byChange(Change.Id changeId, String label) throws OrmException {
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      return getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId))
-          .stream()
-          .map(Account.Id::parse)
-          .filter(accountId -> hasStar(repo, changeId, accountId, label))
-          .collect(toSet());
-    } catch (IOException e) {
-      throw new OrmException(
-          String.format("Get accounts that starred change %d failed", changeId.get()), e);
-    }
-  }
-
-  @Deprecated
-  // To be used only for IsStarredByLegacyPredicate.
-  public Set<Change.Id> byAccount(Account.Id accountId, String label) throws OrmException {
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      return getRefNames(repo, RefNames.REFS_STARRED_CHANGES)
-          .stream()
-          .filter(refPart -> refPart.endsWith("/" + accountId.get()))
-          .map(Change.Id::fromRefPart)
-          .filter(changeId -> hasStar(repo, changeId, accountId, label))
-          .collect(toSet());
-    } catch (IOException e) {
-      throw new OrmException(
-          String.format("Get changes that were starred by %d failed", accountId.get()), e);
-    }
-  }
-
-  private boolean hasStar(Repository repo, Change.Id changeId, Account.Id accountId, String label) {
-    try {
-      return readLabels(repo, RefNames.refsStarredChanges(changeId, accountId))
-          .labels()
-          .contains(label);
-    } catch (IOException e) {
-      log.error(
-          String.format(
-              "Cannot query stars by account %d on change %d", accountId.get(), changeId.get()),
-          e);
-      return false;
-    }
-  }
-
   public ImmutableListMultimap<Account.Id, String> byChangeFromIndex(Change.Id changeId)
       throws OrmException {
     Set<String> fields = ImmutableSet.of(ChangeField.ID.getName(), ChangeField.STAR.getName());
@@ -351,7 +307,7 @@
   }
 
   public boolean isIgnoredBy(Change.Id changeId, Account.Id accountId) throws OrmException {
-    return byChange(changeId, IGNORE_LABEL).contains(accountId);
+    return getLabels(accountId, changeId).contains(IGNORE_LABEL);
   }
 
   private static String getMuteLabel(Change change) {
@@ -379,7 +335,7 @@
   }
 
   public boolean isMutedBy(Change change, Account.Id accountId) throws OrmException {
-    return byChange(change.getId(), getMuteLabel(change)).contains(accountId);
+    return getLabels(accountId, change.getId()).contains(getMuteLabel(change));
   }
 
   private static StarRef readLabels(Repository repo, String refName) throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 040b6de..d8ff050 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.collect.Lists;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 import com.google.gerrit.extensions.common.FileInfo;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -22,10 +24,10 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.CacheControl;
 import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.ETagView;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -38,6 +40,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListKey;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -90,7 +93,7 @@
     return new FileResource(rev, id.get());
   }
 
-  public static final class ListFiles implements RestReadView<RevisionResource> {
+  public static final class ListFiles implements ETagView<RevisionResource> {
     private static final Logger log = LoggerFactory.getLogger(ListFiles.class);
 
     @Option(name = "--base", metaVar = "revision-id")
@@ -322,5 +325,15 @@
       this.parentNum = parentNum;
       return this;
     }
+
+    @Override
+    public String getETag(RevisionResource resource) {
+      Hasher h = Hashing.murmur3_128().newHasher();
+      resource.prepareETag(h, resource.getUser());
+      // File list comes from the PatchListCache, so any change to the key or value should
+      // invalidate ETag.
+      h.putLong(PatchListKey.serialVersionUID);
+      return h.hash().toString();
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
index 32132bc..a582e2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestResource.HasETag;
 import com.google.gerrit.extensions.restapi.RestView;
@@ -82,10 +84,15 @@
 
   @Override
   public String getETag() {
-    // Conservative estimate: refresh the revision if its parent change has
-    // changed, so we don't have to check whether a given modification affected
-    // this revision specifically.
-    return change.getETag();
+    Hasher h = Hashing.murmur3_128().newHasher();
+    prepareETag(h, getUser());
+    return h.hash().toString();
+  }
+
+  void prepareETag(Hasher h, CurrentUser user) {
+    // Conservative estimate: refresh the revision if its parent change has changed, so we don't
+    // have to check whether a given modification affected this revision specifically.
+    change.prepareETag(h, user);
   }
 
   Account.Id getAccountId() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
index 2ac6695..674a5c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
@@ -159,6 +159,7 @@
   }
 
   public void setGroupReference(String name, GroupReference value) {
-    setString(name, value.toConfigValue());
+    GroupReference groupRef = projectConfig.resolve(value);
+    setString(name, groupRef.toConfigValue());
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
index db0066d..a48b3ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
@@ -29,7 +29,7 @@
 import java.io.OutputStream;
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
@@ -125,7 +125,7 @@
   }
 
   private final ReceiveCommits rc;
-  private final ScheduledThreadPoolExecutor executor;
+  private final ScheduledExecutorService executor;
   private final RequestScopePropagator scopePropagator;
   private final MultiProgressMonitor progress;
   private final long timeoutMillis;
@@ -133,7 +133,7 @@
   @Inject
   AsyncReceiveCommits(
       ReceiveCommits.Factory factory,
-      @ReceiveCommitsExecutor ScheduledThreadPoolExecutor executor,
+      @ReceiveCommitsExecutor ScheduledExecutorService executor,
       RequestScopePropagator scopePropagator,
       @Named(TIMEOUT_NAME) long timeoutMillis,
       @Assisted ProjectControl projectControl,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 0c73d05..91379fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -416,7 +416,13 @@
   }
 
   public GroupReference resolve(GroupReference group) {
-    return groupList.resolve(group);
+    GroupReference groupRef = groupList.resolve(group);
+    if (groupRef != null
+        && groupRef.getUUID() != null
+        && !groupsByName.containsKey(groupRef.getName())) {
+      groupsByName.put(groupRef.getName(), groupRef);
+    }
+    return groupRef;
   }
 
   /** @return the group reference, if the group is used by at least one rule. */
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 86daf8e..8cac1a4 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
@@ -1070,22 +1070,19 @@
     }
 
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
-    boolean ok;
-    try {
-      permissions.ref(cmd.getRefName()).check(RefPermission.CREATE);
-      ok = true;
-    } catch (AuthException err) {
-      ok = false;
+    String rejectReason = ctl.canCreate(rp.getRepository(), obj);
+    if (rejectReason != null) {
+      reject(cmd, "prohibited by Gerrit: " + rejectReason);
+      return;
     }
-    if (ok && ctl.canCreate(rp.getRepository(), obj)) {
-      if (!validRefOperation(cmd)) {
-        return;
-      }
-      validateNewCommits(ctl, cmd);
-      actualCommands.add(cmd);
-    } else {
-      reject(cmd, "prohibited by Gerrit: create access denied for " + cmd.getRefName());
+
+    if (!validRefOperation(cmd)) {
+      // validRefOperation sets messages, so no need to provide more feedback.
+      return;
     }
+
+    validateNewCommits(ctl, cmd);
+    actualCommands.add(cmd);
   }
 
   private void parseUpdate(ReceiveCommand cmd) throws PermissionBackendException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
index 55707bd..76f1369 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
@@ -24,7 +24,7 @@
 import com.google.inject.Singleton;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.lib.Config;
@@ -37,7 +37,7 @@
   @Provides
   @Singleton
   @ReceiveCommitsExecutor
-  public ScheduledThreadPoolExecutor createReceiveCommitsExecutor(
+  public ScheduledExecutorService createReceiveCommitsExecutor(
       @GerritServerConfig Config config, WorkQueue queues) {
     int poolSize =
         config.getInt(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
index 33e7961..0adb45a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -35,6 +35,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.RunnableScheduledFuture;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
@@ -82,7 +83,7 @@
         }
       };
 
-  private ScheduledThreadPoolExecutor defaultQueue;
+  private ScheduledExecutorService defaultQueue;
   private int defaultQueueSize;
   private final IdGenerator idGenerator;
   private final CopyOnWriteArrayList<Executor> queues;
@@ -99,7 +100,7 @@
   }
 
   /** Get the default work queue, for miscellaneous tasks. */
-  public synchronized ScheduledThreadPoolExecutor getDefaultQueue() {
+  public synchronized ScheduledExecutorService getDefaultQueue() {
     if (defaultQueue == null) {
       defaultQueue = createQueue(defaultQueueSize, "WorkQueue");
     }
@@ -107,7 +108,7 @@
   }
 
   /** Create a new executor queue. */
-  public ScheduledThreadPoolExecutor createQueue(int poolsize, String prefix) {
+  public ScheduledExecutorService createQueue(int poolsize, String prefix) {
     return createQueue(poolsize, prefix, Thread.NORM_PRIORITY);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index e42a1cf..0c15063 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -117,8 +117,9 @@
         }
       }
 
-      if (!refControl.canCreate(repo, object)) {
-        throw new AuthException("Cannot create \"" + ref + "\"");
+      String rejectReason = refControl.canCreate(repo, object);
+      if (rejectReason != null) {
+        throw new AuthException("Cannot create \"" + ref + "\": " + rejectReason);
       }
 
       try {
@@ -155,7 +156,7 @@
               }
               refPrefix = RefUtil.getRefPrefix(refPrefix);
             }
-            //$FALL-THROUGH$
+            // $FALL-THROUGH$
           case FORCED:
           case IO_FAILURE:
           case NOT_ATTEMPTED:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index de22e23..2a22b1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
@@ -259,17 +260,18 @@
    *
    * @param repo repository on which user want to create
    * @param object the object the user will start the reference with.
-   * @return {@code true} if the user specified can create a new Git ref
+   * @return {@code null} if the user specified can create a new Git ref, or a String describing why
+   *     the creation is not allowed.
    */
-  public boolean canCreate(Repository repo, RevObject object) {
+  @Nullable
+  public String canCreate(Repository repo, RevObject object) {
     if (!isProjectStatePermittingWrite()) {
-      return false;
+      return "project state does not permit write";
     }
 
     if (object instanceof RevCommit) {
       if (!canPerform(Permission.CREATE)) {
-        // No create permissions.
-        return false;
+        return "lacks permission: " + Permission.CREATE;
       }
       return canCreateCommit(repo, (RevCommit) object);
     } else if (object instanceof RevTag) {
@@ -277,7 +279,13 @@
       try (RevWalk rw = new RevWalk(repo)) {
         rw.parseBody(tag);
       } catch (IOException e) {
-        return false;
+        String msg =
+            String.format(
+                "RevWalk(%s) for pushing tag %s:",
+                projectControl.getProject().getNameKey(), tag.name());
+        log.error(msg, e);
+
+        return "I/O exception for revwalk";
       }
 
       // If tagger is present, require it matches the user's email.
@@ -292,18 +300,20 @@
           valid = false;
         }
         if (!valid && !canForgeCommitter()) {
-          return false;
+          return "lacks permission: " + Permission.FORGE_COMMITTER;
         }
       }
 
       RevObject tagObject = tag.getObject();
       if (tagObject instanceof RevCommit) {
-        if (!canCreateCommit(repo, (RevCommit) tagObject)) {
-          return false;
+        String rejectReason = canCreateCommit(repo, (RevCommit) tagObject);
+        if (rejectReason != null) {
+          return rejectReason;
         }
       } else {
-        if (!canCreate(repo, tagObject)) {
-          return false;
+        String rejectReason = canCreate(repo, tagObject);
+        if (rejectReason != null) {
+          return rejectReason;
         }
       }
 
@@ -311,27 +321,34 @@
       // than if it doesn't have a PGP signature.
       //
       if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
-        return canPerform(Permission.CREATE_SIGNED_TAG);
+        return canPerform(Permission.CREATE_SIGNED_TAG)
+            ? null
+            : "lacks permission: " + Permission.CREATE_SIGNED_TAG;
       }
-      return canPerform(Permission.CREATE_TAG);
-    } else {
-      return false;
+      return canPerform(Permission.CREATE_TAG) ? null : "lacks permission " + Permission.CREATE_TAG;
     }
+
+    return null;
   }
 
-  private boolean canCreateCommit(Repository repo, RevCommit commit) {
+  /**
+   * Check if the user is allowed to create a new commit object if this introduces a new commit to
+   * the project. If not allowed, returns a string describing why it's not allowed.
+   */
+  @Nullable
+  private String canCreateCommit(Repository repo, RevCommit commit) {
     if (canUpdate()) {
       // If the user has push permissions, they can create the ref regardless
       // of whether they are pushing any new objects along with the create.
-      return true;
+      return null;
     } else if (isMergedIntoBranchOrTag(repo, commit)) {
       // If the user has no push permissions, check whether the object is
       // merged into a branch or tag readable by this user. If so, they are
       // not effectively "pushing" more objects, so they can create the ref
       // even if they don't have push permission.
-      return true;
+      return null;
     }
-    return false;
+    return "lacks permission " + Permission.PUSH + " for creating new commit object";
   }
 
   private boolean isMergedIntoBranchOrTag(Repository repo, RevCommit commit) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index 914e074..fed0be4 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -474,6 +474,14 @@
                 + "\tkey1 = "
                 + staff.toConfigValue()
                 + "\n");
+    assertThat(text(rev, "groups"))
+        .isEqualTo(
+            "# UUID\tGroup Name\n" //
+                + "#\n" //
+                + staff.getUUID().get()
+                + "     \t"
+                + staff.getName()
+                + "\n");
   }
 
   private ProjectConfig read(RevCommit rev) throws IOException, ConfigInvalidException {
diff --git a/lib/fonts/BUILD b/lib/fonts/BUILD
index 6b3b4ee..57429f3 100644
--- a/lib/fonts/BUILD
+++ b/lib/fonts/BUILD
@@ -10,4 +10,4 @@
     ],
     data = ["//lib:LICENSE-Apache2.0"],
     visibility = ["//visibility:public"],
-)
\ No newline at end of file
+)
diff --git a/plugins/replication b/plugins/replication
index 1085b45..fae5360 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 1085b453039b0fe230c3584e0024a7965cc1e323
+Subproject commit fae5360380023e8351f39be3d4effd4bb2cd8906
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.html b/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.html
index a9dd3df8..448e713 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.html
@@ -97,7 +97,7 @@
                         id="submitTypeSelect"
                         is="gr-select"
                         bind-value="{{_projectConfig.submit_type}}"
-                        disabled$="[[_readOnly]]">>
+                        disabled$="[[_readOnly]]">
                       <template is="dom-repeat" items="[[_submitTypes]]">
                         <option value="[[item.value]]">[[item.label]]</option>
                       </template>
@@ -128,7 +128,7 @@
                         id="newChangeSelect"
                         is="gr-select"
                         bind-value="{{_projectConfig.create_new_change_for_all_not_in_target.configured_value}}"
-                        disabled$="[[_readOnly]]">>
+                        disabled$="[[_readOnly]]">
                       <template is="dom-repeat"
                           items="[[_formatBooleanSelect(_projectConfig.create_new_change_for_all_not_in_target)]]">
                         <option value="[[item.value]]">[[item.label]]</option>
@@ -143,7 +143,7 @@
                         id="requireChangeIdSelect"
                         is="gr-select"
                         bind-value="{{_projectConfig.require_change_id.configured_value}}"
-                        disabled$="[[_readOnly]]">>
+                        disabled$="[[_readOnly]]">
                       <template is="dom-repeat"
                           items="[[_formatBooleanSelect(_projectConfig.require_change_id)]]">
                         <option value="[[item.value]]">[[item.label]]</option>
@@ -159,7 +159,7 @@
                         id="rejectImplicitMergesSelect"
                         is="gr-select"
                         bind-value="{{_projectConfig.reject_implicit_merges.configured_value}}"
-                        disabled$="[[_readOnly]]">>
+                        disabled$="[[_readOnly]]">
                       <template is="dom-repeat"
                           items="[[_formatBooleanSelect(_projectConfig.reject_implicit_merges)]]">
                         <option value="[[item.value]]">[[item.label]]</option>
@@ -189,7 +189,7 @@
                         id="contributorAgreementSelect"
                         is="gr-select"
                         bind-value="{{_projectConfig.use_contributor_agreements.configured_value}}"
-                        disabled$="[[_readOnly]]">>
+                        disabled$="[[_readOnly]]">
                     <template is="dom-repeat"
                         items="[[_formatBooleanSelect(_projectConfig.use_contributor_agreements)]]">
                       <option value="[[item.value]]">[[item.label]]</option>
@@ -204,7 +204,7 @@
                         id="useSignedOffBySelect"
                         is="gr-select"
                         bind-value="{{_projectConfig.use_signed_off_by.configured_value}}"
-                        disabled$="[[_readOnly]]">>
+                        disabled$="[[_readOnly]]">
                     <template is="dom-repeat"
                         items="[[_formatBooleanSelect(_projectConfig.use_signed_off_by)]]">
                       <option value="[[item.value]]">[[item.label]]</option>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
index c1640fa..010a01f 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
@@ -93,8 +93,14 @@
       this._reviewers = result.filter(reviewer => {
         return reviewer._account_id != owner._account_id;
       });
-      this._displayedReviewers =
+      // If there is one more than the max reviewers, don't show the 'show more'
+      // button, because it takes up just as much space.
+      if (this._reviewers.length <= MAX_REVIEWERS_DISPLAYED + 1) {
+        this._displayedReviewers = this._reviewers;
+      } else {
+        this._displayedReviewers =
           this._reviewers.slice(0, MAX_REVIEWERS_DISPLAYED);
+      }
     },
 
     _computeHiddenCount(reviewers, displayedReviewers) {
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index 4e0541e6..3c1773d 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -188,9 +188,9 @@
           {value: {ccsOnly: true}}));
     });
 
-    test('no show all reviewers button with 5 reviewers', () => {
+    test('no show all reviewers button with 6 reviewers', () => {
       const reviewers = [];
-      for (let i = 0; i < 5; i++) {
+      for (let i = 0; i < 6; i++) {
         reviewers.push(
           {email: i+'reviewer@google.com', name: 'reviewer-' + i});
       }
@@ -206,11 +206,34 @@
       };
       flushAsynchronousOperations();
       assert.equal(element._hiddenReviewerCount, 0);
-      assert.equal(element._displayedReviewers.length, 5);
-      assert.equal(element._reviewers.length, 5);
+      assert.equal(element._displayedReviewers.length, 6);
+      assert.equal(element._reviewers.length, 6);
       assert.isTrue(element.$$('.hiddenReviewers').hidden);
     });
 
+    test('how all reviewers button with 7 reviewers', () => {
+      const reviewers = [];
+      for (let i = 0; i < 7; i++) {
+        reviewers.push(
+          {email: i+'reviewer@google.com', name: 'reviewer-' + i});
+      }
+      element.ccsOnly = true;
+
+      element.change = {
+        owner: {
+          _account_id: 1,
+        },
+        reviewers: {
+          CC: reviewers,
+        },
+      };
+      flushAsynchronousOperations();
+      assert.equal(element._hiddenReviewerCount, 2);
+      assert.equal(element._displayedReviewers.length, 5);
+      assert.equal(element._reviewers.length, 7);
+      assert.isFalse(element.$$('.hiddenReviewers').hidden);
+    });
+
     test('show all reviewers button', () => {
       const reviewers = [];
       for (let i = 0; i < 100; i++) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 5319ace..6aae8d1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -142,20 +142,6 @@
       .content.remove {
         background-color: var(--light-remove-highlight-color);
       }
-      .dueToRebase .content.add .intraline,
-      .delta.total.dueToRebase .content.add {
-        background-color: var(--dark-rebased-add-highlight-color);
-      }
-      .dueToRebase .content.add {
-        background-color: var(--light-rebased-add-highlight-color);
-      }
-      .dueToRebase .content.remove .intraline,
-      .delta.total.dueToRebase .content.remove {
-        background-color: var(--dark-rebased-remove-highlight-color);
-      }
-      .dueToRebase .content.remove {
-        background-color: var(--light-rebased-remove-highlight-color);
-      }
       .content .contentText:after {
         /* Newline, to ensure all lines are one line-height tall. */
         content: '\A';
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
new file mode 100644
index 0000000..c93c509
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
@@ -0,0 +1,57 @@
+<!--
+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.
+-->
+
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<dom-module id="gr-agreements-list">
+  <template>
+    <style include="shared-styles">
+      .nameHeader {
+        width: 15em;
+      }
+      .descriptionHeader {
+        width: 21.5em;
+      }
+    </style>
+    <style include="gr-form-styles"></style>
+    <div class="gr-form-styles">
+      <table>
+        <thead>
+          <tr>
+            <th class="nameHeader">Name</th>
+            <th class="descriptionHeader">Description</th>
+          </tr>
+        </thead>
+        <tbody>
+          <template is="dom-repeat" items="[[_agreements]]">
+            <tr>
+              <td><a href$="[[getUrlBase(item.url)]]">[[item.name]]</a></td>
+              <td>[[item.description]]</td>
+            </tr>
+          </template>
+        </tbody>
+      </table>
+      <!-- TODO: Renable this when supported in polygerrit -->
+      <!-- <a href$="[[getUrl()]]">New Contributor Agreement</a> -->
+    </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-agreements-list.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
new file mode 100644
index 0000000..4e25523
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
@@ -0,0 +1,46 @@
+// 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.
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-agreements-list',
+
+    properties: {
+      _agreements: Array,
+    },
+
+    behaviors: [
+      Gerrit.BaseUrlBehavior,
+    ],
+
+    attached() {
+      this.loadData();
+    },
+
+    loadData() {
+      return this.$.restAPI.getAccountAgreements().then(agreements => {
+        this._agreements = agreements;
+      });
+    },
+
+    getUrl() {
+      return this.getBaseUrl() + '/settings/new-agreement';
+    },
+
+    getUrlBase(item) {
+      return this.getBaseUrl() + '/' + item;
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
new file mode 100644
index 0000000..13b8952
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-settings-view</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-agreements-list.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-agreements-list></gr-agreements-list>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-agreements-list tests', () => {
+    let element;
+    let agreements;
+
+    setup(done => {
+      agreements = [{
+        url: 'some url',
+        description: 'Agreements 1 description',
+        name: 'Agreements 1',
+      }];
+
+      stub('gr-rest-api-interface', {
+        getAccountGroups() { return Promise.resolve(agreements); },
+      });
+
+      element = fixture('basic');
+
+      element.loadData().then(() => { flush(done); });
+    });
+
+    test('renders', () => {
+      const rows = Polymer.dom(element.root).querySelectorAll('tbody tr');
+
+      assert.equal(rows.length, 3);
+
+      const nameCells = rows.map(row =>
+        row.querySelectorAll('td')[0].textContent
+      );
+
+      assert.equal(nameCells[0], 'Agreements 1');
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index aa568b3..2cf3c94 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -16,22 +16,23 @@
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 
-<link rel="import" href="../gr-account-info/gr-account-info.html">
-<link rel="import" href="../gr-email-editor/gr-email-editor.html">
-<link rel="import" href="../gr-group-list/gr-group-list.html">
-<link rel="import" href="../gr-http-password/gr-http-password.html">
-<link rel="import" href="../gr-menu-editor/gr-menu-editor.html">
-<link rel="import" href="../gr-ssh-editor/gr-ssh-editor.html">
-<link rel="import" href="../gr-watched-projects-editor/gr-watched-projects-editor.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../../styles/gr-menu-page-styles.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
 <link rel="import" href="../../settings/gr-change-table-editor/gr-change-table-editor.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
 <link rel="import" href="../../shared/gr-page-nav/gr-page-nav.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../shared/gr-select/gr-select.html">
-<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../../styles/gr-menu-page-styles.html">
-<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../gr-account-info/gr-account-info.html">
+<link rel="import" href="../gr-agreements-list/gr-agreements-list.html">
+<link rel="import" href="../gr-email-editor/gr-email-editor.html">
+<link rel="import" href="../gr-group-list/gr-group-list.html">
+<link rel="import" href="../gr-http-password/gr-http-password.html">
+<link rel="import" href="../gr-menu-editor/gr-menu-editor.html">
+<link rel="import" href="../gr-ssh-editor/gr-ssh-editor.html">
+<link rel="import" href="../gr-watched-projects-editor/gr-watched-projects-editor.html">
 
 <dom-module id="gr-settings-view">
   <template>
@@ -58,6 +59,9 @@
             SSH Keys
           </a></li>
           <li><a href="#Groups">Groups</a></li>
+          <li hidden$="[[!_serverConfig.auth.contributor_agreements]]">
+            <a href="#Agreements">Agreements</a>
+          </li>
         </ul>
       </gr-page-nav>
       <main class="gr-form-styles">
@@ -360,6 +364,12 @@
         <fieldset>
           <gr-group-list id="groupList"></gr-group-list>
         </fieldset>
+        <div hidden$="[[!_serverConfig.auth.contributor_agreements]]">
+          <h2 id="Agreements">Agreements</h2>
+          <fieldset>
+            <gr-agreements-list id="agreementsList"></gr-agreements-list>
+          </fieldset>
+        </div>
       </main>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
index f093505..6d7f979 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
@@ -48,6 +48,7 @@
 
     /* Functions used for test purposes */
     _getOffsetParent(element) {
+      if (!element.offsetParent) { return ''; }
       return element.offsetParent;
     },
 
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 1a192bd..eb2b8c6 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
@@ -282,6 +282,10 @@
       return this._fetchSharedCacheURL('/accounts/self/groups');
     },
 
+    getAccountAgreements() {
+      return this._fetchSharedCacheURL('/accounts/self/agreements');
+    },
+
     getAccountCapabilities(opt_params) {
       let queryString = '';
       if (opt_params) {
diff --git a/polygerrit-ui/app/embed/change-diff-views.html b/polygerrit-ui/app/embed/change-diff-views.html
index b4f521b..8426585 100644
--- a/polygerrit-ui/app/embed/change-diff-views.html
+++ b/polygerrit-ui/app/embed/change-diff-views.html
@@ -16,3 +16,4 @@
 <link rel="import" href="../bower_components/polymer/polymer.html">
 <link rel="import" href="../elements/change/gr-change-view/gr-change-view.html">
 <link rel="import" href="../elements/diff/gr-diff-view/gr-diff-view.html">
+<link rel="import" href="../styles/app-theme.html">
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index f2a97bf..a42acc3 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -122,6 +122,9 @@
 }
 
 func injectLocalPlugins(r io.Reader) io.Reader {
+	if len(*plugins) == 0 {
+		return r
+	}
 	// Skip escape prefix
 	io.CopyN(ioutil.Discard, r, 5)
 	dec := json.NewDecoder(r)