Merge "Make sure that the EventListener receives replication events" into stable-3.4
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/transport/TransportProvider.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/transport/TransportProvider.java
index c52db82..ec7f655 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/transport/TransportProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/transport/TransportProvider.java
@@ -56,6 +56,7 @@
throws NotSupportedException, TransportException {
Transport tn = Transport.open(local, uri);
tn.applyConfig(remoteConfig);
+ tn.setRemoveDeletedRefs(remoteConfig.isMirror());
if (tn instanceof TransportHttp && bearerToken.isPresent()) {
((TransportHttp) tn)
.setAdditionalHeaders(ImmutableMap.of(HDR_AUTHORIZATION, "Bearer " + bearerToken.get()));
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 0c3e02c..b7db2c0 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -523,6 +523,14 @@
By default, false, do *not* replicate project deletions.
+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.
+
+ By default, false, do not remove remote branches or tags.
+
remote.NAME.authGroup
: Specifies the name of a group that the remote should use to
access the repositories. Multiple authGroups may be specified
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 0c33df4..be9f902 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
@@ -66,7 +66,7 @@
}
}
- Project.NameKey createTestProject(String name) throws Exception {
+ Project.NameKey createTestProject(String name) {
return projectOperations.newProject().name(name).create();
}
}
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 76ff02b..e5cd33d 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
@@ -14,11 +14,21 @@
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;
+
import com.google.common.collect.Lists;
+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.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;
@@ -30,12 +40,20 @@
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;
@SkipProjectClone
@@ -46,6 +64,17 @@
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;
+
+ @Before
+ public void allowRefDeletion() {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allow(Permission.DELETE).ref("refs/*").group(adminGroupUuid()))
+ .update();
+ }
@Test(expected = PermanentTransportException.class)
public void shouldThrowPermanentTransportExceptionWhenRefDoesNotExists() throws Exception {
@@ -59,12 +88,67 @@
}
}
+ @Test
+ public void shouldPruneRefsWhenMirrorIsTrue() 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;
+
+ 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)) {
+ List<RefUpdateState> fetchCreated = fetchAllRefs(localRepo);
+ assertThat(fetchCreated.toString())
+ .contains(new RefUpdateState(branchRef, RefUpdate.Result.NEW).toString());
+ assertThat(fetchCreated.toString())
+ .contains(new RefUpdateState(tagRef, RefUpdate.Result.NEW).toString());
+
+ assertThat(getRef(localRepo, branchRef)).isNotNull();
+
+ PushResult deleteBranchResult = deleteRef(testRepo, branchRef);
+ assertOkStatus(deleteBranchResult, branchRef);
+
+ 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();
+ }
+ }
+
+ 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 {
@Override
protected void configure() {
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);