From 5b034f8d0286f6e01d5cf84e8fe0d3f6809e1ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 13 Apr 2020 12:22:13 +0200 Subject: [PATCH] Introduce cache layer for workdirs --- .../util/CacheSupportingWorkdirProvider.java | 42 +++++++++++ .../util/CachingAllWorkdirProvider.java | 72 +++++++++++++++++++ .../util/NoneCachingWorkdirProvider.java | 51 +++++++++++++ .../repository/util/SimpleWorkdirFactory.java | 47 ++++++++++-- .../scm/repository/util/WorkingCopy.java | 8 ++- .../util/SimpleWorkdirFactoryTest.java | 43 ++++++++++- .../spi/SimpleGitWorkdirFactory.java | 12 +++- .../repository/spi/GitMergeCommandTest.java | 3 +- .../spi/GitMergeCommand_Conflict_Test.java | 3 +- .../repository/spi/GitModifyCommandTest.java | 3 +- .../spi/GitModifyCommand_LFSTest.java | 3 +- ...ModifyCommand_withEmptyRepositoryTest.java | 3 +- .../spi/SimpleGitWorkdirFactoryTest.java | 13 ++-- .../spi/SimpleHgWorkdirFactory.java | 25 +++++-- .../repository/spi/HgBranchCommandTest.java | 5 +- .../repository/spi/HgModifyCommandTest.java | 5 +- .../spi/SimpleSvnWorkDirFactory.java | 12 +++- .../spi/SimpleSvnWorkDirFactoryTest.java | 11 +-- .../repository/spi/SvnModifyCommandTest.java | 3 +- .../modules/ApplicationModuleProvider.java | 3 +- .../scm/lifecycle/modules/WorkdirModule.java | 50 +++++++++++++ 21 files changed, 370 insertions(+), 47 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/CacheSupportingWorkdirProvider.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/CachingAllWorkdirProvider.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/NoneCachingWorkdirProvider.java create mode 100644 scm-webapp/src/main/java/sonia/scm/lifecycle/modules/WorkdirModule.java diff --git a/scm-core/src/main/java/sonia/scm/repository/util/CacheSupportingWorkdirProvider.java b/scm-core/src/main/java/sonia/scm/repository/util/CacheSupportingWorkdirProvider.java new file mode 100644 index 0000000000..e067d88f11 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/CacheSupportingWorkdirProvider.java @@ -0,0 +1,42 @@ +/* + * 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.util; + +import sonia.scm.repository.Repository; + +import java.io.File; +import java.io.IOException; + +public interface CacheSupportingWorkdirProvider { + SimpleWorkdirFactory.ParentAndClone getWorkdir( + Repository scmRepository, + String requestedBranch, + C context, + SimpleWorkdirFactory.WorkdirInitializer initializer, + SimpleWorkdirFactory.WorkdirReclaimer reclaimer + ) throws IOException; + + boolean cache(Repository repository, File target) throws IOException; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/CachingAllWorkdirProvider.java b/scm-core/src/main/java/sonia/scm/repository/util/CachingAllWorkdirProvider.java new file mode 100644 index 0000000000..ebb9b7a7c6 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/CachingAllWorkdirProvider.java @@ -0,0 +1,72 @@ +/* + * 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.util; + +import sonia.scm.repository.Repository; +import sonia.scm.util.IOUtil; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CachingAllWorkdirProvider implements CacheSupportingWorkdirProvider { + + private final Map workdirs = new HashMap<>(); + + private final WorkdirProvider workdirProvider; + + @Inject + public CachingAllWorkdirProvider(WorkdirProvider workdirProvider) { + this.workdirProvider = workdirProvider; + } + + @Override + public SimpleWorkdirFactory.ParentAndClone getWorkdir(Repository scmRepository, String requestedBranch, C context, SimpleWorkdirFactory.WorkdirInitializer initializer, SimpleWorkdirFactory.WorkdirReclaimer reclaimer) throws IOException { + String id = scmRepository.getId(); + if (workdirs.containsKey(id)) { + File existingWorkdir = workdirs.get(id); + try { + return reclaimer.reclaim(existingWorkdir); + } catch (SimpleWorkdirFactory.ReclaimFailedException e) { + workdirs.remove(id); + IOUtil.delete(existingWorkdir, true); + } + } + return createNewWorkdir(initializer, id); + } + + public SimpleWorkdirFactory.ParentAndClone createNewWorkdir(SimpleWorkdirFactory.WorkdirInitializer initializer, String id) throws IOException { + File newWorkdir = workdirProvider.createNewWorkdir(); + workdirs.put(id, newWorkdir); + return initializer.initialize(newWorkdir); + } + + @Override + public boolean cache(Repository repository, File target) { + return true; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/NoneCachingWorkdirProvider.java b/scm-core/src/main/java/sonia/scm/repository/util/NoneCachingWorkdirProvider.java new file mode 100644 index 0000000000..b46c0dcdf3 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/NoneCachingWorkdirProvider.java @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.util; + +import sonia.scm.repository.Repository; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; + +public class NoneCachingWorkdirProvider implements CacheSupportingWorkdirProvider { + + private final WorkdirProvider workdirProvider; + + @Inject + public NoneCachingWorkdirProvider(WorkdirProvider workdirProvider) { + this.workdirProvider = workdirProvider; + } + + @Override + public SimpleWorkdirFactory.ParentAndClone getWorkdir(Repository scmRepository, String requestedBranch, C context, SimpleWorkdirFactory.WorkdirInitializer initializer, SimpleWorkdirFactory.WorkdirReclaimer reclaimer) throws IOException { + return initializer.initialize(workdirProvider.createNewWorkdir()); + } + + @Override + public boolean cache(Repository repository, File target) { + return false; + } +} 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 index 5aed4fad77..22a0662b21 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.util; import org.slf4j.Logger; @@ -36,23 +36,45 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory createWorkingCopy(C context, String initialBranch) { try { - File directory = workdirProvider.createNewWorkdir(); - ParentAndClone parentAndClone = cloneRepository(context, directory, initialBranch); - return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::closeWorkdir, this::closeCentral, directory); + ParentAndClone parentAndClone = workdirProvider.getWorkdir( + getScmRepository(context), + initialBranch, + context, + newFolder -> cloneRepository(context, newFolder, initialBranch), + cachedFolder -> reclaimRepository(context, cachedFolder, initialBranch) + ); + return new WorkingCopy(parentAndClone.getClone(), parentAndClone.getParent(), this::closeWorkdir, this::closeCentral, parentAndClone.getDirectory()) { + @Override + public void delete() throws IOException { + if (!workdirProvider.cache(getScmRepository(context), getDirectory())) { + super.delete(); + } + } + }; } catch (IOException e) { throw new InternalRepositoryException(getScmRepository(context), "could not clone repository in temporary directory", e); } } + @FunctionalInterface + public interface WorkdirInitializer { + ParentAndClone initialize(File target) throws IOException; + } + + @FunctionalInterface + public interface WorkdirReclaimer { + ParentAndClone reclaim(File target) throws IOException, ReclaimFailedException; + } + protected abstract Repository getScmRepository(C context); @SuppressWarnings("squid:S00112") @@ -64,6 +86,8 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory cloneRepository(C context, File target, String initialBranch) throws IOException; + protected abstract ParentAndClone reclaimRepository(C context, File target, String initialBranch) throws IOException; + private void closeCentral(R repository) { try { closeRepository(repository); @@ -83,10 +107,12 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory { private final R parent; private final W clone; + private final File directory; - public ParentAndClone(R parent, W clone) { + public ParentAndClone(R parent, W clone, File directory) { this.parent = parent; this.clone = clone; + this.directory = directory; } public R getParent() { @@ -96,5 +122,12 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory implements AutoCloseable { try { cleanupWorkdir.accept(workingRepository); cleanupCentral.accept(centralRepository); - IOUtil.delete(directory); + delete(); } catch (IOException e) { LOG.warn("could not delete temporary workdir '{}'", directory, e); } } + + void delete() throws IOException { + IOUtil.delete(directory); + } } diff --git a/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java b/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java index 1e36307288..ce634ee6d2 100644 --- a/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.util; @@ -52,10 +52,25 @@ public class SimpleWorkdirFactoryTest { private String initialBranchForLastCloneCall; + private boolean workdirIsCached = false; + private File workdir; + @Before public void initFactory() throws IOException { WorkdirProvider workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); - simpleWorkdirFactory = new SimpleWorkdirFactory(workdirProvider) { + CacheSupportingWorkdirProvider configurableTestWorkdirProvider = new CacheSupportingWorkdirProvider() { + @Override + public SimpleWorkdirFactory.ParentAndClone getWorkdir(Repository scmRepository, String requestedBranch, C context, SimpleWorkdirFactory.WorkdirInitializer initializer, SimpleWorkdirFactory.WorkdirReclaimer reclaimer) throws IOException { + workdir = workdirProvider.createNewWorkdir(); + return initializer.initialize(workdir); + } + + @Override + public boolean cache(Repository repository, File target) { + return workdirIsCached; + } + }; + simpleWorkdirFactory = new SimpleWorkdirFactory(configurableTestWorkdirProvider) { @Override protected Repository getScmRepository(Context context) { return REPOSITORY; @@ -66,6 +81,11 @@ public class SimpleWorkdirFactoryTest { repository.close(); } + @Override + protected ParentAndClone reclaimRepository(Context context, File target, String initialBranch) throws IOException { + throw new UnsupportedOperationException(); + } + @Override protected void closeWorkdirInternal(Closeable workdir) throws Exception { workdir.close(); @@ -74,7 +94,7 @@ public class SimpleWorkdirFactoryTest { @Override protected ParentAndClone cloneRepository(Context context, File target, String initialBranch) { initialBranchForLastCloneCall = initialBranch; - return new ParentAndClone<>(parent, clone); + return new ParentAndClone<>(parent, clone, target); } }; } @@ -104,6 +124,23 @@ public class SimpleWorkdirFactoryTest { verify(clone).close(); } + @Test + public void shouldDeleteWorkdirIfNotCached() { + Context context = new Context(); + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {} + + assertThat(workdir).doesNotExist(); + } + + @Test + public void shouldNotDeleteWorkdirIfCached() { + Context context = new Context(); + workdirIsCached = true; + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {} + + assertThat(workdir).exists(); + } + @Test public void shouldPropagateInitialBranch() { Context context = new Context(); 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 ff0101f297..6c72e58ec8 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 @@ -30,10 +30,11 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ScmTransportProtocol; +import sonia.scm.repository.GitUtil; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.util.CacheSupportingWorkdirProvider; import sonia.scm.repository.util.SimpleWorkdirFactory; -import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.util.SystemUtil; import javax.inject.Inject; @@ -46,7 +47,7 @@ import static sonia.scm.NotFoundException.notFound; public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { @Inject - public SimpleGitWorkdirFactory(WorkdirProvider workdirProvider) { + public SimpleGitWorkdirFactory(CacheSupportingWorkdirProvider workdirProvider) { super(workdirProvider); } @@ -66,12 +67,17 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory(null, clone); + return new ParentAndClone<>(null, clone, target); } catch (GitAPIException | IOException e) { throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e); } } + @Override + protected ParentAndClone reclaimRepository(GitContext context, File target, String initialBranch) throws IOException { + return new ParentAndClone<>(null, GitUtil.open(target), target); + } + private String createScmTransportProtocolUri(File bareRepository) { if (SystemUtil.isWindows()) { return ScmTransportProtocol.NAME + ":///" + bareRepository.getAbsolutePath().replaceAll("\\\\", "/"); 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 ed21f683fb..8e3524dc9a 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 @@ -44,6 +44,7 @@ import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeStrategy; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.user.User; @@ -424,7 +425,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { } private GitMergeCommand createCommand(Consumer interceptor) { - return new GitMergeCommand(createContext(), new SimpleGitWorkdirFactory(new WorkdirProvider())) { + return new GitMergeCommand(createContext(), new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(new WorkdirProvider()))) { @Override > R inClone(Function workerSupplier, GitWorkdirFactory workdirFactory, String initialBranch) { Function interceptedWorkerSupplier = git -> { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java index 391ac2d762..fc0a457a29 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java @@ -27,6 +27,7 @@ package sonia.scm.repository.spi; import org.junit.Rule; import org.junit.Test; import sonia.scm.repository.spi.MergeConflictResult.SingleMergeConflict; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import java.io.IOException; @@ -91,7 +92,7 @@ public class GitMergeCommand_Conflict_Test extends AbstractGitCommandTestBase { } private MergeConflictResult computeMergeConflictResult(String branchToMerge, String targetBranch) { - GitMergeCommand gitMergeCommand = new GitMergeCommand(createContext(), new SimpleGitWorkdirFactory(new WorkdirProvider())); + GitMergeCommand gitMergeCommand = new GitMergeCommand(createContext(), new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(new WorkdirProvider()))); MergeCommandRequest mergeCommandRequest = new MergeCommandRequest(); mergeCommandRequest.setBranchToMerge(branchToMerge); mergeCommandRequest.setTargetBranch(targetBranch); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java index 746f05f61c..2f53b25a71 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java @@ -42,6 +42,7 @@ import sonia.scm.BadRequestException; import sonia.scm.ConcurrentModificationException; import sonia.scm.NotFoundException; import sonia.scm.repository.Person; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.lfs.LfsBlobStoreFactory; @@ -323,7 +324,7 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase { } private GitModifyCommand createCommand() { - return new GitModifyCommand(createContext(), new SimpleGitWorkdirFactory(new WorkdirProvider()), lfsBlobStoreFactory); + return new GitModifyCommand(createContext(), new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(new WorkdirProvider())), lfsBlobStoreFactory); } @FunctionalInterface diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java index bdad922b01..293c251a23 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java @@ -35,6 +35,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import sonia.scm.repository.Person; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.store.Blob; import sonia.scm.store.BlobStore; @@ -130,7 +131,7 @@ public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase { } private GitModifyCommand createCommand() { - return new GitModifyCommand(createContext(), new SimpleGitWorkdirFactory(new WorkdirProvider()), lfsBlobStoreFactory); + return new GitModifyCommand(createContext(), new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(new WorkdirProvider())), lfsBlobStoreFactory); } @Override diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_withEmptyRepositoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_withEmptyRepositoryTest.java index ae1c2fd854..e339817d0d 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_withEmptyRepositoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_withEmptyRepositoryTest.java @@ -38,6 +38,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import sonia.scm.repository.Person; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.lfs.LfsBlobStoreFactory; @@ -101,7 +102,7 @@ public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommand } private GitModifyCommand createCommand() { - return new GitModifyCommand(createContext(), new SimpleGitWorkdirFactory(new WorkdirProvider()), lfsBlobStoreFactory); + return new GitModifyCommand(createContext(), new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(new WorkdirProvider())), lfsBlobStoreFactory); } @FunctionalInterface 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 4d76a4952d..aa24f33a87 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; import org.eclipse.jgit.lib.Repository; @@ -35,6 +35,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.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.repository.util.WorkingCopy; @@ -66,7 +67,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void emptyPoolShouldCreateNewWorkdir() { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(workdirProvider)); File masterRepo = createRepositoryDirectory(); try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { @@ -84,7 +85,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void shouldCheckoutInitialBranch() { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(workdirProvider)); try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), "test-branch")) { assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt")) @@ -96,7 +97,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void shouldCheckoutDefaultBranch() { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(workdirProvider)); try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt")) @@ -108,7 +109,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void cloneFromPoolShouldNotBeReused() { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(workdirProvider)); File firstDirectory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { @@ -122,7 +123,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void cloneFromPoolShouldBeDeletedOnClose() { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(new NoneCachingWorkdirProvider(workdirProvider)); File directory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { 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 index 5a25aa247d..99c02d6af8 100644 --- 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 @@ -21,7 +21,7 @@ * 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.BaseRepository; @@ -29,8 +29,8 @@ import com.aragost.javahg.Repository; import com.aragost.javahg.commands.CloneCommand; import com.aragost.javahg.commands.PullCommand; import com.aragost.javahg.commands.flags.CloneCommandFlags; +import sonia.scm.repository.util.CacheSupportingWorkdirProvider; import sonia.scm.repository.util.SimpleWorkdirFactory; -import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.HgRepositoryEnvironmentBuilder; import javax.inject.Inject; @@ -45,15 +45,13 @@ public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory hgRepositoryEnvironmentBuilder; @Inject - public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder, WorkdirProvider workdirProvider) { + public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder, CacheSupportingWorkdirProvider workdirProvider) { super(workdirProvider); this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; } @Override public ParentAndClone cloneRepository(HgCommandContext context, File target, String initialBranch) throws IOException { - BiConsumer> repositoryMapBiConsumer = - (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); - Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); + Repository centralRepository = openCentral(context); CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository); if (initialBranch != null) { cloneCommand.updaterev(initialBranch); @@ -62,7 +60,20 @@ public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory(centralRepository, clone); + return new ParentAndClone<>(centralRepository, clone, target); + } + + public Repository openCentral(HgCommandContext context) { + BiConsumer> repositoryMapBiConsumer = + (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); + return context.openWithSpecialEnvironment(repositoryMapBiConsumer); + } + + @Override + protected ParentAndClone reclaimRepository(HgCommandContext context, File target, String initialBranch) throws IOException { + Repository centralRepository = openCentral(context); + BaseRepository clone = Repository.open(target); + return new ParentAndClone<>(centralRepository, clone, target); } @Override 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 1d7f0ca627..eab4749b61 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 @@ -21,7 +21,7 @@ * 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.PullCommand; @@ -32,6 +32,7 @@ import sonia.scm.repository.Branch; import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.BranchRequest; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.HgRepositoryEnvironmentBuilder; @@ -49,7 +50,7 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager()); - workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), new WorkdirProvider()) { + workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), new NoneCachingWorkdirProvider(new WorkdirProvider())) { @Override public void configure(PullCommand pullCommand) { // we do not want to configure http hooks in this unit test diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java index 1a944f0681..6cbc647447 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java @@ -21,7 +21,7 @@ * 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.inject.util.Providers; @@ -35,6 +35,7 @@ import sonia.scm.NotFoundException; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.Person; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.HgRepositoryEnvironmentBuilder; @@ -55,7 +56,7 @@ public class HgModifyCommandTest extends AbstractHgCommandTestBase { public void initHgModifyCommand() { HgHookManager hookManager = HgTestUtil.createHookManager(); HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager); - SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(environmentBuilder), new WorkdirProvider()) { + SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(environmentBuilder), new NoneCachingWorkdirProvider(new WorkdirProvider())) { @Override public void configure(com.aragost.javahg.commands.PullCommand pullCommand) { // we do not want to configure http hooks in this unit test diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java index dcd06f9207..9c8bcfe987 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java @@ -32,16 +32,17 @@ import org.tmatesoft.svn.core.wc2.SvnTarget; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.repository.SvnWorkDirFactory; +import sonia.scm.repository.util.CacheSupportingWorkdirProvider; import sonia.scm.repository.util.SimpleWorkdirFactory; -import sonia.scm.repository.util.WorkdirProvider; import javax.inject.Inject; import java.io.File; +import java.io.IOException; public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory implements SvnWorkDirFactory { @Inject - public SimpleSvnWorkDirFactory(WorkdirProvider workdirProvider) { + public SimpleSvnWorkDirFactory(CacheSupportingWorkdirProvider workdirProvider) { super(workdirProvider); } @@ -73,7 +74,12 @@ public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory(context.getDirectory(), workingCopy); + return new ParentAndClone<>(context.getDirectory(), workingCopy, workingCopy); + } + + @Override + protected ParentAndClone reclaimRepository(SvnContext context, File target, String initialBranch) throws IOException { + return new ParentAndClone<>(context.getDirectory(), target, target); } @Override diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java index a21a0319e6..8f1f7b00c9 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java @@ -21,7 +21,7 @@ * 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; @@ -30,6 +30,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.tmatesoft.svn.core.SVNException; import sonia.scm.repository.Repository; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.repository.util.WorkingCopy; @@ -53,7 +54,7 @@ public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { @Test public void shouldCheckoutLatestRevision() throws SVNException, IOException { - SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(new NoneCachingWorkdirProvider(workdirProvider)); try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { assertThat(new File(workingCopy.getWorkingRepository(), "a.txt")) @@ -65,7 +66,7 @@ public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { @Test public void cloneFromPoolshouldNotBeReused() { - SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(new NoneCachingWorkdirProvider(workdirProvider)); File firstDirectory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { @@ -79,7 +80,7 @@ public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { @Test public void shouldDeleteCloneOnClose() { - SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(new NoneCachingWorkdirProvider(workdirProvider)); File directory; File workingRepository; @@ -94,7 +95,7 @@ public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { @Test public void shouldReturnRepository() { - SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(new NoneCachingWorkdirProvider(workdirProvider)); Repository scmRepository = factory.getScmRepository(createContext()); assertThat(scmRepository).isSameAs(repository); } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java index 533e012dc3..4060060ed8 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import sonia.scm.AlreadyExistsException; import sonia.scm.repository.Person; +import sonia.scm.repository.util.NoneCachingWorkdirProvider; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.repository.util.WorkingCopy; @@ -56,7 +57,7 @@ public class SvnModifyCommandTest extends AbstractSvnCommandTestBase { @Before public void initSvnModifyCommand() { context = createContext(); - workDirFactory = new SimpleSvnWorkDirFactory(new WorkdirProvider(context.getDirectory())); + workDirFactory = new SimpleSvnWorkDirFactory(new NoneCachingWorkdirProvider(new WorkdirProvider(context.getDirectory()))); svnModifyCommand = new SvnModifyCommand(context, workDirFactory); } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java index 4b65f9094a..b34610f8ce 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.lifecycle.modules; import com.google.common.base.Throwables; @@ -77,6 +77,7 @@ public class ApplicationModuleProvider implements ModuleProvider { } moduleList.add(new MapperModule()); moduleList.add(new ExecutorModule()); + moduleList.add(new WorkdirModule(pluginLoader)); return moduleList; } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/WorkdirModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/WorkdirModule.java new file mode 100644 index 0000000000..1f7e72ee9c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/WorkdirModule.java @@ -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.lifecycle.modules; + +import com.google.inject.AbstractModule; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.repository.util.CacheSupportingWorkdirProvider; + +public class WorkdirModule extends AbstractModule { + public static final String DEFAULT_WORKDIR_CACHE_STRATEGY = "sonia.scm.repository.util.NoneCachingWorkdirProvider"; + public static final String WORKDIR_CACHE_STRATEGY_PROPERTY = "scm.workdirCacheStrategy"; + private final PluginLoader pluginLoader; + + public WorkdirModule(PluginLoader pluginLoader) { + this.pluginLoader = pluginLoader; + } + + @Override + protected void configure() { + try { + String workdirCacheStrategy = System.getProperty(WORKDIR_CACHE_STRATEGY_PROPERTY, DEFAULT_WORKDIR_CACHE_STRATEGY); + Class strategyClass = (Class) pluginLoader.getUberClassLoader().loadClass(workdirCacheStrategy); + bind(CacheSupportingWorkdirProvider.class).to(strategyClass); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +}