diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java index b4b3de7d7f..20072d3722 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnMirrorCommand.java @@ -24,6 +24,10 @@ package sonia.scm.repository.spi; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.BasicAuthenticationManager; @@ -33,7 +37,6 @@ import org.tmatesoft.svn.core.auth.SVNSSLAuthentication; import org.tmatesoft.svn.core.wc.SVNWCUtil; import org.tmatesoft.svn.core.wc.admin.SVNAdminClient; -import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.MirrorCommandResult; import sonia.scm.repository.api.Pkcs12ClientCertificateCredential; import sonia.scm.repository.api.UsernamePasswordCredential; @@ -44,10 +47,12 @@ import java.util.Collection; import java.util.function.Function; -import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static java.util.Collections.emptyList; public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorCommand { + private static final Logger LOG = LoggerFactory.getLogger(SvnMirrorCommand.class); + private final TrustManager trustManager; private final Function urlFactory; @@ -59,19 +64,31 @@ public class SvnMirrorCommand extends AbstractSvnCommand implements MirrorComman @Override public MirrorCommandResult mirror(MirrorCommandRequest mirrorCommandRequest) { - + Stopwatch stopwatch = Stopwatch.createStarted(); + long beforeUpdate; + long afterUpdate; try { + beforeUpdate = context.open().getLatestRevision(); SVNURL url = urlFactory.apply(context.getDirectory()); - SVNURL next = SVNURL.parseURIEncoded(mirrorCommandRequest.getSourceUrl()); - SVNAdminClient admin = createAdminClient(url, mirrorCommandRequest); admin.doCompleteSynchronize(next, url); + afterUpdate = context.open().getLatestRevision(); } catch (SVNException e) { - throw new InternalRepositoryException(entity(String.class, mirrorCommandRequest.getSourceUrl()), "could not mirror svn repository", e); + LOG.error("Could not mirror svn repository", e); + return new MirrorCommandResult(false, emptyList(), stopwatch.stop().elapsed()); } - return null; + return new MirrorCommandResult( + true, + ImmutableList.of("Updated from revision " + beforeUpdate + " to revision " + afterUpdate), + stopwatch.stop().elapsed() + ); + } + + @Override + public MirrorCommandResult update(MirrorCommandRequest request) { + return mirror(request); } private SVNAdminClient createAdminClient(SVNURL url, MirrorCommandRequest mirrorCommandRequest) { @@ -101,9 +118,4 @@ private SVNSSLAuthentication createTlsAuth(SVNURL url, Pkcs12ClientCertificateCr url, true); } - - @Override - public MirrorCommandResult update(MirrorCommandRequest mirrorCommandRequest) { - return null; - } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java index 8228d42f39..962a940c2c 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java @@ -24,78 +24,36 @@ package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- - import org.junit.After; import java.io.IOException; -/** - * - * @author Sebastian Sdorra - */ -public class AbstractSvnCommandTestBase extends ZippedRepositoryTestBase -{ +public class AbstractSvnCommandTestBase extends ZippedRepositoryTestBase { - /** - * Method description - * - * - * @throws IOException - */ @After - public void close() throws IOException - { - if (context != null) - { + public void close() throws IOException { + if (context != null) { context.close(); } } - /** - * Method description - * - * - * @return - */ - public SvnContext createContext() - { - if (context == null) - { + public SvnContext createContext() { + if (context == null) { context = new SvnContext(repository, repositoryDirectory); } return context; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ @Override - protected String getType() - { + protected String getType() { return "svn"; } - /** - * Method description - * - * - * @return - */ @Override - protected String getZippedRepositoryResource() - { + protected String getZippedRepositoryResource() { return "sonia/scm/repository/spi/scm-svn-spi-test.zip"; } - //~--- fields --------------------------------------------------------------- - - /** Field description */ private SvnContext context; } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java new file mode 100644 index 0000000000..5c359611bb --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnMirrorCommandTest.java @@ -0,0 +1,122 @@ +/* + * 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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.io.SVNRepositoryFactory; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.repository.api.MirrorCommandResult; +import sonia.scm.repository.api.SimpleUsernamePasswordCredential; + +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class SvnMirrorCommandTest extends AbstractSvnCommandTestBase { + + @Mock + private X509TrustManager trustManager; + + private SvnContext emptyContext; + + @Before + public void bendContextToNewRepository() throws IOException, SVNException { + emptyContext = createEmptyContext(); + } + + @Test + public void shouldDoInitialMirror() { + MirrorCommandResult result = callMirror(emptyContext, repositoryDirectory, c -> { + }); + + assertThat(result.isSuccess()).isTrue(); + } + + @Test + public void shouldDoMirrorUpdate() throws SVNException, IOException { + File newRepoDir = tempFolder.newFolder(); + SVNRepositoryFactory.createLocalRepository(newRepoDir, true, true); + MirrorCommandResult result = callMirrorUpdate(emptyContext, createContext().getDirectory()); + + assertThat(result.isSuccess()).isTrue(); + assertThat(result.getLog()).contains("Updated from revision 0 to revision 1"); + } + + @Test + public void shouldUseCredentials() throws IOException, SVNException { + File newRepoDir = tempFolder.newFolder(); + SVNRepositoryFactory.createLocalRepository(newRepoDir, true, true); + MirrorCommandResult result = callMirror(emptyContext, newRepoDir, createCredential("svnadmin", "secret")); + + assertThat(result.isSuccess()).isTrue(); + } + + private MirrorCommandResult callMirrorUpdate(SvnContext context, File source) { + MirrorCommandRequest request = new MirrorCommandRequest(); + SvnMirrorCommand command = createMirrorCommand(context, source, request); + return command.update(request); + } + + private MirrorCommandResult callMirror(SvnContext context, File source, Consumer consumer) { + MirrorCommandRequest request = new MirrorCommandRequest(); + SvnMirrorCommand command = createMirrorCommand(context, source, request); + consumer.accept(request); + return command.mirror(request); + } + + private SvnMirrorCommand createMirrorCommand(SvnContext context, File source, MirrorCommandRequest request) { + return new SvnMirrorCommand(context, trustManager, c -> + { + try { + request.setSourceUrl(SVNURL.fromFile(c).toString()); + return SVNURL.fromFile(source); + } catch (SVNException e) { + throw new IllegalStateException(e); + } + }); + } + + private Consumer createCredential(String username, String password) { + return request -> request.setCredentials(singletonList(new SimpleUsernamePasswordCredential(username, password.toCharArray()))); + } + + private SvnContext createEmptyContext() throws SVNException, IOException { + File dir = tempFolder.newFolder(); + SVNRepositoryFactory.createLocalRepository(dir, true, true); + return new SvnContext(RepositoryTestData.createHappyVerticalPeopleTransporter(), dir); + } + +}