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:
Eduard Heimbuch
2021-02-22 09:20:15 +01:00
committed by GitHub
parent eef74e3a50
commit 83a9c90130
31 changed files with 1400 additions and 607 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
} }

View File

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

View File

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

View File

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

View File

@@ -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");
}
}

View File

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

View File

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

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

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

View File

@@ -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;
} }
} }

View File

@@ -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;
} }

View File

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

View File

@@ -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;
}
}
}
} }

View File

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