From 68b297657833a6070293d5ca85c15bb2cb23f9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 28 Jul 2021 12:04:57 +0200 Subject: [PATCH] Filter master branch correctly on initial mirror (#1747) On the first synchronization, the clone has the implicit branch "master". This cannot be changed in JGit. When we fetch the refs from the repository that should be mirrored, the master branch of the clone will be updated to the revision of the remote repository (if it has a master branch). If now the master branch shall be filtered from mirroring (ie. if it is rejected), we normally would delete the ref in this clone. But because it is the current branch, it cannot be deleted. We detect this and later, after we have pushed the result, delete the master branch by pushing an empty ref to the central repository. --- .../changelog/master_on_initial_mirror.yaml | 2 ++ .../scm/repository/spi/GitMirrorCommand.java | 25 +++++++++++++++- .../repository/spi/GitMirrorCommandTest.java | 30 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 gradle/changelog/master_on_initial_mirror.yaml diff --git a/gradle/changelog/master_on_initial_mirror.yaml b/gradle/changelog/master_on_initial_mirror.yaml new file mode 100644 index 0000000000..d5572ba76f --- /dev/null +++ b/gradle/changelog/master_on_initial_mirror.yaml @@ -0,0 +1,2 @@ +- type: fixed + description: Handle rejected master branch on initial mirror synchronization correctly ([#1747](https://github.com/scm-manager/scm-manager/pull/1747)) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMirrorCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMirrorCommand.java index c785d72c23..a1825d5412 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMirrorCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMirrorCommand.java @@ -74,6 +74,7 @@ import static java.util.Collections.unmodifiableMap; import static java.util.Optional.empty; import static java.util.Optional.of; import static org.eclipse.jgit.lib.RefUpdate.Result.NEW; +import static org.eclipse.jgit.lib.RefUpdate.Result.REJECTED_CURRENT_BRANCH; import static sonia.scm.repository.api.MirrorCommandResult.ResultType.FAILED; import static sonia.scm.repository.api.MirrorCommandResult.ResultType.OK; import static sonia.scm.repository.api.MirrorCommandResult.ResultType.REJECTED_UPDATES; @@ -135,6 +136,18 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman private ResultType result = OK; + /** + * On the first synchronization, the clone has the implicit branch "master". This cannot be + * changed in JGit. When we fetch the refs from the repository that should be mirrored, the + * master branch of the clone will be updated to the revision of the remote repository (if + * it has a master branch). If now the master branch shall be filtered from mirroring (ie. + * if it is rejected), we normally would delete the ref in this clone. But because it is + * the current branch, it cannot be deleted. We detect this, set this variable to + * {@code true}, and later, after we have pushed the result, delete the master branch by + * pushing an empty ref to the central repository. + */ + private boolean deleteMasterAfterInitialSync = false; + private Worker(GitContext context, MirrorCommandRequest mirrorCommandRequest, sonia.scm.repository.Repository repository, Git git) { super(git, context, repository); this.mirrorCommandRequest = mirrorCommandRequest; @@ -167,9 +180,16 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman } push(generatePushRefSpecs().toArray(new String[0])); + cleanUpMasterIfNecessary(); return new MirrorCommandResult(result, mirrorLog, stopwatch.stop().elapsed()); } + private void cleanUpMasterIfNecessary() { + if (deleteMasterAfterInitialSync) { + push(":refs/heads/master"); + } + } + private Collection generatePushRefSpecs() { Collection refSpecs = new ArrayList<>(); refSpecs.add("refs/heads/*:refs/heads/*"); @@ -302,7 +322,10 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman private void deleteReference(String targetRef) throws IOException { RefUpdate deleteUpdate = git.getRepository().getRefDatabase().newUpdate(targetRef, true); deleteUpdate.setForceUpdate(true); - deleteUpdate.delete(); + RefUpdate.Result deleteResult = deleteUpdate.delete(); + if (deleteResult == REJECTED_CURRENT_BRANCH) { + deleteMasterAfterInitialSync = true; + } } private boolean isDeletedReference(TrackingRefUpdate ref) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMirrorCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMirrorCommandTest.java index a5474add54..4c591c85f7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMirrorCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMirrorCommandTest.java @@ -112,6 +112,36 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase { } } + @Test + public void shouldFilterMasterBranchWhenFilteredOnInitialMirror() throws IOException, GitAPIException { + MirrorCommandResult result = callMirrorCommand(repositoryDirectory.getAbsolutePath(), c -> { + c.setFilter(new MirrorFilter() { + @Override + public Filter getFilter(FilterContext context) { + return new Filter() { + @Override + public Result acceptBranch(BranchUpdate branch) { + if (branch.getBranchName().equals("master")) { + return Result.reject("master not accepted"); + } else { + return Result.accept(); + } + } + }; + } + }); + }); + + assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); + assertThat(result.getLog()) + .contains("- 000000000..fcd0ef183 master (master not accepted)"); + + try (Git createdMirror = Git.open(clone)) { + assertThat(createdMirror.branchList().call().stream().filter(r -> r.getName().contains("master")).findAny()) + .isEmpty(); + } + } + @Test public void shouldCreateEmptyLogWhenNoChangesFound() { callMirrorCommand();