mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-01 19:15:52 +01:00
Feature/mirror (#1683)
Add mirror command and extension points. Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com> Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
@@ -37,6 +37,7 @@ dependencies {
|
||||
implementation "sonia.jgit:org.eclipse.jgit.gpg.bc:${jgitVersion}"
|
||||
implementation libraries.commonsCompress
|
||||
|
||||
testImplementation "sonia.jgit:org.eclipse.jgit.junit.http:${jgitVersion}"
|
||||
testImplementation libraries.shiroUnit
|
||||
testImplementation libraries.awaitility
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.protocolcommand.git;
|
||||
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import sonia.scm.repository.spi.GitMirrorCommand;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
final class MirrorRefFilter {
|
||||
|
||||
private MirrorRefFilter() {
|
||||
}
|
||||
|
||||
static Map<String, Ref> filterMirrors(Map<String, Ref> refs) {
|
||||
return refs.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !entry.getKey().startsWith(GitMirrorCommand.MIRROR_REF_PREFIX))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.protocolcommand.git;
|
||||
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
@@ -32,6 +32,8 @@ import sonia.scm.protocolcommand.RepositoryContext;
|
||||
public class ScmUploadPackFactory implements UploadPackFactory<RepositoryContext> {
|
||||
@Override
|
||||
public UploadPack create(RepositoryContext repositoryContext, Repository repository) {
|
||||
return new UploadPack(repository);
|
||||
UploadPack uploadPack = new UploadPack(repository);
|
||||
uploadPack.setRefFilter(MirrorRefFilter::filterMirrors);
|
||||
return uploadPack;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.protocolcommand.git;
|
||||
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.transport.UploadPack;
|
||||
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class ScmUploadPackFactoryForHttpServletRequest implements UploadPackFactory<HttpServletRequest> {
|
||||
@Override
|
||||
public UploadPack create(HttpServletRequest repositoryContext, Repository repository) {
|
||||
UploadPack uploadPack = new UploadPack(repository);
|
||||
uploadPack.setRefFilter(MirrorRefFilter::filterMirrors);
|
||||
return uploadPack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import sonia.scm.security.GPG;
|
||||
import sonia.scm.security.PublicKey;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
class GPGSignatureResolver {
|
||||
|
||||
private final GPG gpg;
|
||||
private final Map<String, PublicKey> additionalPublicKeys;
|
||||
|
||||
GPGSignatureResolver(GPG gpg, Iterable<PublicKey> additionalPublicKeys) {
|
||||
this.gpg = gpg;
|
||||
this.additionalPublicKeys = createKeyMap(additionalPublicKeys);
|
||||
}
|
||||
|
||||
private Map<String, PublicKey> createKeyMap(Iterable<PublicKey> additionalPublicKeys) {
|
||||
ImmutableMap.Builder<String, PublicKey> builder = ImmutableMap.builder();
|
||||
for (PublicKey key : additionalPublicKeys) {
|
||||
appendKey(builder, key);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void appendKey(ImmutableMap.Builder<String, PublicKey> builder, PublicKey key) {
|
||||
builder.put(key.getId(), key);
|
||||
for (String subkey : key.getSubkeys()) {
|
||||
builder.put(subkey, key);
|
||||
}
|
||||
}
|
||||
|
||||
String findPublicKeyId(byte[] signature) {
|
||||
return gpg.findPublicKeyId(signature);
|
||||
}
|
||||
|
||||
Optional<PublicKey> findPublicKey(String id) {
|
||||
PublicKey publicKey = additionalPublicKeys.get(id);
|
||||
if (publicKey != null) {
|
||||
return Optional.of(publicKey);
|
||||
}
|
||||
return gpg.findPublicKey(id);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.util.RawParseUtils;
|
||||
import sonia.scm.security.GPG;
|
||||
import sonia.scm.security.PublicKey;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
@@ -56,11 +55,11 @@ import java.util.Optional;
|
||||
*/
|
||||
public class GitChangesetConverter implements Closeable {
|
||||
|
||||
private final GPG gpg;
|
||||
private final GPGSignatureResolver gpg;
|
||||
private final Multimap<ObjectId, String> tags;
|
||||
private final TreeWalk treeWalk;
|
||||
|
||||
public GitChangesetConverter(GPG gpg, org.eclipse.jgit.lib.Repository repository, RevWalk revWalk) {
|
||||
GitChangesetConverter(GPGSignatureResolver gpg, org.eclipse.jgit.lib.Repository repository, RevWalk revWalk) {
|
||||
this.gpg = gpg;
|
||||
this.tags = GitUtil.createTagMap(repository, revWalk);
|
||||
this.treeWalk = new TreeWalk(repository);
|
||||
|
||||
@@ -27,8 +27,13 @@ package sonia.scm.repository;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import sonia.scm.security.GPG;
|
||||
import sonia.scm.security.PublicKey;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class GitChangesetConverterFactory {
|
||||
|
||||
@@ -40,11 +45,52 @@ public class GitChangesetConverterFactory {
|
||||
}
|
||||
|
||||
public GitChangesetConverter create(Repository repository) {
|
||||
return new GitChangesetConverter(gpg, repository, new RevWalk(repository));
|
||||
return builder(repository).create();
|
||||
}
|
||||
|
||||
public GitChangesetConverter create(Repository repository, RevWalk revWalk) {
|
||||
return new GitChangesetConverter(gpg, repository, revWalk);
|
||||
return builder(repository).withRevWalk(revWalk).create();
|
||||
}
|
||||
|
||||
public Builder builder(Repository repository) {
|
||||
return new Builder(gpg, repository);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final GPG gpg;
|
||||
private final Repository repository;
|
||||
private RevWalk revWalk;
|
||||
private final List<PublicKey> additionalPublicKeys = new ArrayList<>();
|
||||
|
||||
private Builder(GPG gpg, Repository repository) {
|
||||
this.gpg = gpg;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Builder withRevWalk(RevWalk revWalk) {
|
||||
this.revWalk = revWalk;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAdditionalPublicKeys(PublicKey... publicKeys) {
|
||||
additionalPublicKeys.addAll(Arrays.asList(publicKeys));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAdditionalPublicKeys(Collection<PublicKey> publicKeys) {
|
||||
additionalPublicKeys.addAll(publicKeys);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GitChangesetConverter create() {
|
||||
return new GitChangesetConverter(
|
||||
new GPGSignatureResolver(gpg, additionalPublicKeys),
|
||||
repository,
|
||||
revWalk != null ? revWalk : new RevWalk(repository)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.eclipse.jgit.transport.HttpTransport;
|
||||
import sonia.scm.web.ScmHttpConnectionFactory;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
@Extension
|
||||
public class GitHttpTransportRegistration implements ServletContextListener {
|
||||
|
||||
private final ScmHttpConnectionFactory connectionFactory;
|
||||
|
||||
@Inject
|
||||
public GitHttpTransportRegistration(ScmHttpConnectionFactory connectionFactory) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent servletContextEvent) {
|
||||
// Override default http connection factory to inject our own ssl context
|
||||
HttpTransport.setConnectionFactory(connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent servletContextEvent) {
|
||||
// Nothing to destroy
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import org.eclipse.jgit.revwalk.filter.RevFilter;
|
||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.transport.RefSpec;
|
||||
import org.eclipse.jgit.transport.TagOpt;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
import org.eclipse.jgit.util.LfsFactory;
|
||||
@@ -97,6 +98,7 @@ public final class GitUtil {
|
||||
* the logger for GitUtil
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(GitUtil.class);
|
||||
private static final String REF_SPEC = "refs/heads/*:refs/heads/*";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
@@ -691,4 +693,10 @@ public final class GitUtil {
|
||||
private static RefSpec createRefSpec(Repository repository) {
|
||||
return new RefSpec(String.format(REFSPEC, repository.getId()));
|
||||
}
|
||||
|
||||
public static FetchCommand createFetchCommandWithBranchAndTagUpdate(Git git) {
|
||||
return git.fetch()
|
||||
.setRefSpecs(new RefSpec(REF_SPEC))
|
||||
.setTagOpt(TagOpt.FETCH_TAGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.base.Strings;
|
||||
import org.eclipse.jgit.api.FetchCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevTag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.TrackingRefUpdate;
|
||||
import org.eclipse.jgit.transport.TransportHttp;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.api.MirrorCommandResult;
|
||||
import sonia.scm.repository.api.MirrorCommandResult.ResultType;
|
||||
import sonia.scm.repository.api.MirrorFilter;
|
||||
import sonia.scm.repository.api.MirrorFilter.Result;
|
||||
import sonia.scm.repository.api.Pkcs12ClientCertificateCredential;
|
||||
import sonia.scm.repository.api.UsernamePasswordCredential;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.lang.String.format;
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* Implementation of the mirror command for git. This implementation makes use of a special
|
||||
* "ref" called <code>mirror</code>. A synchronization works in principal in the following way:
|
||||
* <ol>
|
||||
* <li>The mirror reference is updated. This is done by calling the jgit equivalent of
|
||||
* <pre>git fetch -pf <source url> "refs/heads/*:refs/mirror/heads/*" "refs/tags/*:refs/mirror/tags/*"</pre>
|
||||
* </li>
|
||||
* <li>These updates are then presented to the filter. Here single updates can be rejected.
|
||||
* Such rejected updates have to be reverted in the mirror, too.
|
||||
* </li>
|
||||
* <li>Accepted ref updates are copied to the "normal" refs.</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class GitMirrorCommand extends AbstractGitCommand implements MirrorCommand {
|
||||
|
||||
public static final String MIRROR_REF_PREFIX = "refs/mirror/";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitMirrorCommand.class);
|
||||
|
||||
private final PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory;
|
||||
private final MirrorHttpConnectionProvider mirrorHttpConnectionProvider;
|
||||
private final GitChangesetConverterFactory converterFactory;
|
||||
private final GitTagConverter gitTagConverter;
|
||||
|
||||
@Inject
|
||||
GitMirrorCommand(GitContext context, PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory, MirrorHttpConnectionProvider mirrorHttpConnectionProvider, GitChangesetConverterFactory converterFactory, GitTagConverter gitTagConverter) {
|
||||
super(context);
|
||||
this.mirrorHttpConnectionProvider = mirrorHttpConnectionProvider;
|
||||
this.postReceiveRepositoryHookEventFactory = postReceiveRepositoryHookEventFactory;
|
||||
this.converterFactory = converterFactory;
|
||||
this.gitTagConverter = gitTagConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MirrorCommandResult mirror(MirrorCommandRequest mirrorCommandRequest) {
|
||||
return update(mirrorCommandRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MirrorCommandResult update(MirrorCommandRequest mirrorCommandRequest) {
|
||||
try (Repository repository = context.open(); Git git = Git.wrap(repository)) {
|
||||
return new Worker(mirrorCommandRequest, repository, git).update();
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "error during git fetch", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class Worker {
|
||||
|
||||
private final MirrorCommandRequest mirrorCommandRequest;
|
||||
private final List<String> mirrorLog = new ArrayList<>();
|
||||
private final Stopwatch stopwatch;
|
||||
|
||||
private final Repository repository;
|
||||
private final Git git;
|
||||
|
||||
private FetchResult fetchResult;
|
||||
private GitFilterContext filterContext;
|
||||
private MirrorFilter.Filter filter;
|
||||
|
||||
private ResultType result = OK;
|
||||
|
||||
private Worker(MirrorCommandRequest mirrorCommandRequest, Repository repository, Git git) {
|
||||
this.mirrorCommandRequest = mirrorCommandRequest;
|
||||
this.repository = repository;
|
||||
this.git = git;
|
||||
stopwatch = Stopwatch.createStarted();
|
||||
}
|
||||
|
||||
MirrorCommandResult update() {
|
||||
try {
|
||||
return doUpdate();
|
||||
} catch (GitAPIException e) {
|
||||
result = FAILED;
|
||||
mirrorLog.add("failed to synchronize: " + e.getMessage());
|
||||
return new MirrorCommandResult(FAILED, mirrorLog, stopwatch.stop().elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
private MirrorCommandResult doUpdate() throws GitAPIException {
|
||||
fetchResult = createFetchCommand().call();
|
||||
filterContext = new GitFilterContext();
|
||||
filter = mirrorCommandRequest.getFilter().getFilter(filterContext);
|
||||
|
||||
if (fetchResult.getTrackingRefUpdates().isEmpty()) {
|
||||
mirrorLog.add("No updates found");
|
||||
} else {
|
||||
handleBranches();
|
||||
handleTags();
|
||||
}
|
||||
|
||||
postReceiveRepositoryHookEventFactory.fireForFetch(git, fetchResult);
|
||||
return new MirrorCommandResult(result, mirrorLog, stopwatch.stop().elapsed());
|
||||
}
|
||||
|
||||
private void handleBranches() {
|
||||
LoggerWithHeader logger = new LoggerWithHeader("Branches:");
|
||||
doForEachRefStartingWith(MIRROR_REF_PREFIX + "heads", ref -> handleBranch(logger, ref));
|
||||
}
|
||||
|
||||
private void handleBranch(LoggerWithHeader logger, TrackingRefUpdate ref) {
|
||||
MirrorReferenceUpdateHandler refHandler = new MirrorReferenceUpdateHandler(logger, ref, "heads/", "branch");
|
||||
refHandler.handleRef(ref1 -> refHandler.testFilterForBranch());
|
||||
}
|
||||
|
||||
private void handleTags() {
|
||||
LoggerWithHeader logger = new LoggerWithHeader("Tags:");
|
||||
doForEachRefStartingWith(MIRROR_REF_PREFIX + "tags", ref -> handleTag(logger, ref));
|
||||
}
|
||||
|
||||
private void handleTag(LoggerWithHeader logger, TrackingRefUpdate ref) {
|
||||
MirrorReferenceUpdateHandler refHandler = new MirrorReferenceUpdateHandler(logger, ref, "tags/", "tag");
|
||||
refHandler.handleRef(ref1 -> refHandler.testFilterForTag());
|
||||
}
|
||||
|
||||
private class MirrorReferenceUpdateHandler {
|
||||
private final LoggerWithHeader logger;
|
||||
private final TrackingRefUpdate ref;
|
||||
private final String refType;
|
||||
private final String typeForLog;
|
||||
|
||||
public MirrorReferenceUpdateHandler(LoggerWithHeader logger, TrackingRefUpdate ref, String refType, String typeForLog) {
|
||||
this.logger = logger;
|
||||
this.ref = ref;
|
||||
this.refType = refType;
|
||||
this.typeForLog = typeForLog;
|
||||
}
|
||||
|
||||
private void handleRef(Function<TrackingRefUpdate, Result> filter) {
|
||||
Result filterResult = filter.apply(ref);
|
||||
try {
|
||||
String referenceName = ref.getLocalName().substring(MIRROR_REF_PREFIX.length() + refType.length());
|
||||
if (filterResult.isAccepted()) {
|
||||
handleAcceptedReference(referenceName);
|
||||
} else {
|
||||
handleRejectedRef(referenceName, filterResult);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleReferenceUpdateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Result testFilterForBranch() {
|
||||
try {
|
||||
return filter.acceptBranch(filterContext.getBranchUpdate(ref.getLocalName()));
|
||||
} catch (Exception e) {
|
||||
return handleExceptionFromFilter(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReferenceUpdateException(Exception e) {
|
||||
LOG.warn("got exception processing ref {} in repository {}", ref.getLocalName(), GitMirrorCommand.this.repository, e);
|
||||
mirrorLog.add(format("got error processing reference %s: %s", ref.getLocalName(), e.getMessage()));
|
||||
mirrorLog.add("mirror may be damaged");
|
||||
}
|
||||
|
||||
private void handleRejectedRef(String referenceName, Result filterResult) throws IOException {
|
||||
result = REJECTED_UPDATES;
|
||||
LOG.trace("{} ref rejected in {}: {}", typeForLog, GitMirrorCommand.this.repository, ref.getLocalName());
|
||||
if (ref.getResult() == NEW) {
|
||||
deleteReference(ref.getLocalName());
|
||||
} else {
|
||||
updateReference(ref.getLocalName(), ref.getOldObjectId());
|
||||
}
|
||||
logger.logChange(ref, referenceName, filterResult.getRejectReason().orElse("rejected due to filter"));
|
||||
}
|
||||
|
||||
private void handleAcceptedReference(String referenceName) throws IOException {
|
||||
String targetRef = "refs/" + refType + referenceName;
|
||||
if (isDeletedReference(ref)) {
|
||||
LOG.trace("deleting {} ref in {}: {}", typeForLog, GitMirrorCommand.this.repository, targetRef);
|
||||
deleteReference(targetRef);
|
||||
logger.logChange(ref, referenceName, "deleted");
|
||||
} else {
|
||||
LOG.trace("updating {} ref in {}: {}", typeForLog, GitMirrorCommand.this.repository, targetRef);
|
||||
updateReference(targetRef, ref.getNewObjectId());
|
||||
logger.logChange(ref, referenceName, getUpdateType(ref));
|
||||
}
|
||||
}
|
||||
|
||||
private Result testFilterForTag() {
|
||||
try {
|
||||
return filter.acceptTag(filterContext.getTagUpdate(ref.getLocalName()));
|
||||
} catch (Exception e) {
|
||||
return handleExceptionFromFilter(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Result handleExceptionFromFilter(Exception e) {
|
||||
LOG.warn("got exception from filter for ref {} in repository {}", ref.getLocalName(), GitMirrorCommand.this.repository, e);
|
||||
mirrorLog.add("! got error checking filter for update: " + e.getMessage());
|
||||
return Result.reject("exception in filter");
|
||||
}
|
||||
|
||||
private void deleteReference(String targetRef) throws IOException {
|
||||
RefUpdate deleteUpdate = repository.getRefDatabase().newUpdate(targetRef, true);
|
||||
deleteUpdate.setForceUpdate(true);
|
||||
deleteUpdate.delete();
|
||||
}
|
||||
|
||||
private boolean isDeletedReference(TrackingRefUpdate ref) {
|
||||
return ref.asReceiveCommand().getType() == ReceiveCommand.Type.DELETE;
|
||||
}
|
||||
|
||||
private void updateReference(String reference, ObjectId objectId) throws IOException {
|
||||
LOG.trace("updating ref in {}: {} -> {}", GitMirrorCommand.this.repository, reference, objectId);
|
||||
RefUpdate refUpdate = repository.getRefDatabase().newUpdate(reference, true);
|
||||
refUpdate.setNewObjectId(objectId);
|
||||
refUpdate.forceUpdate();
|
||||
}
|
||||
|
||||
private String getUpdateType(TrackingRefUpdate trackingRefUpdate) {
|
||||
return trackingRefUpdate.getResult().name().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggerWithHeader {
|
||||
private final String header;
|
||||
private boolean headerWritten = false;
|
||||
|
||||
private LoggerWithHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
void logChange(TrackingRefUpdate ref, String branchName, String type) {
|
||||
logLine(
|
||||
format("- %s..%s %s (%s)",
|
||||
ref.getOldObjectId().abbreviate(9).name(),
|
||||
ref.getNewObjectId().abbreviate(9).name(),
|
||||
branchName,
|
||||
type
|
||||
));
|
||||
}
|
||||
|
||||
void logLine(String line) {
|
||||
if (!headerWritten) {
|
||||
headerWritten = true;
|
||||
mirrorLog.add(header);
|
||||
}
|
||||
mirrorLog.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void doForEachRefStartingWith(String prefix, RefUpdateConsumer refUpdateConsumer) {
|
||||
fetchResult.getTrackingRefUpdates()
|
||||
.stream()
|
||||
.filter(ref -> ref.getLocalName().startsWith(prefix))
|
||||
.forEach(ref -> {
|
||||
try {
|
||||
refUpdateConsumer.accept(ref);
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(GitMirrorCommand.this.repository, "error updating mirror references", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private FetchCommand createFetchCommand() {
|
||||
FetchCommand fetchCommand = Git.wrap(repository).fetch()
|
||||
.setRefSpecs("refs/heads/*:" + MIRROR_REF_PREFIX + "heads/*", "refs/tags/*:" + MIRROR_REF_PREFIX + "tags/*")
|
||||
.setForceUpdate(true)
|
||||
.setRemoveDeletedRefs(true)
|
||||
.setRemote(mirrorCommandRequest.getSourceUrl());
|
||||
|
||||
mirrorCommandRequest.getCredential(Pkcs12ClientCertificateCredential.class)
|
||||
.ifPresent(c -> fetchCommand.setTransportConfigCallback(transport -> {
|
||||
if (transport instanceof TransportHttp) {
|
||||
TransportHttp transportHttp = (TransportHttp) transport;
|
||||
transportHttp.setHttpConnectionFactory(mirrorHttpConnectionProvider.createHttpConnectionFactory(c, mirrorLog));
|
||||
}
|
||||
}));
|
||||
mirrorCommandRequest.getCredential(UsernamePasswordCredential.class)
|
||||
.ifPresent(c -> fetchCommand
|
||||
.setCredentialsProvider(
|
||||
new UsernamePasswordCredentialsProvider(
|
||||
Strings.nullToEmpty(c.username()),
|
||||
Strings.nullToEmpty(new String(c.password()))
|
||||
))
|
||||
);
|
||||
|
||||
return fetchCommand;
|
||||
}
|
||||
|
||||
private class GitFilterContext implements MirrorFilter.FilterContext {
|
||||
|
||||
private final Map<String, MirrorFilter.BranchUpdate> branchUpdates;
|
||||
private final Map<String, MirrorFilter.TagUpdate> tagUpdates;
|
||||
|
||||
public GitFilterContext() {
|
||||
Map<String, MirrorFilter.BranchUpdate> extractedBranchUpdates = new HashMap<>();
|
||||
Map<String, MirrorFilter.TagUpdate> extractedTagUpdates = new HashMap<>();
|
||||
|
||||
fetchResult.getTrackingRefUpdates().forEach(refUpdate -> {
|
||||
if (refUpdate.getLocalName().startsWith(MIRROR_REF_PREFIX + "heads")) {
|
||||
extractedBranchUpdates.put(refUpdate.getLocalName(), new GitBranchUpdate(refUpdate));
|
||||
}
|
||||
if (refUpdate.getLocalName().startsWith(MIRROR_REF_PREFIX + "tags")) {
|
||||
extractedTagUpdates.put(refUpdate.getLocalName(), new GitTagUpdate(refUpdate));
|
||||
}
|
||||
});
|
||||
|
||||
this.branchUpdates = unmodifiableMap(extractedBranchUpdates);
|
||||
this.tagUpdates = unmodifiableMap(extractedTagUpdates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MirrorFilter.BranchUpdate> getBranchUpdates() {
|
||||
return branchUpdates.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MirrorFilter.TagUpdate> getTagUpdates() {
|
||||
return tagUpdates.values();
|
||||
}
|
||||
|
||||
MirrorFilter.BranchUpdate getBranchUpdate(String ref) {
|
||||
return branchUpdates.get(ref);
|
||||
}
|
||||
|
||||
MirrorFilter.TagUpdate getTagUpdate(String ref) {
|
||||
return tagUpdates.get(ref);
|
||||
}
|
||||
}
|
||||
|
||||
private class GitBranchUpdate implements MirrorFilter.BranchUpdate {
|
||||
|
||||
private final TrackingRefUpdate refUpdate;
|
||||
|
||||
private final String branchName;
|
||||
|
||||
private Changeset changeset;
|
||||
|
||||
public GitBranchUpdate(TrackingRefUpdate refUpdate) {
|
||||
this.refUpdate = refUpdate;
|
||||
this.branchName = refUpdate.getLocalName().substring(MIRROR_REF_PREFIX.length() + "heads/".length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBranchName() {
|
||||
return branchName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Changeset> getChangeset() {
|
||||
if (isOfTypeOrEmpty(getUpdateType(), MirrorFilter.UpdateType.DELETE)) {
|
||||
return empty();
|
||||
}
|
||||
if (changeset == null) {
|
||||
changeset = computeChangeset();
|
||||
}
|
||||
return of(changeset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getNewRevision() {
|
||||
if (isOfTypeOrEmpty(getUpdateType(), MirrorFilter.UpdateType.DELETE)) {
|
||||
return empty();
|
||||
}
|
||||
return of(refUpdate.getNewObjectId().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getOldRevision() {
|
||||
if (isOfTypeOrEmpty(getUpdateType(), MirrorFilter.UpdateType.CREATE)) {
|
||||
return empty();
|
||||
}
|
||||
return of(refUpdate.getOldObjectId().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MirrorFilter.UpdateType> getUpdateType() {
|
||||
return getUpdateTypeFor(refUpdate.asReceiveCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForcedUpdate() {
|
||||
return refUpdate.getResult() == RefUpdate.Result.FORCED;
|
||||
}
|
||||
|
||||
private Changeset computeChangeset() {
|
||||
try (RevWalk revWalk = new RevWalk(repository); GitChangesetConverter gitChangesetConverter = converter(revWalk)) {
|
||||
try {
|
||||
RevCommit revCommit = revWalk.parseCommit(refUpdate.getNewObjectId());
|
||||
return gitChangesetConverter.createChangeset(revCommit, refUpdate.getLocalName());
|
||||
} catch (Exception e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "got exception while validating branch", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GitChangesetConverter converter(RevWalk revWalk) {
|
||||
return converterFactory.builder(repository)
|
||||
.withRevWalk(revWalk)
|
||||
.withAdditionalPublicKeys(mirrorCommandRequest.getPublicKeys())
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
private class GitTagUpdate implements MirrorFilter.TagUpdate {
|
||||
|
||||
private final TrackingRefUpdate refUpdate;
|
||||
|
||||
private final String tagName;
|
||||
|
||||
private Tag tag;
|
||||
|
||||
public GitTagUpdate(TrackingRefUpdate refUpdate) {
|
||||
this.refUpdate = refUpdate;
|
||||
this.tagName = refUpdate.getLocalName().substring(MIRROR_REF_PREFIX.length() + "tags/".length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTagName() {
|
||||
return tagName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Tag> getTag() {
|
||||
if (isOfTypeOrEmpty(getUpdateType(), MirrorFilter.UpdateType.DELETE)) {
|
||||
return empty();
|
||||
}
|
||||
if (tag == null) {
|
||||
tag = computeTag();
|
||||
}
|
||||
return of(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getNewRevision() {
|
||||
return getTag().map(Tag::getRevision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getOldRevision() {
|
||||
if (isOfTypeOrEmpty(getUpdateType(), MirrorFilter.UpdateType.CREATE)) {
|
||||
return empty();
|
||||
}
|
||||
return of(refUpdate.getOldObjectId().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MirrorFilter.UpdateType> getUpdateType() {
|
||||
return getUpdateTypeFor(refUpdate.asReceiveCommand());
|
||||
}
|
||||
|
||||
private Tag computeTag() {
|
||||
try (RevWalk revWalk = new RevWalk(repository)) {
|
||||
try {
|
||||
RevObject revObject = revWalk.parseAny(refUpdate.getNewObjectId());
|
||||
if (revObject.getType() == Constants.OBJ_TAG) {
|
||||
RevTag revTag = revWalk.parseTag(revObject.getId());
|
||||
return gitTagConverter.buildTag(revTag, revWalk);
|
||||
} else if (revObject.getType() == Constants.OBJ_COMMIT) {
|
||||
Ref ref = repository.getRefDatabase().findRef(refUpdate.getLocalName());
|
||||
Tag t = gitTagConverter.buildTag(repository, revWalk, ref);
|
||||
return new Tag(tagName, t.getRevision(), t.getDate().orElse(null), t.getDeletable());
|
||||
} else {
|
||||
throw new InternalRepositoryException(context.getRepository(), "invalid object type for tag");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "got exception while validating tag", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOfTypeOrEmpty(Optional<MirrorFilter.UpdateType> updateType, MirrorFilter.UpdateType type) {
|
||||
return !updateType.isPresent() || updateType.get() == type;
|
||||
}
|
||||
|
||||
private Optional<MirrorFilter.UpdateType> getUpdateTypeFor(ReceiveCommand receiveCommand) {
|
||||
switch (receiveCommand.getType()) {
|
||||
case UPDATE:
|
||||
case UPDATE_NONFASTFORWARD:
|
||||
return of(MirrorFilter.UpdateType.UPDATE);
|
||||
case CREATE:
|
||||
return of(MirrorFilter.UpdateType.CREATE);
|
||||
case DELETE:
|
||||
return of(MirrorFilter.UpdateType.DELETE);
|
||||
default:
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface RefUpdateConsumer {
|
||||
void accept(TrackingRefUpdate refUpdate) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -32,26 +32,20 @@ import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.transport.RefSpec;
|
||||
import org.eclipse.jgit.transport.TagOpt;
|
||||
import org.eclipse.jgit.transport.TrackingRefUpdate;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.api.ImportFailedException;
|
||||
import sonia.scm.repository.api.PullResponse;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Sebastian Sdorra
|
||||
@@ -59,19 +53,15 @@ import java.util.stream.Collectors;
|
||||
public class GitPullCommand extends AbstractGitPushOrPullCommand
|
||||
implements PullCommand {
|
||||
|
||||
private static final String REF_SPEC = "refs/heads/*:refs/heads/*";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitPullCommand.class);
|
||||
private final ScmEventBus eventBus;
|
||||
private final GitRepositoryHookEventFactory eventFactory;
|
||||
private final PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory;
|
||||
|
||||
@Inject
|
||||
public GitPullCommand(GitRepositoryHandler handler,
|
||||
GitContext context,
|
||||
ScmEventBus eventBus,
|
||||
GitRepositoryHookEventFactory eventFactory) {
|
||||
PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory) {
|
||||
super(handler, context);
|
||||
this.eventBus = eventBus;
|
||||
this.eventFactory = eventFactory;
|
||||
this.postReceiveRepositoryHookEventFactory = postReceiveRepositoryHookEventFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,8 +148,8 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
||||
|
||||
org.eclipse.jgit.lib.Repository source = null;
|
||||
|
||||
try {
|
||||
source = Git.open(sourceDirectory).getRepository();
|
||||
try (Git git = Git.open(sourceDirectory)) {
|
||||
source = git.getRepository();
|
||||
response = new PullResponse(push(source, getRemoteUrl(targetDirectory)));
|
||||
} finally {
|
||||
GitUtil.close(source);
|
||||
@@ -177,16 +167,14 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
||||
FetchResult result;
|
||||
try {
|
||||
//J-
|
||||
result = git.fetch()
|
||||
result = GitUtil.createFetchCommandWithBranchAndTagUpdate(git)
|
||||
.setCredentialsProvider(
|
||||
new UsernamePasswordCredentialsProvider(
|
||||
Strings.nullToEmpty(request.getUsername()),
|
||||
Strings.nullToEmpty(request.getUsername()),
|
||||
Strings.nullToEmpty(request.getPassword())
|
||||
)
|
||||
)
|
||||
.setRefSpecs(new RefSpec(REF_SPEC))
|
||||
.setRemote(request.getRemoteUrl().toExternalForm())
|
||||
.setTagOpt(TagOpt.FETCH_TAGS)
|
||||
.call();
|
||||
//J+
|
||||
|
||||
@@ -206,31 +194,6 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
||||
}
|
||||
|
||||
private void firePostReceiveRepositoryHookEvent(Git git, FetchResult result) {
|
||||
try {
|
||||
List<String> branches = getBranchesFromFetchResult(result);
|
||||
List<Tag> tags = getTagsFromFetchResult(result);
|
||||
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(context.getRepository(), git);
|
||||
eventBus.post(eventFactory.createEvent(context, branches, tags, changesetResolver));
|
||||
} catch (IOException e) {
|
||||
throw new ImportFailedException(
|
||||
ContextEntry.ContextBuilder.entity(context.getRepository()).build(),
|
||||
"Could not fire post receive repository hook event after unbundle",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Tag> getTagsFromFetchResult(FetchResult result) {
|
||||
return result.getAdvertisedRefs().stream()
|
||||
.filter(r -> r.getName().startsWith("refs/tags/"))
|
||||
.map(r -> new Tag(r.getName().substring("refs/tags/".length()), r.getObjectId().getName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> getBranchesFromFetchResult(FetchResult result) {
|
||||
return result.getAdvertisedRefs().stream()
|
||||
.filter(r -> r.getName().startsWith("refs/heads/"))
|
||||
.map(r -> r.getLeaf().getName().substring("refs/heads/".length()))
|
||||
.collect(Collectors.toList());
|
||||
postReceiveRepositoryHookEventFactory.fireForFetch(git, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
Command.MERGE,
|
||||
Command.MODIFY,
|
||||
Command.BUNDLE,
|
||||
Command.UNBUNDLE
|
||||
Command.UNBUNDLE,
|
||||
Command.MIRROR
|
||||
);
|
||||
|
||||
protected static final Set<Feature> FEATURES = EnumSet.of(Feature.INCOMING_REVISION);
|
||||
@@ -171,6 +172,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
return commandInjector.getInstance(GitUnbundleCommand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MirrorCommand getMirrorCommand() {
|
||||
return commandInjector.getInstance(GitMirrorCommand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Command> getSupportedCommands() {
|
||||
return COMMANDS;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevTag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.security.GPG;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
|
||||
class GitTagConverter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitTagConverter.class);
|
||||
|
||||
private final GPG gpg;
|
||||
|
||||
@Inject
|
||||
GitTagConverter(GPG gpg) {
|
||||
this.gpg = gpg;
|
||||
}
|
||||
|
||||
public Tag buildTag(RevTag revTag, RevWalk revWalk) {
|
||||
Tag tag = null;
|
||||
try {
|
||||
RevCommit revCommit = revWalk.parseCommit(revTag.getObject().getId());
|
||||
tag = new Tag(revTag.getTagName(), revCommit.getId().name(), revTag.getTaggerIdent().getWhen().getTime());
|
||||
GitUtil.getTagSignature(revTag, gpg, revWalk).ifPresent(tag::addSignature);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("could not get commit for tag", ex);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
public Tag buildTag(Repository repository, RevWalk revWalk, Ref ref) {
|
||||
Tag tag = null;
|
||||
|
||||
try {
|
||||
RevCommit revCommit = GitUtil.getCommit(repository, revWalk, ref);
|
||||
if (revCommit != null) {
|
||||
String name = GitUtil.getTagName(ref);
|
||||
tag = new Tag(name, revCommit.getId().name(), GitUtil.getTagTime(revWalk, ref.getObjectId()));
|
||||
RevObject revObject = revWalk.parseAny(ref.getObjectId());
|
||||
if (revObject.getType() == Constants.OBJ_TAG) {
|
||||
RevTag revTag = (RevTag) revObject;
|
||||
GitUtil.getTagSignature(revTag, gpg, revWalk)
|
||||
.ifPresent(tag::addSignature);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOG.error("could not get commit for tag", ex);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,28 +26,19 @@ package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevTag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.security.GPG;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -55,7 +46,7 @@ import java.util.List;
|
||||
*/
|
||||
public class GitTagsCommand extends AbstractGitCommand implements TagsCommand {
|
||||
|
||||
private final GPG gpg;
|
||||
private final GitTagConverter gitTagConverter;
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
@@ -63,108 +54,23 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand {
|
||||
* @param context
|
||||
*/
|
||||
@Inject
|
||||
public GitTagsCommand(GitContext context, GPG gpp) {
|
||||
public GitTagsCommand(GitContext context, GitTagConverter gitTagConverter) {
|
||||
super(context);
|
||||
this.gpg = gpp;
|
||||
this.gitTagConverter = gitTagConverter;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public List<Tag> getTags() throws IOException {
|
||||
List<Tag> tags;
|
||||
|
||||
RevWalk revWalk = null;
|
||||
|
||||
try (Git git = new Git(open())) {
|
||||
revWalk = new RevWalk(git.getRepository());
|
||||
|
||||
try (Git git = new Git(open()); RevWalk revWalk = new RevWalk(git.getRepository())) {
|
||||
List<Ref> tagList = git.tagList().call();
|
||||
|
||||
tags = Lists.transform(tagList,
|
||||
new TransformFunction(git.getRepository(), revWalk, gpg));
|
||||
return tagList.stream()
|
||||
.map(ref -> gitTagConverter.buildTag(git.getRepository(), revWalk, ref))
|
||||
.collect(toList());
|
||||
} catch (GitAPIException ex) {
|
||||
throw new InternalRepositoryException(repository, "could not read tags from repository", ex);
|
||||
} finally {
|
||||
GitUtil.release(revWalk);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
//~--- inner classes --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class description
|
||||
*
|
||||
* @author Enter your name here...
|
||||
* @version Enter version here..., 12/07/06
|
||||
*/
|
||||
private static class TransformFunction implements Function<Ref, Tag> {
|
||||
|
||||
/**
|
||||
* the logger for TransformFuntion
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(TransformFunction.class);
|
||||
|
||||
//~--- constructors -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
* @param repository
|
||||
* @param revWalk
|
||||
*/
|
||||
public TransformFunction(Repository repository,
|
||||
RevWalk revWalk,
|
||||
GPG gpg) {
|
||||
this.repository = repository;
|
||||
this.revWalk = revWalk;
|
||||
this.gpg = gpg;
|
||||
}
|
||||
|
||||
//~--- methods ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
* @param ref
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Tag apply(Ref ref) {
|
||||
Tag tag = null;
|
||||
|
||||
try {
|
||||
RevCommit revCommit = GitUtil.getCommit(repository, revWalk, ref);
|
||||
if (revCommit != null) {
|
||||
String name = GitUtil.getTagName(ref);
|
||||
tag = new Tag(name, revCommit.getId().name(), GitUtil.getTagTime(revWalk, ref.getObjectId()));
|
||||
RevObject revObject = revWalk.parseAny(ref.getObjectId());
|
||||
if (revObject.getType() == Constants.OBJ_TAG) {
|
||||
RevTag revTag = (RevTag) revObject;
|
||||
GitUtil.getTagSignature(revTag, gpg, revWalk)
|
||||
.ifPresent(tag::addSignature);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.error("could not get commit for tag", ex);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
//~--- fields -------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Field description
|
||||
*/
|
||||
private final org.eclipse.jgit.lib.Repository repository;
|
||||
|
||||
/**
|
||||
* Field description
|
||||
*/
|
||||
private final RevWalk revWalk;
|
||||
private final GPG gpg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.transport.http.HttpConnectionFactory2;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.api.Pkcs12ClientCertificateCredential;
|
||||
import sonia.scm.web.ScmHttpConnectionFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.List;
|
||||
|
||||
class MirrorHttpConnectionProvider {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MirrorHttpConnectionProvider.class);
|
||||
|
||||
private final Provider<TrustManager> trustManagerProvider;
|
||||
|
||||
@Inject
|
||||
public MirrorHttpConnectionProvider(Provider<TrustManager> trustManagerProvider) {
|
||||
this.trustManagerProvider = trustManagerProvider;
|
||||
}
|
||||
|
||||
public HttpConnectionFactory2 createHttpConnectionFactory(Pkcs12ClientCertificateCredential credential, List<String> log) {
|
||||
return new ScmHttpConnectionFactory(trustManagerProvider, createKeyManagers(credential, log));
|
||||
}
|
||||
|
||||
private KeyManager[] createKeyManagers(Pkcs12ClientCertificateCredential credential, List<String> log) {
|
||||
try {
|
||||
KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
|
||||
pkcs12.load(new ByteArrayInputStream(credential.getCertificate()), credential.getPassword());
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
keyManagerFactory.init(pkcs12, credential.getPassword());
|
||||
log.add("added pkcs12 certificate");
|
||||
return keyManagerFactory.getKeyManagers();
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
LOG.info("could not create key store from pkcs12 credential", e);
|
||||
log.add("failed to add pkcs12 certificate: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new KeyManager[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.RepositoryHookEvent;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.WrappedRepositoryHookEvent;
|
||||
import sonia.scm.repository.api.ImportFailedException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class PostReceiveRepositoryHookEventFactory {
|
||||
|
||||
private final ScmEventBus eventBus;
|
||||
private final GitRepositoryHookEventFactory eventFactory;
|
||||
private final GitContext context;
|
||||
|
||||
@Inject
|
||||
PostReceiveRepositoryHookEventFactory(ScmEventBus eventBus, GitRepositoryHookEventFactory eventFactory, GitContext context) {
|
||||
this.eventBus = eventBus;
|
||||
this.eventFactory = eventFactory;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void fireForFetch(Git git, FetchResult result) {
|
||||
PostReceiveRepositoryHookEvent event;
|
||||
try {
|
||||
List<String> branches = getBranchesFromFetchResult(result);
|
||||
List<Tag> tags = getTagsFromFetchResult(result);
|
||||
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(context.getRepository(), git);
|
||||
event = new PostReceiveRepositoryHookEvent(WrappedRepositoryHookEvent.wrap(eventFactory.createEvent(context, branches, tags, changesetResolver)));
|
||||
} catch (IOException e) {
|
||||
throw new ImportFailedException(
|
||||
ContextEntry.ContextBuilder.entity(context.getRepository()).build(),
|
||||
"Could not fire post receive repository hook event after fetch",
|
||||
e
|
||||
);
|
||||
}
|
||||
eventBus.post(event);
|
||||
}
|
||||
|
||||
private List<Tag> getTagsFromFetchResult(FetchResult result) {
|
||||
return result.getAdvertisedRefs().stream()
|
||||
.filter(r -> r.getName().startsWith("refs/tags/"))
|
||||
.map(r -> new Tag(r.getName().substring("refs/tags/".length()), r.getObjectId().getName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> getBranchesFromFetchResult(FetchResult result) {
|
||||
return result.getAdvertisedRefs().stream()
|
||||
.filter(r -> r.getName().startsWith("refs/heads/"))
|
||||
.map(r -> r.getLeaf().getName().substring("refs/heads/".length()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -38,16 +38,13 @@ import sonia.scm.repository.spi.SimpleGitWorkingCopyFactory;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Extension
|
||||
public class GitServletModule extends ServletModule
|
||||
{
|
||||
public class GitServletModule extends ServletModule {
|
||||
|
||||
@Override
|
||||
protected void configureServlets()
|
||||
{
|
||||
protected void configureServlets() {
|
||||
bind(GitRepositoryViewer.class);
|
||||
bind(GitRepositoryResolver.class);
|
||||
bind(GitReceivePackFactory.class);
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.google.inject.Singleton;
|
||||
import org.eclipse.jgit.http.server.GitServlet;
|
||||
import org.eclipse.jgit.lfs.lib.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import sonia.scm.protocolcommand.git.ScmUploadPackFactoryForHttpServletRequest;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryRequestListenerUtil;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
@@ -71,6 +72,7 @@ public class ScmGitServlet extends GitServlet implements ScmProviderHttpServlet
|
||||
@Inject
|
||||
public ScmGitServlet(GitRepositoryResolver repositoryResolver,
|
||||
GitReceivePackFactory receivePackFactory,
|
||||
ScmUploadPackFactoryForHttpServletRequest scmUploadPackFactory,
|
||||
GitRepositoryViewer repositoryViewer,
|
||||
RepositoryRequestListenerUtil repositoryRequestListenerUtil,
|
||||
LfsServletFactory lfsServletFactory)
|
||||
@@ -81,6 +83,7 @@ public class ScmGitServlet extends GitServlet implements ScmProviderHttpServlet
|
||||
|
||||
setRepositoryResolver(repositoryResolver);
|
||||
setReceivePackFactory(receivePackFactory);
|
||||
setUploadPackFactory(scmUploadPackFactory);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.eclipse.jgit.transport.http.HttpConnection;
|
||||
import org.eclipse.jgit.transport.http.JDKHttpConnection;
|
||||
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
|
||||
import org.eclipse.jgit.transport.http.NoCheckX509TrustManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
public class ScmHttpConnectionFactory extends JDKHttpConnectionFactory {
|
||||
|
||||
private final Provider<TrustManager> trustManagerProvider;
|
||||
private final KeyManager[] keyManagers;
|
||||
|
||||
@Inject
|
||||
public ScmHttpConnectionFactory(Provider<TrustManager> trustManagerProvider) {
|
||||
this(trustManagerProvider, null);
|
||||
}
|
||||
|
||||
public ScmHttpConnectionFactory(Provider<TrustManager> trustManagerProvider, KeyManager[] keyManagers) {
|
||||
this.trustManagerProvider = trustManagerProvider;
|
||||
this.keyManagers = keyManagers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GitSession newSession() {
|
||||
return new ScmConnectionSession(trustManagerProvider.get(), keyManagers);
|
||||
}
|
||||
|
||||
private static class ScmConnectionSession implements GitSession {
|
||||
|
||||
private final TrustManager trustManager;
|
||||
private final KeyManager[] keyManagers;
|
||||
|
||||
private ScmConnectionSession(TrustManager trustManager, KeyManager[] keyManagers) {
|
||||
this.trustManager = trustManager;
|
||||
this.keyManagers = keyManagers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("java:S5527")
|
||||
public JDKHttpConnection configure(HttpConnection connection,
|
||||
boolean sslVerify) throws GeneralSecurityException {
|
||||
if (!(connection instanceof JDKHttpConnection)) {
|
||||
throw new IllegalArgumentException(MessageFormat.format(
|
||||
JGitText.get().httpWrongConnectionType,
|
||||
JDKHttpConnection.class.getName(),
|
||||
connection.getClass().getName()));
|
||||
}
|
||||
JDKHttpConnection conn = (JDKHttpConnection) connection;
|
||||
String scheme = conn.getURL().getProtocol();
|
||||
if ("https".equals(scheme) && sslVerify) { //$NON-NLS-1$
|
||||
// sslVerify == true: use the JDK defaults
|
||||
conn.configure(keyManagers, new TrustManager[]{trustManager}, null);
|
||||
} else if ("https".equals(scheme)) {
|
||||
conn.configure(keyManagers, new TrustManager[]{new NoCheckX509TrustManager()}, null);
|
||||
conn.setHostnameVerifier((name, value) -> true);
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.protocolcommand.git;
|
||||
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class MirrorRefFilterTest {
|
||||
|
||||
@Test
|
||||
void shouldRemoveMirrorRefs() {
|
||||
Map<String, Ref> refs = new HashMap<>();
|
||||
Ref master = mock(Ref.class);
|
||||
Ref mirror = mock(Ref.class);
|
||||
refs.put("refs/heads/master", master);
|
||||
refs.put("refs/mirror/some/other", mirror);
|
||||
|
||||
Map<String, Ref> filteredRefs = MirrorRefFilter.filterMirrors(refs);
|
||||
|
||||
assertThat(filteredRefs).containsOnly(entry("refs/heads/master", master));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.security.GPG;
|
||||
import sonia.scm.security.PublicKey;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GPGSignatureResolverTest {
|
||||
|
||||
@Mock
|
||||
private GPG gpg;
|
||||
|
||||
@Test
|
||||
void shouldDelegateFindPublicKeyId() {
|
||||
GPGSignatureResolver signatureResolver = new GPGSignatureResolver(gpg, emptySet());
|
||||
|
||||
byte[] signature = new byte[]{4, 2};
|
||||
when(gpg.findPublicKeyId(signature)).thenReturn("0x42");
|
||||
|
||||
assertThat(signatureResolver.findPublicKeyId(signature)).isEqualTo("0x42");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveStoredGpgKey() {
|
||||
GPGSignatureResolver signatureResolver = new GPGSignatureResolver(gpg, emptySet());
|
||||
|
||||
PublicKey publicKey = createPublicKey("0x42");
|
||||
when(gpg.findPublicKey("0x42")).thenReturn(Optional.of(publicKey));
|
||||
|
||||
Optional<PublicKey> resolverPublicKey = signatureResolver.findPublicKey("0x42");
|
||||
assertThat(resolverPublicKey).contains(publicKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveKeyFormList() {
|
||||
PublicKey publicKey = createPublicKey("0x21");
|
||||
GPGSignatureResolver signatureResolver = new GPGSignatureResolver(gpg, singleton(publicKey));
|
||||
|
||||
Optional<PublicKey> resolverPublicKey = signatureResolver.findPublicKey("0x21");
|
||||
assertThat(resolverPublicKey).contains(publicKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveSubkeyFormList() {
|
||||
PublicKey publicKey = createPublicKey("0x21", "0x42");
|
||||
GPGSignatureResolver signatureResolver = new GPGSignatureResolver(gpg, singleton(publicKey));
|
||||
|
||||
Optional<PublicKey> resolverPublicKey = signatureResolver.findPublicKey("0x42");
|
||||
assertThat(resolverPublicKey).contains(publicKey);
|
||||
}
|
||||
|
||||
private PublicKey createPublicKey(String id, String... subkeys) {
|
||||
PublicKey publicKey = mock(PublicKey.class);
|
||||
lenient().when(publicKey.getId()).thenReturn(id);
|
||||
lenient().when(publicKey.getSubkeys()).thenReturn(ImmutableSet.copyOf(subkeys));
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,12 +93,13 @@ public class GitIncomingCommandTest
|
||||
|
||||
commit(outgoing, "added a");
|
||||
|
||||
GitContext context = new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig());
|
||||
PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory = new PostReceiveRepositoryHookEventFactory(eventBus, eventFactory, context);
|
||||
|
||||
GitPullCommand pull = new GitPullCommand(
|
||||
handler,
|
||||
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
|
||||
eventBus,
|
||||
eventFactory
|
||||
);
|
||||
context,
|
||||
postReceiveRepositoryHookEventFactory);
|
||||
PullCommandRequest req = new PullCommandRequest();
|
||||
req.setRemoteRepository(outgoingRepository);
|
||||
pull.pull(req);
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.junit.http.AppServer;
|
||||
import org.eclipse.jgit.junit.http.SimpleHttpServer;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.api.MirrorCommandResult;
|
||||
import sonia.scm.repository.api.MirrorFilter;
|
||||
import sonia.scm.repository.api.SimpleUsernamePasswordCredential;
|
||||
import sonia.scm.security.GPG;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
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;
|
||||
|
||||
public class GitMirrorCommandTest extends AbstractGitCommandTestBase {
|
||||
|
||||
public static final Consumer<MirrorCommandRequest> ACCEPT_ALL = r -> {
|
||||
};
|
||||
public static final Consumer<MirrorCommandRequest> REJECT_ALL = r -> r.setFilter(new DenyAllMirrorFilter());
|
||||
private final PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory = mock(PostReceiveRepositoryHookEventFactory.class);
|
||||
private final MirrorHttpConnectionProvider mirrorHttpConnectionProvider = mock(MirrorHttpConnectionProvider.class);
|
||||
private final GPG gpg = mock(GPG.class);
|
||||
private final GitChangesetConverterFactory gitChangesetConverterFactory = new GitChangesetConverterFactory(gpg);
|
||||
private final GitTagConverter gitTagConverter = new GitTagConverter(gpg);
|
||||
|
||||
private File clone;
|
||||
private GitMirrorCommand command;
|
||||
|
||||
@Before
|
||||
public void bendContextToNewRepository() throws IOException, GitAPIException {
|
||||
clone = tempFolder.newFolder();
|
||||
Git.init().setBare(true).setDirectory(clone).call();
|
||||
|
||||
GitContext emptyContext = createMirrorContext(clone);
|
||||
command = new GitMirrorCommand(emptyContext, postReceiveRepositoryHookEventFactory, mirrorHttpConnectionProvider, gitChangesetConverterFactory, gitTagConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateInitialMirror() throws IOException, GitAPIException {
|
||||
MirrorCommandResult result = callMirrorCommand();
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).contains("Branches:")
|
||||
.contains("- 000000000..fcd0ef183 master (new)")
|
||||
.contains("- 000000000..3f76a12f0 test-branch (new)")
|
||||
.contains("Tags:")
|
||||
.contains("- 000000000..86a6645ec test-tag (new)");
|
||||
|
||||
try (Git createdMirror = Git.open(clone)) {
|
||||
assertThat(createdMirror.branchList().call()).isNotEmpty();
|
||||
assertThat(createdMirror.tagList().call()).isNotEmpty();
|
||||
}
|
||||
|
||||
verify(postReceiveRepositoryHookEventFactory).fireForFetch(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateEmptyLogWhenNoChangesFound() {
|
||||
callMirrorCommand();
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly("No updates found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateMirrorWithNewBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setName("added-branch").call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Branches:",
|
||||
"- 000000000..fcd0ef183 added-branch (new)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> addedBranch = findBranch(updatedMirror, "added-branch");
|
||||
assertThat(addedBranch).isPresent();
|
||||
}
|
||||
|
||||
// event should be thrown two times, once for the initial clone, and once for the update
|
||||
verify(postReceiveRepositoryHookEventFactory, times(2)).fireForFetch(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateMirrorWithForcedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setStartPoint("9e93d8631675a89615fac56b09209686146ff3c0").setName("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Branches:",
|
||||
"- 3f76a12f0..9e93d8631 test-branch (forced)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> updatedBranch = findBranch(updatedMirror, "test-branch");
|
||||
assertThat(updatedBranch).hasValueSatisfying(ref -> assertThat(ref.getObjectId().getName()).isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateMirrorWithDeletedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchDelete().setBranchNames("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Branches:",
|
||||
"- 3f76a12f0..000000000 test-branch (deleted)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> deletedBranch = findBranch(updatedMirror, "test-branch");
|
||||
assertThat(deletedBranch).isNotPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateMirrorWithNewTag() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
RevObject revObject = getRevObject(existingClone, "9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
existingClone.tag().setName("added-tag").setAnnotated(false).setObjectId(revObject).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 000000000..9e93d8631 added-tag (new)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> addedTag = findTag(updatedMirror, "added-tag");
|
||||
assertThat(addedTag).hasValueSatisfying(ref -> assertThat(ref.getObjectId().getName()).isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0"));
|
||||
}
|
||||
|
||||
// event should be thrown two times, once for the initial clone, and once for the update
|
||||
verify(postReceiveRepositoryHookEventFactory, times(2)).fireForFetch(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateMirrorWithChangedTag() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
RevObject revObject = getRevObject(existingClone, "9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
existingClone.tag().setName("test-tag").setObjectId(revObject).setForceUpdate(true).setAnnotated(false).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 86a6645ec..9e93d8631 test-tag (forced)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> updatedTag = findTag(updatedMirror, "test-tag");
|
||||
assertThat(updatedTag).hasValueSatisfying(ref -> assertThat(ref.getObjectId().getName()).isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateMirrorWithDeletedTag() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.tagDelete().setTags("test-tag").call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(ACCEPT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 86a6645ec..000000000 test-tag (deleted)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> deletedTag = findTag(updatedMirror, "test-tag");
|
||||
assertThat(deletedTag).isNotPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevertRejectedAddedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setName("added-branch").call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(REJECT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Branches:",
|
||||
"- 000000000..fcd0ef183 added-branch (rejected due to filter)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> rejectedBranch = findBranch(updatedMirror, "added-branch");
|
||||
assertThat(rejectedBranch).isNotPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevertRejectedChangedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setStartPoint("9e93d8631675a89615fac56b09209686146ff3c0").setName("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(REJECT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Branches:",
|
||||
"- 3f76a12f0..9e93d8631 test-branch (rejected due to filter)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> rejectedBranch = findBranch(updatedMirror, "test-branch");
|
||||
assertThat(rejectedBranch).get().extracting("objectId.name").hasToString("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevertRejectedDeletedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchDelete().setBranchNames("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(REJECT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Branches:",
|
||||
"- 3f76a12f0..000000000 test-branch (rejected due to filter)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> rejectedBranch = findBranch(updatedMirror, "test-branch");
|
||||
assertThat(rejectedBranch).get().extracting("objectId.name").hasToString("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevertRejectedNewTag() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
RevObject revObject = getRevObject(existingClone, "9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
existingClone.tag().setName("added-tag").setAnnotated(false).setObjectId(revObject).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(REJECT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 000000000..9e93d8631 added-tag (rejected due to filter)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> rejectedTag = findTag(updatedMirror, "added-tag");
|
||||
assertThat(rejectedTag).isNotPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevertRejectedChangedTag() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
RevObject revObject = getRevObject(existingClone, "9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
existingClone.tag().setName("test-tag").setObjectId(revObject).setForceUpdate(true).setAnnotated(false).call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(REJECT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 86a6645ec..9e93d8631 test-tag (rejected due to filter)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> rejectedTag = findTag(updatedMirror, "test-tag");
|
||||
assertThat(rejectedTag).hasValueSatisfying(ref -> assertThat(ref.getObjectId().getName()).isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevertRejectedDeletedTag() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.tagDelete().setTags("test-tag").call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(REJECT_ALL);
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 86a6645ec..000000000 test-tag (rejected due to filter)"
|
||||
);
|
||||
|
||||
try (Git updatedMirror = Git.open(clone)) {
|
||||
Optional<Ref> rejectedTag = findTag(updatedMirror, "test-tag");
|
||||
assertThat(rejectedTag).hasValueSatisfying(ref -> assertThat(ref.getObjectId().getName()).isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectWithCustomMessage() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.tagDelete().setTags("test-tag").call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(r -> r.setFilter(new DenyAllWithReasonMirrorFilter("thou shalt not pass")));
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"Tags:",
|
||||
"- 86a6645ec..000000000 test-tag (thou shalt not pass)"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLogExceptionsFromFilter() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.tagDelete().setTags("test-tag").call();
|
||||
}
|
||||
|
||||
MirrorCommandResult result = callUpdate(r -> r.setFilter(new ErroneousMirrorFilterThrowingExceptions()));
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(REJECTED_UPDATES);
|
||||
assertThat(result.getLog()).containsExactly(
|
||||
"! got error checking filter for update: this tag creates an exception",
|
||||
"Tags:",
|
||||
"- 86a6645ec..000000000 test-tag (exception in filter)"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMarkForcedBranchUpdate() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setStartPoint("9e93d8631675a89615fac56b09209686146ff3c0").setName("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
InvocationCheck filterInvokedCheck = mock(InvocationCheck.class);
|
||||
|
||||
callUpdate(r -> r.setFilter(new MirrorFilter() {
|
||||
@Override
|
||||
public Filter getFilter(FilterContext context) {
|
||||
filterInvokedCheck.invoked();
|
||||
context.getBranchUpdates().forEach(branchUpdate -> {
|
||||
assertThat(branchUpdate.getBranchName()).isEqualTo("test-branch");
|
||||
assertThat(branchUpdate.isForcedUpdate()).isTrue();
|
||||
});
|
||||
return MirrorFilter.super.getFilter(context);
|
||||
}
|
||||
}));
|
||||
|
||||
verify(filterInvokedCheck).invoked();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotMarkFastForwardBranchUpdateAsForced() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setStartPoint("d81ad6c63d7e2162308d69637b339dedd1d9201c").setName("master").setForce(true).call();
|
||||
}
|
||||
|
||||
InvocationCheck filterInvokedCheck = mock(InvocationCheck.class);
|
||||
|
||||
callUpdate(r -> r.setFilter(new MirrorFilter() {
|
||||
@Override
|
||||
public Filter getFilter(FilterContext context) {
|
||||
filterInvokedCheck.invoked();
|
||||
context.getBranchUpdates().forEach(branchUpdate -> {
|
||||
assertThat(branchUpdate.getBranchName()).isEqualTo("master");
|
||||
assertThat(branchUpdate.isForcedUpdate()).isFalse();
|
||||
});
|
||||
return MirrorFilter.super.getFilter(context);
|
||||
}
|
||||
}));
|
||||
|
||||
verify(filterInvokedCheck).invoked();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseCredentials() throws Exception {
|
||||
SimpleHttpServer simpleHttpServer = new SimpleHttpServer(Git.open(repositoryDirectory).getRepository());
|
||||
simpleHttpServer.start();
|
||||
|
||||
try {
|
||||
MirrorCommandResult result =
|
||||
callMirrorCommand(
|
||||
simpleHttpServer.getUri().toASCIIString(),
|
||||
createCredential(AppServer.username, AppServer.password));
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(OK);
|
||||
assertThat(result.getLog()).contains("Branches:")
|
||||
.contains("- 000000000..fcd0ef183 master (new)")
|
||||
.contains("- 000000000..3f76a12f0 test-branch (new)")
|
||||
.contains("Tags:")
|
||||
.contains("- 000000000..86a6645ec test-tag (new)");
|
||||
} finally {
|
||||
simpleHttpServer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailWithIncorrectCredentials() throws Exception {
|
||||
SimpleHttpServer simpleHttpServer = new SimpleHttpServer(Git.open(repositoryDirectory).getRepository());
|
||||
simpleHttpServer.start();
|
||||
|
||||
try {
|
||||
MirrorCommandResult result =
|
||||
callMirrorCommand(
|
||||
simpleHttpServer.getUri().toASCIIString(),
|
||||
createCredential("wrong", "credentials"));
|
||||
|
||||
assertThat(result.getResult()).isEqualTo(FAILED);
|
||||
|
||||
verify(postReceiveRepositoryHookEventFactory, never()).fireForFetch(any(), any());
|
||||
} finally {
|
||||
simpleHttpServer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForCreatedTags() throws IOException, GitAPIException {
|
||||
try (Git updatedSource = Git.open(repositoryDirectory)) {
|
||||
RevObject revObject = getRevObject(updatedSource, "9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
updatedSource.tag().setAnnotated(true).setName("42").setMessage("annotated tag").setObjectId(revObject).call();
|
||||
}
|
||||
|
||||
List<MirrorFilter.TagUpdate> collectedTagUpdates = callMirrorAndCollectUpdates().tagUpdates;
|
||||
|
||||
assertThat(collectedTagUpdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.CREATE);
|
||||
assertThat(update.getTagName()).isEqualTo("42");
|
||||
assertThat(update.getNewRevision()).get().isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
assertThat(update.getOldRevision()).isEmpty();
|
||||
assertThat(update.getTag()).get().extracting("name").isEqualTo("42");
|
||||
assertThat(update.getTag()).get().extracting("revision").isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
})
|
||||
.anySatisfy(tagUpdate -> {
|
||||
assertThat(tagUpdate.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.CREATE);
|
||||
assertThat(tagUpdate.getTagName()).isEqualTo("test-tag");
|
||||
assertThat(tagUpdate.getNewRevision()).get().isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
|
||||
assertThat(tagUpdate.getOldRevision()).isEmpty();
|
||||
assertThat(tagUpdate.getTag()).get().extracting("name").isEqualTo("test-tag");
|
||||
assertThat(tagUpdate.getTag()).get().extracting("revision").isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForDeletedTags() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git updatedSource = Git.open(repositoryDirectory)) {
|
||||
updatedSource.tagDelete().setTags("test-tag").call();
|
||||
}
|
||||
|
||||
List<MirrorFilter.TagUpdate> collectedTagUPdates = callMirrorAndCollectUpdates().tagUpdates;
|
||||
|
||||
assertThat(collectedTagUPdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.DELETE);
|
||||
assertThat(update.getTagName()).isEqualTo("test-tag");
|
||||
assertThat(update.getNewRevision()).isEmpty();
|
||||
assertThat(update.getOldRevision()).get().isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
|
||||
assertThat(update.getTag()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForUpdatedTags() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git updatedSource = Git.open(repositoryDirectory)) {
|
||||
RevObject revObject = getRevObject(updatedSource, "9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
updatedSource.tag().setName("test-tag").setObjectId(revObject).setForceUpdate(true).call();
|
||||
}
|
||||
|
||||
List<MirrorFilter.TagUpdate> collectedTagUpdates = callMirrorAndCollectUpdates().tagUpdates;
|
||||
|
||||
assertThat(collectedTagUpdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.UPDATE);
|
||||
assertThat(update.getTagName()).isEqualTo("test-tag");
|
||||
assertThat(update.getNewRevision()).get().isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
assertThat(update.getOldRevision()).get().isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
|
||||
assertThat(update.getTag()).get().extracting("name").isEqualTo("test-tag");
|
||||
assertThat(update.getTag()).get().extracting("revision").isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForNewBranch() {
|
||||
List<MirrorFilter.BranchUpdate> collectedBranchUpdates = callMirrorAndCollectUpdates().branchUpdates;
|
||||
|
||||
assertThat(collectedBranchUpdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.CREATE);
|
||||
assertThat(update.getBranchName()).isEqualTo("test-branch");
|
||||
assertThat(update.getNewRevision()).get().isEqualTo("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
assertThat(update.getOldRevision()).isEmpty();
|
||||
assertThat(update.getChangeset()).get().extracting("id").isEqualTo("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForForcedUpdatedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setStartPoint("9e93d8631675a89615fac56b09209686146ff3c0").setName("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
List<MirrorFilter.BranchUpdate> collectedBranchUpdates = callMirrorAndCollectUpdates().branchUpdates;
|
||||
|
||||
assertThat(collectedBranchUpdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.UPDATE);
|
||||
assertThat(update.isForcedUpdate()).isTrue();
|
||||
assertThat(update.getBranchName()).isEqualTo("test-branch");
|
||||
assertThat(update.getNewRevision()).get().isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
assertThat(update.getOldRevision()).get().isEqualTo("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
assertThat(update.getChangeset()).get().extracting("id").isEqualTo("9e93d8631675a89615fac56b09209686146ff3c0");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForFastForwardUpdatedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git existingClone = Git.open(repositoryDirectory)) {
|
||||
existingClone.branchCreate().setStartPoint("a8495c0335a13e6e432df90b3727fa91943189a7").setName("master").setForce(true).call();
|
||||
}
|
||||
|
||||
List<MirrorFilter.BranchUpdate> collectedBranchUpdates = callMirrorAndCollectUpdates().branchUpdates;
|
||||
|
||||
assertThat(collectedBranchUpdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getUpdateType()).get().isEqualTo(MirrorFilter.UpdateType.UPDATE);
|
||||
assertThat(update.isForcedUpdate()).isFalse();
|
||||
assertThat(update.getBranchName()).isEqualTo("master");
|
||||
assertThat(update.getNewRevision()).get().isEqualTo("a8495c0335a13e6e432df90b3727fa91943189a7");
|
||||
assertThat(update.getOldRevision()).get().isEqualTo("fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
|
||||
assertThat(update.getChangeset()).get().extracting("id").isEqualTo("a8495c0335a13e6e432df90b3727fa91943189a7");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUpdateObjectForDeletedBranch() throws IOException, GitAPIException {
|
||||
callMirrorCommand();
|
||||
|
||||
try (Git updatedSource = Git.open(repositoryDirectory)) {
|
||||
updatedSource.branchDelete().setBranchNames("test-branch").setForce(true).call();
|
||||
}
|
||||
|
||||
List<MirrorFilter.BranchUpdate> collectedBranchUpdates = callMirrorAndCollectUpdates().branchUpdates;
|
||||
|
||||
assertThat(collectedBranchUpdates)
|
||||
.anySatisfy(update -> {
|
||||
assertThat(update.getBranchName()).isEqualTo("test-branch");
|
||||
assertThat(update.getNewRevision()).isEmpty();
|
||||
assertThat(update.getOldRevision()).get().isEqualTo("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
assertThat(update.getChangeset()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
private Updates callMirrorAndCollectUpdates() {
|
||||
Updates updates = new Updates();
|
||||
|
||||
MirrorCommandRequest request = new MirrorCommandRequest();
|
||||
request.setSourceUrl(repositoryDirectory.getAbsolutePath());
|
||||
request.setFilter(new MirrorFilter() {
|
||||
@Override
|
||||
public Filter getFilter(FilterContext context) {
|
||||
return new Filter() {
|
||||
@Override
|
||||
public Result acceptTag(TagUpdate tagUpdate) {
|
||||
updates.tagUpdates.add(tagUpdate);
|
||||
return Result.accept();
|
||||
}
|
||||
@Override
|
||||
public Result acceptBranch(BranchUpdate branchUpdate) {
|
||||
updates.branchUpdates.add(branchUpdate);
|
||||
return Result.accept();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
command.mirror(request);
|
||||
return updates;
|
||||
}
|
||||
|
||||
private class Updates {
|
||||
private final List<MirrorFilter.BranchUpdate> branchUpdates = new ArrayList<>();
|
||||
private final List<MirrorFilter.TagUpdate> tagUpdates = new ArrayList<>();
|
||||
}
|
||||
|
||||
private RevObject getRevObject(Git existingClone, String revision) throws IOException {
|
||||
RevWalk walk = new RevWalk(existingClone.getRepository());
|
||||
ObjectId id = existingClone.getRepository().resolve(revision);
|
||||
return walk.parseAny(id);
|
||||
}
|
||||
|
||||
private MirrorCommandResult callUpdate(Consumer<MirrorCommandRequest> requestModifier) {
|
||||
MirrorCommandRequest request = new MirrorCommandRequest();
|
||||
request.setSourceUrl(repositoryDirectory.getAbsolutePath());
|
||||
requestModifier.accept(request);
|
||||
return command.update(request);
|
||||
}
|
||||
|
||||
private Optional<Ref> findBranch(Git git, String branchName) throws GitAPIException {
|
||||
return git.branchList().call().stream().filter(ref -> ref.getName().equals("refs/heads/" + branchName)).findFirst();
|
||||
}
|
||||
|
||||
private Optional<Ref> findTag(Git git, String tagName) throws GitAPIException {
|
||||
return git.tagList().call().stream().filter(ref -> ref.getName().equals("refs/tags/" + tagName)).findFirst();
|
||||
}
|
||||
|
||||
private MirrorCommandResult callMirrorCommand() {
|
||||
return callMirrorCommand(repositoryDirectory.getAbsolutePath(), c -> {
|
||||
});
|
||||
}
|
||||
|
||||
private MirrorCommandResult callMirrorCommand(String source, Consumer<MirrorCommandRequest> requestConsumer) {
|
||||
MirrorCommandRequest request = new MirrorCommandRequest();
|
||||
request.setSourceUrl(source);
|
||||
requestConsumer.accept(request);
|
||||
return command.mirror(request);
|
||||
}
|
||||
|
||||
private Consumer<MirrorCommandRequest> createCredential(String wrong, String credentials) {
|
||||
return request -> request.setCredentials(singletonList(new SimpleUsernamePasswordCredential(wrong, credentials.toCharArray())));
|
||||
}
|
||||
|
||||
private GitContext createMirrorContext(File clone) {
|
||||
return new GitContext(clone, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()), new GitConfig());
|
||||
}
|
||||
|
||||
private static class DenyAllMirrorFilter implements MirrorFilter {
|
||||
@Override
|
||||
public Filter getFilter(FilterContext context) {
|
||||
return new Filter() {
|
||||
@Override
|
||||
public Result acceptBranch(BranchUpdate branch) {
|
||||
return Result.reject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result acceptTag(TagUpdate tag) {
|
||||
return Result.reject();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class DenyAllWithReasonMirrorFilter implements MirrorFilter {
|
||||
|
||||
private final String reason;
|
||||
|
||||
private DenyAllWithReasonMirrorFilter(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter(FilterContext context) {
|
||||
return new Filter() {
|
||||
@Override
|
||||
public Result acceptBranch(BranchUpdate branch) {
|
||||
return Result.reject(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result acceptTag(TagUpdate tag) {
|
||||
return Result.reject(reason);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class ErroneousMirrorFilterThrowingExceptions implements MirrorFilter {
|
||||
|
||||
@Override
|
||||
public Filter getFilter(FilterContext context) {
|
||||
return new Filter() {
|
||||
@Override
|
||||
public Result acceptBranch(BranchUpdate branch) {
|
||||
throw new RuntimeException("this branch creates an exception");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result acceptTag(TagUpdate tag) {
|
||||
throw new RuntimeException("this tag creates an exception");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private interface InvocationCheck {
|
||||
void invoked();
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,13 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitTestHelper;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
||||
|
||||
@@ -112,12 +111,12 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
||||
PushCommandRequest request = new PushCommandRequest();
|
||||
request.setRemoteRepository(incomingRepository);
|
||||
cmd.push(request);
|
||||
GitContext context = new GitContext(incomingDirectory, incomingRepository, null, new GitConfig());
|
||||
PostReceiveRepositoryHookEventFactory postReceiveRepositoryHookEventFactory = new PostReceiveRepositoryHookEventFactory(eventBus, eventFactory, context);
|
||||
GitPullCommand pullCommand = new GitPullCommand(
|
||||
handler,
|
||||
new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()),
|
||||
eventBus,
|
||||
eventFactory
|
||||
);
|
||||
context,
|
||||
postReceiveRepositoryHookEventFactory);
|
||||
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||
pullRequest.setRemoteRepository(incomingRepository);
|
||||
pullCommand.pull(pullRequest);
|
||||
|
||||
@@ -154,7 +154,7 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
private List<Tag> readTags(GitContext context) throws IOException {
|
||||
return new GitTagsCommand(context, gpg).getTags();
|
||||
return new GitTagsCommand(context, new GitTagConverter(gpg)).getTags();
|
||||
}
|
||||
|
||||
private Optional<Tag> findTag(GitContext context, String name) throws IOException {
|
||||
|
||||
@@ -55,7 +55,7 @@ public class GitTagsCommandTest extends AbstractGitCommandTestBase {
|
||||
@Test
|
||||
public void shouldGetDatesCorrectly() throws IOException {
|
||||
final GitContext gitContext = createContext();
|
||||
final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext, gpg);
|
||||
final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext, new GitTagConverter(gpg));
|
||||
final List<Tag> tags = tagsCommand.getTags();
|
||||
assertThat(tags).hasSize(3);
|
||||
|
||||
@@ -94,7 +94,7 @@ public class GitTagsCommandTest extends AbstractGitCommandTestBase {
|
||||
when(publicKey.verify(signedContent.getBytes(), signature.getBytes())).thenReturn(true);
|
||||
|
||||
final GitContext gitContext = createContext();
|
||||
final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext, gpg);
|
||||
final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext, new GitTagConverter(gpg));
|
||||
final List<Tag> tags = tagsCommand.getTags();
|
||||
|
||||
assertThat(tags).hasSize(3);
|
||||
|
||||
Reference in New Issue
Block a user