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.
This commit is contained in:
René Pfeuffer
2021-07-28 12:04:57 +02:00
committed by GitHub
parent ae21da7cd2
commit 68b2976578
3 changed files with 56 additions and 1 deletions

View File

@@ -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))

View File

@@ -74,6 +74,7 @@ import static java.util.Collections.unmodifiableMap;
import static java.util.Optional.empty; import static java.util.Optional.empty;
import static java.util.Optional.of; import static java.util.Optional.of;
import static org.eclipse.jgit.lib.RefUpdate.Result.NEW; 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.FAILED;
import static sonia.scm.repository.api.MirrorCommandResult.ResultType.OK; import static sonia.scm.repository.api.MirrorCommandResult.ResultType.OK;
import static sonia.scm.repository.api.MirrorCommandResult.ResultType.REJECTED_UPDATES; 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; 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) { private Worker(GitContext context, MirrorCommandRequest mirrorCommandRequest, sonia.scm.repository.Repository repository, Git git) {
super(git, context, repository); super(git, context, repository);
this.mirrorCommandRequest = mirrorCommandRequest; this.mirrorCommandRequest = mirrorCommandRequest;
@@ -167,9 +180,16 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
} }
push(generatePushRefSpecs().toArray(new String[0])); push(generatePushRefSpecs().toArray(new String[0]));
cleanUpMasterIfNecessary();
return new MirrorCommandResult(result, mirrorLog, stopwatch.stop().elapsed()); return new MirrorCommandResult(result, mirrorLog, stopwatch.stop().elapsed());
} }
private void cleanUpMasterIfNecessary() {
if (deleteMasterAfterInitialSync) {
push(":refs/heads/master");
}
}
private Collection<String> generatePushRefSpecs() { private Collection<String> generatePushRefSpecs() {
Collection<String> refSpecs = new ArrayList<>(); Collection<String> refSpecs = new ArrayList<>();
refSpecs.add("refs/heads/*:refs/heads/*"); refSpecs.add("refs/heads/*:refs/heads/*");
@@ -302,7 +322,10 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
private void deleteReference(String targetRef) throws IOException { private void deleteReference(String targetRef) throws IOException {
RefUpdate deleteUpdate = git.getRepository().getRefDatabase().newUpdate(targetRef, true); RefUpdate deleteUpdate = git.getRepository().getRefDatabase().newUpdate(targetRef, true);
deleteUpdate.setForceUpdate(true); deleteUpdate.setForceUpdate(true);
deleteUpdate.delete(); RefUpdate.Result deleteResult = deleteUpdate.delete();
if (deleteResult == REJECTED_CURRENT_BRANCH) {
deleteMasterAfterInitialSync = true;
}
} }
private boolean isDeletedReference(TrackingRefUpdate ref) { private boolean isDeletedReference(TrackingRefUpdate ref) {

View File

@@ -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 @Test
public void shouldCreateEmptyLogWhenNoChangesFound() { public void shouldCreateEmptyLogWhenNoChangesFound() {
callMirrorCommand(); callMirrorCommand();