Extract opening repos to its own class

Move opening the repos (with caching and translating URL -> repoName) to
its own class.

Providers can offer different implementations adjusted to
their backends, and supporting e.g. cross-host reading. The injection
code comes in follow up changes.

Change-Id: I9f346e6733bd35170a2a94a27d7acbd6390679bf
diff --git a/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java b/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
index 11e1233..c82c830 100644
--- a/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
+++ b/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
@@ -52,7 +52,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
@@ -330,7 +329,9 @@
             String.format("invalid toolType: %s", c.getToolType().name()));
     }
     try (GerritRemoteReader reader =
-        new GerritRemoteReader(repoManager, canonicalWebUrl.toString())) {
+        new GerritRemoteReader(
+            new GerritSuperManifestRepoManager(repoManager, canonicalWebUrl.toString()),
+            canonicalWebUrl.toString())) {
       subModuleUpdater.update(reader, c, refName);
     }
   }
@@ -356,12 +357,11 @@
   // GerritRemoteReader is for injecting Gerrit's Git implementation into JGit.
   static class GerritRemoteReader implements RepoCommand.RemoteReader, AutoCloseable {
     private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-    private final Map<Project.NameKey, Repository> repos;
-    private final GitRepositoryManager repoManager;
     private final String canonicalWebUrl;
+    private final SuperManifestRepoManager repoManager;
 
-    GerritRemoteReader(GitRepositoryManager repoManager, @CanonicalWebUrl String canonicalWebUrl) {
-      this.repos = new HashMap<>();
+    GerritRemoteReader(
+        SuperManifestRepoManager repoManager, @CanonicalWebUrl String canonicalWebUrl) {
       this.repoManager = repoManager;
       this.canonicalWebUrl = canonicalWebUrl;
     }
@@ -378,7 +378,9 @@
         // When the remote is fetch="<relative path>" the manifest parser uses a repoName as URI.
         // Do a poor man's guessing if we have a repoName or URI
         Repository repo =
-            uriStr.contains("://") ? doOpenByUri(uriStr) : doOpenByName(Project.nameKey(uriStr));
+            uriStr.contains("://")
+                ? repoManager.openByUri(uriStr)
+                : repoManager.openByName(Project.nameKey(uriStr));
         Ref ref = repo.findRef(refName);
         if (ref == null || ref.getObjectId() == null) {
           logger.atWarning().log(
@@ -406,7 +408,9 @@
       // When the remote is fetch="<relative path>" the manifest parser uses a repoName as URI.
       // Do a poor man's guessing if we have a repoName or URI
       Repository repo =
-          uriStr.contains("://") ? doOpenByUri(uriStr) : doOpenByName(Project.nameKey(uriStr));
+          uriStr.contains("://")
+              ? repoManager.openByUri(uriStr)
+              : repoManager.openByName(Project.nameKey(uriStr));
       Ref r = repo.findRef(ref);
       ObjectId objectId = r == null ? repo.resolve(ref) : r.getObjectId();
       if (objectId == null) {
@@ -421,10 +425,40 @@
     }
 
     public Repository openRepository(String name) throws IOException {
-      return doOpenByName(Project.nameKey(name));
+      return repoManager.openByName(Project.nameKey(name));
     }
 
-    private Repository doOpenByName(Project.NameKey name) throws IOException {
+    @Override
+    public void close() {
+      try {
+        repoManager.close();
+      } catch (Exception e) {
+        logger.atWarning().log("Error closing the repoManager");
+      }
+    }
+  }
+
+  // AutoCloseable so implementations can keep a cache
+  public interface SuperManifestRepoManager extends AutoCloseable {
+    Repository openByUri(String uriStr) throws IOException;
+
+    Repository openByName(Project.NameKey repoName) throws IOException;
+  }
+
+  static class GerritSuperManifestRepoManager implements SuperManifestRepoManager {
+    private final HashMap<Project.NameKey, Repository> repos;
+    private final GitRepositoryManager repoManager;
+    private final String canonicalWebUrl;
+
+    GerritSuperManifestRepoManager(
+        GitRepositoryManager repoManager, @CanonicalWebUrl String canonicalWebUrl) {
+      this.repos = new HashMap<>();
+      this.repoManager = repoManager;
+      this.canonicalWebUrl = canonicalWebUrl;
+    }
+
+    @Override
+    public Repository openByName(Project.NameKey name) throws IOException {
       if (repos.containsKey(name)) {
         return repos.get(name);
       }
@@ -434,7 +468,8 @@
       return repo;
     }
 
-    private Repository doOpenByUri(String uriStr) throws IOException {
+    @Override
+    public Repository openByUri(String uriStr) throws IOException {
       // A URL in this host is <canonicalWebUrl>/<repoName>.
       if (!uriStr.startsWith(canonicalWebUrl)) {
         // Cannot open repos in another host.
@@ -450,7 +485,7 @@
         repoName = repoName.substring(1);
       }
 
-      return doOpenByName(Project.nameKey(repoName));
+      return openByName(Project.nameKey(repoName));
     }
 
     @Override
diff --git a/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritRemoteReaderTest.java b/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritRemoteReaderTest.java
index 539bdc4..bf32da6 100644
--- a/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritRemoteReaderTest.java
+++ b/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritRemoteReaderTest.java
@@ -25,8 +25,12 @@
   private static final String PROJECT_URL = CANONICAL_WEB_URL + "/" + PROJECT_REPONAME;
 
   private GitRepositoryManager repoManager = new InMemoryRepositoryManager();
+  SuperManifestRefUpdatedListener.GerritSuperManifestRepoManager superManifestRepoManager =
+      new SuperManifestRefUpdatedListener.GerritSuperManifestRepoManager(
+          repoManager, CANONICAL_WEB_URL);
   private SuperManifestRefUpdatedListener.GerritRemoteReader reader =
-      new SuperManifestRefUpdatedListener.GerritRemoteReader(repoManager, CANONICAL_WEB_URL);
+      new SuperManifestRefUpdatedListener.GerritRemoteReader(
+          superManifestRepoManager, CANONICAL_WEB_URL);
   private Repository projectX;
   private RevCommit projectXTip;
 
diff --git a/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritSuperManifestRepoManagerTest.java b/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritSuperManifestRepoManagerTest.java
new file mode 100644
index 0000000..08980fe
--- /dev/null
+++ b/javatests/com/googlesource/gerrit/plugins/supermanifest/GerritSuperManifestRepoManagerTest.java
@@ -0,0 +1,47 @@
+package com.googlesource.gerrit.plugins.supermanifest;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import java.io.IOException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GerritSuperManifestRepoManagerTest {
+  private static final String CANONICAL_WEB_URL = "https://example.com/gerrit/";
+
+  SuperManifestRefUpdatedListener.GerritSuperManifestRepoManager superManifestRepoManager;
+
+  @Before
+  public void setUp() throws IOException {
+    GitRepositoryManager repoManager = new InMemoryRepositoryManager();
+    repoManager.createRepository(Project.nameKey("project/x"));
+    superManifestRepoManager =
+        new SuperManifestRefUpdatedListener.GerritSuperManifestRepoManager(
+            repoManager, CANONICAL_WEB_URL);
+  }
+
+  @Test
+  public void openByRepoName() throws Exception {
+    Repository repo = superManifestRepoManager.openByName(Project.nameKey("project/x"));
+    assertThat(repo).isNotNull();
+  }
+
+  @Test
+  public void openByUri_canonical_repoNameExcludingCanonical() throws Exception {
+    Repository repo = superManifestRepoManager.openByUri(CANONICAL_WEB_URL + "project/x");
+    assertThat(repo).isNotNull();
+  }
+
+  @Test
+  public void openByUri_nonCanonical_uriPathIsRepoName() {
+    assertThrows(
+        RepositoryNotFoundException.class,
+        () -> superManifestRepoManager.openByUri("https://otherhost.com/project/x"));
+  }
+}