Merge branch 'stable-3.3' into stable-3.4

* stable-3.3:
  Extract FetchApiClient interface
  Add user/password authentication for CGit client

Change-Id: Id84173f7f7bc0cad6a4ada86bfc684b14172bb7e
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/DeleteProjectTask.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/DeleteProjectTask.java
index ed584b7..44c6556 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/DeleteProjectTask.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/DeleteProjectTask.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
-import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
+import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.HttpResult;
 import java.io.IOException;
 import java.net.URISyntaxException;
@@ -39,11 +39,11 @@
   private final Source source;
   private final String uri;
   private final Project.NameKey project;
-  private final FetchRestApiClient.Factory fetchClientFactory;
+  private final FetchApiClient.Factory fetchClientFactory;
 
   @Inject
   DeleteProjectTask(
-      FetchRestApiClient.Factory fetchClientFactory,
+      FetchApiClient.Factory fetchClientFactory,
       IdGenerator ig,
       @Assisted Source source,
       @Assisted String uri,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
index 29ec93a..c17d5df 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
@@ -43,6 +43,7 @@
 import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
 import com.googlesource.gerrit.plugins.replication.StartReplicationCapability;
 import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiModule;
+import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.HttpClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.SourceHttpClient;
@@ -82,7 +83,10 @@
             .build(SourceHttpClient.Factory.class));
 
     install(new FactoryModuleBuilder().build(Source.Factory.class));
-    install(new FactoryModuleBuilder().build(FetchRestApiClient.Factory.class));
+    install(
+        new FactoryModuleBuilder()
+            .implement(FetchApiClient.class, FetchRestApiClient.class)
+            .build(FetchApiClient.Factory.class));
 
     bind(FetchReplicationMetrics.class).in(Scopes.SINGLETON);
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index 7449263..cfe01e2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -31,7 +31,7 @@
 import com.googlesource.gerrit.plugins.replication.pull.FetchResultProcessing.GitUpdateProcessing;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
 import com.googlesource.gerrit.plugins.replication.pull.api.exception.MissingParentObjectException;
-import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
+import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.HttpResult;
 import com.googlesource.gerrit.plugins.replication.pull.filter.ExcludedRefsFilter;
 import java.io.IOException;
@@ -71,7 +71,7 @@
   private volatile boolean running;
   private volatile boolean replaying;
   private final Queue<ReferenceUpdatedEvent> beforeStartupEventsQueue;
-  private FetchRestApiClient.Factory fetchClientFactory;
+  private FetchApiClient.Factory fetchClientFactory;
   private Integer fetchCallsTimeout;
   private ExcludedRefsFilter refsFilter;
   private RevisionReader revisionReader;
@@ -82,7 +82,7 @@
       Provider<SourcesCollection> rd,
       DynamicItem<EventDispatcher> dis,
       ReplicationStateListeners sl,
