mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 03:25:56 +01:00
Post receive hook after import (#1544)
Fire post receive repository hook event after pull from remote and after unbundle (Git, HG and SVN) Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
2
gradle/changelog/url_import_post_receive_hook.yaml
Normal file
2
gradle/changelog/url_import_post_receive_hook.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- type: changed
|
||||||
|
description: Fire post receive repository hook event after the repository import has been finished. ([#1544](https://github.com/scm-manager/scm-manager/pull/1544))
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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.revwalk.RevCommit;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.GitChangesetConverter;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
class GitConvertingChangesetIterable implements Iterable<Changeset> {
|
||||||
|
|
||||||
|
private final Iterable<RevCommit> commitIterable;
|
||||||
|
private final GitChangesetConverter converter;
|
||||||
|
|
||||||
|
GitConvertingChangesetIterable(Iterable<RevCommit> commitIterable,
|
||||||
|
GitChangesetConverter converter) {
|
||||||
|
this.commitIterable = commitIterable;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Changeset> iterator() {
|
||||||
|
return new ConvertingChangesetIterator(commitIterable.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConvertingChangesetIterator implements Iterator<Changeset> {
|
||||||
|
|
||||||
|
private final Iterator<RevCommit> commitIterator;
|
||||||
|
|
||||||
|
private ConvertingChangesetIterator(Iterator<RevCommit> commitIterator) {
|
||||||
|
this.commitIterator = commitIterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return commitIterator.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Changeset next() {
|
||||||
|
return converter.createChangeset(commitIterator.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.collect.ImmutableSet;
|
||||||
|
import sonia.scm.repository.GitChangesetConverter;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.HookBranchProvider;
|
||||||
|
import sonia.scm.repository.api.HookFeature;
|
||||||
|
import sonia.scm.repository.api.HookTagProvider;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class GitImportHookContextProvider extends HookContextProvider {
|
||||||
|
private final GitChangesetConverter converter;
|
||||||
|
private final List<Tag> newTags;
|
||||||
|
private final GitLazyChangesetResolver changesetResolver;
|
||||||
|
private final List<String> newBranches;
|
||||||
|
|
||||||
|
GitImportHookContextProvider(GitChangesetConverter converter,
|
||||||
|
List<String> newBranches,
|
||||||
|
List<Tag> newTags,
|
||||||
|
GitLazyChangesetResolver changesetResolver) {
|
||||||
|
this.converter = converter;
|
||||||
|
this.newTags = newTags;
|
||||||
|
this.changesetResolver = changesetResolver;
|
||||||
|
this.newBranches = newBranches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<HookFeature> getSupportedFeatures() {
|
||||||
|
return ImmutableSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookTagProvider getTagProvider() {
|
||||||
|
return new HookTagProvider() {
|
||||||
|
@Override
|
||||||
|
public List<Tag> getCreatedTags() {
|
||||||
|
return newTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Tag> getDeletedTags() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookBranchProvider getBranchProvider() {
|
||||||
|
return new HookBranchProvider() {
|
||||||
|
@Override
|
||||||
|
public List<String> getCreatedOrModified() {
|
||||||
|
return newBranches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getDeletedOrClosed() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookChangesetProvider getChangesetProvider() {
|
||||||
|
GitConvertingChangesetIterable changesets = new GitConvertingChangesetIterable(changesetResolver.call(), converter);
|
||||||
|
return r -> new HookChangesetResponse(changesets);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.revwalk.RevCommit;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
|
|
||||||
|
class GitLazyChangesetResolver implements Callable<Iterable<RevCommit>> {
|
||||||
|
private final Repository repository;
|
||||||
|
private final Git git;
|
||||||
|
|
||||||
|
public GitLazyChangesetResolver(Repository repository, Git git) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.git = git;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<RevCommit> call() {
|
||||||
|
try {
|
||||||
|
return git.log().all().call();
|
||||||
|
} catch (IOException | GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(
|
||||||
|
entity(repository).build(),
|
||||||
|
"Could not resolve changesets for imported repository",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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 sonia.scm.repository.GitChangesetConverter;
|
||||||
|
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.HookContext;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static sonia.scm.repository.RepositoryHookType.POST_RECEIVE;
|
||||||
|
|
||||||
|
class GitPostReceiveRepositoryHookEventFactory {
|
||||||
|
|
||||||
|
private final HookContextFactory hookContextFactory;
|
||||||
|
private final GitChangesetConverterFactory changesetConverterFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GitPostReceiveRepositoryHookEventFactory(HookContextFactory hookContextFactory, GitChangesetConverterFactory changesetConverterFactory) {
|
||||||
|
this.hookContextFactory = hookContextFactory;
|
||||||
|
this.changesetConverterFactory = changesetConverterFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
PostReceiveRepositoryHookEvent createEvent(GitContext gitContext,
|
||||||
|
List<String> branches,
|
||||||
|
List<Tag> tags,
|
||||||
|
GitLazyChangesetResolver changesetResolver
|
||||||
|
) throws IOException {
|
||||||
|
GitChangesetConverter converter = changesetConverterFactory.create(gitContext.open());
|
||||||
|
GitImportHookContextProvider contextProvider = new GitImportHookContextProvider(converter, branches, tags, changesetResolver);
|
||||||
|
HookContext context = hookContextFactory.createContext(contextProvider, gitContext.getRepository());
|
||||||
|
RepositoryHookEvent repositoryHookEvent = new RepositoryHookEvent(context, gitContext.getRepository(), POST_RECEIVE);
|
||||||
|
return new PostReceiveRepositoryHookEvent(repositoryHookEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -41,15 +39,19 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
import sonia.scm.repository.api.ImportFailedException;
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
import sonia.scm.repository.api.PullResponse;
|
import sonia.scm.repository.api.PullResponse;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -57,39 +59,21 @@ import java.io.IOException;
|
|||||||
public class GitPullCommand extends AbstractGitPushOrPullCommand
|
public class GitPullCommand extends AbstractGitPushOrPullCommand
|
||||||
implements PullCommand {
|
implements PullCommand {
|
||||||
|
|
||||||
/**
|
|
||||||
* Field description
|
|
||||||
*/
|
|
||||||
private static final String REF_SPEC = "refs/heads/*:refs/heads/*";
|
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 GitPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
/**
|
|
||||||
* Field description
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(GitPullCommand.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* @param context
|
|
||||||
*/
|
|
||||||
@Inject
|
@Inject
|
||||||
public GitPullCommand(GitRepositoryHandler handler, GitContext context) {
|
public GitPullCommand(GitRepositoryHandler handler,
|
||||||
|
GitContext context,
|
||||||
|
ScmEventBus eventBus,
|
||||||
|
GitPostReceiveRepositoryHookEventFactory eventFactory) {
|
||||||
super(handler, context);
|
super(handler, context);
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.eventFactory = eventFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public PullResponse pull(PullCommandRequest request)
|
public PullResponse pull(PullCommandRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -108,24 +92,17 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PullResponse convert(Git git, FetchResult fetch) {
|
private PullResponse convert(Git git, FetchResult fetch) {
|
||||||
long counter = 0l;
|
long counter = 0;
|
||||||
|
|
||||||
for (TrackingRefUpdate tru : fetch.getTrackingRefUpdates()) {
|
for (TrackingRefUpdate tru : fetch.getTrackingRefUpdates()) {
|
||||||
counter += count(git, tru);
|
counter += count(git, tru);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("received {} changesets by pull", counter);
|
LOG.debug("received {} changesets by pull", counter);
|
||||||
|
|
||||||
return new PullResponse(counter);
|
return new PullResponse(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
* @param git
|
|
||||||
* @param tru
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private long count(Git git, TrackingRefUpdate tru) {
|
private long count(Git git, TrackingRefUpdate tru) {
|
||||||
long counter = 0;
|
long counter = 0;
|
||||||
|
|
||||||
@@ -151,12 +128,12 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
|||||||
counter += Iterables.size(commits);
|
counter += Iterables.size(commits);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("counting {} commits for ref update {}", counter, tru);
|
LOG.trace("counting {} commits for ref update {}", counter, tru);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("could not count pushed/pulled changesets", ex);
|
LOG.error("could not count pushed/pulled changesets", ex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("do not count non branch ref update {}", tru);
|
LOG.debug("do not count non branch ref update {}", tru);
|
||||||
}
|
}
|
||||||
|
|
||||||
return counter;
|
return counter;
|
||||||
@@ -174,10 +151,10 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
|||||||
Preconditions.checkArgument(sourceDirectory.exists(),
|
Preconditions.checkArgument(sourceDirectory.exists(),
|
||||||
"target repository directory does not exists");
|
"target repository directory does not exists");
|
||||||
|
|
||||||
logger.debug("pull changes from {} to {}",
|
LOG.debug("pull changes from {} to {}",
|
||||||
sourceDirectory.getAbsolutePath(), repository.getId());
|
sourceDirectory.getAbsolutePath(), repository.getId());
|
||||||
|
|
||||||
PullResponse response = null;
|
PullResponse response;
|
||||||
|
|
||||||
org.eclipse.jgit.lib.Repository source = null;
|
org.eclipse.jgit.lib.Repository source = null;
|
||||||
|
|
||||||
@@ -193,14 +170,14 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
|||||||
|
|
||||||
private PullResponse pullFromUrl(PullCommandRequest request)
|
private PullResponse pullFromUrl(PullCommandRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
logger.debug("pull changes from {} to {}", request.getRemoteUrl(), repository);
|
LOG.debug("pull changes from {} to {}", request.getRemoteUrl(), repository);
|
||||||
|
|
||||||
PullResponse response;
|
PullResponse response;
|
||||||
Git git = Git.wrap(open());
|
Git git = Git.wrap(open());
|
||||||
|
FetchResult result;
|
||||||
try {
|
try {
|
||||||
//J-
|
//J-
|
||||||
FetchResult result = git.fetch()
|
result = git.fetch()
|
||||||
.setCredentialsProvider(
|
.setCredentialsProvider(
|
||||||
new UsernamePasswordCredentialsProvider(
|
new UsernamePasswordCredentialsProvider(
|
||||||
Strings.nullToEmpty(request.getUsername()),
|
Strings.nullToEmpty(request.getUsername()),
|
||||||
@@ -223,6 +200,37 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firePostReceiveRepositoryHookEvent(git, result);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UnbundleCommand getUnbundleCommand() {
|
public UnbundleCommand getUnbundleCommand() {
|
||||||
return new GitUnbundleCommand(context);
|
return commandInjector.getInstance(GitUnbundleCommand.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,13 +24,23 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
import sonia.scm.repository.api.UnbundleResponse;
|
import sonia.scm.repository.api.UnbundleResponse;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static sonia.scm.util.Archives.extractTar;
|
import static sonia.scm.util.Archives.extractTar;
|
||||||
@@ -39,13 +49,21 @@ public class GitUnbundleCommand extends AbstractGitCommand implements UnbundleCo
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GitUnbundleCommand.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GitUnbundleCommand.class);
|
||||||
|
|
||||||
GitUnbundleCommand(GitContext context) {
|
private final ScmEventBus eventBus;
|
||||||
|
private final GitPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
GitUnbundleCommand(GitContext context,
|
||||||
|
ScmEventBus eventBus,
|
||||||
|
GitPostReceiveRepositoryHookEventFactory eventFactory) {
|
||||||
super(context);
|
super(context);
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.eventFactory = eventFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UnbundleResponse unbundle(UnbundleCommandRequest request) throws IOException {
|
public UnbundleResponse unbundle(UnbundleCommandRequest request) throws IOException {
|
||||||
ByteSource archive = checkNotNull(request.getArchive(),"archive is required");
|
ByteSource archive = checkNotNull(request.getArchive(), "archive is required");
|
||||||
Path repositoryDir = context.getDirectory().toPath();
|
Path repositoryDir = context.getDirectory().toPath();
|
||||||
LOG.debug("archive repository {} to {}", repositoryDir, archive);
|
LOG.debug("archive repository {} to {}", repositoryDir, archive);
|
||||||
|
|
||||||
@@ -54,9 +72,39 @@ public class GitUnbundleCommand extends AbstractGitCommand implements UnbundleCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
unbundleRepositoryFromRequest(request, repositoryDir);
|
unbundleRepositoryFromRequest(request, repositoryDir);
|
||||||
|
firePostReceiveRepositoryHookEvent();
|
||||||
|
|
||||||
return new UnbundleResponse(0);
|
return new UnbundleResponse(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void firePostReceiveRepositoryHookEvent() {
|
||||||
|
try {
|
||||||
|
Git git = Git.wrap(context.open());
|
||||||
|
List<String> branches = extractBranches(git);
|
||||||
|
List<Tag> tags = extractTags(git);
|
||||||
|
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(context.getRepository(), git);
|
||||||
|
eventBus.post(eventFactory.createEvent(context, branches, tags, changesetResolver));
|
||||||
|
} catch (IOException | GitAPIException e) {
|
||||||
|
throw new ImportFailedException(
|
||||||
|
ContextEntry.ContextBuilder.entity(context.getRepository()).build(),
|
||||||
|
"Could not fire post receive repository hook event after unbundle",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Tag> extractTags(Git git) throws GitAPIException {
|
||||||
|
return git.tagList().call().stream()
|
||||||
|
.map(r -> new Tag(r.getName(), r.getObjectId().getName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> extractBranches(Git git) throws GitAPIException {
|
||||||
|
return git.branchList().call().stream()
|
||||||
|
.map(Ref::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
private void unbundleRepositoryFromRequest(UnbundleCommandRequest request, Path repositoryDir) throws IOException {
|
private void unbundleRepositoryFromRequest(UnbundleCommandRequest request, Path repositoryDir) throws IOException {
|
||||||
extractTar(request.getArchive().openBufferedStream(), repositoryDir).run();
|
extractTar(request.getArchive().openBufferedStream(), repositoryDir).run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import com.google.inject.Provider;
|
|
||||||
import org.eclipse.jgit.api.CommitCommand;
|
import org.eclipse.jgit.api.CommitCommand;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
@@ -39,8 +38,8 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
@@ -57,22 +56,18 @@ import static org.mockito.Mockito.when;
|
|||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class AbstractRemoteCommandTestBase
|
public class AbstractRemoteCommandTestBase {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws IOException, GitAPIException
|
public void setup() throws IOException, GitAPIException {
|
||||||
{
|
|
||||||
incomingDirectory = tempFolder.newFile("incoming");
|
incomingDirectory = tempFolder.newFile("incoming");
|
||||||
incomingDirectory.delete();
|
incomingDirectory.delete();
|
||||||
outgoingDirectory = tempFolder.newFile("outgoing");
|
outgoingDirectory = tempFolder.newFile("outgoing");
|
||||||
@@ -84,6 +79,9 @@ public class AbstractRemoteCommandTestBase
|
|||||||
incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call();
|
incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call();
|
||||||
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
|
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
|
||||||
|
|
||||||
|
eventBus = mock(ScmEventBus.class);
|
||||||
|
eventFactory = mock(GitPostReceiveRepositoryHookEventFactory.class);
|
||||||
|
|
||||||
handler = mock(GitRepositoryHandler.class);
|
handler = mock(GitRepositoryHandler.class);
|
||||||
when(handler.getDirectory(incomingRepository.getId())).thenReturn(
|
when(handler.getDirectory(incomingRepository.getId())).thenReturn(
|
||||||
incomingDirectory);
|
incomingDirectory);
|
||||||
@@ -93,11 +91,9 @@ public class AbstractRemoteCommandTestBase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@After
|
@After
|
||||||
public void tearDownProtocol()
|
public void tearDownProtocol() {
|
||||||
{
|
|
||||||
Transport.unregister(proto);
|
Transport.unregister(proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,11 +101,9 @@ public class AbstractRemoteCommandTestBase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Before
|
@Before
|
||||||
public void setUpProtocol()
|
public void setUpProtocol() {
|
||||||
{
|
|
||||||
|
|
||||||
// store reference to handle weak references
|
// store reference to handle weak references
|
||||||
proto = new ScmTransportProtocol(GitTestHelper::createConverterFactory, () -> null, () -> null);
|
proto = new ScmTransportProtocol(GitTestHelper::createConverterFactory, () -> null, () -> null);
|
||||||
@@ -121,12 +115,10 @@ public class AbstractRemoteCommandTestBase
|
|||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param expected
|
* @param expected
|
||||||
* @param actual
|
* @param actual
|
||||||
*/
|
*/
|
||||||
protected void assertCommitsEquals(RevCommit expected, Changeset actual)
|
protected void assertCommitsEquals(RevCommit expected, Changeset actual) {
|
||||||
{
|
|
||||||
assertEquals(expected.getId().name(), actual.getId());
|
assertEquals(expected.getId().name(), actual.getId());
|
||||||
assertEquals(expected.getAuthorIdent().getName(),
|
assertEquals(expected.getAuthorIdent().getName(),
|
||||||
actual.getAuthor().getName());
|
actual.getAuthor().getName());
|
||||||
@@ -138,16 +130,12 @@ public class AbstractRemoteCommandTestBase
|
|||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param git
|
* @param git
|
||||||
* @param message
|
* @param message
|
||||||
*
|
|
||||||
* @return
|
* @return
|
||||||
*
|
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
*/
|
*/
|
||||||
protected RevCommit commit(Git git, String message) throws GitAPIException
|
protected RevCommit commit(Git git, String message) throws GitAPIException {
|
||||||
{
|
|
||||||
User trillian = UserTestData.createTrillian();
|
User trillian = UserTestData.createTrillian();
|
||||||
CommitCommand cc = git.commit();
|
CommitCommand cc = git.commit();
|
||||||
|
|
||||||
@@ -160,18 +148,15 @@ public class AbstractRemoteCommandTestBase
|
|||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param git
|
* @param git
|
||||||
* @param parent
|
* @param parent
|
||||||
* @param name
|
* @param name
|
||||||
* @param content
|
* @param content
|
||||||
*
|
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
protected void write(Git git, File parent, String name, String content)
|
protected void write(Git git, File parent, String name, String content)
|
||||||
throws IOException, GitAPIException
|
throws IOException, GitAPIException {
|
||||||
{
|
|
||||||
File file = new File(parent, name);
|
File file = new File(parent, name);
|
||||||
|
|
||||||
Files.write(content, file, Charsets.UTF_8);
|
Files.write(content, file, Charsets.UTF_8);
|
||||||
@@ -180,31 +165,52 @@ public class AbstractRemoteCommandTestBase
|
|||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
@Rule
|
@Rule
|
||||||
public TemporaryFolder tempFolder = new TemporaryFolder();
|
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected GitRepositoryHandler handler;
|
protected GitRepositoryHandler handler;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected Repository incomingRepository;
|
protected Repository incomingRepository;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected Git incoming;
|
protected Git incoming;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected File incomingDirectory;
|
protected File incomingDirectory;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected Git outgoing;
|
protected Git outgoing;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected File outgoingDirectory;
|
protected File outgoingDirectory;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
protected Repository outgoingRepository;
|
protected Repository outgoingRepository;
|
||||||
|
|
||||||
/** Field description */
|
/**
|
||||||
|
* Field description
|
||||||
|
*/
|
||||||
private ScmTransportProtocol proto;
|
private ScmTransportProtocol proto;
|
||||||
|
|
||||||
|
protected ScmEventBus eventBus;
|
||||||
|
protected GitPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,24 +44,20 @@ import static org.junit.Assert.assertNotNull;
|
|||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class GitIncomingCommandTest
|
public class GitIncomingCommandTest
|
||||||
extends AbstractRemoteCommandTestBase
|
extends AbstractRemoteCommandTestBase {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetIncomingChangesets()
|
public void testGetIncomingChangesets()
|
||||||
throws IOException, GitAPIException
|
throws IOException, GitAPIException {
|
||||||
{
|
|
||||||
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
||||||
|
|
||||||
RevCommit c1 = commit(outgoing, "added a");
|
RevCommit c1 = commit(outgoing, "added a");
|
||||||
@@ -84,22 +80,25 @@ public class GitIncomingCommandTest
|
|||||||
assertCommitsEquals(c2, cpr.getChangesets().get(1));
|
assertCommitsEquals(c2, cpr.getChangesets().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetIncomingChangesetsWithAllreadyPullChangesets()
|
public void testGetIncomingChangesetsWithAlreadyPulledChangesets()
|
||||||
throws IOException, GitAPIException
|
throws IOException, GitAPIException {
|
||||||
{
|
|
||||||
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
||||||
|
|
||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()));
|
GitPullCommand pull = new GitPullCommand(
|
||||||
|
handler,
|
||||||
|
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
|
||||||
|
eventBus,
|
||||||
|
eventFactory
|
||||||
|
);
|
||||||
PullCommandRequest req = new PullCommandRequest();
|
PullCommandRequest req = new PullCommandRequest();
|
||||||
req.setRemoteRepository(outgoingRepository);
|
req.setRemoteRepository(outgoingRepository);
|
||||||
pull.pull(req);
|
pull.pull(req);
|
||||||
@@ -124,13 +123,11 @@ public class GitIncomingCommandTest
|
|||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetIncomingChangesetsWithEmptyRepository()
|
public void testGetIncomingChangesetsWithEmptyRepository()
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
GitIncomingCommand cmd = createCommand();
|
GitIncomingCommand cmd = createCommand();
|
||||||
IncomingCommandRequest request = new IncomingCommandRequest();
|
IncomingCommandRequest request = new IncomingCommandRequest();
|
||||||
|
|
||||||
@@ -146,15 +143,13 @@ public class GitIncomingCommandTest
|
|||||||
/**
|
/**
|
||||||
* Check for correct behaviour
|
* Check for correct behaviour
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
public void testGetIncomingChangesetsWithUnrelatedRepository()
|
public void testGetIncomingChangesetsWithUnrelatedRepository()
|
||||||
throws IOException, GitAPIException
|
throws IOException, GitAPIException {
|
||||||
{
|
|
||||||
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
||||||
|
|
||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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.collect.Iterables;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class GitLazyChangesetResolverTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResolveChangesets() throws IOException {
|
||||||
|
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(repository, Git.wrap(createContext().open()));
|
||||||
|
Iterable<RevCommit> commits = changesetResolver.call();
|
||||||
|
|
||||||
|
RevCommit firstCommit = commits.iterator().next();
|
||||||
|
assertThat(firstCommit.getId().toString()).isEqualTo("commit a8495c0335a13e6e432df90b3727fa91943189a7 1602078219 -----sp");
|
||||||
|
assertThat(firstCommit.getCommitTime()).isEqualTo(1602078219);
|
||||||
|
assertThat(firstCommit.getFullMessage()).isEqualTo("add deeper paths\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResolveAllChangesets() throws IOException, GitAPIException {
|
||||||
|
Git git = Git.wrap(createContext().open());
|
||||||
|
GitLazyChangesetResolver changesetResolver = new GitLazyChangesetResolver(repository, git);
|
||||||
|
Iterable<RevCommit> allCommits = changesetResolver.call();
|
||||||
|
int allCommitsCounter = Iterables.size(allCommits);
|
||||||
|
int singleBranchCommitsCounter = Iterables.size(git.log().call());
|
||||||
|
|
||||||
|
assertThat(allCommitsCounter).isGreaterThan(singleBranchCommitsCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ImportFailedException.class)
|
||||||
|
public void shouldThrowImportFailedException() {
|
||||||
|
Git git = mock(Git.class);
|
||||||
|
doThrow(ImportFailedException.class).when(git).log();
|
||||||
|
new GitLazyChangesetResolver(repository, git).call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.repository.GitConfig;
|
import sonia.scm.repository.GitConfig;
|
||||||
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -111,7 +112,12 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
PushCommandRequest request = new PushCommandRequest();
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
request.setRemoteRepository(incomingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
cmd.push(request);
|
cmd.push(request);
|
||||||
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()));
|
GitPullCommand pullCommand = new GitPullCommand(
|
||||||
|
handler,
|
||||||
|
new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()),
|
||||||
|
eventBus,
|
||||||
|
eventFactory
|
||||||
|
);
|
||||||
PullCommandRequest pullRequest = new PullCommandRequest();
|
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||||
pullRequest.setRemoteRepository(incomingRepository);
|
pullRequest.setRemoteRepository(incomingRepository);
|
||||||
pullCommand.pull(pullRequest);
|
pullCommand.pull(pullRequest);
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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.collect.ImmutableList;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.HookContext;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class GitPostReceiveRepositoryHookEventFactoryTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
private HookContext hookContext;
|
||||||
|
|
||||||
|
private GitPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
HookContextFactory hookContextFactory = mock(HookContextFactory.class);
|
||||||
|
hookContext = mock(HookContext.class, RETURNS_DEEP_STUBS);
|
||||||
|
when(hookContextFactory.createContext(any(), eq(repository))).thenReturn(hookContext);
|
||||||
|
GitChangesetConverterFactory converterFactory = mock(GitChangesetConverterFactory.class);
|
||||||
|
eventFactory = new GitPostReceiveRepositoryHookEventFactory(hookContextFactory, converterFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateEvent() throws IOException {
|
||||||
|
ImmutableList<String> branches = ImmutableList.of("master", "develop");
|
||||||
|
ImmutableList<Tag> tags = ImmutableList.of(new Tag("1.0", "123"), new Tag("2.0", "456"));
|
||||||
|
ImmutableList<Changeset> changesets = ImmutableList.of(new Changeset("1", 0L, null, "first"));
|
||||||
|
when(hookContext.getChangesetProvider().getChangesetList()).thenReturn(changesets);
|
||||||
|
when(hookContext.getBranchProvider().getCreatedOrModified()).thenReturn(branches);
|
||||||
|
when(hookContext.getTagProvider().getCreatedTags()).thenReturn(tags);
|
||||||
|
|
||||||
|
PostReceiveRepositoryHookEvent event = eventFactory.createEvent(
|
||||||
|
createContext(),
|
||||||
|
branches,
|
||||||
|
tags,
|
||||||
|
new GitLazyChangesetResolver(repository, Git.wrap(createContext().open()))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isSameAs(branches);
|
||||||
|
assertThat(event.getContext().getTagProvider().getCreatedTags()).isSameAs(tags);
|
||||||
|
assertThat(event.getContext().getChangesetProvider().getChangesetList().get(0).getId()).isEqualTo("1");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,70 +24,79 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.Files;
|
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.Before;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
import sonia.scm.util.Archives;
|
import sonia.scm.util.Archives;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
class GitUnbundleCommandTest extends AbstractGitCommandTestBase {
|
public class GitUnbundleCommandTest extends AbstractGitCommandTestBase {
|
||||||
private GitContext gitContext;
|
private ScmEventBus eventBus;
|
||||||
private GitUnbundleCommand unbundleCommand;
|
private GitUnbundleCommand unbundleCommand;
|
||||||
|
private GitPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
@BeforeEach
|
@Before
|
||||||
void initCommand() {
|
public void initUnbundleCommand() {
|
||||||
gitContext = mock(GitContext.class);
|
eventBus = mock(ScmEventBus.class);
|
||||||
unbundleCommand = new GitUnbundleCommand(gitContext);
|
eventFactory = mock(GitPostReceiveRepositoryHookEventFactory.class);
|
||||||
|
unbundleCommand = new GitUnbundleCommand(createContext(), eventBus, eventFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUnbundleRepositoryFiles(@TempDir Path temp) throws IOException {
|
public void shouldUnbundleRepositoryFiles() throws IOException {
|
||||||
|
when(eventFactory.createEvent(eq(createContext()), any(), any(), any()))
|
||||||
|
.thenReturn(new PostReceiveRepositoryHookEvent(new RepositoryHookEvent(null, repository, RepositoryHookType.POST_RECEIVE)));
|
||||||
|
|
||||||
String filePath = "test-input";
|
String filePath = "test-input";
|
||||||
String fileContent = "HeartOfGold";
|
String fileContent = "HeartOfGold";
|
||||||
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(temp, filePath, fileContent);
|
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(filePath, fileContent);
|
||||||
|
|
||||||
unbundleCommand.unbundle(unbundleCommandRequest);
|
unbundleCommand.unbundle(unbundleCommandRequest);
|
||||||
|
|
||||||
assertFileWithContentWasCreated(temp, filePath, fileContent);
|
assertFileWithContentWasCreated(createContext().getDirectory(), filePath, fileContent);
|
||||||
|
|
||||||
|
verify(eventBus).post(any(PostReceiveRepositoryHookEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUnbundleNestedRepositoryFiles(@TempDir Path temp) throws IOException {
|
public void shouldUnbundleNestedRepositoryFiles() throws IOException {
|
||||||
String filePath = "objects/pack/test-input";
|
String filePath = "objects/pack/test-input";
|
||||||
String fileContent = "hitchhiker";
|
String fileContent = "hitchhiker";
|
||||||
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(temp, filePath, fileContent);
|
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(filePath, fileContent);
|
||||||
|
|
||||||
unbundleCommand.unbundle(unbundleCommandRequest);
|
unbundleCommand.unbundle(unbundleCommandRequest);
|
||||||
|
|
||||||
assertFileWithContentWasCreated(temp, filePath, fileContent);
|
assertFileWithContentWasCreated(createContext().getDirectory(), filePath, fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertFileWithContentWasCreated(@TempDir Path temp, String filePath, String fileContent) throws IOException {
|
private void assertFileWithContentWasCreated(File temp, String filePath, String fileContent) throws IOException {
|
||||||
File createdFile = temp.resolve(filePath).toFile();
|
File createdFile = temp.toPath().resolve(filePath).toFile();
|
||||||
assertThat(createdFile).exists();
|
assertThat(createdFile).exists();
|
||||||
assertThat(Files.readLines(createdFile, StandardCharsets.UTF_8).get(0)).isEqualTo(fileContent);
|
assertThat(createdFile).hasContent(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnbundleCommandRequest createUnbundleCommandRequestForFile(Path temp, String filePath, String fileContent) throws IOException {
|
private UnbundleCommandRequest createUnbundleCommandRequestForFile(String filePath, String fileContent) throws IOException {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
TarArchiveOutputStream taos = Archives.createTarOutputStream(baos);
|
TarArchiveOutputStream taos = Archives.createTarOutputStream(baos);
|
||||||
addEntry(taos, filePath, fileContent);
|
addEntry(taos, filePath, fileContent);
|
||||||
taos.finish();
|
taos.finish();
|
||||||
taos.close();
|
taos.close();
|
||||||
|
|
||||||
when(gitContext.getDirectory()).thenReturn(temp.toFile());
|
|
||||||
ByteSource byteSource = ByteSource.wrap(baos.toByteArray());
|
ByteSource byteSource = ByteSource.wrap(baos.toByteArray());
|
||||||
UnbundleCommandRequest unbundleCommandRequest = new UnbundleCommandRequest(byteSource);
|
UnbundleCommandRequest unbundleCommandRequest = new UnbundleCommandRequest(byteSource);
|
||||||
return unbundleCommandRequest;
|
return unbundleCommandRequest;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.aragost.javahg.Branch;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S3252") // This is just how to use javahg
|
||||||
|
final class HgBranchesTagsExtractor {
|
||||||
|
|
||||||
|
private HgBranchesTagsExtractor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Tag> extractTags(HgCommandContext context) {
|
||||||
|
return com.aragost.javahg.commands.TagsCommand.on(context.open()).execute().stream()
|
||||||
|
.map(t -> new Tag(t.getName(), t.getChangeset().toString()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> extractBranches(HgCommandContext context) {
|
||||||
|
return com.aragost.javahg.commands.BranchesCommand.on(context.open()).execute().stream()
|
||||||
|
.map(Branch::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.collect.ImmutableSet;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.HookBranchProvider;
|
||||||
|
import sonia.scm.repository.api.HookFeature;
|
||||||
|
import sonia.scm.repository.api.HookTagProvider;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class HgImportHookContextProvider extends HookContextProvider {
|
||||||
|
private final List<String> newBranches;
|
||||||
|
private final List<Tag> newTags;
|
||||||
|
private final HgLazyChangesetResolver changesetResolver;
|
||||||
|
|
||||||
|
HgImportHookContextProvider(List<String> newBranches, List<Tag> newTags, HgLazyChangesetResolver changesetResolver) {
|
||||||
|
this.newBranches = newBranches;
|
||||||
|
this.newTags = newTags;
|
||||||
|
this.changesetResolver = changesetResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<HookFeature> getSupportedFeatures() {
|
||||||
|
return ImmutableSet.of(HookFeature.CHANGESET_PROVIDER, HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookTagProvider getTagProvider() {
|
||||||
|
return new HookTagProvider() {
|
||||||
|
@Override
|
||||||
|
public List<Tag> getCreatedTags() {
|
||||||
|
return newTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Tag> getDeletedTags() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookBranchProvider getBranchProvider() {
|
||||||
|
return new HookBranchProvider() {
|
||||||
|
@Override
|
||||||
|
public List<String> getCreatedOrModified() {
|
||||||
|
return newBranches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getDeletedOrClosed() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookChangesetProvider getChangesetProvider() {
|
||||||
|
Iterable<Changeset> changesets = changesetResolver.call();
|
||||||
|
return r -> new HookChangesetResponse(changesets);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.aragost.javahg.commands.LogCommand;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
class HgLazyChangesetResolver implements Callable<Iterable<Changeset>> {
|
||||||
|
|
||||||
|
private final HgRepositoryFactory factory;
|
||||||
|
private final Repository repository;
|
||||||
|
|
||||||
|
HgLazyChangesetResolver(HgRepositoryFactory factory, Repository repository) {
|
||||||
|
this.factory = factory;
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Changeset> call() {
|
||||||
|
Iterator<Changeset> iterator = LogCommand.on(factory.openForRead(repository)).execute().stream()
|
||||||
|
.map(changeset -> new Changeset(
|
||||||
|
changeset.getNode(),
|
||||||
|
changeset.getTimestamp().getDate().getTime(),
|
||||||
|
Person.toPerson(changeset.getUser()),
|
||||||
|
changeset.getMessage())
|
||||||
|
)
|
||||||
|
.iterator();
|
||||||
|
return () -> iterator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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 sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.HookContext;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static sonia.scm.repository.RepositoryHookType.POST_RECEIVE;
|
||||||
|
import static sonia.scm.repository.spi.HgBranchesTagsExtractor.extractBranches;
|
||||||
|
import static sonia.scm.repository.spi.HgBranchesTagsExtractor.extractTags;
|
||||||
|
|
||||||
|
class HgPostReceiveRepositoryHookEventFactory {
|
||||||
|
|
||||||
|
private final HookContextFactory hookContextFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HgPostReceiveRepositoryHookEventFactory(HookContextFactory hookContextFactory) {
|
||||||
|
this.hookContextFactory = hookContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
PostReceiveRepositoryHookEvent createEvent(HgCommandContext hgContext, HgLazyChangesetResolver changesetResolver) {
|
||||||
|
List<String> branches = extractBranches(hgContext);
|
||||||
|
List<Tag> tags = extractTags(hgContext);
|
||||||
|
HgImportHookContextProvider contextProvider = new HgImportHookContextProvider(branches, tags, changesetResolver);
|
||||||
|
HookContext context = hookContextFactory.createContext(contextProvider, hgContext.getScmRepository());
|
||||||
|
RepositoryHookEvent repositoryHookEvent = new RepositoryHookEvent(context, hgContext.getScmRepository(), POST_RECEIVE);
|
||||||
|
return new PostReceiveRepositoryHookEvent(repositoryHookEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import com.google.common.base.Strings;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.io.INIConfiguration;
|
import sonia.scm.io.INIConfiguration;
|
||||||
import sonia.scm.io.INIConfigurationReader;
|
import sonia.scm.io.INIConfigurationReader;
|
||||||
import sonia.scm.io.INIConfigurationWriter;
|
import sonia.scm.io.INIConfigurationWriter;
|
||||||
@@ -47,9 +48,20 @@ public class HgPullCommand extends AbstractHgPushOrPullCommand implements PullCo
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(HgPullCommand.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HgPullCommand.class);
|
||||||
private static final String AUTH_SECTION = "auth";
|
private static final String AUTH_SECTION = "auth";
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
private final HgLazyChangesetResolver changesetResolver;
|
||||||
|
private final HgPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
public HgPullCommand(HgRepositoryHandler handler, HgCommandContext context) {
|
public HgPullCommand(HgRepositoryHandler handler,
|
||||||
|
HgCommandContext context,
|
||||||
|
ScmEventBus eventBus,
|
||||||
|
HgLazyChangesetResolver changesetResolver,
|
||||||
|
HgPostReceiveRepositoryHookEventFactory eventFactory
|
||||||
|
) {
|
||||||
super(handler, context);
|
super(handler, context);
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.changesetResolver = changesetResolver;
|
||||||
|
this.eventFactory = eventFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,9 +86,15 @@ public class HgPullCommand extends AbstractHgPushOrPullCommand implements PullCo
|
|||||||
removeAuthenticationConfig();
|
removeAuthenticationConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firePostReceiveRepositoryHookEvent();
|
||||||
|
|
||||||
return new PullResponse(result.size());
|
return new PullResponse(result.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void firePostReceiveRepositoryHookEvent() {
|
||||||
|
eventBus.post(eventFactory.createEvent(context, changesetResolver));
|
||||||
|
}
|
||||||
|
|
||||||
public void addAuthenticationConfig(PullCommandRequest request, String url) throws IOException {
|
public void addAuthenticationConfig(PullCommandRequest request, String url) throws IOException {
|
||||||
INIConfiguration ini = readIniConfiguration();
|
INIConfiguration ini = readIniConfiguration();
|
||||||
INISection authSection = ini.getSection(AUTH_SECTION);
|
INISection authSection = ini.getSection(AUTH_SECTION);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.google.common.io.Closeables;
|
import com.google.common.io.Closeables;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.Feature;
|
import sonia.scm.repository.Feature;
|
||||||
import sonia.scm.repository.HgRepositoryFactory;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
@@ -37,7 +38,6 @@ import java.util.EnumSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||||
@@ -65,49 +65,34 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
|
|
||||||
private final HgRepositoryHandler handler;
|
private final HgRepositoryHandler handler;
|
||||||
private final HgCommandContext context;
|
private final HgCommandContext context;
|
||||||
|
private final HgLazyChangesetResolver lazyChangesetResolver;
|
||||||
|
private final HgPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
|
||||||
HgRepositoryServiceProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository) {
|
HgRepositoryServiceProvider(HgRepositoryHandler handler,
|
||||||
|
HgRepositoryFactory factory,
|
||||||
|
HgPostReceiveRepositoryHookEventFactory eventFactory,
|
||||||
|
ScmEventBus eventBus,
|
||||||
|
Repository repository) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.eventFactory = eventFactory;
|
||||||
this.context = new HgCommandContext(handler, factory, repository);
|
this.context = new HgCommandContext(handler, factory, repository);
|
||||||
|
this.lazyChangesetResolver = new HgLazyChangesetResolver(factory, repository);
|
||||||
}
|
}
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException
|
public void close() throws IOException {
|
||||||
{
|
|
||||||
Closeables.close(context, true);
|
Closeables.close(context, true);
|
||||||
}
|
}
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public HgBlameCommand getBlameCommand()
|
public HgBlameCommand getBlameCommand() {
|
||||||
{
|
|
||||||
return new HgBlameCommand(context);
|
return new HgBlameCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public BranchesCommand getBranchesCommand()
|
public BranchesCommand getBranchesCommand() {
|
||||||
{
|
|
||||||
return new HgBranchesCommand(context);
|
return new HgBranchesCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,63 +101,28 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
return new HgBranchCommand(context, handler.getWorkingCopyFactory());
|
return new HgBranchCommand(context, handler.getWorkingCopyFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public HgBrowseCommand getBrowseCommand()
|
public HgBrowseCommand getBrowseCommand() {
|
||||||
{
|
|
||||||
return new HgBrowseCommand(context);
|
return new HgBrowseCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public HgCatCommand getCatCommand()
|
public HgCatCommand getCatCommand() {
|
||||||
{
|
|
||||||
return new HgCatCommand(context);
|
return new HgCatCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public HgDiffCommand getDiffCommand()
|
public HgDiffCommand getDiffCommand() {
|
||||||
{
|
|
||||||
return new HgDiffCommand(context);
|
return new HgDiffCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public IncomingCommand getIncomingCommand()
|
public IncomingCommand getIncomingCommand() {
|
||||||
{
|
|
||||||
return new HgIncomingCommand(context, handler);
|
return new HgIncomingCommand(context, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public HgLogCommand getLogCommand()
|
public HgLogCommand getLogCommand() {
|
||||||
{
|
|
||||||
return new HgLogCommand(context);
|
return new HgLogCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,39 +137,18 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
return new HgModificationsCommand(context);
|
return new HgModificationsCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public OutgoingCommand getOutgoingCommand()
|
public OutgoingCommand getOutgoingCommand() {
|
||||||
{
|
|
||||||
return new HgOutgoingCommand(context, handler);
|
return new HgOutgoingCommand(context, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public PullCommand getPullCommand()
|
public PullCommand getPullCommand() {
|
||||||
{
|
return new HgPullCommand(handler, context, eventBus, lazyChangesetResolver, eventFactory);
|
||||||
return new HgPullCommand(handler, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public PushCommand getPushCommand()
|
public PushCommand getPushCommand() {
|
||||||
{
|
|
||||||
return new HgPushCommand(handler, context);
|
return new HgPushCommand(handler, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,39 +157,18 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
return new HgModifyCommand(context, handler.getWorkingCopyFactory());
|
return new HgModifyCommand(context, handler.getWorkingCopyFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Command> getSupportedCommands()
|
public Set<Command> getSupportedCommands() {
|
||||||
{
|
|
||||||
return COMMANDS;
|
return COMMANDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Feature> getSupportedFeatures()
|
public Set<Feature> getSupportedFeatures() {
|
||||||
{
|
|
||||||
return FEATURES;
|
return FEATURES;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public TagsCommand getTagsCommand()
|
public TagsCommand getTagsCommand() {
|
||||||
{
|
|
||||||
return new HgTagsCommand(context);
|
return new HgTagsCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +184,6 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UnbundleCommand getUnbundleCommand() {
|
public UnbundleCommand getUnbundleCommand() {
|
||||||
return new HgUnbundleCommand(context);
|
return new HgUnbundleCommand(context, eventBus, lazyChangesetResolver, eventFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,13 +25,13 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.HgRepositoryFactory;
|
import sonia.scm.repository.HgRepositoryFactory;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Extension
|
@Extension
|
||||||
@@ -39,11 +39,19 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
|
|||||||
|
|
||||||
private final HgRepositoryHandler handler;
|
private final HgRepositoryHandler handler;
|
||||||
private final HgRepositoryFactory factory;
|
private final HgRepositoryFactory factory;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
private final HgPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgRepositoryServiceResolver(HgRepositoryHandler handler, HgRepositoryFactory factory) {
|
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
|
||||||
|
HgRepositoryFactory factory,
|
||||||
|
ScmEventBus eventBus,
|
||||||
|
HgPostReceiveRepositoryHookEventFactory eventFactory
|
||||||
|
) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.eventFactory = eventFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,7 +59,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
|
|||||||
HgRepositoryServiceProvider provider = null;
|
HgRepositoryServiceProvider provider = null;
|
||||||
|
|
||||||
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||||
provider = new HgRepositoryServiceProvider(handler, factory, repository);
|
provider = new HgRepositoryServiceProvider(handler, factory, eventFactory, eventBus, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
|
|||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.api.UnbundleResponse;
|
import sonia.scm.repository.api.UnbundleResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -36,19 +37,28 @@ import java.nio.file.Path;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static sonia.scm.util.Archives.extractTar;
|
import static sonia.scm.util.Archives.extractTar;
|
||||||
|
|
||||||
|
public class HgUnbundleCommand implements UnbundleCommand {
|
||||||
public class HgUnbundleCommand implements UnbundleCommand {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(HgUnbundleCommand.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HgUnbundleCommand.class);
|
||||||
|
|
||||||
private final HgCommandContext context;
|
private final HgCommandContext context;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
private final HgLazyChangesetResolver changesetResolver;
|
||||||
|
private final HgPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
HgUnbundleCommand(HgCommandContext context) {
|
HgUnbundleCommand(HgCommandContext context,
|
||||||
|
ScmEventBus eventBus,
|
||||||
|
HgLazyChangesetResolver changesetResolver,
|
||||||
|
HgPostReceiveRepositoryHookEventFactory eventFactory
|
||||||
|
) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.changesetResolver = changesetResolver;
|
||||||
|
this.eventFactory = eventFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UnbundleResponse unbundle(UnbundleCommandRequest request) throws IOException {
|
public UnbundleResponse unbundle(UnbundleCommandRequest request) throws IOException {
|
||||||
ByteSource archive = checkNotNull(request.getArchive(),"archive is required");
|
ByteSource archive = checkNotNull(request.getArchive(), "archive is required");
|
||||||
Path repositoryDir = context.getDirectory().toPath();
|
Path repositoryDir = context.getDirectory().toPath();
|
||||||
LOG.debug("archive repository {} to {}", repositoryDir, archive);
|
LOG.debug("archive repository {} to {}", repositoryDir, archive);
|
||||||
|
|
||||||
@@ -57,9 +67,14 @@ public class HgUnbundleCommand implements UnbundleCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unbundleRepositoryFromRequest(request, repositoryDir);
|
unbundleRepositoryFromRequest(request, repositoryDir);
|
||||||
|
firePostReceiveRepositoryHookEvent();
|
||||||
return new UnbundleResponse(0);
|
return new UnbundleResponse(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void firePostReceiveRepositoryHookEvent() {
|
||||||
|
eventBus.post(eventFactory.createEvent(context, changesetResolver));
|
||||||
|
}
|
||||||
|
|
||||||
private void unbundleRepositoryFromRequest(UnbundleCommandRequest request, Path repositoryDir) throws IOException {
|
private void unbundleRepositoryFromRequest(UnbundleCommandRequest request, Path repositoryDir) throws IOException {
|
||||||
extractTar(request.getArchive().openBufferedStream(), repositoryDir).run();
|
extractTar(request.getArchive().openBufferedStream(), repositoryDir).run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.junit.Test;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.HgTestUtil;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class HgLazyChangesetResolverTest extends AbstractHgCommandTestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResolveChangesets() {
|
||||||
|
HgLazyChangesetResolver changesetResolver = new HgLazyChangesetResolver(HgTestUtil.createFactory(handler, repositoryDirectory), repository);
|
||||||
|
Iterable<Changeset> changesets = changesetResolver.call();
|
||||||
|
|
||||||
|
Changeset firstChangeset = changesets.iterator().next();
|
||||||
|
assertThat(firstChangeset.getId()).isEqualTo("2baab8e80280ef05a9aa76c49c76feca2872afb7");
|
||||||
|
assertThat(firstChangeset.getDate()).isEqualTo(1339586381000L);
|
||||||
|
assertThat(firstChangeset.getAuthor()).isEqualTo(Person.toPerson("Zaphod Beeblebrox <zaphod.beeblebrox@hitchhiker.com>"));
|
||||||
|
assertThat(firstChangeset.getDescription()).isEqualTo("added new line for blame");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.collect.ImmutableList;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.Tag;
|
||||||
|
import sonia.scm.repository.api.HookContext;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class HgPostReceiveRepositoryHookEventFactoryTest extends AbstractHgCommandTestBase {
|
||||||
|
|
||||||
|
private HookContext hookContext;
|
||||||
|
|
||||||
|
private HgPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
HookContextFactory hookContextFactory = mock(HookContextFactory.class);
|
||||||
|
hookContext = mock(HookContext.class, RETURNS_DEEP_STUBS);
|
||||||
|
when(hookContextFactory.createContext(any(), any())).thenReturn(hookContext);
|
||||||
|
eventFactory = new HgPostReceiveRepositoryHookEventFactory(hookContextFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateEvent() {
|
||||||
|
ImmutableList<String> branches = ImmutableList.of("master", "develop");
|
||||||
|
ImmutableList<Tag> tags = ImmutableList.of(new Tag("1.0", "123"), new Tag("2.0", "456"));
|
||||||
|
ImmutableList<Changeset> changesets = ImmutableList.of(new Changeset("1", 0L, null, "first"));
|
||||||
|
when(hookContext.getChangesetProvider().getChangesetList()).thenReturn(changesets);
|
||||||
|
when(hookContext.getBranchProvider().getCreatedOrModified()).thenReturn(branches);
|
||||||
|
when(hookContext.getTagProvider().getCreatedTags()).thenReturn(tags);
|
||||||
|
|
||||||
|
HgLazyChangesetResolver changesetResolver = mock(HgLazyChangesetResolver.class);
|
||||||
|
|
||||||
|
PostReceiveRepositoryHookEvent event = eventFactory.createEvent(cmdContext, changesetResolver);
|
||||||
|
|
||||||
|
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isSameAs(branches);
|
||||||
|
assertThat(event.getContext().getTagProvider().getCreatedTags()).isSameAs(tags);
|
||||||
|
assertThat(event.getContext().getChangesetProvider().getChangesetList().get(0).getId()).isEqualTo("1");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,70 +28,82 @@ import com.google.common.io.ByteSource;
|
|||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.Before;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.HgTestUtil;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
import sonia.scm.util.Archives;
|
import sonia.scm.util.Archives;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
class HgUnbundleCommandTest {
|
public class HgUnbundleCommandTest extends AbstractHgCommandTestBase {
|
||||||
private HgCommandContext hgContext;
|
|
||||||
private HgUnbundleCommand unbundleCommand;
|
|
||||||
|
|
||||||
@BeforeEach
|
private ScmEventBus eventBus;
|
||||||
void initCommand() {
|
private HgUnbundleCommand unbundleCommand;
|
||||||
hgContext = mock(HgCommandContext.class);
|
private HgPostReceiveRepositoryHookEventFactory eventFactory;
|
||||||
unbundleCommand = new HgUnbundleCommand(hgContext);
|
|
||||||
|
@Before
|
||||||
|
public void initUnbundleCommand() {
|
||||||
|
eventBus = mock(ScmEventBus.class);
|
||||||
|
eventFactory = mock(HgPostReceiveRepositoryHookEventFactory.class);
|
||||||
|
unbundleCommand = new HgUnbundleCommand(cmdContext, eventBus, new HgLazyChangesetResolver(HgTestUtil.createFactory(handler, repositoryDirectory), null), eventFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUnbundleRepositoryFiles(@TempDir Path temp) throws IOException {
|
public void shouldUnbundleRepositoryFiles() throws IOException {
|
||||||
|
when(eventFactory.createEvent(eq(cmdContext), any()))
|
||||||
|
.thenReturn(new PostReceiveRepositoryHookEvent(new RepositoryHookEvent(null, repository, RepositoryHookType.POST_RECEIVE)));
|
||||||
|
|
||||||
|
|
||||||
String filePath = "test-input";
|
String filePath = "test-input";
|
||||||
String fileContent = "HeartOfGold";
|
String fileContent = "HeartOfGold";
|
||||||
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(temp, filePath, fileContent);
|
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(filePath, fileContent);
|
||||||
|
|
||||||
unbundleCommand.unbundle(unbundleCommandRequest);
|
unbundleCommand.unbundle(unbundleCommandRequest);
|
||||||
|
|
||||||
assertFileWithContentWasCreated(temp, filePath, fileContent);
|
assertFileWithContentWasCreated(cmdContext.getDirectory(), filePath, fileContent);
|
||||||
|
verify(eventBus).post(any(PostReceiveRepositoryHookEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUnbundleNestedRepositoryFiles(@TempDir Path temp) throws IOException {
|
public void shouldUnbundleNestedRepositoryFiles() throws IOException {
|
||||||
String filePath = "objects/pack/test-input";
|
String filePath = "objects/pack/test-input";
|
||||||
String fileContent = "hitchhiker";
|
String fileContent = "hitchhiker";
|
||||||
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(temp, filePath, fileContent);
|
UnbundleCommandRequest unbundleCommandRequest = createUnbundleCommandRequestForFile(filePath, fileContent);
|
||||||
|
|
||||||
unbundleCommand.unbundle(unbundleCommandRequest);
|
unbundleCommand.unbundle(unbundleCommandRequest);
|
||||||
|
|
||||||
assertFileWithContentWasCreated(temp, filePath, fileContent);
|
assertFileWithContentWasCreated(cmdContext.getDirectory(), filePath, fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertFileWithContentWasCreated(@TempDir Path temp, String filePath, String fileContent) throws IOException {
|
private void assertFileWithContentWasCreated(File temp, String filePath, String fileContent) throws IOException {
|
||||||
File createdFile = temp.resolve(filePath).toFile();
|
File createdFile = temp.toPath().resolve(filePath).toFile();
|
||||||
assertThat(createdFile).exists();
|
assertThat(createdFile).exists();
|
||||||
assertThat(Files.readLines(createdFile, StandardCharsets.UTF_8).get(0)).isEqualTo(fileContent);
|
assertThat(Files.readLines(createdFile, StandardCharsets.UTF_8).get(0)).isEqualTo(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnbundleCommandRequest createUnbundleCommandRequestForFile(Path temp, String filePath, String fileContent) throws IOException {
|
private UnbundleCommandRequest createUnbundleCommandRequestForFile(String filePath, String fileContent) throws IOException {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
TarArchiveOutputStream taos = Archives.createTarOutputStream(baos);
|
TarArchiveOutputStream taos = Archives.createTarOutputStream(baos);
|
||||||
addEntry(taos, filePath, fileContent);
|
addEntry(taos, filePath, fileContent);
|
||||||
taos.finish();
|
taos.finish();
|
||||||
taos.close();
|
taos.close();
|
||||||
|
|
||||||
when(hgContext.getDirectory()).thenReturn(temp.toFile());
|
|
||||||
ByteSource byteSource = ByteSource.wrap(baos.toByteArray());
|
ByteSource byteSource = ByteSource.wrap(baos.toByteArray());
|
||||||
UnbundleCommandRequest unbundleCommandRequest = new UnbundleCommandRequest(byteSource);
|
return new UnbundleCommandRequest(byteSource);
|
||||||
return unbundleCommandRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEntry(TarArchiveOutputStream taos, String name, String input) throws IOException {
|
private void addEntry(TarArchiveOutputStream taos, String name, String input) throws IOException {
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@@ -46,49 +44,34 @@ import java.util.List;
|
|||||||
|
|
||||||
import static sonia.scm.repository.SvnUtil.parseRevision;
|
import static sonia.scm.repository.SvnUtil.parseRevision;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
public class SvnLogCommand extends AbstractSvnCommand implements LogCommand {
|
||||||
|
|
||||||
public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
private static final Logger LOG = LoggerFactory.getLogger(SvnLogCommand.class);
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
SvnLogCommand(SvnContext context) {
|
||||||
* the logger for SvnLogCommand
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(SvnLogCommand.class);
|
|
||||||
|
|
||||||
SvnLogCommand(SvnContext context)
|
|
||||||
{
|
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Changeset getChangeset(String revision, LogCommandRequest request) {
|
public Changeset getChangeset(String revision, LogCommandRequest request) {
|
||||||
Changeset changeset = null;
|
Changeset changeset = null;
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (LOG.isDebugEnabled()) {
|
||||||
{
|
LOG.debug("fetch changeset {}", revision);
|
||||||
logger.debug("fetch changeset {}", revision);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
long revisioNumber = parseRevision(revision, repository);
|
long revisioNumber = parseRevision(revision, repository);
|
||||||
Preconditions.checkArgument(revisioNumber > 0, "revision must be greater than zero: %d", revisioNumber);
|
Preconditions.checkArgument(revisioNumber > 0, "revision must be greater than zero: %d", revisioNumber);
|
||||||
SVNRepository repo = open();
|
SVNRepository repo = open();
|
||||||
Collection<SVNLogEntry> entries = repo.log(null, null, revisioNumber,
|
Collection<SVNLogEntry> entries = repo.log(null, null, revisioNumber,
|
||||||
revisioNumber, true, true);
|
revisioNumber, true, true);
|
||||||
|
|
||||||
if (Util.isNotEmpty(entries))
|
if (Util.isNotEmpty(entries)) {
|
||||||
{
|
|
||||||
changeset = SvnUtil.createChangeset(entries.iterator().next());
|
changeset = SvnUtil.createChangeset(entries.iterator().next());
|
||||||
}
|
}
|
||||||
}
|
} catch (SVNException ex) {
|
||||||
catch (SVNException ex)
|
|
||||||
{
|
|
||||||
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,73 +81,48 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public ChangesetPagingResult getChangesets(LogCommandRequest request) {
|
public ChangesetPagingResult getChangesets(LogCommandRequest request) {
|
||||||
if (logger.isDebugEnabled())
|
if (LOG.isDebugEnabled()) {
|
||||||
{
|
LOG.debug("fetch changesets for {}", request);
|
||||||
logger.debug("fetch changesets for {}", request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangesetPagingResult changesets = null;
|
ChangesetPagingResult changesets;
|
||||||
int start = request.getPagingStart();
|
int start = request.getPagingStart();
|
||||||
int limit = request.getPagingLimit();
|
int limit = request.getPagingLimit();
|
||||||
long startRevision = parseRevision(request.getStartChangeset(), repository);
|
long startRevision = parseRevision(request.getStartChangeset(), repository);
|
||||||
long endRevision = parseRevision(request.getEndChangeset(), repository);
|
long endRevision = parseRevision(request.getEndChangeset(), repository);
|
||||||
String[] pathArray = null;
|
String[] pathArray = null;
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(request.getPath()))
|
if (!Strings.isNullOrEmpty(request.getPath())) {
|
||||||
{
|
pathArray = new String[]{request.getPath()};
|
||||||
pathArray = new String[] { request.getPath() };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
SVNRepository repo = open();
|
SVNRepository repo = open();
|
||||||
|
|
||||||
if ((startRevision > 0) || (pathArray != null))
|
if ((startRevision > 0) || (pathArray != null)) {
|
||||||
{
|
|
||||||
changesets = getChangesets(repo, startRevision, endRevision, start,
|
changesets = getChangesets(repo, startRevision, endRevision, start,
|
||||||
limit, pathArray);
|
limit, pathArray);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
changesets = getChangesets(repo, start, limit);
|
changesets = getChangesets(repo, start, limit);
|
||||||
}
|
}
|
||||||
}
|
} catch (SVNException ex) {
|
||||||
catch (SVNException ex)
|
|
||||||
{
|
|
||||||
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return changesets;
|
return changesets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repo
|
|
||||||
* @param start
|
|
||||||
* @param limit
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws SVNException
|
|
||||||
*/
|
|
||||||
private ChangesetPagingResult getChangesets(SVNRepository repo, int start,
|
private ChangesetPagingResult getChangesets(SVNRepository repo, int start,
|
||||||
int limit)
|
int limit)
|
||||||
throws SVNException
|
throws SVNException {
|
||||||
{
|
|
||||||
long latest = repo.getLatestRevision();
|
long latest = repo.getLatestRevision();
|
||||||
long startRev = latest - start;
|
long startRev = latest - start;
|
||||||
long endRev = Math.max(startRev - (limit - 1), 1);
|
long endRev = Math.max(startRev - (limit - 1), 1);
|
||||||
|
|
||||||
final List<Changeset> changesets = Lists.newArrayList();
|
final List<Changeset> changesets = Lists.newArrayList();
|
||||||
|
|
||||||
if (startRev > 0)
|
if (startRev > 0) {
|
||||||
{
|
LOG.debug("fetch changeset from {} to {}", startRev, endRev);
|
||||||
logger.debug("fetch changeset from {} to {}", startRev, endRev);
|
|
||||||
repo.log(null, startRev, endRev, true, true,
|
repo.log(null, startRev, endRev, true, true,
|
||||||
new ChangesetCollector(changesets));
|
new ChangesetCollector(changesets));
|
||||||
}
|
}
|
||||||
@@ -172,51 +130,30 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
|||||||
return new ChangesetPagingResult((int) latest, changesets);
|
return new ChangesetPagingResult((int) latest, changesets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repo
|
|
||||||
* @param startRevision
|
|
||||||
* @param endRevision
|
|
||||||
* @param start
|
|
||||||
* @param limit
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws SVNException
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private ChangesetPagingResult getChangesets(SVNRepository repo,
|
private ChangesetPagingResult getChangesets(SVNRepository repo,
|
||||||
long startRevision, long endRevision, int start, int limit, String[] path)
|
long startRevision, long endRevision, int start, int limit, String[] path)
|
||||||
throws SVNException
|
throws SVNException {
|
||||||
{
|
|
||||||
long startRev;
|
long startRev;
|
||||||
long endRev = Math.max(endRevision, 0);
|
long endRev = Math.max(endRevision, 0);
|
||||||
long maxRev = repo.getLatestRevision();
|
long maxRev = repo.getLatestRevision();
|
||||||
|
|
||||||
if (startRevision >= 0l)
|
if (startRevision >= 0) {
|
||||||
{
|
|
||||||
startRev = startRevision;
|
startRev = startRevision;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
startRev = maxRev;
|
startRev = maxRev;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SVNLogEntry> changesetList = Lists.newArrayList();
|
List<SVNLogEntry> changesetList = Lists.newArrayList();
|
||||||
|
|
||||||
logger.debug("fetch changeset from {} to {} for path {}", startRev, endRev,
|
LOG.debug("fetch changeset from {} to {} for path {}", startRev, endRev,
|
||||||
path);
|
path);
|
||||||
|
|
||||||
Collection<SVNLogEntry> entries = repo.log(path, null, startRev, endRev,
|
Collection<SVNLogEntry> entries = repo.log(path, null, startRev, endRev,
|
||||||
true, true);
|
true, true);
|
||||||
|
|
||||||
for (SVNLogEntry entry : entries)
|
for (SVNLogEntry entry : entries) {
|
||||||
{
|
if (entry.getRevision() <= maxRev) {
|
||||||
if (entry.getRevision() <= maxRev)
|
|
||||||
{
|
|
||||||
changesetList.add(entry);
|
changesetList.add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,17 +162,15 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
|||||||
int max = limit + start;
|
int max = limit + start;
|
||||||
int end = total;
|
int end = total;
|
||||||
|
|
||||||
if ((max > 0) && (end > max))
|
if ((max > 0) && (end > max)) {
|
||||||
{
|
|
||||||
end = max;
|
end = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start < 0)
|
if (start < 0) {
|
||||||
{
|
|
||||||
start = 0;
|
start = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace(
|
LOG.trace(
|
||||||
"create sublist from {} to {} of total {} collected changesets", start,
|
"create sublist from {} to {} of total {} collected changesets", start,
|
||||||
end, total);
|
end, total);
|
||||||
|
|
||||||
@@ -245,45 +180,20 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
|||||||
SvnUtil.createChangesets(changesetList));
|
SvnUtil.createChangesets(changesetList));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect and convert changesets.
|
* Collect and convert changesets.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private static class ChangesetCollector implements ISVNLogEntryHandler
|
private static class ChangesetCollector implements ISVNLogEntryHandler {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
private final Collection<Changeset> changesets;
|
||||||
* Constructs ...
|
|
||||||
*
|
public ChangesetCollector(Collection<Changeset> changesets) {
|
||||||
*
|
|
||||||
* @param changesets
|
|
||||||
*/
|
|
||||||
public ChangesetCollector(Collection<Changeset> changesets)
|
|
||||||
{
|
|
||||||
this.changesets = changesets;
|
this.changesets = changesets;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param logEntry
|
|
||||||
*
|
|
||||||
* @throws SVNException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void handleLogEntry(SVNLogEntry logEntry) throws SVNException
|
public void handleLogEntry(SVNLogEntry logEntry) {
|
||||||
{
|
|
||||||
changesets.add(SvnUtil.createChangeset(logEntry));
|
changesets.add(SvnUtil.createChangeset(logEntry));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final Collection<Changeset> changesets;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,23 +26,21 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.io.Closeables;
|
import com.google.common.io.Closeables;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.SvnRepositoryHandler;
|
import sonia.scm.repository.SvnRepositoryHandler;
|
||||||
import sonia.scm.repository.SvnWorkingCopyFactory;
|
import sonia.scm.repository.SvnWorkingCopyFactory;
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
|
public class SvnRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
//J-
|
//J-
|
||||||
public static final Set<Command> COMMANDS = ImmutableSet.of(
|
public static final Set<Command> COMMANDS = ImmutableSet.of(
|
||||||
Command.BLAME, Command.BROWSE, Command.CAT, Command.DIFF,
|
Command.BLAME, Command.BROWSE, Command.CAT, Command.DIFF,
|
||||||
@@ -50,101 +48,55 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
@Inject
|
private final SvnContext context;
|
||||||
|
private final SvnWorkingCopyFactory workingCopyFactory;
|
||||||
|
private final HookContextFactory hookContextFactory;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
|
||||||
SvnRepositoryServiceProvider(SvnRepositoryHandler handler,
|
SvnRepositoryServiceProvider(SvnRepositoryHandler handler,
|
||||||
Repository repository, SvnWorkingCopyFactory workingCopyFactory)
|
Repository repository,
|
||||||
{
|
SvnWorkingCopyFactory workingCopyFactory,
|
||||||
|
HookContextFactory hookContextFactory,
|
||||||
|
ScmEventBus eventBus) {
|
||||||
this.context = new SvnContext(repository, handler.getDirectory(repository.getId()));
|
this.context = new SvnContext(repository, handler.getDirectory(repository.getId()));
|
||||||
this.workingCopyFactory = workingCopyFactory;
|
this.workingCopyFactory = workingCopyFactory;
|
||||||
|
this.hookContextFactory = hookContextFactory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException
|
public void close() throws IOException {
|
||||||
{
|
|
||||||
Closeables.close(context, true);
|
Closeables.close(context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public SvnBlameCommand getBlameCommand()
|
public SvnBlameCommand getBlameCommand() {
|
||||||
{
|
|
||||||
return new SvnBlameCommand(context);
|
return new SvnBlameCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public SvnBrowseCommand getBrowseCommand()
|
public SvnBrowseCommand getBrowseCommand() {
|
||||||
{
|
|
||||||
return new SvnBrowseCommand(context);
|
return new SvnBrowseCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public BundleCommand getBundleCommand()
|
public BundleCommand getBundleCommand() {
|
||||||
{
|
|
||||||
return new SvnBundleCommand(context);
|
return new SvnBundleCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public SvnCatCommand getCatCommand()
|
public SvnCatCommand getCatCommand() {
|
||||||
{
|
|
||||||
return new SvnCatCommand(context);
|
return new SvnCatCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public SvnDiffCommand getDiffCommand()
|
public SvnDiffCommand getDiffCommand() {
|
||||||
{
|
|
||||||
return new SvnDiffCommand(context);
|
return new SvnDiffCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public SvnLogCommand getLogCommand()
|
public SvnLogCommand getLogCommand() {
|
||||||
{
|
|
||||||
return new SvnLogCommand(context);
|
return new SvnLogCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,34 +115,13 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new SvnLookupCommand(context);
|
return new SvnLookupCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Command> getSupportedCommands()
|
public Set<Command> getSupportedCommands() {
|
||||||
{
|
|
||||||
return COMMANDS;
|
return COMMANDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public UnbundleCommand getUnbundleCommand()
|
public UnbundleCommand getUnbundleCommand() {
|
||||||
{
|
return new SvnUnbundleCommand(context, hookContextFactory, eventBus, new SvnLogCommand(context));
|
||||||
return new SvnUnbundleCommand(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final SvnContext context;
|
|
||||||
|
|
||||||
private final SvnWorkingCopyFactory workingCopyFactory;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,21 +25,31 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.SvnRepositoryHandler;
|
import sonia.scm.repository.SvnRepositoryHandler;
|
||||||
import sonia.scm.repository.SvnWorkingCopyFactory;
|
import sonia.scm.repository.SvnWorkingCopyFactory;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
@Extension
|
@Extension
|
||||||
public class SvnRepositoryServiceResolver implements RepositoryServiceResolver {
|
public class SvnRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||||
|
|
||||||
private SvnRepositoryHandler handler;
|
private final SvnRepositoryHandler handler;
|
||||||
private SvnWorkingCopyFactory workingCopyFactory;
|
private final SvnWorkingCopyFactory workingCopyFactory;
|
||||||
|
private final HookContextFactory hookContextFactory;
|
||||||
|
private final ScmEventBus eventBus;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SvnRepositoryServiceResolver(SvnRepositoryHandler handler, SvnWorkingCopyFactory workingCopyFactory) {
|
public SvnRepositoryServiceResolver(SvnRepositoryHandler handler,
|
||||||
|
SvnWorkingCopyFactory workingCopyFactory,
|
||||||
|
HookContextFactory hookContextFactory,
|
||||||
|
ScmEventBus eventBus
|
||||||
|
) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.workingCopyFactory = workingCopyFactory;
|
this.workingCopyFactory = workingCopyFactory;
|
||||||
|
this.hookContextFactory = hookContextFactory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,7 +57,7 @@ public class SvnRepositoryServiceResolver implements RepositoryServiceResolver {
|
|||||||
SvnRepositoryServiceProvider provider = null;
|
SvnRepositoryServiceProvider provider = null;
|
||||||
|
|
||||||
if (SvnRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
if (SvnRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||||
provider = new SvnRepositoryServiceProvider(handler, repository, workingCopyFactory);
|
provider = new SvnRepositoryServiceProvider(handler, repository, workingCopyFactory, hookContextFactory, eventBus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
|
|||||||
@@ -24,82 +24,74 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.Closeables;
|
import com.google.common.io.Closeables;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.tmatesoft.svn.core.SVNException;
|
import org.tmatesoft.svn.core.SVNException;
|
||||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||||
import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;
|
import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryHookEvent;
|
||||||
import sonia.scm.repository.SvnUtil;
|
import sonia.scm.repository.SvnUtil;
|
||||||
|
import sonia.scm.repository.api.HookContext;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
import sonia.scm.repository.api.HookFeature;
|
||||||
|
import sonia.scm.repository.api.ImportFailedException;
|
||||||
import sonia.scm.repository.api.UnbundleResponse;
|
import sonia.scm.repository.api.UnbundleResponse;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.*;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static sonia.scm.repository.RepositoryHookType.POST_RECEIVE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra <s.sdorra@gmail.com>
|
* @author Sebastian Sdorra <s.sdorra@gmail.com>
|
||||||
*/
|
*/
|
||||||
public class SvnUnbundleCommand extends AbstractSvnCommand
|
public class SvnUnbundleCommand extends AbstractSvnCommand implements UnbundleCommand {
|
||||||
implements UnbundleCommand
|
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
private static final Logger LOG = LoggerFactory.getLogger(SvnUnbundleCommand.class);
|
||||||
private static final Logger logger =
|
private final HookContextFactory hookContextFactory;
|
||||||
LoggerFactory.getLogger(SvnUnbundleCommand.class);
|
private final ScmEventBus eventBus;
|
||||||
|
private final SvnLogCommand svnLogCommand;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
public SvnUnbundleCommand(SvnContext context,
|
||||||
|
HookContextFactory hookContextFactory,
|
||||||
/**
|
ScmEventBus eventBus,
|
||||||
* Constructs ...
|
SvnLogCommand svnLogCommand
|
||||||
*
|
) {
|
||||||
* @param context
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public SvnUnbundleCommand(SvnContext context)
|
|
||||||
{
|
|
||||||
super(context);
|
super(context);
|
||||||
|
this.hookContextFactory = hookContextFactory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.svnLogCommand = svnLogCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public UnbundleResponse unbundle(UnbundleCommandRequest request)
|
public UnbundleResponse unbundle(UnbundleCommandRequest request)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
ByteSource archive = checkNotNull(request.getArchive(),
|
ByteSource archive = checkNotNull(request.getArchive(),
|
||||||
"archive is required");
|
"archive is required");
|
||||||
|
|
||||||
logger.debug("archive repository {} to {}", context.getDirectory(),
|
LOG.debug("archive repository {} to {}", context.getDirectory(),
|
||||||
archive);
|
archive);
|
||||||
|
|
||||||
UnbundleResponse response;
|
UnbundleResponse response;
|
||||||
|
|
||||||
SVNClientManager clientManager = null;
|
SVNClientManager clientManager = null;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
clientManager = SVNClientManager.newInstance();
|
clientManager = SVNClientManager.newInstance();
|
||||||
|
|
||||||
SVNAdminClient adminClient = clientManager.getAdminClient();
|
SVNAdminClient adminClient = clientManager.getAdminClient();
|
||||||
@@ -107,44 +99,124 @@ public class SvnUnbundleCommand extends AbstractSvnCommand
|
|||||||
restore(adminClient, archive, context.getDirectory());
|
restore(adminClient, archive, context.getDirectory());
|
||||||
|
|
||||||
response = new UnbundleResponse(context.open().getLatestRevision());
|
response = new UnbundleResponse(context.open().getLatestRevision());
|
||||||
}
|
} catch (SVNException ex) {
|
||||||
catch (SVNException ex)
|
|
||||||
{
|
|
||||||
throw new IOException("could not restore dump", ex);
|
throw new IOException("could not restore dump", ex);
|
||||||
}
|
} finally {
|
||||||
finally
|
|
||||||
{
|
|
||||||
SvnUtil.dispose(clientManager);
|
SvnUtil.dispose(clientManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firePostReceiveRepositoryHookEvent();
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void firePostReceiveRepositoryHookEvent() {
|
||||||
* Method description
|
eventBus.post(createEvent());
|
||||||
*
|
}
|
||||||
*
|
|
||||||
* @param adminClient
|
private PostReceiveRepositoryHookEvent createEvent() {
|
||||||
* @param dump
|
Repository repository = this.context.getRepository();
|
||||||
* @param repository
|
HookContext context = hookContextFactory.createContext(new SvnImportHookContextProvider(repository, svnLogCommand), repository);
|
||||||
*
|
RepositoryHookEvent repositoryHookEvent = new RepositoryHookEvent(context, repository, POST_RECEIVE);
|
||||||
* @throws IOException
|
return new PostReceiveRepositoryHookEvent(repositoryHookEvent);
|
||||||
* @throws SVNException
|
}
|
||||||
*/
|
|
||||||
private void restore(SVNAdminClient adminClient, ByteSource dump,
|
private void restore(SVNAdminClient adminClient, ByteSource dump, File repository) throws SVNException, IOException {
|
||||||
File repository)
|
|
||||||
throws SVNException, IOException
|
|
||||||
{
|
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
inputStream = dump.openBufferedStream();
|
inputStream = dump.openBufferedStream();
|
||||||
adminClient.doLoad(repository, inputStream);
|
adminClient.doLoad(repository, inputStream);
|
||||||
}
|
} finally {
|
||||||
finally
|
|
||||||
{
|
|
||||||
Closeables.close(inputStream, true);
|
Closeables.close(inputStream, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class SvnImportHookContextProvider extends HookContextProvider {
|
||||||
|
|
||||||
|
private final Repository repository;
|
||||||
|
private final LogCommand logCommand;
|
||||||
|
|
||||||
|
private SvnImportHookContextProvider(Repository repository, SvnLogCommand logCommand) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.logCommand = logCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<HookFeature> getSupportedFeatures() {
|
||||||
|
return ImmutableSet.of(HookFeature.CHANGESET_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookChangesetProvider getChangesetProvider() {
|
||||||
|
ChangesetResolver changesetResolver = new ChangesetResolver(repository, logCommand);
|
||||||
|
return r -> new HookChangesetResponse(changesetResolver.call());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ChangesetResolver implements Callable<Iterable<Changeset>> {
|
||||||
|
|
||||||
|
private final Repository repository;
|
||||||
|
private final LogCommand logCommand;
|
||||||
|
|
||||||
|
ChangesetResolver(Repository repository, LogCommand logCommand) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.logCommand = logCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Changeset> call() {
|
||||||
|
return SingleLogRequestChangesetIterator::new;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SingleLogRequestChangesetIterator implements Iterator<Changeset> {
|
||||||
|
|
||||||
|
private int currentNumber = 0;
|
||||||
|
private Changeset nextChangeset;
|
||||||
|
|
||||||
|
SingleLogRequestChangesetIterator() {
|
||||||
|
prefetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return nextChangeset != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Changeset next() {
|
||||||
|
if (nextChangeset == null) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
Changeset currentChangeset = nextChangeset;
|
||||||
|
prefetch();
|
||||||
|
return currentChangeset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prefetch() {
|
||||||
|
try {
|
||||||
|
List<Changeset> changesets = fetchSingleChangesetPage();
|
||||||
|
if (changesets.isEmpty()) {
|
||||||
|
nextChangeset = null;
|
||||||
|
} else {
|
||||||
|
nextChangeset = changesets.get(0);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ImportFailedException(
|
||||||
|
ContextEntry.ContextBuilder.entity(repository).build(),
|
||||||
|
"Could not provide changeset nr " + currentNumber + " for imported repository",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Changeset> fetchSingleChangesetPage() throws IOException {
|
||||||
|
LogCommandRequest request = new LogCommandRequest();
|
||||||
|
request.setPagingStart(currentNumber);
|
||||||
|
request.setPagingLimit(1);
|
||||||
|
List<Changeset> changesets = logCommand.getChangesets(request).getChangesets();
|
||||||
|
currentNumber = currentNumber + 1;
|
||||||
|
return changesets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,77 +24,101 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.tmatesoft.svn.core.SVNException;
|
import org.tmatesoft.svn.core.SVNException;
|
||||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||||
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
|
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
|
||||||
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
import sonia.scm.repository.SvnUtil;
|
import sonia.scm.repository.SvnUtil;
|
||||||
|
import sonia.scm.repository.api.HookChangesetBuilder;
|
||||||
|
import sonia.scm.repository.api.HookContext;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
import sonia.scm.repository.api.UnbundleResponse;
|
import sonia.scm.repository.api.UnbundleResponse;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
public class SvnUnbundleCommandTest extends AbstractSvnCommandTestBase {
|
||||||
|
|
||||||
/**
|
private final Repository repository = RepositoryTestData.createHeartOfGold("svn");
|
||||||
*
|
private HookContextFactory hookContextFactory;
|
||||||
* @author Sebastian Sdorra
|
private ScmEventBus eventBus;
|
||||||
*/
|
private SvnLogCommand logCommand;
|
||||||
public class SvnUnbundleCommandTest extends AbstractSvnCommandTestBase
|
private HookChangesetBuilder hookChangesetBuilder;
|
||||||
{
|
|
||||||
|
@Captor
|
||||||
|
private final ArgumentCaptor<PostReceiveRepositoryHookEvent> eventCaptor =
|
||||||
|
ArgumentCaptor.forClass(PostReceiveRepositoryHookEvent.class);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void initMocks() {
|
||||||
|
hookContextFactory = mock(HookContextFactory.class);
|
||||||
|
eventBus = mock(ScmEventBus.class);
|
||||||
|
logCommand = mock(SvnLogCommand.class);
|
||||||
|
HookContext hookContext = mock(HookContext.class);
|
||||||
|
hookChangesetBuilder = mock(HookChangesetBuilder.class);
|
||||||
|
when(hookContextFactory.createContext(any(), eq(repository))).thenReturn(hookContext);
|
||||||
|
when(hookContext.getChangesetProvider()).thenReturn(hookChangesetBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnbundle()
|
public void shouldFirePostCommitEventAfterUnbundle() throws IOException, SVNException {
|
||||||
throws IOException, SVNException
|
Changeset first = new Changeset("1", 0L, new Person("trillian"), "first commit");
|
||||||
{
|
when(hookChangesetBuilder.getChangesetList()).thenReturn(ImmutableList.of(first));
|
||||||
|
|
||||||
File bundle = bundle();
|
File bundle = bundle();
|
||||||
SvnContext ctx = createEmptyContext();
|
SvnContext ctx = createEmptyContext();
|
||||||
//J-
|
//J-
|
||||||
UnbundleResponse res = new SvnUnbundleCommand(ctx)
|
UnbundleResponse res = new SvnUnbundleCommand(ctx, hookContextFactory, eventBus, logCommand)
|
||||||
.unbundle(new UnbundleCommandRequest(Files.asByteSource(bundle))
|
.unbundle(new UnbundleCommandRequest(Files.asByteSource(bundle))
|
||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
assertThat(res, notNullValue());
|
assertThat(res).isNotNull();
|
||||||
assertThat(res.getChangesetCount(), is(5l));
|
assertThat(res.getChangesetCount()).isEqualTo(5);
|
||||||
|
|
||||||
SVNRepository repo = ctx.open();
|
SVNRepository repo = ctx.open();
|
||||||
|
assertThat(repo.getLatestRevision()).isEqualTo(5);
|
||||||
|
|
||||||
|
verify(eventBus).post(eventCaptor.capture());
|
||||||
|
PostReceiveRepositoryHookEvent event = eventCaptor.getValue();
|
||||||
|
List<Changeset> changesets = event.getContext().getChangesetProvider().getChangesetList();
|
||||||
|
assertThat(changesets).hasSize(1);
|
||||||
|
assertThat(changesets).contains(first);
|
||||||
|
|
||||||
assertThat(repo.getLatestRevision(), is(5l));
|
|
||||||
SvnUtil.closeSession(repo);
|
SvnUtil.closeSession(repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private File bundle() throws IOException
|
private File bundle() throws IOException {
|
||||||
{
|
|
||||||
File file = tempFolder.newFile();
|
File file = tempFolder.newFile();
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
new SvnBundleCommand(createContext())
|
new SvnBundleCommand(createContext())
|
||||||
.bundle(new BundleCommandRequest(Files.asByteSink(file))
|
.bundle(new BundleCommandRequest(Files.asByteSink(file))
|
||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private SvnContext createEmptyContext() throws IOException, SVNException {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws SVNException
|
|
||||||
*/
|
|
||||||
private SvnContext createEmptyContext() throws IOException, SVNException
|
|
||||||
{
|
|
||||||
File folder = tempFolder.newFolder();
|
File folder = tempFolder.newFolder();
|
||||||
|
|
||||||
SVNRepositoryFactory.createLocalRepository(folder, true, true);
|
SVNRepositoryFactory.createLocalRepository(folder, true, true);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
mock-maker-inline
|
||||||
Reference in New Issue
Block a user