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();