Allow to specify whether to store reflog for new projects Introduce a new remote.NAME.storeRefLog for configuring the core.logallrefupdates in the new repository's config file when it is created by replication. Release-Notes: Allow configuring the reflog for newly created repositories with remote.NAME.storeRefLog config. Bug: Issue 464210139 Change-Id: Ia14a4b5829cceb226a537c0651d08f2057fce8aa
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AdminApi.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AdminApi.java index 77e1836..f3bc7cc 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/AdminApi.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AdminApi.java
@@ -17,8 +17,31 @@ import com.google.gerrit.entities.Project; public interface AdminApi { + + /** + * Create a new project without honouring the reflog configuration. + * + * @param project the new project to create + * @param head initial value for the HEAD + * @return true if the project was created + * @deprecated this method should not be used as it does not respect the reflog settings + * <p>Use {@link AdminApi#createProject(Project.NameKey,String,boolean)} instead. + */ + @Deprecated(since = "3.14", forRemoval = true) boolean createProject(Project.NameKey project, String head); + /** + * Create a new project honouring the reflog configuration. + * + * @param project the new project to create + * @param head initial value for the HEAD + * @param enableRefLog true if the reflog tracking should be enabled + * @return true if the project was created + */ + default boolean createProject(Project.NameKey project, String head, boolean enableRefLog) { + return createProject(project, head); + } + boolean deleteProject(Project.NameKey project); boolean updateHead(Project.NameKey project, String newHead);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java b/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java index 8cbb2a2..32903ab 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java
@@ -53,15 +53,16 @@ public boolean create() { return destinations .getURIs(Optional.of(config.getName()), project, FilterType.PROJECT_CREATION) - .values() + .entries() .stream() - .map(u -> createProject(u, project, head)) + .map(entry -> createProject(entry.getValue(), project, head, entry.getKey().storeRefLog())) .reduce(true, (a, b) -> a && b); } - private boolean createProject(URIish replicateURI, Project.NameKey projectName, String head) { + private boolean createProject( + URIish replicateURI, Project.NameKey projectName, String head, boolean storeRefLog) { Optional<AdminApi> adminApi = adminApiFactory.get().create(replicateURI); - if (adminApi.isPresent() && adminApi.get().createProject(projectName, head)) { + if (adminApi.isPresent() && adminApi.get().createProject(projectName, head, storeRefLog)) { return true; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java index 41a9a49..f0b26f9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -894,6 +894,10 @@ return config.excludedRefsPattern(); } + boolean storeRefLog() { + return config.storeRefLog(); + } + private static boolean matches(URIish uri, String urlMatch) { if (urlMatch == null || urlMatch.equals("") || urlMatch.equals("*")) { return true;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java index 41ab4c4..b9e1be9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
@@ -56,6 +56,7 @@ private final int slowLatencyThreshold; private final Supplier<Integer> pushBatchSize; private final ImmutableList<Pattern> excludedRefsPattern; + private final boolean storeRefLog; protected DestinationConfiguration(RemoteConfig remoteConfig, Config cfg) { this.remoteConfig = remoteConfig; @@ -122,6 +123,7 @@ return 0; }); excludedRefsPattern = getExcludedRefsPattern(cfg, name); + storeRefLog = cfg.getBoolean("remote", name, "storeRefLog", false); } @Override @@ -227,6 +229,11 @@ return excludedRefsPattern; } + @Override + public boolean storeRefLog() { + return storeRefLog; + } + private ImmutableList<Pattern> getExcludedRefsPattern(Config cfg, String name) { List<Pattern> patterns = new ArrayList<>(); for (String regex : cfg.getStringList("remote", name, "excludedRefsPattern")) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/LocalFS.java b/src/main/java/com/googlesource/gerrit/plugins/replication/LocalFS.java index b092363..49c97f0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/LocalFS.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/LocalFS.java
@@ -20,9 +20,11 @@ import java.io.File; import java.io.IOException; import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.URIish; public class LocalFS implements AdminApi { @@ -35,6 +37,11 @@ @Override public boolean createProject(Project.NameKey project, String head) { + return createProject(project, head, false); + } + + @Override + public boolean createProject(Project.NameKey project, String head, boolean enableRefLog) { try (Repository repo = new FileRepository(uri.getPath())) { repo.create(true /* bare */); @@ -43,6 +50,15 @@ u.disableRefLog(); u.link(head); } + + StoredConfig config = repo.getConfig(); + config.setBoolean( + ConfigConstants.CONFIG_CORE_SECTION, + null, + ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, + enableRefLog); + config.save(); + repLog.atInfo().log("Created local repository: %s", uri); } catch (IOException e) { repLog.atSevere().withCause(e).log("Error creating local repository %s", uri.getPath());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java index 67d23b0..c725d40 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
@@ -152,4 +152,11 @@ default ImmutableList<Pattern> excludedRefsPattern() { return ImmutableList.of(); } + + /** + * reflog storage flag for newly created repositories + * + * @return true if new repositories should store ref-updates in their reflog + */ + boolean storeRefLog(); }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index f2954f5..150810e 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -344,6 +344,17 @@ Defaults to `git-receive-pack`. +remote.NAME.storeRefLog +: `true` if the remote repositories should be enabled for storing + ref updates in the reflog once they are created by replication. + + NOTE: Enabling the reflog would prevent the unreferenced objects + from being garbage collected, potentially impacting the post-gc + repository size. Also, the ref updates may take additional time + because of the need to store the old and new SHA1s in the reflog. + + Defaults to `false`. + remote.NAME.uploadpack : Path of the `git-upload-pack` executable on the remote system, if using the SSH transport.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java index e25a5d6..45cf5a7 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
@@ -29,6 +29,7 @@ import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; +import java.io.IOException; import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -38,13 +39,16 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; 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.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.junit.Test; @@ -72,18 +76,53 @@ } @Test - public void shouldReplicateNewProject() throws Exception { + public void shouldReplicateNewProjectWithoutRefLog() throws Exception { setReplicationDestination("foo", "replica", ALL_PROJECTS); reloadConfig(); - Project.NameKey sourceProject = createTestProject("foo"); + Project.NameKey sourceProject = createTestProject("no_reflog_project"); + Project.NameKey replicaProject = Project.nameKey(sourceProject + "replica.git"); - WaitUtil.waitUntil( - () -> nonEmptyProjectExists(Project.nameKey(sourceProject + "replica.git")), - TEST_NEW_PROJECT_TIMEOUT); + waitForProjectCreated(replicaProject); - ProjectInfo replicaProject = gApi.projects().name(sourceProject + "replica").get(); - assertThat(replicaProject).isNotNull(); + ProjectInfo replicaProjectInfo = gApi.projects().name(replicaProject.get()).get(); + assertThat(replicaProjectInfo).isNotNull(); + applyToRepositoryConfig(replicaProject, assertStoreRefLog(false)); + } + + @Test + public void shouldCreateNewProjectWithRefLog() throws Exception { + config.setBoolean("remote", "foo", "storeRefLog", true); + setReplicationDestination("foo", "replica", ALL_PROJECTS); + reloadConfig(); + + Project.NameKey sourceProject = createTestProject("reflog_project"); + Project.NameKey replicaProject = Project.nameKey(sourceProject + "replica.git"); + + waitForProjectCreated(replicaProject); + + applyToRepositoryConfig(replicaProject, assertStoreRefLog(true)); + } + + private void waitForProjectCreated(Project.NameKey replicaProject) throws InterruptedException { + WaitUtil.waitUntil(() -> nonEmptyProjectExists(replicaProject), TEST_NEW_PROJECT_TIMEOUT); + } + + private static Consumer<StoredConfig> assertStoreRefLog(boolean expectedValue) { + return conf -> + assertThat( + conf.getBoolean( + ConfigConstants.CONFIG_CORE_SECTION, + null, + ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES)) + .isEqualTo(expectedValue); + } + + private void applyToRepositoryConfig( + Project.NameKey projectName, Consumer<StoredConfig> functionToApply) throws IOException { + try (Repository repo = repoManager.openRepository(projectName)) { + functionToApply.accept(repo.getConfig()); + } } @Test