Merge "Accept more than one endpoint from the same plugin"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index cdf6b30..d333347 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -727,13 +727,29 @@
 A user must have this access granted in order to see a project, its
 changes, or any of its data.
 
-This category has a special behavior, where the per-project ACL is
-evaluated before the global all projects ACL.  If the per-project
-ACL has granted `Read` with 'DENY', and does not otherwise grant
-`Read` with 'ALLOW', then a `Read` in the all projects ACL
-is ignored.  This behavior is useful to hide a handful of projects
+[[read_special_behaviors]]
+==== Special behaviors
+
+This category has multiple special behaviors:
+
+The per-project ACL is evaluated before the global all projects ACL.
+If the per-project ACL has granted `Read` with 'DENY', and does not
+otherwise grant `Read` with 'ALLOW', then a `Read` in the all projects
+ACL is ignored.  This behavior is useful to hide a handful of projects
 on an otherwise public server.
 
+You cannot grant `Read` on the `refs/tags/` namespace.  Visibility to
+`refs/tags/` is derived from `Read` grants on refs namespaces other than
+`refs/tags/`, `refs/changes/`, and `refs/cache-automerge/` by finding
+tags reachable from those refs.  For example, if a tag `refs/tags/test`
+points to a commit on the branch `refs/heads/master`, then allowing
+`Read` access to `refs/heads/master` would also allow access to
+`refs/tags/test`.  If a tag is reachable from multiple refs, allowing
+access to any of those refs allows access to the tag.
+
+[[read_typical_usage]]
+==== Typical usage
+
 For an open source, public Gerrit installation it is common to grant
 `Read` to `Anonymous Users` in the `All-Projects` ACL, enabling
 casual browsing of any project's changes, as well as fetching any
@@ -911,7 +927,7 @@
 
 Suggested access rights to grant:
 
-* xref:category_read[`Read`] on 'refs/heads/\*' and 'refs/tags/*'
+* xref:category_read[`Read`] on 'refs/heads/\*'
 * xref:category_push[`Push`] to 'refs/for/refs/heads/*'
 * link:config-labels.html#label_Code-Review[`Code-Review`] with range '-1' to '+1' for 'refs/heads/*'
 
@@ -939,7 +955,7 @@
 
 Suggested access rights to grant:
 
-* xref:category_read[`Read`] on 'refs/heads/\*' and 'refs/tags/*'
+* xref:category_read[`Read`] on 'refs/heads/\*'
 * xref:category_push[`Push`] to 'refs/for/refs/heads/*'
 * xref:category_push_merge[`Push merge commit`] to 'refs/for/refs/heads/*'
 * xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*'
@@ -994,7 +1010,7 @@
 
 Suggested access rights to grant, that won't block changes:
 
-* xref:category_read[`Read`] on 'refs/heads/\*' and 'refs/tags/*'
+* xref:category_read[`Read`] on 'refs/heads/\*'
 * link:config-labels.html#label_Code-Review[`Label: Code-Review`] with range '-1' to '0' for 'refs/heads/*'
 * link:config-labels.html#label_Verified[`Label: Verified`] with range '0' to '+1' for 'refs/heads/*'
 
diff --git a/WORKSPACE b/WORKSPACE
index 7aa09e2..0d3f9ce 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -885,30 +885,30 @@
     sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0",
 )
 
