From b65e84249d46a7d07bafe33c2d6d95b56b3ba831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Mar 2019 10:08:20 +0100 Subject: [PATCH] Use clone and push to create branches Generalize workdir creation for git and hg and create branches in clones instead of the scm repository, so that hooks will be fired correctly once the changes are pushed back. Missing: - Evaluation of the git response from the push command - configuration of the hg environment and the authentication, so that the scmhooks.py script can be triggeret correctly and can callback the scm manager --- .../repository/util}/CloseableWrapper.java | 12 ++-- .../repository/util/SimpleWorkdirFactory.java | 57 ++++++++++++++++++ .../scm/repository/util/WorkdirFactory.java | 5 ++ .../scm/repository/util/WorkingCopy.java | 31 ++++++++++ .../scm/repository/GitWorkdirFactory.java | 6 +- .../scm/repository/spi/GitBranchCommand.java | 28 +++++++-- .../scm/repository/spi/GitMergeCommand.java | 3 +- .../spi/GitRepositoryServiceProvider.java | 2 +- .../spi/SimpleGitWorkdirFactory.java | 59 +++++++------------ .../sonia/scm/repository/spi/WorkingCopy.java | 12 ---- .../scm/repository/CloseableWrapperTest.java | 1 + .../spi/AbstractGitCommandTestBase.java | 9 +++ .../spi/BindTransportProtocolRule.java | 38 ++++++++++++ .../repository/spi/GitBranchCommandTest.java | 6 +- .../repository/spi/GitMergeCommandTest.java | 23 +------- .../spi/SimpleGitWorkdirFactoryTest.java | 33 ++--------- .../scm/repository/HgRepositoryHandler.java | 10 +++- .../scm/repository/spi/HgBranchCommand.java | 41 +++++++++---- .../spi/HgRepositoryServiceProvider.java | 2 +- .../scm/repository/spi/HgWorkdirFactory.java | 6 ++ .../spi/SimpleHgWorkdirFactory.java | 43 ++++++++++++++ .../java/sonia/scm/web/HgServletModule.java | 4 ++ .../repository/HgRepositoryHandlerTest.java | 4 +- .../java/sonia/scm/repository/HgTestUtil.java | 2 +- .../repository/spi/HgBranchCommandTest.java | 5 +- 25 files changed, 306 insertions(+), 136 deletions(-) rename {scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository => scm-core/src/main/java/sonia/scm/repository/util}/CloseableWrapper.java (50%) create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/CloseableWrapper.java b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java similarity index 50% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/CloseableWrapper.java rename to scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java index 553f0f5a00..17980ced36 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/CloseableWrapper.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java @@ -1,18 +1,18 @@ -package sonia.scm.repository; +package sonia.scm.repository.util; import java.util.function.Consumer; -public class CloseableWrapper implements AutoCloseable { +public class CloseableWrapper implements AutoCloseable { - private final C wrapped; - private final Consumer cleanup; + private final T wrapped; + private final Consumer cleanup; - public CloseableWrapper(C wrapped, Consumer cleanup) { + public CloseableWrapper(T wrapped, Consumer cleanup) { this.wrapped = wrapped; this.cleanup = cleanup; } - public C get() { return wrapped; } + public T get() { return wrapped; } @Override public void close() { diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java new file mode 100644 index 0000000000..f2d436dffd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -0,0 +1,57 @@ +package sonia.scm.repository.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class SimpleWorkdirFactory { + + private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); + + private final File poolDirectory; + + private final CloneProvider cloneProvider; + private final Repository repository; + + public SimpleWorkdirFactory(Repository repository, CloneProvider cloneProvider) { + this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), repository, cloneProvider); + } + + public SimpleWorkdirFactory(File poolDirectory, Repository repository, CloneProvider cloneProvider) { + this.poolDirectory = poolDirectory; + this.cloneProvider = cloneProvider; + this.repository = repository; + poolDirectory.mkdirs(); + } + + public WorkingCopy createWorkingCopy(C context) { + try { + File directory = createNewWorkdir(); + T clone = cloneProvider.cloneRepository(context, directory); + return new WorkingCopy<>(clone, this::close, directory); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "could not create temporary directory for clone of repository", e); + } + } + + private File createNewWorkdir() throws IOException { + return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); + } + + private void close(T repository) { + try { + repository.close(); + } catch (Exception e) { + logger.warn("could not close temporary repository clone", e); + } + } + + public interface CloneProvider { + T cloneRepository(C context, File target) throws IOException; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java new file mode 100644 index 0000000000..1d3878b250 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java @@ -0,0 +1,5 @@ +package sonia.scm.repository.util; + +public interface WorkdirFactory { + WorkingCopy createWorkingCopy(C gitContext); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java new file mode 100644 index 0000000000..6271b8e199 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java @@ -0,0 +1,31 @@ +package sonia.scm.repository.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.util.IOUtil; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +public class WorkingCopy extends CloseableWrapper { + + private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class); + + private final File directory; + + public WorkingCopy(T wrapped, Consumer cleanup, File directory) { + super(wrapped, cleanup); + this.directory = directory; + } + + @Override + public void close() { + super.close(); + try { + IOUtil.delete(directory); + } catch (IOException e) { + LOG.warn("could not delete temporary workdir '{}'", directory, e); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java index f93713a221..d3ed353677 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java @@ -1,8 +1,8 @@ package sonia.scm.repository; +import org.eclipse.jgit.lib.Repository; import sonia.scm.repository.spi.GitContext; -import sonia.scm.repository.spi.WorkingCopy; +import sonia.scm.repository.util.WorkdirFactory; -public interface GitWorkdirFactory { - WorkingCopy createWorkingCopy(GitContext gitContext); +public interface GitWorkdirFactory extends WorkdirFactory { } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 23be8883fa..42667f6e51 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -35,26 +35,44 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RemoteRefUpdate; import sonia.scm.repository.Branch; import sonia.scm.repository.GitUtil; +import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; +import sonia.scm.repository.util.WorkingCopy; -import java.io.IOException; +import java.util.stream.StreamSupport; public class GitBranchCommand extends AbstractGitCommand implements BranchCommand { - GitBranchCommand(GitContext context, Repository repository) { + private final GitWorkdirFactory workdirFactory; + + GitBranchCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) { super(context, repository); + this.workdirFactory = workdirFactory; } @Override - public Branch branch(String name) throws IOException { - try (Git git = new Git(open())) { - Ref ref = git.branchCreate().setName(name).call(); + public Branch branch(String name) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { + Git clone = new Git(workingCopy.get()); + Ref ref = clone.branchCreate().setName(name).call(); + Iterable call = clone.push().add(name).call(); + StreamSupport.stream(call.spliterator(), false) + .flatMap(pushResult -> pushResult.getRemoteUpdates().stream()) + .filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) + .findFirst() + .ifPresent(this::handlePushError); return Branch.normalBranch(name, GitUtil.getId(ref.getObjectId())); } catch (GitAPIException ex) { throw new InternalRepositoryException(repository, "could not create branch " + name, ex); } } + + private void handlePushError(RemoteRefUpdate remoteRefUpdate) { + // TODO handle failed remote update + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index f328011815..f402d63c34 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -20,6 +20,7 @@ import sonia.scm.repository.Person; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; +import sonia.scm.repository.util.WorkingCopy; import sonia.scm.user.User; import java.io.IOException; @@ -48,7 +49,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand public MergeCommandResult merge(MergeCommandRequest request) { RepositoryPermissions.push(context.getRepository().getId()).check(); - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { Repository repository = workingCopy.get(); logger.debug("cloned repository to folder {}", repository.getWorkTree()); return new MergeWorker(repository, request).merge(); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 898c5875d3..f4b19d1e85 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -129,7 +129,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider @Override public BranchCommand getBranchCommand() { - return new GitBranchCommand(context, repository); + return new GitBranchCommand(context, repository, handler.getWorkdirFactory()); } /** diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index f12818aa80..bd38329ea3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -9,59 +9,40 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.repository.util.WorkingCopy; import java.io.File; import java.io.IOException; import java.nio.file.Files; -public class SimpleGitWorkdirFactory implements GitWorkdirFactory { - - private static final Logger logger = LoggerFactory.getLogger(SimpleGitWorkdirFactory.class); - - private final File poolDirectory; +public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { public SimpleGitWorkdirFactory() { - this(new File(System.getProperty("java.io.tmpdir"), "scmm-git-pool")); + super(null, new GitCloneProvider()); } public SimpleGitWorkdirFactory(File poolDirectory) { - this.poolDirectory = poolDirectory; - poolDirectory.mkdirs(); + super(poolDirectory, null, new GitCloneProvider()); } - public WorkingCopy createWorkingCopy(GitContext gitContext) { - try { - Repository clone = cloneRepository(gitContext.getDirectory(), createNewWorkdir()); - return new WorkingCopy(clone, this::close); - } catch (GitAPIException e) { - throw new InternalRepositoryException(gitContext.getRepository(), "could not clone working copy of repository", e); - } catch (IOException e) { - throw new InternalRepositoryException(gitContext.getRepository(), "could not create temporary directory for clone of repository", e); + private static class GitCloneProvider implements CloneProvider { + + @Override + public Repository cloneRepository(GitContext context, File target) { + try { + return Git.cloneRepository() + .setURI(createScmTransportProtocolUri(context.getDirectory())) + .setDirectory(target) + .call() + .getRepository(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e); + } } - } - private File createNewWorkdir() throws IOException { - return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); - } - - protected Repository cloneRepository(File bareRepository, File target) throws GitAPIException { - return Git.cloneRepository() - .setURI(createScmTransportProtocolUri(bareRepository)) - .setDirectory(target) - .call() - .getRepository(); - } - - private String createScmTransportProtocolUri(File bareRepository) { - return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath(); - } - - private void close(Repository repository) { - repository.close(); - try { - FileUtils.delete(repository.getWorkTree(), FileUtils.RECURSIVE); - } catch (IOException e) { - logger.warn("could not delete temporary git workdir '{}'", repository.getWorkTree(), e); + private String createScmTransportProtocolUri(File bareRepository) { + return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath(); } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java deleted file mode 100644 index fd0cba510b..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java +++ /dev/null @@ -1,12 +0,0 @@ -package sonia.scm.repository.spi; - -import org.eclipse.jgit.lib.Repository; -import sonia.scm.repository.CloseableWrapper; - -import java.util.function.Consumer; - -public class WorkingCopy extends CloseableWrapper { - WorkingCopy(Repository wrapped, Consumer cleanup) { - super(wrapped, cleanup); - } -} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java index e92ee7abb5..3bf0cb8ef7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java @@ -1,6 +1,7 @@ package sonia.scm.repository; import org.junit.Test; +import sonia.scm.repository.util.CloseableWrapper; import java.util.function.Consumer; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java index f2a4ed4954..8c4b682b18 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java @@ -34,11 +34,19 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import org.eclipse.jgit.transport.ScmTransportProtocol; +import org.eclipse.jgit.transport.Transport; import org.junit.After; +import org.junit.Before; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.GitRepositoryConfig; +import sonia.scm.repository.PreProcessorUtil; +import sonia.scm.repository.api.HookContextFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; +import static com.google.inject.util.Providers.of; +import static org.mockito.Mockito.mock; + /** * * @author Sebastian Sdorra @@ -105,4 +113,5 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase /** Field description */ private GitContext context; + private ScmTransportProtocol scmTransportProtocol; } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java new file mode 100644 index 0000000000..49800fc9e8 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java @@ -0,0 +1,38 @@ +package sonia.scm.repository.spi; + +import org.eclipse.jgit.transport.ScmTransportProtocol; +import org.eclipse.jgit.transport.Transport; +import org.junit.rules.ExternalResource; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.PreProcessorUtil; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.api.HookContextFactory; + +import static com.google.inject.util.Providers.of; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BindTransportProtocolRule extends ExternalResource { + + private ScmTransportProtocol scmTransportProtocol; + + @Override + protected void before() throws Throwable { + HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); + RepositoryManager repositoryManager = mock(RepositoryManager.class); + HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory); + GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); + scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); + + Transport.register(scmTransportProtocol); + + when(gitRepositoryHandler.getRepositoryId(any())).thenReturn("1"); + when(repositoryManager.get("1")).thenReturn(new sonia.scm.repository.Repository()); + } + + @Override + protected void after() { + Transport.unregister(scmTransportProtocol); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index d5dfc17698..83f211fed8 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -1,6 +1,7 @@ package sonia.scm.repository.spi; import org.assertj.core.api.Assertions; +import org.junit.Rule; import org.junit.Test; import sonia.scm.repository.Branch; @@ -10,13 +11,16 @@ import java.util.List; public class GitBranchCommandTest extends AbstractGitCommandTestBase { + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); + @Test public void shouldCreateBranch() throws IOException { GitContext context = createContext(); Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new GitBranchCommand(context, repository).branch("new_branch"); + new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch("new_branch"); Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index c926935496..e8a62f5b86 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -41,27 +41,8 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { @Rule public ShiroRule shiro = new ShiroRule(); - - private ScmTransportProtocol scmTransportProtocol; - - @Before - public void bindScmProtocol() { - HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); - RepositoryManager repositoryManager = mock(RepositoryManager.class); - HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory); - GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); - scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); - - Transport.register(scmTransportProtocol); - - when(gitRepositoryHandler.getRepositoryId(any())).thenReturn("1"); - when(repositoryManager.get("1")).thenReturn(new sonia.scm.repository.Repository()); - } - - @After - public void unregisterScmProtocol() { - Transport.unregister(scmTransportProtocol); - } + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); @Test public void shouldDetectMergeableBranches() { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java index 70c34fa122..4b8e32cb09 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java @@ -12,6 +12,7 @@ import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.api.HookContextFactory; +import sonia.scm.repository.util.WorkingCopy; import java.io.File; import java.io.IOException; @@ -44,7 +45,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); File masterRepo = createRepositoryDirectory(); - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { assertThat(workingCopy.get().getDirectory()) .exists() @@ -57,25 +58,15 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { } } - @Test - public void cloneFromPoolShouldBeClosed() throws IOException { - PoolWithSpy factory = new PoolWithSpy(temporaryFolder.newFolder()); - - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { - assertThat(workingCopy).isNotNull(); - } - verify(factory.createdClone).close(); - } - @Test public void cloneFromPoolShouldNotBeReused() throws IOException { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); File firstDirectory; - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { firstDirectory = workingCopy.get().getDirectory(); } - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { File secondDirectory = workingCopy.get().getDirectory(); assertThat(secondDirectory).isNotEqualTo(firstDirectory); } @@ -86,23 +77,9 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); File directory; - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { directory = workingCopy.get().getWorkTree(); } assertThat(directory).doesNotExist(); } - - private static class PoolWithSpy extends SimpleGitWorkdirFactory { - PoolWithSpy(File poolDirectory) { - super(poolDirectory); - } - - Repository createdClone; - - @Override - protected Repository cloneRepository(File bareRepository, File destination) throws GitAPIException { - createdClone = spy(super.cloneRepository(bareRepository, destination)); - return createdClone; - } - } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index d2da936c48..4ccf13e738 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -53,6 +53,7 @@ import sonia.scm.io.INISection; import sonia.scm.plugin.Extension; import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.HgRepositoryServiceProvider; +import sonia.scm.repository.spi.HgWorkdirFactory; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.IOUtil; import sonia.scm.util.SystemUtil; @@ -113,10 +114,11 @@ public class HgRepositoryHandler public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, Provider hgContextProvider, RepositoryLocationResolver repositoryLocationResolver, - PluginLoader pluginLoader) + PluginLoader pluginLoader, HgWorkdirFactory workdirFactory) { super(storeFactory, repositoryLocationResolver, pluginLoader); this.hgContextProvider = hgContextProvider; + this.workdirFactory = workdirFactory; try { @@ -408,6 +410,10 @@ public class HgRepositoryHandler } } + public HgWorkdirFactory getWorkdirFactory() { + return workdirFactory; + } + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -415,4 +421,6 @@ public class HgRepositoryHandler /** Field description */ private JAXBContext jaxbContext; + + private final HgWorkdirFactory workdirFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 01c5c5fc98..99976f59df 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -32,10 +32,14 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; +import com.aragost.javahg.commands.PushCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Repository; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.IOException; /** * Mercurial implementation of the {@link BranchCommand}. @@ -45,24 +49,37 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { private static final Logger LOG = LoggerFactory.getLogger(HgBranchCommand.class); - HgBranchCommand(HgCommandContext context, Repository repository) { + private final HgWorkdirFactory workdirFactory; + + HgBranchCommand(HgCommandContext context, Repository repository, HgWorkdirFactory workdirFactory) { super(context, repository); + this.workdirFactory = workdirFactory; } @Override - public Branch branch(String name) { - com.aragost.javahg.Repository repository = open(); - com.aragost.javahg.commands.BranchCommand.on(repository).set(name); + public Branch branch(String name) throws IOException { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { + com.aragost.javahg.Repository repository = workingCopy.get().get(); + com.aragost.javahg.commands.BranchCommand.on(repository).set(name); - Changeset emptyChangeset = CommitCommand - .on(repository) - .user("SCM-Manager") - .message("Create new branch " + name) - .execute(); + Changeset emptyChangeset = CommitCommand + .on(repository) + .user("SCM-Manager") + .message("Create new branch " + name) + .execute(); - LOG.debug("Created new branch '{}' in repository {} with changeset {}", - name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + PushCommand pushCommand = PushCommand + .on(repository) + .branch(name) + .newBranch(); + pushCommand + .cmdAppend("--config", ""); + pushCommand .execute(); - return Branch.normalBranch(name, emptyChangeset.getNode()); + LOG.debug("Created new branch '{}' in repository {} with changeset {}", + name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + + return Branch.normalBranch(name, emptyChangeset.getNode()); + } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index 0b88c07cb1..d60e888cac 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -128,7 +128,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider @Override public BranchCommand getBranchCommand() { - return new HgBranchCommand(context, repository); + return new HgBranchCommand(context, repository, handler.getWorkdirFactory()); } /** diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java new file mode 100644 index 0000000000..b32b485cca --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java @@ -0,0 +1,6 @@ +package sonia.scm.repository.spi; + +import sonia.scm.repository.util.WorkdirFactory; + +public interface HgWorkdirFactory extends WorkdirFactory { +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java new file mode 100644 index 0000000000..b893b53fec --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java @@ -0,0 +1,43 @@ +package sonia.scm.repository.spi; + +import com.aragost.javahg.Repository; +import com.aragost.javahg.commands.CloneCommand; +import sonia.scm.repository.util.SimpleWorkdirFactory; + +import java.io.File; +import java.io.IOException; + +public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { + public SimpleHgWorkdirFactory() { + super(null, new HgCloneProvider()); + } + + public SimpleHgWorkdirFactory(File poolDirectory) { + super(poolDirectory, null, new HgCloneProvider()); + } + + private static class HgCloneProvider implements CloneProvider { + + @Override + public RepositoryCloseableWrapper cloneRepository(HgCommandContext context, File target) throws IOException { + String execute = CloneCommand.on(context.open()).execute(target.getAbsolutePath()); + return new RepositoryCloseableWrapper(Repository.open(target)); + } + } +} + +class RepositoryCloseableWrapper implements AutoCloseable { + private final Repository delegate; + + RepositoryCloseableWrapper(Repository delegate) { + this.delegate = delegate; + } + + Repository get() { + return delegate; + } + + @Override + public void close() { + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index ba9ae3a0b9..7b66688036 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -46,6 +46,8 @@ import sonia.scm.plugin.Extension; import sonia.scm.repository.HgContext; import sonia.scm.repository.HgContextProvider; import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.spi.HgWorkdirFactory; +import sonia.scm.repository.spi.SimpleHgWorkdirFactory; /** * @@ -81,5 +83,7 @@ public class HgServletModule extends ServletModule // bind servlets serve(MAPPING_HOOK).with(HgHookCallbackServlet.class); + + bind(HgWorkdirFactory.class).to(SimpleHgWorkdirFactory.class); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index ed222f5119..c3b66525f9 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null); + HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null, null); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null); + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null, null); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index 131dad0837..1cecbb21be 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -105,7 +105,7 @@ public final class HgTestUtil RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); HgRepositoryHandler handler = - new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null); + new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null); Path repoDir = directory.toPath(); when(repoDao.getPath(any())).thenReturn(repoDir); handler.init(context); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 8c0b1862d1..24b262948f 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -4,14 +4,15 @@ import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.repository.Branch; +import java.io.IOException; import java.util.List; public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Test - public void x() { + public void x() throws IOException { Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new HgBranchCommand(cmdContext, repository).branch("new_branch"); + new HgBranchCommand(cmdContext, repository, new SimpleHgWorkdirFactory()).branch("new_branch"); Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); }