Merge "Accept remotes without `fetch` option on primary"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
index d02cb02..66aa8d4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
@@ -17,6 +17,7 @@
 import static com.googlesource.gerrit.plugins.replication.pull.PullReplicationLogger.repLog;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
+import com.google.common.base.Suppliers;
 import com.google.common.base.Throwables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
@@ -49,12 +50,14 @@
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.RemoteRepositoryException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.RefSpec;
@@ -82,6 +85,7 @@
   private final RemoteConfig config;
   private final PerThreadRequestScope.Scoper threadScoper;
 
+  private final FetchRefsDatabase fetchRefsDatabase;
   private final Project.NameKey projectName;
   private final URIish uri;
   private final Set<String> delta = Sets.newHashSetWithExpectedSize(4);
@@ -105,6 +109,16 @@
   private DynamicItem<ReplicationFetchFilter> replicationFetchFilter;
   private boolean succeeded;
 
+  private final Supplier<List<RefSpec>> fetchRefSpecsSupplier =
+      Suppliers.memoize(
+          () -> {
+            try {
+              return computeFetchRefSpecs();
+            } catch (IOException e) {
+              throw new RuntimeException("Could not compute refs specs to fetch", e);
+            }
+          });
+
   @Inject
   FetchOne(
       GitRepositoryManager grm,
@@ -115,6 +129,7 @@
       ReplicationStateListeners sl,
       FetchReplicationMetrics m,
       FetchFactory fetchFactory,
+      FetchRefsDatabase fetchRefsDatabase,
       @Assisted Project.NameKey d,
       @Assisted URIish u,
       @Assisted Optional<PullReplicationApiRequestMetrics> apiRequestMetrics) {
@@ -122,6 +137,7 @@
     pool = s;
     config = c.getRemoteConfig();
     threadScoper = ts;
+    this.fetchRefsDatabase = fetchRefsDatabase;
     projectName = d;
     uri = u;
     lockRetryCount = 0;
@@ -441,7 +457,7 @@
 
   private List<RefSpec> runImpl() throws IOException {
     Fetch fetch = fetchFactory.create(taskIdHex, uri, git);
-    List<RefSpec> fetchRefSpecs = getFetchRefSpecs();
+    List<RefSpec> fetchRefSpecs = fetchRefSpecsSupplier.get();
 
     try {
       updateStates(fetch.fetch(fetchRefSpecs));
@@ -467,24 +483,76 @@
     return fetchRefSpecs;
   }
 
+  /**
+   * Return the list of refSpecs to fetch, possibly after having been filtered.
+   *
+   * <p>When {@link FetchOne#delta} is empty and no {@link FetchOne#replicationFetchFilter} was
+   * provided, the configured refsSpecs is returned.
+   *
+   * <p>When {@link FetchOne#delta} is empty and {@link FetchOne#replicationFetchFilter} was
+   * provided, the configured refsSpecs is expanded to the delta of refs that requires fetching:
+   * that is, refs that are not already up-to-date. The result is then passed to the filter.
+   *
+   * <p>When {@link FetchOne#delta} is not empty and {@link FetchOne#replicationFetchFilter} was
+   * provided, the filtered refsSpecs are returned.
+   *
+   * @return The list of refSpecs to fetch
+   */
   public List<RefSpec> getFetchRefSpecs() {
+    return fetchRefSpecsSupplier.get();
+  }
+
+  private List<RefSpec> computeFetchRefSpecs() throws IOException {
     List<RefSpec> configRefSpecs = config.getFetchRefSpecs();
-    if (delta.isEmpty()) {
+
+    if (delta.isEmpty() && replicationFetchFilter().isEmpty()) {
       return configRefSpecs;
     }
 
-    return runRefsFilter(delta).stream()
+    return runRefsFilter(computeDelta(configRefSpecs)).stream()
         .map(ref -> refToFetchRefSpec(ref, configRefSpecs))
         .filter(Optional::isPresent)
         .map(Optional::get)
         .collect(Collectors.toList());
   }
 
-  private Set<String> runRefsFilter(Set<String> refs) {
+  private Set<String> computeDelta(List<RefSpec> configRefSpecs) throws IOException {
+    if (!delta.isEmpty()) {
+      return delta;
+    }
+    Map<String, Ref> localRefsMap = fetchRefsDatabase.getLocalRefsMap(git);
+    Map<String, Ref> remoteRefsMap = fetchRefsDatabase.getRemoteRefsMap(git, uri);
+
+    return remoteRefsMap.keySet().stream()
+        .filter(
+            srcRef -> {
+              // that match our configured refSpecs
+              return refToFetchRefSpec(srcRef, configRefSpecs)
+                  .flatMap(
+                      spec ->
+                          shouldBeFetched(srcRef, localRefsMap, remoteRefsMap)
+                              ? Optional.of(srcRef)
+                              : Optional.empty())
+                  .isPresent();
+            })
+        .collect(Collectors.toSet());
+  }
+
+  private boolean shouldBeFetched(
+      String srcRef, Map<String, Ref> localRefsMap, Map<String, Ref> remoteRefsMap) {
+    // If we don't have it locally
+    return localRefsMap.get(srcRef) == null
+        // OR we have it, but with a different localRefsMap value
+        || !localRefsMap.get(srcRef).getObjectId().equals(remoteRefsMap.get(srcRef).getObjectId());
+  }
+
+  private Optional<ReplicationFetchFilter> replicationFetchFilter() {
     return Optional.ofNullable(replicationFetchFilter)
-        .flatMap(filter -> Optional.ofNullable(filter.get()))
-        .map(f -> f.filter(this.projectName.get(), refs))
-        .orElse(refs);
+        .flatMap(filter -> Optional.ofNullable(filter.get()));
+  }
+
+  private Set<String> runRefsFilter(Set<String> refs) {
+    return replicationFetchFilter().map(f -> f.filter(this.projectName.get(), refs)).orElse(refs);
   }
 
   private Optional<RefSpec> refToFetchRefSpec(String ref, List<RefSpec> configRefSpecs) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefsDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefsDatabase.java
new file mode 100644
index 0000000..e536aad
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefsDatabase.java
@@ -0,0 +1,49 @@
+// 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 java.util.stream.Collectors.toMap;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.replication.pull.transport.TransportProvider;
+import java.io.IOException;
+import java.util.Map;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.FetchConnection;
+import org.eclipse.jgit.transport.Transport;
+import org.eclipse.jgit.transport.URIish;
+
+@Singleton
+public class FetchRefsDatabase {
+  private final TransportProvider transportProvider;
+
+  @Inject
+  public FetchRefsDatabase(TransportProvider transportProvider) {
+    this.transportProvider = transportProvider;
+  }
+
+  public Map<String, Ref> getRemoteRefsMap(Repository repository, URIish uri) throws IOException {
+    try (Transport tn = transportProvider.open(repository, uri);
+        FetchConnection fc = tn.openFetch()) {
+      return fc.getRefsMap();
+    }
+  }
+
+  public Map<String, Ref> getLocalRefsMap(Repository repository) throws IOException {
+    return repository.getRefDatabase().getRefs().stream().collect(toMap(Ref::getName, r -> r));
+  }
+}
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 cbdfe1b..fdd2a0f 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
@@ -25,25 +25,19 @@
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.EventTypes;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.google.inject.ProvisionException;
 import com.google.inject.Scopes;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 import com.google.inject.internal.UniqueAnnotations;
 import com.google.inject.name.Names;
-import com.googlesource.gerrit.plugins.replication.AutoReloadConfigDecorator;
 import com.googlesource.gerrit.plugins.replication.AutoReloadSecureCredentialsFactoryDecorator;
 import com.googlesource.gerrit.plugins.replication.ConfigParser;
 import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
-import com.googlesource.gerrit.plugins.replication.FanoutReplicationConfig;
-import com.googlesource.gerrit.plugins.replication.MainReplicationConfig;
 import com.googlesource.gerrit.plugins.replication.ObservableQueue;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
-import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigModule;
 import com.googlesource.gerrit.plugins.replication.StartReplicationCapability;
 import com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability;
 import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob;
@@ -55,24 +49,17 @@
 import com.googlesource.gerrit.plugins.replication.pull.event.EventsBrokerConsumerModule;
 import com.googlesource.gerrit.plugins.replication.pull.event.StreamEventModule;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.ApplyObject;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
 
 class PullReplicationModule extends AbstractModule {
-  private final SitePaths site;
-  private final Path cfgPath;
+
   private final MetricMaker pluginMetricMaker;
+  private final ReplicationConfigModule configModule;
 
   @Inject
-  public PullReplicationModule(SitePaths site, MetricMaker pluginMetricMaker) {
-    this.site = site;
-    cfgPath = site.etc_dir.resolve("replication.config");
+  public PullReplicationModule(
+      ReplicationConfigModule configModule, MetricMaker pluginMetricMaker) {
+    this.configModule = configModule;
     this.pluginMetricMaker = pluginMetricMaker;
   }
 
@@ -86,6 +73,7 @@
         .annotatedWith(Exports.named(CALL_FETCH_ACTION))
         .to(FetchApiCapability.class);
 
+    install(configModule);
     install(new PullReplicationGroupModule());
     bind(BearerTokenProvider.class).in(Scopes.SINGLETON);
     bind(RevisionReader.class).in(Scopes.SINGLETON);
@@ -136,19 +124,7 @@
 
     bind(ConfigParser.class).to(SourceConfigParser.class).in(Scopes.SINGLETON);
 
-    Config replicationConfig = getReplicationConfig();
-    if (replicationConfig.getBoolean("gerrit", "autoReload", false)) {
-      bind(ReplicationConfig.class)
-          .annotatedWith(MainReplicationConfig.class)
-          .to(getReplicationConfigClass());
-      bind(ReplicationConfig.class).to(AutoReloadConfigDecorator.class).in(Scopes.SINGLETON);
-      bind(LifecycleListener.class)
-          .annotatedWith(UniqueAnnotations.create())
-          .to(AutoReloadConfigDecorator.class);
-    } else {
-      bind(ReplicationConfig.class).to(getReplicationConfigClass()).in(Scopes.SINGLETON);
-    }
-
+    Config replicationConfig = configModule.getReplicationConfig();
     String eventBrokerTopic = replicationConfig.getString("replication", null, "eventBrokerTopic");
     if (replicationConfig.getBoolean("replication", "consumeStreamEvents", false)) {
       install(new StreamEventModule());
@@ -162,22 +138,4 @@
     EventTypes.register(FetchRefReplicationDoneEvent.TYPE, FetchRefReplicationDoneEvent.class);
     EventTypes.register(FetchReplicationScheduledEvent.TYPE, FetchReplicationScheduledEvent.class);
   }
-
-  private FileBasedConfig getReplicationConfig() {
-    File replicationConfigFile = cfgPath.toFile();
-    FileBasedConfig config = new FileBasedConfig(replicationConfigFile, FS.DETECTED);
-    try {
-      config.load();
-    } catch (IOException | ConfigInvalidException e) {
-      throw new ProvisionException("Unable to load " + replicationConfigFile.getAbsolutePath(), e);
-    }
-    return config;
-  }
-
-  private Class<? extends ReplicationConfig> getReplicationConfigClass() {
-    if (Files.exists(site.etc_dir.resolve("replication"))) {
-      return FanoutReplicationConfig.class;
-    }
-    return ReplicationFileBasedConfig.class;
-  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
index 98e8d1d..218592b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
@@ -14,7 +14,7 @@
 
 package com.googlesource.gerrit.plugins.replication.pull;
 
-import static com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig.replaceName;
+import static com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl.replaceName;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
index 1921e7b..00fa23d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
@@ -67,6 +67,13 @@
     apis = ImmutableList.copyOf(cfg.getStringList("remote", name, "apiUrl"));
     connectionTimeout =
         cfg.getInt("remote", name, "connectionTimeout", DEFAULT_CONNECTION_TIMEOUT_MS);
+    int connectionTimeoutInSec = connectionTimeout / 1000;
+    if (connectionTimeoutInSec < getRemoteConfig().getTimeout()) {
+      logger.atWarning().log(
+          "The connection timeout is currently set to %s sec, which is less than the timeout value of %s sec. "
+              + "To avoid potential issues, consider increasing the connection timeout to exceed the timeout value.",
+          connectionTimeoutInSec, getRemoteConfig().getTimeout());
+    }
     idleTimeout = cfg.getInt("remote", name, "idleTimeout", DEFAULT_MAX_CONNECTION_INACTIVITY_MS);
     maxConnectionsPerRoute =
         cfg.getInt("replication", "maxConnectionsPerRoute", DEFAULT_CONNECTIONS_PER_ROUTE);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SshModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SshModule.java
index 9c89781..ab3a76c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SshModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SshModule.java
@@ -14,9 +14,17 @@
 
 package com.googlesource.gerrit.plugins.replication.pull;
 
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.sshd.PluginCommandModule;
+import com.google.inject.Inject;
 
 class SshModule extends PluginCommandModule {
+
+  @Inject
+  SshModule(@PluginName String pluginName) {
+    super(pluginName);
+  }
+
   @Override
   protected void configureCommands() {
     command(StartFetchCommand.class);
diff --git a/src/main/resources/Documentation/cmd-start.md b/src/main/resources/Documentation/cmd-start.md
index 51d0a53..057567f 100644
--- a/src/main/resources/Documentation/cmd-start.md
+++ b/src/main/resources/Documentation/cmd-start.md
@@ -39,6 +39,28 @@
 with a `*`. If the pattern starts with `^` and ends with `*`, it is
 treated as a regular expression.
 
+FILTERING
+---------
+If the `fetch-filter` is enabled, this command will compare all remote refs
+that match the configured refSpecs against the local refs and select only
+the ones that are not already up-to-date.
+
+The configured refsSpecs is effectively expanded into
+an explicit set of refs that need fetching, meaning that only new refs, or
+refs whose sha1 differs from the remote one will be fetched.
+
+For example: `refs/*:refs/*` might be expanded to `refs/heads/master` and `refs/tags/v1`).
+
+The resulting refs list will then be passed to the provided `fetch-filter`
+implementation (see [extension-point.md](./extension-point.md))
+documentation for more information on this.
+
+*Note* This ref expansion-strategy prevents the `mirror`ing option from
+being honoured, since local refs that no longer exist at the source repository
+are effectively ignored.
+
+This behaviour has been captured in issue [319395646](https://issues.gerritcodereview.com/issues/319395646).
+
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
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 3429983..fc329bf 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
@@ -23,14 +23,18 @@
 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.extensions.registration.DynamicItem;
 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.ConfigResource;
 import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
+import com.googlesource.gerrit.plugins.replication.FileConfigResource;
 import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
-import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigOverrides;
 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;
@@ -117,7 +121,9 @@
       try {
         RemoteConfig remoteConfig = new RemoteConfig(cf(), "test_config");
         SourceConfiguration sourceConfig = new SourceConfiguration(remoteConfig, cf());
-        bind(ReplicationConfig.class).to(ReplicationFileBasedConfig.class);
+        DynamicItem.itemOf(binder(), ReplicationConfigOverrides.class);
+        bind(ConfigResource.class).to(FileConfigResource.class);
+        bind(ReplicationConfig.class).to(ReplicationConfigImpl.class);
         bind(CredentialsFactory.class)
             .to(AutoReloadSecureCredentialsFactoryDecorator.class)
             .in(Scopes.SINGLETON);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
index 3445604..86b0792 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
@@ -35,9 +35,11 @@
 import com.googlesource.gerrit.plugins.replication.pull.fetch.FetchFactory;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.InexistentRefTransportException;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.RefUpdateState;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -45,10 +47,13 @@
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
 import org.junit.Before;
 import org.junit.Test;
@@ -66,6 +71,8 @@
   private final String URI_PATTERN = "http://test.com/" + TEST_PROJECT_NAME + ".git";
   private final TestMetricMaker testMetricMaker = new TestMetricMaker();
 
+  private final RefSpec ALL_REFS_SPEC = new RefSpec("refs/*:refs/*");
+
   @Mock private GitRepositoryManager grm;
   @Mock private Repository repository;
   @Mock private Source source;
@@ -77,6 +84,9 @@
   @Mock private PullReplicationApiRequestMetrics pullReplicationApiRequestMetrics;
   @Mock private RemoteConfig remoteConfig;
   @Mock private DynamicItem<ReplicationFetchFilter> replicationFilter;
+  @Mock private FetchRefsDatabase fetchRefsDatabase;
+
+  @Mock private Transport transport;
 
   private URIish urIish;
   private FetchOne objectUnderTest;
@@ -98,7 +108,7 @@
     pullReplicationApiRequestMetrics = mock(PullReplicationApiRequestMetrics.class);
     remoteConfig = mock(RemoteConfig.class);
     replicationFilter = mock(DynamicItem.class);
-
+    fetchRefsDatabase = mock(FetchRefsDatabase.class);
     when(sourceConfiguration.getRemoteConfig()).thenReturn(remoteConfig);
     when(idGenerator.next()).thenReturn(1);
     int maxLockRetries = 1;
@@ -114,6 +124,7 @@
             replicationStateListeners,
             fetchReplicationMetrics,
             fetchFactory,
+            fetchRefsDatabase,
             PROJECT_NAME,
             urIish,
             Optional.of(pullReplicationApiRequestMetrics));
@@ -327,6 +338,73 @@
   }
 
   @Test
+  public void fetchWithoutDelta_shouldPassNewRefsToFilter() throws Exception {
+    setupMocks(true);
+    String REMOTE_REF = "refs/heads/remote";
+    Set<String> remoteRefs = Set.of(REMOTE_REF);
+    Map<String, Ref> localRefsMap = Map.of();
+    Map<String, Ref> remoteRefsMap = Map.of(REMOTE_REF, mock(Ref.class));
+    setupRemoteConfigMock(List.of(ALL_REFS_SPEC));
+    setupFetchRefsDatabaseMock(localRefsMap, remoteRefsMap);
+    ReplicationFetchFilter mockFilter = setupReplicationFilterMock(remoteRefs);
+
+    objectUnderTest.run();
+
+    verify(mockFilter).filter(TEST_PROJECT_NAME, remoteRefs);
+  }
+
+  @Test
+  public void fetchWithoutDelta_shouldPassUpdatedRefsToFilter() throws Exception {
+    setupMocks(true);
+    String REF = "refs/heads/someRef";
+    Set<String> remoteRefs = Set.of(REF);
+    Map<String, Ref> localRefsMap =
+        Map.of(REF, mockRef("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+    Map<String, Ref> remoteRefsMap =
+        Map.of(REF, mockRef("badc0feebadc0feebadc0feebadc0feebadc0fee"));
+    setupRemoteConfigMock(List.of(ALL_REFS_SPEC));
+    setupFetchRefsDatabaseMock(localRefsMap, remoteRefsMap);
+    ReplicationFetchFilter mockFilter = setupReplicationFilterMock(remoteRefs);
+
+    objectUnderTest.run();
+
+    verify(mockFilter).filter(TEST_PROJECT_NAME, remoteRefs);
+  }
+
+  @Test
+  public void fetchWithoutDelta_shouldNotPassUpToDateRefsToFilter() throws Exception {
+    setupMocks(true);
+    String REF = "refs/heads/someRef";
+    Set<String> remoteRefs = Set.of(REF);
+    Ref refValue = mockRef("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+    Map<String, Ref> localRefsMap = Map.of(REF, refValue);
+    Map<String, Ref> remoteRefsMap = Map.of(REF, refValue);
+    setupRemoteConfigMock(List.of(ALL_REFS_SPEC));
+    setupFetchRefsDatabaseMock(localRefsMap, remoteRefsMap);
+    ReplicationFetchFilter mockFilter = setupReplicationFilterMock(remoteRefs);
+
+    objectUnderTest.run();
+
+    verify(mockFilter).filter(TEST_PROJECT_NAME, Set.of());
+  }
+
+  @Test
+  public void fetchWithoutDelta_shouldNotPassRefsNonMatchingConfigToFilter() throws Exception {
+    setupMocks(true);
+    String REF = "refs/non-dev/someRef";
+    Set<String> remoteRefs = Set.of(REF);
+    RefSpec DEV_REFS_SPEC = new RefSpec("refs/dev/*:refs/dev/*");
+
+    setupRemoteConfigMock(List.of(DEV_REFS_SPEC));
+    setupFetchRefsDatabaseMock(Map.of(), Map.of(REF, mock(Ref.class)));
+    ReplicationFetchFilter mockFilter = setupReplicationFilterMock(remoteRefs);
+
+    objectUnderTest.run();
+
+    verify(mockFilter).filter(TEST_PROJECT_NAME, Set.of());
+  }
+
+  @Test
   public void shouldMarkTheReplicationStatusAsSucceededOnSuccessfulReplicationOfARef()
       throws Exception {
     setupMocks(true);
@@ -727,10 +805,7 @@
         List.of(new FetchFactoryEntry.Builder().refSpecNameWithDefaults(TEST_REF).build()),
         Optional.of(List.of(TEST_REF)));
     objectUnderTest.addRefs(refSpecs);
-    objectUnderTest.setReplicationFetchFilter(replicationFilter);
-    ReplicationFetchFilter mockFilter = mock(ReplicationFetchFilter.class);
-    when(replicationFilter.get()).thenReturn(mockFilter);
-    when(mockFilter.filter(TEST_PROJECT_NAME, refSpecs)).thenReturn(Collections.emptySet());
+    setupReplicationFilterMock(Collections.emptySet());
 
     objectUnderTest.run();
 
@@ -789,6 +864,26 @@
     when(grm.openRepository(PROJECT_NAME)).thenReturn(repository);
   }
 
+  private Ref mockRef(String objectIdStr) {
+    Ref r = mock(Ref.class);
+    when(r.getObjectId()).thenReturn(ObjectId.fromString(objectIdStr));
+    return r;
+  }
+
+  private void setupFetchRefsDatabaseMock(Map<String, Ref> local, Map<String, Ref> remote)
+      throws IOException {
+    when(fetchRefsDatabase.getLocalRefsMap(repository)).thenReturn(local);
+    when(fetchRefsDatabase.getRemoteRefsMap(repository, urIish)).thenReturn(remote);
+  }
+
+  private ReplicationFetchFilter setupReplicationFilterMock(Set<String> inRefs) {
+    objectUnderTest.setReplicationFetchFilter(replicationFilter);
+    ReplicationFetchFilter mockFilter = mock(ReplicationFetchFilter.class);
+    when(replicationFilter.get()).thenReturn(mockFilter);
+    when(mockFilter.filter(TEST_PROJECT_NAME, inRefs)).thenReturn(inRefs);
+    return mockFilter;
+  }
+
   private List<ReplicationState> createTestStates(String ref, int numberOfStates) {
     List<ReplicationState> states =
         IntStream.rangeClosed(1, numberOfStates)
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationBatchRefUpdatedIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationBatchRefUpdatedIT.java
index 2e727d1..38f2b83 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationBatchRefUpdatedIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationBatchRefUpdatedIT.java
@@ -22,7 +22,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public class PullReplicationBatchRefUpdatedIT extends PullReplicationITBase {
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigBatchRefUpdateEventIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigBatchRefUpdateEventIT.java
index 7babf34..994ef36 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigBatchRefUpdateEventIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigBatchRefUpdateEventIT.java
@@ -21,7 +21,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public class PullReplicationFanoutConfigBatchRefUpdateEventIT
     extends PullReplicationFanoutConfigBase {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigRefUpdatedEventIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigRefUpdatedEventIT.java
index 16b0b02..c5273ee 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigRefUpdatedEventIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationFanoutConfigRefUpdatedEventIT.java
@@ -21,7 +21,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public class PullReplicationFanoutConfigRefUpdatedEventIT extends PullReplicationFanoutConfigBase {
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITAbstract.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITAbstract.java
index 130bb41..c791770 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITAbstract.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITAbstract.java
@@ -34,13 +34,14 @@
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.replication.ApiModule;
 import com.googlesource.gerrit.plugins.replication.AutoReloadConfigDecorator;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigModule;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -70,13 +71,15 @@
 
   public static class PullReplicationTestModule extends PullReplicationModule {
     @Inject
-    public PullReplicationTestModule(SitePaths site, InMemoryMetricMaker memMetric) {
-      super(site, memMetric);
+    public PullReplicationTestModule(
+        ReplicationConfigModule configModule, InMemoryMetricMaker memMetric) {
+      super(configModule, memMetric);
     }
 
     @Override
     protected void configure() {
       super.configure();
+      install(new ApiModule());
 
       DynamicSet.bind(binder(), EventListener.class)
           .to(BufferedEventListener.class)
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationRefUpdatedIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationRefUpdatedIT.java
index 6e7c369..c6f4b8d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationRefUpdatedIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationRefUpdatedIT.java
@@ -22,7 +22,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public class PullReplicationRefUpdatedIT extends PullReplicationITBase {
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBase.java
index 2e95ef1..d0c25e4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBase.java
@@ -35,7 +35,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public abstract class PullReplicationWithGitHttpTransportProtocolBase
     extends PullReplicationSetupBase {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBatchRefUpdatedIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBatchRefUpdatedIT.java
index 8c8ca37..2d36f1a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBatchRefUpdatedIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolBatchRefUpdatedIT.java
@@ -22,7 +22,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public class PullReplicationWithGitHttpTransportProtocolBatchRefUpdatedIT
     extends PullReplicationWithGitHttpTransportProtocolBase {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolRefUpdatedIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolRefUpdatedIT.java
index 5f3c7b6..8c93bc4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolRefUpdatedIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationWithGitHttpTransportProtocolRefUpdatedIT.java
@@ -22,7 +22,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public class PullReplicationWithGitHttpTransportProtocolRefUpdatedIT
     extends PullReplicationWithGitHttpTransportProtocolBase {
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 e3eb65b..84fee55 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
@@ -47,8 +47,10 @@
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.replication.FileConfigResource;
+import com.googlesource.gerrit.plugins.replication.MergedConfigResource;
 import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
-import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.BatchApplyObjectData;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
 import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
@@ -127,7 +129,11 @@
     Path sitePath = createTempPath("site");
     sitePaths = new SitePaths(sitePath);
     Path pluginDataPath = createTempPath("data");
-    ReplicationConfig replicationConfig = new ReplicationFileBasedConfig(sitePaths, pluginDataPath);
+    ReplicationConfig replicationConfig =
+        new ReplicationConfigImpl(
+            MergedConfigResource.withBaseOnly(new FileConfigResource(sitePaths)),
+            sitePaths,
+            pluginDataPath);
     refsFilter = new ExcludedRefsFilter(replicationConfig);
     when(source.getConnectionTimeout()).thenReturn(CONNECTION_TIMEOUT);
     when(source.wouldFetchProject(any())).thenReturn(true);
@@ -507,7 +513,11 @@
         new FileBasedConfig(sitePaths.etc_dir.resolve("replication.config").toFile(), FS.DETECTED);
     fileConfig.setString("replication", null, "excludeRefs", "refs/multi-site/version");
     fileConfig.save();
-    ReplicationConfig replicationConfig = new ReplicationFileBasedConfig(sitePaths, pluginDataPath);
+    ReplicationConfig replicationConfig =
+        new ReplicationConfigImpl(
+            MergedConfigResource.withBaseOnly(new FileConfigResource(sitePaths)),
+            sitePaths,
+            pluginDataPath);
     refsFilter = new ExcludedRefsFilter(replicationConfig);
 
     objectUnderTest =
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
index 8e7dc35..f1fbfe2 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
@@ -35,8 +35,11 @@
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.server.Sequence;
 import com.google.inject.Scopes;
+import com.googlesource.gerrit.plugins.replication.ApiModule;
+import com.googlesource.gerrit.plugins.replication.ConfigResource;
+import com.googlesource.gerrit.plugins.replication.FileConfigResource;
 import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
-import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData;
 import com.googlesource.gerrit.plugins.replication.pull.fetch.ApplyObject;
@@ -60,12 +63,12 @@
 public class RevisionReaderIT extends LightweightPluginDaemonTest {
   RevisionReader objectUnderTest;
 
-  ReplicationFileBasedConfig replicationConfig;
+  ReplicationConfigImpl replicationConfig;
 
   @Before
   public void setup() {
     objectUnderTest = plugin.getSysInjector().getInstance(RevisionReader.class);
-    replicationConfig = plugin.getSysInjector().getInstance(ReplicationFileBasedConfig.class);
+    replicationConfig = plugin.getSysInjector().getInstance(ReplicationConfigImpl.class);
   }
 
   @Test
@@ -305,7 +308,9 @@
   private static class TestModule extends FactoryModule {
     @Override
     protected void configure() {
-      bind(ReplicationConfig.class).to(ReplicationFileBasedConfig.class);
+      install(new ApiModule());
+      bind(ConfigResource.class).to(FileConfigResource.class);
+      bind(ReplicationConfig.class).to(ReplicationConfigImpl.class);
       bind(RevisionReader.class).in(Scopes.SINGLETON);
       bind(ApplyObject.class);
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/TestPullReplicationModule.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/TestPullReplicationModule.java
new file mode 100644
index 0000000..a20f617
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/TestPullReplicationModule.java
@@ -0,0 +1,21 @@
+package com.googlesource.gerrit.plugins.replication.pull;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.ApiModule;
+
+public class TestPullReplicationModule extends AbstractModule {
+
+  private final PullReplicationModule pullReplicationModule;
+
+  @Inject
+  TestPullReplicationModule(PullReplicationModule pullReplicationModule) {
+    this.pullReplicationModule = pullReplicationModule;
+  }
+
+  @Override
+  protected void configure() {
+    install(new ApiModule());
+    install(pullReplicationModule);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
index f8110d1..6c83d93 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
@@ -62,7 +62,7 @@
 @UseLocalDisk
 @TestPlugin(
     name = "pull-replication",
-    sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.TestPullReplicationModule",
     httpModule = "com.googlesource.gerrit.plugins.replication.pull.api.HttpModule")
 public abstract class ActionITBase extends LightweightPluginDaemonTest {
   protected static final Optional<String> ALL_PROJECTS = Optional.empty();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
index b061de0..087920b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
@@ -28,7 +28,7 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
-import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
 import com.googlesource.gerrit.plugins.replication.pull.BearerTokenProvider;
 import com.googlesource.gerrit.plugins.replication.pull.Source;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.BatchApplyObjectData;
@@ -67,7 +67,7 @@
   @Mock HttpClient httpClient;
   @Mock SourceHttpClient.Factory httpClientFactory;
   @Mock FileBasedConfig config;
-  @Mock ReplicationFileBasedConfig replicationConfig;
+  @Mock ReplicationConfig replicationConfig;
   @Mock Source source;
   @Mock BearerTokenProvider bearerTokenProvider;
   @Captor ArgumentCaptor<HttpPost> httpPostCaptor;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java
index b562933..0a01e21 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java
@@ -36,10 +36,14 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.Inject;
 import com.google.inject.Scopes;
+import com.googlesource.gerrit.plugins.replication.ConfigResource;
+import com.googlesource.gerrit.plugins.replication.FileConfigResource;
 import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
-import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfigOverrides;
 import com.googlesource.gerrit.plugins.replication.pull.RevisionReader;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
 import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData;
@@ -256,7 +260,9 @@
   private static class TestModule extends FactoryModule {
     @Override
     protected void configure() {
-      bind(ReplicationConfig.class).to(ReplicationFileBasedConfig.class);
+      DynamicItem.itemOf(binder(), ReplicationConfigOverrides.class);
+      bind(ConfigResource.class).to(FileConfigResource.class);
+      bind(ReplicationConfig.class).to(ReplicationConfigImpl.class);
       bind(RevisionReader.class).in(Scopes.SINGLETON);
       bind(ApplyObject.class);
     }