-TRUTH_VERS = "0.44"
+TRUTH_VERS = "0.45"
 
 maven_jar(
     name = "truth",
     artifact = "com.google.truth:truth:" + TRUTH_VERS,
-    sha1 = "11eff954c0c14da7d43276d7b3bcf71463105368",
+    sha1 = "e16683346f6a6887b1f140a2984e60c73c66c40a",
 )
 
 maven_jar(
     name = "truth-java8-extension",
     artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
-    sha1 = "2081a0721d3101e1cf559f013e59c6129b4b10b0",
+    sha1 = "f43262ad81c8df9a7f148659ff34de28b952754f",
 )
 
 maven_jar(
     name = "truth-liteproto-extension",
     artifact = "com.google.truth.extensions:truth-liteproto-extension:" + TRUTH_VERS,
-    sha1 = "64f47e4e3f79b0a582573098b9c3c6b73599f7c6",
+    sha1 = "67017d3aaec607c4a181ac95e9be0dc14e6c3fb2",
 )
 
 maven_jar(
     name = "truth-proto-extension",
     artifact = "com.google.truth.extensions:truth-proto-extension:" + TRUTH_VERS,
-    sha1 = "c03fbc16087d8cb3bf0f3265a04566d4beb88a6d",
+    sha1 = "f69edef92d9aceb82c6353e425328712ce1a25e7",
 )
 
 maven_jar(
diff --git a/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java b/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java
index f5e8fca..0b24f8c 100644
--- a/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java
+++ b/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java
@@ -18,7 +18,6 @@
 
 import com.google.common.truth.BooleanSubject;
 import com.google.common.truth.ComparableSubject;
-import com.google.common.truth.DefaultSubject;
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.IterableSubject;
 import com.google.common.truth.StringSubject;
@@ -60,7 +59,7 @@
     return check("getName()").that(group.getName());
   }
 
-  public Subject<DefaultSubject, Object> id() {
+  public Subject<?, ?> id() {
     isNotNull();
     return check("getId()").that(group.getId());
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 73e3982..9eb1270 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -1540,7 +1540,7 @@
   private static void assertIncludes(List<GroupInfo> includes, String... expectedNames) {
     List<String> names = includes.stream().map(i -> i.name).collect(toImmutableList());
     assertThat(names).containsExactlyElementsIn(Arrays.asList(expectedNames));
-    assertThat(names).isOrdered();
+    assertThat(names).isInOrder();
   }
 
   private void assertBadRequest(ListRequest req) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java
index 73731e5..aba7d18 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java
@@ -54,7 +54,7 @@
     createGroup(groupCreation, groupUpdate);
 
     Stream<String> allGroupNames = getAllGroupNames();
-    assertThat(allGroupNames).containsAllOf("users", "verifiers");
+    assertThat(allGroupNames).containsAtLeast("users", "verifiers");
   }
 
   @Test
@@ -70,7 +70,7 @@
     updateGroup(AccountGroup.uuid("users-UUID"), groupUpdate);
 
     Stream<String> allGroupNames = getAllGroupNames();
-    assertThat(allGroupNames).containsAllOf("contributors", "verifiers");
+    assertThat(allGroupNames).containsAtLeast("contributors", "verifiers");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index 4f55046..811ef35 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -74,6 +74,7 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -89,6 +90,7 @@
   private AccountGroup.UUID nonInteractiveUsers;
 
   private ChangeData cd1;
+  private RevCommit rc1;
   private String psRef1;
   private String metaRef1;
 
@@ -156,6 +158,7 @@
         pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%submit");
     mr.assertOkStatus();
     cd1 = mr.getChange();
+    rc1 = mr.getCommit();
     psRef1 = cd1.currentPatchSet().id().toRefName();
     metaRef1 = RefNames.changeMetaRef(cd1.getId());
     PushOneCommit.Result br =
@@ -189,10 +192,18 @@
       btu.setExpectedOldObjectId(ObjectId.zeroId());
       btu.setNewObjectId(repo.exactRef("refs/heads/branch").getObjectId());
       assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+
+      // Create a tag for the tree of the commit on 'master'
+      // tree-tag -> master.tree
+      RefUpdate ttu = repo.updateRef("refs/tags/tree-tag");
+      ttu.setExpectedOldObjectId(ObjectId.zeroId());
+      ttu.setNewObjectId(rc1.getTree().toObjectId());
+      assertThat(ttu.update()).isEqualTo(RefUpdate.Result.NEW);
     }
   }
 
   @Test
+  @GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "false")
   public void uploadPackAllRefsVisibleNoRefsMetaConfig() throws Exception {
     projectOperations
         .project(project)
@@ -217,6 +228,36 @@
         "refs/heads/master",
         "refs/tags/branch-tag",
         "refs/tags/master-tag");