-      FetchRestApiClient.Factory fetchClientFactory,
+      FetchApiClient.Factory fetchClientFactory,
       ExcludedRefsFilter refsFilter,
       RevisionReader revReader) {
     workQueue = wq;
@@ -273,7 +273,7 @@
       for (String apiUrl : source.getApis()) {
         try {
           URIish uri = new URIish(apiUrl);
-          FetchRestApiClient fetchClient = fetchClientFactory.create(source);
+          FetchApiClient fetchClient = fetchClientFactory.create(source);
 
           HttpResult result = fetchClient.callSendObject(project, refName, isDelete, revision, uri);
           if (isProjectMissing(result, project) && source.isCreateMissingRepositories()) {
@@ -309,7 +309,7 @@
       for (String apiUrl : source.getApis()) {
         try {
           URIish uri = new URIish(apiUrl);
-          FetchRestApiClient fetchClient = fetchClientFactory.create(source);
+          FetchApiClient fetchClient = fetchClientFactory.create(source);
           HttpResult result = fetchClient.callFetch(project, refName, uri);
           if (isProjectMissing(result, project) && source.isCreateMissingRepositories()) {
             result = initProject(project, uri, fetchClient, result);
@@ -344,7 +344,7 @@
   }
 
   private HttpResult initProject(
-      Project.NameKey project, URIish uri, FetchRestApiClient fetchClient, HttpResult result)
+      Project.NameKey project, URIish uri, FetchApiClient fetchClient, HttpResult result)
       throws IOException, ClientProtocolException {
     HttpResult initProjectResult = fetchClient.initProject(project, uri);
     if (initProjectResult.isSuccessful()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/UpdateHeadTask.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/UpdateHeadTask.java
index 943ea92..bded7d1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/UpdateHeadTask.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/UpdateHeadTask.java
@@ -22,14 +22,14 @@
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
-import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
+import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.HttpResult;
 import java.io.IOException;
 import org.eclipse.jgit.transport.URIish;
 
 public class UpdateHeadTask implements Runnable {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-  private final FetchRestApiClient.Factory fetchClientFactory;
+  private final FetchApiClient.Factory fetchClientFactory;
   private final Source source;
   private final URIish apiURI;
   private final Project.NameKey project;
@@ -42,7 +42,7 @@
 
   @Inject
   UpdateHeadTask(
-      FetchRestApiClient.Factory fetchClientFactory,
+      FetchApiClient.Factory fetchClientFactory,
       IdGenerator ig,
       @Assisted Source source,
       @Assisted URIish apiURI,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
new file mode 100644
index 0000000..476a35b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2022 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.googlesource.gerrit.plugins.replication.pull.client;
+
+import com.google.gerrit.entities.Project;
+import com.googlesource.gerrit.plugins.replication.pull.Source;
+import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
+import java.io.IOException;
+import org.apache.http.client.ClientProtocolException;
+import org.eclipse.jgit.transport.URIish;
+
+public interface FetchApiClient {
+
+  public interface Factory {
+    FetchApiClient create(Source source);
+  }
+
+  HttpResult callFetch(Project.NameKey project, String refName, URIish targetUri)
+      throws ClientProtocolException, IOException;
+
+  HttpResult initProject(Project.NameKey project, URIish uri) throws IOException;
+
+  HttpResult deleteProject(Project.NameKey project, URIish apiUri) throws IOException;
+
+  HttpResult updateHead(Project.NameKey project, String newHead, URIish apiUri) throws IOException;
+
+  HttpResult callSendObject(
+      Project.NameKey project,
+      String refName,
+      boolean isDelete,
+      RevisionData revisionData,
+      URIish targetUri)
+      throws ClientProtocolException, IOException;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
index ab3d3c5..f2f57cd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -56,17 +56,13 @@
 import org.eclipse.jgit.transport.CredentialItem;
 import org.eclipse.jgit.transport.URIish;
 
-public class FetchRestApiClient implements ResponseHandler<HttpResult> {
+public class FetchRestApiClient implements FetchApiClient, ResponseHandler<HttpResult> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   static String GERRIT_ADMIN_PROTOCOL_PREFIX = "gerrit+";
 
   private static final Gson GSON =
       new GsonBuilder().setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
 
-  public interface Factory {
-    FetchRestApiClient create(Source source);
-  }
-
   private final CredentialsFactory credentials;
   private final SourceHttpClient.Factory httpClientFactory;
   private final Source source;
@@ -95,6 +91,10 @@
         Strings.emptyToNull(instanceLabel), "replication.instanceLabel cannot be null or empty");
   }
 
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#callFetch(com.google.gerrit.entities.Project.NameKey, java.lang.String, org.eclipse.jgit.transport.URIish)
+   */
+  @Override
   public HttpResult callFetch(Project.NameKey project, String refName, URIish targetUri)
       throws ClientProtocolException, IOException {
     String url =
@@ -113,6 +113,10 @@
     return httpClientFactory.create(source).execute(post, this, getContext(targetUri));
   }
 
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#initProject(com.google.gerrit.entities.Project.NameKey, org.eclipse.jgit.transport.URIish)
+   */
+  @Override
   public HttpResult initProject(Project.NameKey project, URIish uri) throws IOException {
     String url =
         String.format(
@@ -123,6 +127,10 @@
     return httpClientFactory.create(source).execute(put, this, getContext(uri));
   }
 
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#deleteProject(com.google.gerrit.entities.Project.NameKey, org.eclipse.jgit.transport.URIish)
+   */
+  @Override
   public HttpResult deleteProject(Project.NameKey project, URIish apiUri) throws IOException {
     String url =
         String.format("%s/%s", apiUri.toASCIIString(), getProjectDeletionUrl(project.get()));
@@ -130,6 +138,10 @@
     return httpClientFactory.create(source).execute(delete, this, getContext(apiUri));
   }
 
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#updateHead(com.google.gerrit.entities.Project.NameKey, java.lang.String, org.eclipse.jgit.transport.URIish)
+   */
+  @Override
   public HttpResult updateHead(Project.NameKey project, String newHead, URIish apiUri)
       throws IOException {
     logger.atFine().log("Updating head of %s on %s", project.get(), newHead);
@@ -142,6 +154,10 @@
     return httpClientFactory.create(source).execute(req, this, getContext(apiUri));
   }
 
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient#callSendObject(com.google.gerrit.entities.Project.NameKey, java.lang.String, boolean, com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData, org.eclipse.jgit.transport.URIish)
+   */
+  @Override
   public HttpResult callSendObject(
       Project.NameKey project,
       String refName,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java
index 9f055c8..c404e30 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/CGitFetch.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.Lists;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
 import com.googlesource.gerrit.plugins.replication.pull.SourceConfiguration;
 import java.io.BufferedReader;
 import java.io.File;
@@ -30,6 +31,8 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.URIish;
 
@@ -40,17 +43,20 @@
   private int timeout;
 
   @Inject
-  public CGitFetch(SourceConfiguration config, @Assisted URIish uri, @Assisted Repository git) {
-
+  public CGitFetch(
+      SourceConfiguration config,
+      CredentialsFactory cpFactory,
+      @Assisted URIish uri,
+      @Assisted Repository git) {
     this.localProjectDirectory = git.getDirectory();
-    this.uri = uri;
+    this.uri = appendCredentials(uri, cpFactory.create(config.getRemoteConfig().getName()));
     this.timeout = config.getRemoteConfig().getTimeout();
   }
 
   @Override
   public List<RefUpdateState> fetch(List<RefSpec> refsSpec) throws IOException {
     List<String> refs = refsSpec.stream().map(s -> s.toString()).collect(Collectors.toList());
-    List<String> command = Lists.newArrayList("git", "fetch", uri.toASCIIString());
+    List<String> command = Lists.newArrayList("git", "fetch", uri.toPrivateASCIIString());
     command.addAll(refs);
     ProcessBuilder pb = new ProcessBuilder().command(command).directory(localProjectDirectory);
     repLog.info("Fetch references {} from {}", refs, uri);
@@ -83,6 +89,19 @@
     }
   }
 
+  protected URIish appendCredentials(URIish uri, CredentialsProvider credentialsProvider) {
+    CredentialItem.Username user = new CredentialItem.Username();
+    CredentialItem.Password pass = new CredentialItem.Password();
+    if (credentialsProvider.supports(user, pass)
+        && credentialsProvider.get(uri, user, pass)
+        && uri.getScheme() != null
+        && !"ssh".equalsIgnoreCase(uri.getScheme())) {
+      return uri.setUser(user.getValue()).setPass(String.valueOf(pass.getValue()));
+    }
+
+    return uri;
+  }
+
   public boolean waitForTaskToFinish(Process process) throws InterruptedException {
     if (timeout == 0) {
       process.waitFor();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchIT.java
index a02f43f..406cd8c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchIT.java
@@ -34,7 +34,12 @@
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
+import com.google.inject.Scopes;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.googlesource.gerrit.plugins.replication.AutoReloadSecureCredentialsFactoryDecorator;
+import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.BatchFetchClient;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.CGitFetch;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.Fetch;
@@ -270,6 +275,10 @@
       try {
         RemoteConfig remoteConfig = new RemoteConfig(cf, "test_config");
         SourceConfiguration sourceConfig = new SourceConfiguration(remoteConfig, cf);
+        bind(ReplicationConfig.class).to(ReplicationFileBasedConfig.class);
+        bind(CredentialsFactory.class)
+            .to(AutoReloadSecureCredentialsFactoryDecorator.class)
+            .in(Scopes.SINGLETON);
 
         bind(SourceConfiguration.class).toInstance(sourceConfig);
         install(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 82dc2b8..4e831bc 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -43,6 +43,7 @@
 import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
 import com.googlesource.gerrit.plugins.replication.pull.api.exception.RefUpdateException;
+import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.HttpResult;
 import com.googlesource.gerrit.plugins.replication.pull.filter.ExcludedRefsFilter;
@@ -73,7 +74,7 @@
   @Mock private DynamicItem<EventDispatcher> dis;
   @Mock ReplicationStateListeners sl;
   @Mock FetchRestApiClient fetchRestApiClient;
-  @Mock FetchRestApiClient.Factory fetchClientFactory;
+  @Mock FetchApiClient.Factory fetchClientFactory;
   @Mock AccountInfo accountInfo;
   @Mock RevisionReader revReader;
   @Mock RevisionData revisionData;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
index 394a4a4..a1c2172 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
@@ -135,7 +135,7 @@
           + "  ]\n"
           + "}";
 
-  FetchRestApiClient objectUnderTest;
+  FetchApiClient objectUnderTest;
 
   @Before
   public void setup() throws ClientProtocolException, IOException {