Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  Make transitive starlark loads explicit
  Make transitive starlark loads explicit
  Make transitive starlark loads explicit

Change-Id: I0a1717ed96101d7426a649a2c9a842bfe1c43709
diff --git a/WORKSPACE b/WORKSPACE
index 1a87c03..e1eb416 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "d100b6aad6b37e7db8fa141020c882dc97fb7723",
+    commit = "51049da0140c58ee26f8285bca4393c5e43ce4a1",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/goimport/GoImportFilter.java b/src/main/java/com/ericsson/gerrit/plugins/goimport/GoImportFilter.java
index d297b26..3675d92 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/goimport/GoImportFilter.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/goimport/GoImportFilter.java
@@ -17,12 +17,18 @@
 import com.google.common.base.Strings;
 import com.google.gerrit.httpd.AllRequestFilter;
 import com.google.gerrit.httpd.HtmlDomUtil;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.RefPermission;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -67,13 +73,21 @@
           + "</body>\n"
           + "</html>";
 
+  private final Provider<AnonymousUser> anonProvider;
+  private final PermissionBackend permissions;
   private final ProjectCache projectCache;
   final String webUrl;
   final String projectPrefix;
 
   @Inject
-  GoImportFilter(ProjectCache projectCache, @CanonicalWebUrl String webUrl)
+  GoImportFilter(
+      Provider<AnonymousUser> anonProvider,
+      PermissionBackend permissions,
+      ProjectCache projectCache,
+      @CanonicalWebUrl String webUrl)
       throws URISyntaxException {
+    this.anonProvider = anonProvider;
+    this.permissions = permissions;
     this.projectCache = projectCache;
     this.webUrl = webUrl.replaceFirst("/?$", "/");
     this.projectPrefix = generateProjectPrefix();
@@ -146,7 +160,23 @@
   }
 
   private CharSequence getContent(String projectName) {
-    return projectPrefix + projectName + " git " + webUrl + "a/" + projectName;
+    return projectPrefix + projectName + " git " + getRepoRoot(projectName);
+  }
+
+  private String getRepoRoot(String projectName) {
+    if (allowsAnonymousAccess(projectName)) {
+      return webUrl + projectName;
+    }
+
+    return webUrl + "a/" + projectName;
+  }
+
+  private boolean allowsAnonymousAccess(String projectName) {
+    AnonymousUser anonymous = anonProvider.get();
+    Branch.NameKey heads =
+        new Branch.NameKey(new Project.NameKey(projectName), RefNames.REFS_HEADS);
+
+    return permissions.user(anonymous).ref(heads).testOrFalse(RefPermission.READ);
   }
 
   private boolean projectExists(String projectName) {
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index d2b86eb..35cfe61 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -47,3 +47,9 @@
 | `/bob/my-project/package1`         | `bob/my-project` |
 | `/bob/my-project/package1/folder2` | `bob/my-project` |
 
+### Access (Anonymous vs. Authenticated URLs)
+The exact git clone URL served to the client go command will depend on the
+project's configured ACLs. If the project is configured to allow anonymous
+read access to `refs/heads/*`, an anonymous URL will be served (e.g.
+`https://gerrit.example/bob/my-project`). Otherwise, a URL that requires
+authentication will be used (e.g. `https://gerrit.example/a/bob/my-project`).
diff --git a/src/test/java/com/ericsson/gerrit/plugins/goimport/GoImportFilterTest.java b/src/test/java/com/ericsson/gerrit/plugins/goimport/GoImportFilterTest.java
index d00e03e..3b24f18 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/goimport/GoImportFilterTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/goimport/GoImportFilterTest.java
@@ -23,8 +23,12 @@
 import static org.mockito.Mockito.when;
 
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.RefPermission;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Provider;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import javax.servlet.FilterChain;
@@ -42,13 +46,17 @@
 public class GoImportFilterTest {
 
   private static final String PROD_URL = "https://gerrit-review.googlesource.com";
+  private static final String PROJECT_URL_AUTH =
+      "https://gerrit-review.googlesource.com/a/projectName";
+  private static final String PROJECT_URL_ANON =
+      "https://gerrit-review.googlesource.com/projectName";
   private static final String PAGE_200 =
       "<!DOCTYPE html>\n"
           + "<html>\n"
           + "<head>\n"
           + "  <title>Gerrit-Go-Import</title>\n"
           + "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"
-          + "  <meta name=\"go-import\" content=\"gerrit-review.googlesource.com/projectName git https://gerrit-review.googlesource.com/a/projectName\"/>\n"
+          + "  <meta name=\"go-import\" content=\"gerrit-review.googlesource.com/projectName git ${content}\"/>\n"
           + "</head>\n"
           + "<body>\n"
           + "<div>\n"
@@ -56,9 +64,16 @@
           + "</div>\n"
           + "</body>\n"
           + "</html>";
+  private static final String PAGE_200_AUTH = PAGE_200.replace("${content}", PROJECT_URL_AUTH);
+  private static final String PAGE_200_ANON = PAGE_200.replace("${content}", PROJECT_URL_ANON);
 
   private GoImportFilter unitUnderTest;
 
+  @Mock private Provider<AnonymousUser> mockAnonProvider;
+  @Mock private AnonymousUser mockAnon;
+  @Mock private PermissionBackend mockPerms;
+  @Mock private PermissionBackend.WithUser mockPermsWithUser;
+  @Mock private PermissionBackend.ForRef mockPermsForRef;
   @Mock private ProjectCache mockProjectCache;
   @Mock private HttpServletRequest mockRequest;
   @Mock private HttpServletResponse mockResponse;
@@ -68,23 +83,31 @@
 
   @Before
   public void setUp() throws Exception {
-    unitUnderTest = new GoImportFilter(mockProjectCache, PROD_URL);
+    unitUnderTest = new GoImportFilter(mockAnonProvider, mockPerms, mockProjectCache, PROD_URL);
     assertThat(unitUnderTest).isNotNull();
     when(mockResponse.getOutputStream()).thenReturn(mockOutputStream);
+
+    when(mockAnonProvider.get()).thenReturn(mockAnon);
+    when(mockPerms.user(mockAnon)).thenReturn(mockPermsWithUser);
+    when(mockPermsWithUser.ref(any())).thenReturn(mockPermsForRef);
   }
 
   @Test
   public void testConstructor() throws Exception {
     assertThat(unitUnderTest.webUrl.endsWith("/")).isTrue();
     unitUnderTest =
-        new GoImportFilter(mockProjectCache, "http://gerrit-review.googlesource.com:8080/");
+        new GoImportFilter(
+            mockAnonProvider,
+            mockPerms,
+            mockProjectCache,
+            "http://gerrit-review.googlesource.com:8080/");
     assertThat(unitUnderTest.webUrl.endsWith("/")).isTrue();
     assertThat(unitUnderTest.projectPrefix).isNotNull();
   }
 
   @Test(expected = URISyntaxException.class)
   public void testConstructorWithURISyntaxException() throws Exception {
-    unitUnderTest = new GoImportFilter(mockProjectCache, "\\\\");
+    unitUnderTest = new GoImportFilter(mockAnonProvider, mockPerms, mockProjectCache, "\\\\");
   }
 
   @Test
@@ -116,11 +139,13 @@
     when(mockRequest.getServletPath()).thenReturn("/projectName");
     when(mockRequest.getParameter("go-get")).thenReturn("1");
     when(mockProjectCache.get(new Project.NameKey("projectName"))).thenReturn(mockProjectState);
+    when(mockPermsForRef.testOrFalse(RefPermission.READ)).thenReturn(false);
     unitUnderTest.doFilter(mockRequest, mockResponse, mockChain);
-    verify(mockOutputStream, times(1)).write(PAGE_200.getBytes());
+    verify(mockOutputStream, times(1)).write(PAGE_200_AUTH.getBytes());
     verify(mockChain, times(0)).doFilter(mockRequest, mockResponse);
     verify(mockProjectCache, times(1)).get(any(Project.NameKey.class));
     verify(mockResponse, times(1)).setStatus(200);
+    verify(mockPermsForRef, times(1)).testOrFalse(RefPermission.READ);
   }
 
   @Test
@@ -128,11 +153,27 @@
     when(mockRequest.getServletPath()).thenReturn("/projectName/my/package");
     when(mockRequest.getParameter("go-get")).thenReturn("1");
     when(mockProjectCache.get(new Project.NameKey("projectName"))).thenReturn(mockProjectState);
+    when(mockPermsForRef.testOrFalse(RefPermission.READ)).thenReturn(false);
     unitUnderTest.doFilter(mockRequest, mockResponse, mockChain);
-    verify(mockOutputStream, times(1)).write(PAGE_200.getBytes());
+    verify(mockOutputStream, times(1)).write(PAGE_200_AUTH.getBytes());
     verify(mockChain, times(0)).doFilter(mockRequest, mockResponse);
     verify(mockProjectCache, times(3)).get(any(Project.NameKey.class));
     verify(mockResponse, times(1)).setStatus(200);
+    verify(mockPermsForRef, times(1)).testOrFalse(RefPermission.READ);
+  }
+
+  @Test
+  public void testDoFilterWithAnonymousAccessibleProject() throws Exception {
+    when(mockRequest.getServletPath()).thenReturn("/projectName");
+    when(mockRequest.getParameter("go-get")).thenReturn("1");
+    when(mockProjectCache.get(new Project.NameKey("projectName"))).thenReturn(mockProjectState);
+    when(mockPermsForRef.testOrFalse(RefPermission.READ)).thenReturn(true);
+    unitUnderTest.doFilter(mockRequest, mockResponse, mockChain);
+    verify(mockOutputStream, times(1)).write(PAGE_200_ANON.getBytes());
+    verify(mockChain, times(0)).doFilter(mockRequest, mockResponse);
+    verify(mockProjectCache, times(1)).get(any(Project.NameKey.class));
+    verify(mockResponse, times(1)).setStatus(200);
+    verify(mockPermsForRef, times(1)).testOrFalse(RefPermission.READ);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/goimport/HttpModuleTest.java b/src/test/java/com/ericsson/gerrit/plugins/goimport/HttpModuleTest.java
index 547c159..a0a107d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/goimport/HttpModuleTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/goimport/HttpModuleTest.java
@@ -16,7 +16,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
@@ -32,6 +34,8 @@
 public class HttpModuleTest {
 
   private HttpModule unitUnderTest;
+  @Mock private AnonymousUser mockAnon;
+  @Mock private PermissionBackend mockPerms;
   @Mock private ProjectCache mockProjectCache;
 
   @Before
@@ -53,6 +57,8 @@
   public class TestModule extends AbstractModule {
     @Override
     protected void configure() {
+      bind(AnonymousUser.class).toInstance(mockAnon);
+      bind(PermissionBackend.class).toInstance(mockPerms);
       bind(ProjectCache.class).toInstance(mockProjectCache);
     }