+    // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
+  }
+
+  @Test
+  @GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "true")
+  public void uploadPackAllRefsVisibleNoRefsMetaConfigSkipFullRefEval() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+        .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(admins))
+        .setExclusiveGroup(permissionKey(Permission.READ).ref(RefNames.REFS_CONFIG), true)
+        .update();
+
+    requestScopeOperations.setApiUser(user.id());
+    assertUploadPackRefs(
+        "HEAD",
+        psRef1,
+        metaRef1,
+        psRef2,
+        metaRef2,
+        psRef3,
+        metaRef3,
+        psRef4,
+        metaRef4,
+        "refs/heads/branch",
+        "refs/heads/master",
+        "refs/tags/branch-tag",
+        "refs/tags/master-tag",
+        "refs/tags/tree-tag");
   }
 
   @Test
@@ -242,7 +283,20 @@
         "refs/heads/master",
         RefNames.REFS_CONFIG,
         "refs/tags/branch-tag",
-        "refs/tags/master-tag");
+        "refs/tags/master-tag",
+        "refs/tags/tree-tag");
+  }
+
+  @Test
+  public void grantReadOnRefsTagsIsNoOp() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref("refs/tags/*").group(REGISTERED_USERS))
+        .update();
+
+    requestScopeOperations.setApiUser(user.id());
+    assertUploadPackRefs(); // We expect no refs returned
   }
 
   @Test
@@ -257,6 +311,8 @@
     requestScopeOperations.setApiUser(user.id());
     assertUploadPackRefs(
         "HEAD", psRef1, metaRef1, psRef3, metaRef3, "refs/heads/master", "refs/tags/master-tag");
+    // tree-tag is not visible because we don't look at trees reachable from
+    // refs
   }
 
   @Test
@@ -279,6 +335,7 @@
         // master branch is not visible but master-tag is reachable from branch
         // (since PushOneCommit always bases changes on each other).
         "refs/tags/master-tag");
+    // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
   }
 
   @Test
@@ -306,6 +363,7 @@
         "refs/heads/master",
         "refs/tags/master-tag",
         "refs/users/01/1000001/edit-" + cd3.getId() + "/1");
+    // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
   }
 
   @Test
@@ -338,6 +396,7 @@
         "refs/tags/master-tag",
         "refs/users/00/1000000/edit-" + cd3.getId() + "/1",
         "refs/users/01/1000001/edit-" + cd3.getId() + "/1");
+    // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
   }
 
   @Test
@@ -374,6 +433,7 @@
         "refs/tags/master-tag",
         // All edits are visible due to accessDatabase capability.
         "refs/users/00/1000000/edit-" + cd3.getId() + "/1");
+    // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
   }
 
   @Test
@@ -413,6 +473,7 @@
         "refs/heads/master",
         "refs/tags/branch-tag",
         "refs/tags/master-tag");
+    // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
   }
 
   @Test
@@ -449,6 +510,35 @@
         metaRef3,
         "refs/heads/master",
         "refs/tags/branch-tag",
+        "refs/tags/master-tag",
+        "refs/tags/tree-tag");
+  }
+
+  @Test
+  public void uploadPackSubsetRefsVisibleOrphanedTagInvisible() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+        .update();
+    // Create a tag for the pending change on 'branch' so that the tag is orphaned
+    try (Repository repo = repoManager.openRepository(project)) {
+      // change4-tag -> psRef4
+      RefUpdate ctu = repo.updateRef("refs/tags/change4-tag");
+      ctu.setExpectedOldObjectId(ObjectId.zeroId());
+      ctu.setNewObjectId(repo.exactRef(psRef4).getObjectId());
+      assertThat(ctu.update()).isEqualTo(RefUpdate.Result.NEW);
+    }
+
+    requestScopeOperations.setApiUser(user.id());
+    assertUploadPackRefs(
+        psRef2,
+        metaRef2,
+        psRef4,
+        metaRef4,
+        "refs/heads/branch",
+        "refs/tags/branch-tag",
+        // See comment in subsetOfBranchesVisibleNotIncludingHead.
         "refs/tags/master-tag");
   }
 
