Fix initial mirror sync if empty (#1842)

Fixes an error, when the initial synchronization of a git mirror had no branches.
This commit is contained in:
René Pfeuffer
2021-11-03 10:51:06 +01:00
committed by GitHub
parent b896df5046
commit cab09680f8
3 changed files with 149 additions and 55 deletions

View File

@@ -0,0 +1,2 @@
- type: fixed
descripion: Initial mirror with no accepted branch ([#1842](https://github.com/scm-manager/scm-manager/pull/1842))

View File

@@ -198,12 +198,18 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
if (fetchResult.getTrackingRefUpdates().isEmpty()) { if (fetchResult.getTrackingRefUpdates().isEmpty()) {
LOG.trace("No updates found for mirror repository {}", repository); LOG.trace("No updates found for mirror repository {}", repository);
mirrorLog.add("No updates found"); mirrorLog.add("No updates found");
return new MirrorCommandResult(result, mirrorLog, stopwatch.stop().elapsed());
} else { } else {
handleBranches(); handleBranches();
handleTags(); handleTags();
} }
defaultBranchSelector.newDefault().ifPresent(this::setNewDefaultBranch); if (!defaultBranchSelector.isChanged()) {
mirrorLog.add("No effective changes detected");
return new MirrorCommandResult(result, mirrorLog, stopwatch.stop().elapsed());
}
defaultBranchSelector.newDefaultBranch().ifPresent(this::setNewDefaultBranch);
String[] pushRefSpecs = generatePushRefSpecs().toArray(new String[0]); String[] pushRefSpecs = generatePushRefSpecs().toArray(new String[0]);
push(pushRefSpecs); push(pushRefSpecs);
@@ -272,7 +278,7 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
} }
private void handleBranch(LoggerWithHeader logger, TrackingRefUpdate ref) { private void handleBranch(LoggerWithHeader logger, TrackingRefUpdate ref) {
MirrorReferenceUpdateHandler refHandler = new MirrorReferenceUpdateHandler(logger, ref, "heads/", "branch"); MirrorReferenceUpdateHandler refHandler = new MirrorReferenceUpdateHandler(logger, ref, RefType.BRANCH);
refHandler.handleRef(ref1 -> refHandler.testFilterForBranch()); refHandler.handleRef(ref1 -> refHandler.testFilterForBranch());
} }
@@ -282,28 +288,26 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
} }
private void handleTag(LoggerWithHeader logger, TrackingRefUpdate ref) { private void handleTag(LoggerWithHeader logger, TrackingRefUpdate ref) {
MirrorReferenceUpdateHandler refHandler = new MirrorReferenceUpdateHandler(logger, ref, "tags/", "tag"); MirrorReferenceUpdateHandler refHandler = new MirrorReferenceUpdateHandler(logger, ref, RefType.TAG);
refHandler.handleRef(ref1 -> refHandler.testFilterForTag()); refHandler.handleRef(ref1 -> refHandler.testFilterForTag());
} }
private class MirrorReferenceUpdateHandler { private class MirrorReferenceUpdateHandler {
private final LoggerWithHeader logger; private final LoggerWithHeader logger;
private final TrackingRefUpdate ref; private final TrackingRefUpdate ref;
private final String refType; private final RefType refType;
private final String typeForLog;
public MirrorReferenceUpdateHandler(LoggerWithHeader logger, TrackingRefUpdate ref, String refType, String typeForLog) { public MirrorReferenceUpdateHandler(LoggerWithHeader logger, TrackingRefUpdate ref, RefType refType) {
this.logger = logger; this.logger = logger;
this.ref = ref; this.ref = ref;
this.refType = refType; this.refType = refType;
this.typeForLog = typeForLog;
} }
private void handleRef(Function<TrackingRefUpdate, Result> filter) { private void handleRef(Function<TrackingRefUpdate, Result> filter) {
LOG.trace("Handling {}", ref.getLocalName()); LOG.trace("Handling {}", ref.getLocalName());
Result filterResult = filter.apply(ref); Result filterResult = filter.apply(ref);
try { try {
String referenceName = ref.getLocalName().substring("refs/".length() + refType.length()); String referenceName = ref.getLocalName().substring("refs/".length() + refType.refPath.length());
if (filterResult.isAccepted()) { if (filterResult.isAccepted()) {
LOG.trace("Accepted ref {}", ref.getLocalName()); LOG.trace("Accepted ref {}", ref.getLocalName());
handleAcceptedReference(referenceName); handleAcceptedReference(referenceName);
@@ -332,7 +336,7 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
private void handleRejectedRef(String referenceName, Result filterResult) throws IOException { private void handleRejectedRef(String referenceName, Result filterResult) throws IOException {
result = REJECTED_UPDATES; result = REJECTED_UPDATES;
LOG.trace("{} ref rejected in {}: {}", typeForLog, GitMirrorCommand.this.repository, ref.getLocalName()); LOG.trace("{} ref rejected in {}: {}", refType.typeForLog, GitMirrorCommand.this.repository, ref.getLocalName());
if (ref.getResult() == NEW) { if (ref.getResult() == NEW) {
deleteReference(ref.getLocalName()); deleteReference(ref.getLocalName());
} else { } else {
@@ -342,16 +346,16 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
} }
private void handleAcceptedReference(String referenceName) throws IOException { private void handleAcceptedReference(String referenceName) throws IOException {
String targetRef = "refs/" + refType + referenceName; String targetRef = "refs/" + refType.refPath + referenceName;
if (isDeletedReference(ref)) { if (isDeletedReference(ref)) {
LOG.trace("deleting {} ref in {}: {}", typeForLog, GitMirrorCommand.this.repository, targetRef); LOG.trace("deleting {} ref in {}: {}", refType.typeForLog, GitMirrorCommand.this.repository, targetRef);
defaultBranchSelector.deleted(referenceName); defaultBranchSelector.deleted(refType, referenceName);
logger.logChange(ref, referenceName, "deleted"); logger.logChange(ref, referenceName, "deleted");
deleteReference(targetRef); deleteReference(targetRef);
deletedRefs.add(targetRef); deletedRefs.add(targetRef);
} else { } else {
LOG.trace("updating {} ref in {}: {}", typeForLog, GitMirrorCommand.this.repository, targetRef); LOG.trace("updating {} ref in {}: {}", refType.typeForLog, GitMirrorCommand.this.repository, targetRef);
defaultBranchSelector.accepted(referenceName); defaultBranchSelector.accepted(refType, referenceName);
logger.logChange(ref, referenceName, getUpdateType(ref)); logger.logChange(ref, referenceName, getUpdateType(ref));
} }
} }
@@ -665,6 +669,8 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
private final Set<String> remainingBranches; private final Set<String> remainingBranches;
private final Set<String> newBranches = new HashSet<>(); private final Set<String> newBranches = new HashSet<>();
private boolean changed = false;
DefaultBranchSelector(String initialDefaultBranch, Collection<String> initialBranches) { DefaultBranchSelector(String initialDefaultBranch, Collection<String> initialBranches) {
this.initialDefaultBranch = initialBranches.isEmpty() ? null : initialDefaultBranch; this.initialDefaultBranch = initialBranches.isEmpty() ? null : initialDefaultBranch;
this.initialBranches = new HashSet<>(initialBranches); this.initialBranches = new HashSet<>(initialBranches);
@@ -706,25 +712,50 @@ public class GitMirrorCommand extends AbstractGitCommand implements MirrorComman
} }
} }
public void accepted(String branch) { public void accepted(RefType refType, String ref) {
newBranches.add(branch); changed = true;
if (refType == RefType.BRANCH) {
newBranches.add(ref);
}
} }
public void deleted(String branch) { public void deleted(RefType refType, String ref) {
remainingBranches.remove(branch); changed = true;
if (refType == RefType.BRANCH) {
remainingBranches.remove(ref);
}
} }
public Optional<String> newDefault() { public boolean isChanged() {
return changed;
}
public Optional<String> newDefaultBranch() {
if (initialDefaultBranch == null && newBranches.contains("master") || remainingBranches.contains(initialDefaultBranch)) { if (initialDefaultBranch == null && newBranches.contains("master") || remainingBranches.contains(initialDefaultBranch)) {
return empty(); return empty();
} else if (!newBranches.isEmpty() && initialBranches.isEmpty()) { } else if (!newBranches.isEmpty() && initialBranches.isEmpty()) {
return of(newBranches.iterator().next()); return of(newBranches.iterator().next());
} else if (initialDefaultBranch == null && newBranches.isEmpty()) {
LOG.info("Could not mirror repository with tags only.");
throw new IllegalStateException("No branch could be mirrored. Initial mirror without accepted branch is not supported. Make sure that at least one branch can be mirrored.");
} else if (remainingBranches.isEmpty()) { } else if (remainingBranches.isEmpty()) {
LOG.warn("Could not compute new default branch."); LOG.info("Could not compute new default branch.");
throw new IllegalStateException("Deleting all existing branches is not supported. Please restore branch '" + initialDefaultBranch + "' or recreate the mirror."); throw new IllegalStateException("Deleting all existing branches is not supported. Please restore branch '" + initialDefaultBranch + "' or recreate the mirror.");
} else { } else {
return of(remainingBranches.iterator().next()); return of(remainingBranches.iterator().next());
} }
} }
} }
enum RefType {
BRANCH("heads/", "branch"), TAG("tags/", "tag");
private final String refPath;
private final String typeForLog;
RefType(String refPath, String typeForLog) {
this.refPath = refPath;
this.typeForLog = typeForLog;
}
}
} }

View File

@@ -82,6 +82,7 @@ import static org.mockito.Mockito.when;
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;
import static sonia.scm.repository.spi.GitMirrorCommand.RefType.BRANCH;
public class GitMirrorCommandTest extends AbstractGitCommandTestBase { public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
@@ -164,6 +165,58 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
} }
} }
@Test
public void shouldAcceptEmptyInitialMirror() 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) {
return Result.reject("nothing accepted");
}
@Override
public Result acceptTag(TagUpdate tag) {
return Result.reject("nothing accepted");
}
};
}
});
});
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).contains("Branches:")
.contains("- 000000000..fcd0ef183 master (nothing accepted)")
.contains("- 000000000..3f76a12f0 test-branch (nothing accepted)")
.contains("Tags:")
.contains("- 000000000..86a6645ec test-tag (nothing accepted)");
try (Git createdMirror = Git.open(clone)) {
assertThat(createdMirror.branchList().call()).isEmpty();
assertThat(createdMirror.tagList().call()).isEmpty();
}
}
@Test
public void shouldAcceptOnlyTagInInitialMirror() {
assertThrows(IllegalStateException.class, () ->
callMirrorCommand(repositoryDirectory.getAbsolutePath(), c -> {
c.setFilter(new MirrorFilter() {
@Override
public Filter getFilter(FilterContext context) {
return new Filter() {
@Override
public Result acceptBranch(BranchUpdate branch) {
return Result.reject("nothing accepted");
}
};
}
});
}));
}
@Test @Test
public void shouldFilterMasterBranchWhenFilteredOnInitialMirror() throws IOException, GitAPIException { public void shouldFilterMasterBranchWhenFilteredOnInitialMirror() throws IOException, GitAPIException {
MirrorCommandResult result = callMirrorCommand(repositoryDirectory.getAbsolutePath(), c -> { MirrorCommandResult result = callMirrorCommand(repositoryDirectory.getAbsolutePath(), c -> {
@@ -356,7 +409,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Branches:", "Branches:",
"- 000000000..fcd0ef183 added-branch (rejected due to filter)" "- 000000000..fcd0ef183 added-branch (rejected due to filter)",
"No effective changes detected"
); );
try (Git updatedMirror = Git.open(clone)) { try (Git updatedMirror = Git.open(clone)) {
@@ -378,7 +432,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Branches:", "Branches:",
"- 3f76a12f0..9e93d8631 test-branch (rejected due to filter)" "- 3f76a12f0..9e93d8631 test-branch (rejected due to filter)",
"No effective changes detected"
); );
try (Git updatedMirror = Git.open(clone)) { try (Git updatedMirror = Git.open(clone)) {
@@ -400,7 +455,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Branches:", "Branches:",
"- 3f76a12f0..000000000 test-branch (rejected due to filter)" "- 3f76a12f0..000000000 test-branch (rejected due to filter)",
"No effective changes detected"
); );
try (Git updatedMirror = Git.open(clone)) { try (Git updatedMirror = Git.open(clone)) {
@@ -423,7 +479,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Tags:", "Tags:",
"- 000000000..9e93d8631 added-tag (rejected due to filter)" "- 000000000..9e93d8631 added-tag (rejected due to filter)",
"No effective changes detected"
); );
try (Git updatedMirror = Git.open(clone)) { try (Git updatedMirror = Git.open(clone)) {
@@ -446,7 +503,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Tags:", "Tags:",
"- 86a6645ec..9e93d8631 test-tag (rejected due to filter)" "- 86a6645ec..9e93d8631 test-tag (rejected due to filter)",
"No effective changes detected"
); );
try (Git updatedMirror = Git.open(clone)) { try (Git updatedMirror = Git.open(clone)) {
@@ -468,7 +526,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Tags:", "Tags:",
"- 86a6645ec..000000000 test-tag (rejected due to filter)" "- 86a6645ec..000000000 test-tag (rejected due to filter)",
"No effective changes detected"
); );
try (Git updatedMirror = Git.open(clone)) { try (Git updatedMirror = Git.open(clone)) {
@@ -490,7 +549,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES); assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"Tags:", "Tags:",
"- 86a6645ec..000000000 test-tag (thou shalt not pass)" "- 86a6645ec..000000000 test-tag (thou shalt not pass)",
"No effective changes detected"
); );
} }
@@ -508,7 +568,8 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
assertThat(result.getLog()).containsExactly( assertThat(result.getLog()).containsExactly(
"! got error checking filter for update: this tag creates an exception", "! got error checking filter for update: this tag creates an exception",
"Tags:", "Tags:",
"- 86a6645ec..000000000 test-tag (exception in filter)" "- 86a6645ec..000000000 test-tag (exception in filter)",
"No effective changes detected"
); );
} }
@@ -777,10 +838,10 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", emptyList()); new GitMirrorCommand.DefaultBranchSelector("master", emptyList());
selector.accepted("master"); selector.accepted(BRANCH, "master");
selector.accepted("something"); selector.accepted(BRANCH, "something");
assertThat(selector.newDefault()).isEmpty(); assertThat(selector.newDefaultBranch()).isEmpty();
} }
@Test @Test
@@ -788,10 +849,10 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES); new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES);
selector.accepted("new"); selector.accepted(BRANCH, "new");
selector.deleted("two"); selector.deleted(BRANCH, "two");
assertThat(selector.newDefault()).isEmpty(); assertThat(selector.newDefaultBranch()).isEmpty();
} }
@Test @Test
@@ -799,9 +860,9 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES); new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES);
selector.deleted("master"); selector.deleted(BRANCH, "master");
assertThat(selector.newDefault()).get().isIn("one", "two", "three"); assertThat(selector.newDefaultBranch()).get().isIn("one", "two", "three");
} }
@Test @Test
@@ -809,11 +870,11 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES); new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES);
selector.deleted("master"); selector.deleted(BRANCH, "master");
selector.deleted("one"); selector.deleted(BRANCH, "one");
selector.deleted("three"); selector.deleted(BRANCH, "three");
assertThat(selector.newDefault()).get().isEqualTo("two"); assertThat(selector.newDefaultBranch()).get().isEqualTo("two");
} }
@Test @Test
@@ -821,11 +882,11 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES); new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES);
selector.deleted("master"); selector.deleted(BRANCH, "master");
selector.deleted("one"); selector.deleted(BRANCH, "one");
selector.deleted("three"); selector.deleted(BRANCH, "three");
assertThat(selector.newDefault()).get().isEqualTo("two"); assertThat(selector.newDefaultBranch()).get().isEqualTo("two");
} }
@Test @Test
@@ -833,13 +894,13 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES); new GitMirrorCommand.DefaultBranchSelector("master", BRANCHES);
selector.deleted("master"); selector.deleted(BRANCH, "master");
selector.deleted("one"); selector.deleted(BRANCH, "one");
selector.deleted("two"); selector.deleted(BRANCH, "two");
selector.accepted("new"); selector.accepted(BRANCH, "new");
selector.deleted("three"); selector.deleted(BRANCH, "three");
assertThrows(IllegalStateException.class, selector::newDefault); assertThrows(IllegalStateException.class, selector::newDefaultBranch);
} }
@Test @Test
@@ -847,10 +908,10 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", emptyList()); new GitMirrorCommand.DefaultBranchSelector("master", emptyList());
selector.accepted("main"); selector.accepted(BRANCH, "main");
selector.deleted("master"); selector.deleted(BRANCH, "master");
assertThat(selector.newDefault()).get().isEqualTo("main"); assertThat(selector.newDefaultBranch()).get().isEqualTo("main");
} }
@Test @Test
@@ -858,9 +919,9 @@ public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
GitMirrorCommand.DefaultBranchSelector selector = GitMirrorCommand.DefaultBranchSelector selector =
new GitMirrorCommand.DefaultBranchSelector("master", emptyList()); new GitMirrorCommand.DefaultBranchSelector("master", emptyList());
selector.accepted("main"); selector.accepted(BRANCH, "main");
assertThat(selector.newDefault()).get().isEqualTo("main"); assertThat(selector.newDefaultBranch()).get().isEqualTo("main");
} }
} }