Merge branch 'stable-3.5' into stable-3.6

* stable-3.5:
  Add JGit client test for unset mirror
  Add mirror replication option for CGit client

Change-Id: I2ee7ccd772ca50bd650329cb6da7a6973d3f3569
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 d3e45da..43ec9bf 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
@@ -44,6 +44,7 @@
   private URIish uri;
   private int timeout;
   private final String taskIdHex;
+  private final boolean isMirror;
 
   @Inject
   public CGitFetch(
@@ -56,12 +57,17 @@
     this.taskIdHex = taskIdHex;
     this.uri = appendCredentials(uri, cpFactory.create(config.getRemoteConfig().getName()));
     this.timeout = config.getRemoteConfig().getTimeout();
+    this.isMirror = config.getRemoteConfig().isMirror();
   }
 
   @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.toPrivateASCIIString());
+    List<String> command = Lists.newArrayList("git", "fetch");
+    if (isMirror) {
+      command.add("--prune");
+    }
+    command.add(uri.toPrivateASCIIString());
     command.addAll(refs);
     ProcessBuilder pb = new ProcessBuilder().command(command).directory(localProjectDirectory);
     repLog.info("[{}] Fetch references {} from {}", taskIdHex, refs, uri);
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index b7db2c0..61d62b4 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -526,8 +526,7 @@
 remote.NAME.mirror
 :	If true, replication will remove local branches and tags that are
 absent remotely or invisible to the replication (for example read access
-denied via `authGroup` option). Note that this option is currently
-implemented for the JGit client only.
+denied via `authGroup` option).
 
 	By default, false, do not remove remote branches or tags.
 
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 baacf20..3ca937c 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
@@ -15,6 +15,9 @@
 package com.googlesource.gerrit.plugins.replication.pull;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.deleteRef;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -22,35 +25,33 @@
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.SkipProjectClone;
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Permission;
 import com.google.gerrit.extensions.api.projects.BranchInput;
-import com.google.gerrit.extensions.config.FactoryModule;
-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.google.inject.Inject;
 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;
-import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchClientImplementation;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchFactory;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.RefUpdateState;
-import java.net.URISyntaxException;
 import java.util.List;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
 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.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.RemoteConfig;
 import org.eclipse.jgit.transport.URIish;
+import org.junit.Before;
 import org.junit.Test;
 
 @SkipProjectClone
@@ -62,6 +63,16 @@
   private static final String TEST_REPLICATION_SUFFIX = "suffix1";
   private static final String TEST_TASK_ID = "taskid";
 
+  @Inject private ProjectOperations projectOperations;
+
+  @Before
+  public void allowRefDeletion() {
+    projectOperations
+        .allProjectsForUpdate()
+        .add(allow(Permission.DELETE).ref("refs/*").group(adminGroupUuid()))
+        .update();
+  }
+
   @Test
   public void shouldFetchRef() throws Exception {
     testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
@@ -224,29 +235,55 @@
     }
   }
 
+  @Test
+  public void shouldNotPruneRefsWhenMirrorIsUnset() throws Exception {
+    testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
+    String BRANCH_REF = Constants.R_HEADS + "anyBranch";
+    String TAG_REF = Constants.R_TAGS + "anyTag";
+
+    PushOneCommit.Result branchPush = pushFactory.create(user.newIdent(), testRepo).to(BRANCH_REF);
+    branchPush.assertOkStatus();
+
+    PushResult tagPush = pushHead(testRepo, TAG_REF, false, false);
+    assertOkStatus(tagPush, TAG_REF);
+
+    try (Repository localRepo = repoManager.openRepository(project)) {
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      waitUntil(
+          () ->
+              checkedGetRef(localRepo, BRANCH_REF) != null
+                  && checkedGetRef(localRepo, TAG_REF) != null);
+      assertThat(getRef(localRepo, BRANCH_REF)).isNotNull();
+      assertThat(getRef(localRepo, TAG_REF)).isNotNull();
+
+      PushResult deleteBranchResult = deleteRef(testRepo, BRANCH_REF);
+      assertOkStatus(deleteBranchResult, BRANCH_REF);
+
+      PushResult deleteTagResult = deleteRef(testRepo, TAG_REF);
+      assertOkStatus(deleteTagResult, TAG_REF);
+
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      waitUntil(
+          () ->
+              checkedGetRef(localRepo, BRANCH_REF) != null
+                  && checkedGetRef(localRepo, TAG_REF) != null);
+      assertThat(getRef(localRepo, BRANCH_REF)).isNotNull();
+      assertThat(getRef(localRepo, TAG_REF)).isNotNull();
+    }
+  }
+
   @SuppressWarnings("unused")