@@ -463,7 +553,8 @@
             "refs/heads/master",
             "refs/meta/config",
             "refs/tags/branch-tag",
-            "refs/tags/master-tag");
+            "refs/tags/master-tag",
+            "refs/tags/tree-tag");
     assertThat(r.additionalHaves()).containsExactly(obj(cd3, 1), obj(cd4, 1));
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
index 37b01a5..0fdeba6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -51,7 +51,7 @@
     Project.NameKey child1_1 = projectOperations.newProject().parent(child1).create();
     Project.NameKey child1_2 = projectOperations.newProject().parent(child1).create();
 
-    assertThatNameList(gApi.projects().name(child1.get()).children()).isOrdered();
+    assertThatNameList(gApi.projects().name(child1.get()).children()).isInOrder();
     assertThatNameList(gApi.projects().name(child1.get()).children())
         .containsExactly(child1_1, child1_2);
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index 1443c99..caef689 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -66,7 +66,7 @@
     Project.NameKey someProject = projectOperations.newProject().create();
     assertThatNameList(gApi.projects().list().get())
         .containsExactly(allProjects, allUsers, project, someProject);
-    assertThatNameList(gApi.projects().list().get()).isOrdered();
+    assertThatNameList(gApi.projects().list().get()).isInOrder();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java b/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
index 3043985..048d59d 100644
--- a/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
@@ -57,7 +57,7 @@
   }
 
   private void assertSearchReturns(List<?> expected, String re, List<String> inputs) {
-    assertThat(inputs).isOrdered();
+    assertThat(inputs).isInOrder();
     assertThat(RegexListSearcher.ofStrings(re).search(inputs))
         .containsExactlyElementsIn(expected)
         .inOrder();
diff --git a/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java b/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
index d9ed577..c92a8e0 100644
--- a/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
+++ b/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
@@ -81,7 +81,7 @@
   private void assertValueRange(LabelType label, Integer... range) {
     List<Integer> rangeList = Arrays.asList(range);
     assertThat(rangeList).isNotEmpty();
-    assertThat(rangeList).isStrictlyOrdered();
+    assertThat(rangeList).isInStrictOrder();
 
     assertThat(label.getValues().stream().map(v -> (int) v.getValue()))
         .containsExactlyElementsIn(rangeList)
diff --git a/package.json b/package.json
index f096e2a..8a18318f 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "web-component-tester": "^6.5.0"
   },
   "scripts": {
+    "start": "polygerrit-ui/run-server.sh",
     "test": "WCT_HEADLESS_MODE=1 WCT_ARGS='--verbose -l chrome' ./polygerrit-ui/app/run_test.sh",
     "eslint": "./node_modules/eslint/bin/eslint.js --ignore-pattern 'bower_components/' --ignore-pattern 'gr-linked-text' --ignore-pattern 'scripts/vendor' --ext .html,.js polygerrit-ui/app || exit 0",
     "test-template": "./polygerrit-ui/app/run_template_test.sh"
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 737863c..9ceda8c 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -59,14 +59,7 @@
 the command line:
 
 ```sh
-<<<<<<< HEAD
 ./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html
-=======
-bazel build gerrit &&
-  $(bazel info output_base)/external/local_jdk/bin/java -DsourceRoot=/path/to/my/checkout \
-  -jar bazel-bin/gerrit.war daemon --polygerrit-dev \
-  -d ../gerrit_testsite --console-log --show-stack-trace
->>>>>>> stable-3.0
 ```
 
 The biggest draw back of this method is that you cannot log in, so cannot test
@@ -96,6 +89,7 @@
 
 ```sh
 $(bazel info output_base)/external/local_jdk/bin/java \
+    -DsourceRoot=$(bazel info workspace) \
     -jar bazel-bin/gerrit.war daemon \
     -d $GERRIT_SITE \
     --console-log \