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