-  private static class TestModule extends FactoryModule {
+  private static class TestModule extends FetchModule<CGitFetch> {
     @Override
-    protected void configure() {
+    Class<CGitFetch> clientClass() {
+      return CGitFetch.class;
+    }
+
+    @Override
+    Config cf() {
       Config cf = new Config();
       cf.setInt("remote", "test_config", "timeout", 0);
-      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(
-            new FactoryModuleBuilder()
-                .implement(Fetch.class, CGitFetch.class)
-                .implement(Fetch.class, FetchClientImplementation.class, CGitFetch.class)
-                .build(FetchFactory.class));
-      } catch (URISyntaxException e) {
-        throw new RuntimeException(e);
-      }
+      return cf;
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchWithMirrorIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchWithMirrorIT.java
new file mode 100644
index 0000000..8a3bae9
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/CGitFetchWithMirrorIT.java
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.deleteRef;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+
+import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.acceptance.SkipProjectClone;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Permission;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.pull.fetch.CGitFetch;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushResult;
+import org.junit.Before;
+import org.junit.Test;
+
+@SkipProjectClone
+@UseLocalDisk
+@TestPlugin(
+    name = "pull-replication",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.CGitFetchWithMirrorIT$TestModule")
+public class CGitFetchWithMirrorIT extends FetchITBase {
+  private static final String TEST_REPLICATION_SUFFIX = "suffix1";
+  private static final String TEST_TASK_ID = "taskid";
+
+  @Inject private ProjectOperations projectOperations;
+
+  @Before
+  public void allowRefDeletion() {
+    projectOperations
+        .allProjectsForUpdate()
+        .add(allow(Permission.DELETE).ref("refs/*").group(adminGroupUuid()))
+        .update();
+  }
+
+  @Test
+  public void shouldPruneRefsWhenMirrorIsTrue() throws Exception {
+    testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
+    String BRANCH_REF = Constants.R_HEADS + "anyBranch";
+    String TAG_REF = Constants.R_TAGS + "anyTag";
+
+    Result branchPush = pushFactory.create(user.newIdent(), testRepo).to(BRANCH_REF);
+    branchPush.assertOkStatus();
+
+    PushResult tagPush = pushHead(testRepo, TAG_REF, false, false);
+    assertOkStatus(tagPush, TAG_REF);
+
+    try (Repository localRepo = repoManager.openRepository(project)) {
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      waitUntil(
+          () ->
+              checkedGetRef(localRepo, BRANCH_REF) != null
+                  && checkedGetRef(localRepo, TAG_REF) != null);
+      assertThat(getRef(localRepo, BRANCH_REF)).isNotNull();
+      assertThat(getRef(localRepo, TAG_REF)).isNotNull();
+
+      PushResult deleteBranchResult = deleteRef(testRepo, BRANCH_REF);
+      assertOkStatus(deleteBranchResult, BRANCH_REF);
+
+      PushResult deleteTagResult = deleteRef(testRepo, TAG_REF);
+      assertOkStatus(deleteTagResult, TAG_REF);
+
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      waitUntil(
+          () ->
+              checkedGetRef(localRepo, BRANCH_REF) == null
+                  && checkedGetRef(localRepo, TAG_REF) == null);
+      assertThat(getRef(localRepo, BRANCH_REF)).isNull();
+      assertThat(getRef(localRepo, TAG_REF)).isNull();
+    }
+  }
+
+  @SuppressWarnings("unused")
+  private static class TestModule extends FetchModule<CGitFetch> {
+    @Override
+    Class<CGitFetch> clientClass() {
+      return CGitFetch.class;
+    }
+
+    @Override
+    Config cf() {
+      Config cf = new Config();
+      cf.setInt("remote", "test_config", "timeout", 0);
+      cf.setBoolean("remote", "test_config", "mirror", true);
+      return cf;
+    }
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchITBase.java
index be9f902..3429983 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchITBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchITBase.java
@@ -14,18 +14,39 @@
 
 package com.googlesource.gerrit.plugins.replication.pull;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.collect.Lists;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.Project;
+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.Fetch;
+import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchClientImplementation;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchFactory;
+import java.io.IOException;
+import java.net.URISyntaxException;
 import java.nio.file.Path;
 import java.time.Duration;
 import java.util.function.Supplier;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.URIish;
 
 public abstract class FetchITBase extends LightweightPluginDaemonTest {
   private static final String TEST_REPLICATION_SUFFIX = "suffix1";
@@ -33,6 +54,7 @@
 
   private static final int TEST_REPLICATION_DELAY = 60;
   private static final Duration TEST_TIMEOUT = Duration.ofSeconds(TEST_REPLICATION_DELAY * 2);
+  private static final RefSpec ALL_REFS = new RefSpec("+refs/*:refs/*");
 
   @Inject private SitePaths sitePaths;
   @Inject private ProjectOperations projectOperations;
@@ -66,7 +88,49 @@
     }
   }
 
+  protected void fetchAllRefs(String taskId, Path remotePath, Repository localRepo)
+      throws URISyntaxException, IOException {
+    fetchFactory
+        .create(taskId, new URIish(remotePath.toString()), localRepo)
+        .fetch(Lists.newArrayList(ALL_REFS));
+  }
+
+  protected static void assertOkStatus(PushResult result, String ref) {
+    RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
+    assertThat(refUpdate).isNotNull();
+    assertWithMessage(refUpdate.getMessage())
+        .that(refUpdate.getStatus())
+        .isEqualTo(RemoteRefUpdate.Status.OK);
+  }
+
   Project.NameKey createTestProject(String name) {
     return projectOperations.newProject().name(name).create();
   }
+
+  protected abstract static class FetchModule<T extends Fetch> extends FactoryModule {
+    abstract Config cf();
+
+    abstract Class<T> clientClass();
+
+    @Override
+    protected void configure() {
+      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(
+            new FactoryModuleBuilder()
+                .implement(Fetch.class, clientClass())
+                .implement(Fetch.class, FetchClientImplementation.class, clientClass())
+                .build(FetchFactory.class));
+      } catch (URISyntaxException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchIT.java
index 77cf80d..b900d8a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchIT.java
@@ -15,7 +15,6 @@
 package com.googlesource.gerrit.plugins.replication.pull;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.gerrit.acceptance.GitUtil.deleteRef;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
@@ -27,31 +26,15 @@
 import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.Permission;
-import com.google.gerrit.extensions.config.FactoryModule;
 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.Fetch;
-import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchClientImplementation;
-import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchFactory;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.JGitFetch;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.PermanentTransportException;
-import com.googlesource.gerrit.plugins.replication.pull.fetch.RefUpdateState;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.List;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
 import org.junit.Before;
 import org.junit.Test;
@@ -64,7 +47,6 @@
 public class JGitFetchIT extends FetchITBase {
   private static final String TEST_REPLICATION_SUFFIX = "suffix1";
   private static final String TEST_TASK_ID = "taskid";
-  private static final RefSpec ALL_REFS = new RefSpec("+refs/*:refs/*");
 
   @Inject private ProjectOperations projectOperations;
 
@@ -89,12 +71,10 @@
   }
 
   @Test
-  public void shouldPruneRefsWhenMirrorIsTrue() throws Exception {
+  public void shouldPruneRefsWhenMirrorIsUnset() throws Exception {
     testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
-    String branchName = "anyBranch";
-    String branchRef = Constants.R_HEADS + branchName;
-    String tagName = "anyTag";
-    String tagRef = Constants.R_TAGS + tagName;
+    String branchRef = Constants.R_HEADS + "anyBranch";
+    String tagRef = Constants.R_TAGS + "anyTag";
 
     PushOneCommit.Result branchPush = pushFactory.create(user.newIdent(), testRepo).to(branchRef);
     branchPush.assertOkStatus();
@@ -103,13 +83,8 @@
     assertOkStatus(tagPush, tagRef);
 
     try (Repository localRepo = repoManager.openRepository(project)) {
-      List<RefUpdateState> fetchCreated = fetchAllRefs(localRepo);
-      assertThat(fetchCreated.toString())
-          .contains(new RefUpdateState(branchRef, RefUpdate.Result.NEW).toString());
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
       assertThat(getRef(localRepo, branchRef)).isNotNull();
-
-      assertThat(fetchCreated.toString())
-          .contains(new RefUpdateState(tagRef, RefUpdate.Result.NEW).toString());
       assertThat(getRef(localRepo, tagRef)).isNotNull();
 
       PushResult deleteBranchResult = deleteRef(testRepo, branchRef);
@@ -118,55 +93,24 @@
       PushResult deleteTagResult = deleteRef(testRepo, tagRef);
       assertOkStatus(deleteTagResult, tagRef);
 
-      List<RefUpdateState> fetchDeleted = fetchAllRefs(localRepo);
-      assertThat(fetchDeleted.toString())
-          .contains(new RefUpdateState(branchRef, RefUpdate.Result.FORCED).toString());
-      assertThat(getRef(localRepo, branchRef)).isNull();
-
-      assertThat(fetchDeleted.toString())
-          .contains(new RefUpdateState(tagRef, RefUpdate.Result.FORCED).toString());
-      assertThat(getRef(localRepo, tagRef)).isNull();
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      assertThat(getRef(localRepo, branchRef)).isNotNull();
+      assertThat(getRef(localRepo, tagRef)).isNotNull();
     }
   }
 
-  private List<RefUpdateState> fetchAllRefs(Repository localRepo)
-      throws URISyntaxException, IOException {
-    Fetch fetch = fetchFactory.create(TEST_TASK_ID, new URIish(testRepoPath.toString()), localRepo);
-    return fetch.fetch(Lists.newArrayList(ALL_REFS));
-  }
-
-  private static void assertOkStatus(PushResult result, String ref) {
-    RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
-    assertThat(refUpdate).isNotNull();
-    assertWithMessage(refUpdate.getMessage())
-        .that(refUpdate.getStatus())
-        .isEqualTo(RemoteRefUpdate.Status.OK);
-  }
-
   @SuppressWarnings("unused")
-  private static class TestModule extends FactoryModule {
+  private static class TestModule extends FetchModule<JGitFetch> {
     @Override
-    protected void configure() {
+    Class<JGitFetch> clientClass() {
+      return JGitFetch.class;
+    }
+
+    @Override
+    Config cf() {
       Config cf = new Config();
       cf.setInt("remote", "test_config", "timeout", 0);
-      cf.setBoolean("remote", "test_config", "mirror", true);
-      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(
-            new FactoryModuleBuilder()
-                .implement(Fetch.class, JGitFetch.class)
-                .implement(Fetch.class, FetchClientImplementation.class, JGitFetch.class)
-                .build(FetchFactory.class));
-      } catch (URISyntaxException e) {
-        throw new RuntimeException(e);
-      }
+      return cf;
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchWithMirrorIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchWithMirrorIT.java
new file mode 100644
index 0000000..2df700f
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/JGitFetchWithMirrorIT.java
@@ -0,0 +1,100 @@
+// Copyright (C) 2023 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.deleteRef;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.SkipProjectClone;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Permission;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.pull.fetch.JGitFetch;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushResult;
+import org.junit.Before;
+import org.junit.Test;
+
+@SkipProjectClone
+@UseLocalDisk
+@TestPlugin(
+    name = "pull-replication",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.JGitFetchWithMirrorIT$TestModule")
+public class JGitFetchWithMirrorIT extends FetchITBase {
+  private static final String TEST_REPLICATION_SUFFIX = "suffix1";
+  private static final String TEST_TASK_ID = "taskid";
+
+  @Inject private ProjectOperations projectOperations;
+
+  @Before
+  public void allowRefDeletion() {
+    projectOperations
+        .allProjectsForUpdate()
+        .add(allow(Permission.DELETE).ref("refs/*").group(adminGroupUuid()))
+        .update();
+  }
+
+  @Test
+  public void shouldPruneRefsWhenMirrorIsTrue() throws Exception {
+    testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX));
+    String branchRef = Constants.R_HEADS + "anyBranch";
+    String tagRef = Constants.R_TAGS + "anyTag";
+
+    PushOneCommit.Result branchPush = pushFactory.create(user.newIdent(), testRepo).to(branchRef);
+    branchPush.assertOkStatus();
+
+    PushResult tagPush = pushHead(testRepo, tagRef, false, false);
+    assertOkStatus(tagPush, tagRef);
+
+    try (Repository localRepo = repoManager.openRepository(project)) {
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      assertThat(getRef(localRepo, branchRef)).isNotNull();
+      assertThat(getRef(localRepo, tagRef)).isNotNull();
+
+      PushResult deleteBranchResult = deleteRef(testRepo, branchRef);
+      assertOkStatus(deleteBranchResult, branchRef);
+
+      PushResult deleteTagResult = deleteRef(testRepo, tagRef);
+      assertOkStatus(deleteTagResult, tagRef);
+
+      fetchAllRefs(TEST_TASK_ID, testRepoPath, localRepo);
+      assertThat(getRef(localRepo, branchRef)).isNull();
+      assertThat(getRef(localRepo, tagRef)).isNull();
+    }
+  }
+
+  @SuppressWarnings("unused")
+  private static class TestModule extends FetchModule<JGitFetch> {
+    @Override
+    Class<JGitFetch> clientClass() {
+      return JGitFetch.class;
+    }
+
+    @Override
+    Config cf() {
+      Config cf = new Config();
+      cf.setInt("remote", "test_config", "timeout", 0);
+      cf.setBoolean("remote", "test_config", "mirror", true);
+      return cf;
+    }
+  }
+}