This commit is contained in:
René Pfeuffer
2020-05-08 18:11:27 +02:00
parent 1163ce9002
commit e968aa17ef
11 changed files with 131 additions and 77 deletions

View File

@@ -27,11 +27,11 @@ package sonia.scm.repository.util;
import com.google.common.base.Stopwatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.util.IOUtil;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -49,13 +49,13 @@ public class CachingAllWorkingCopyPool implements WorkingCopyPool {
}
@Override
public <R, W, C> SimpleWorkingCopyFactory.ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> workingCopyContext) throws IOException {
public <R, W, C> ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> workingCopyContext) {
String id = workingCopyContext.getScmRepository().getId();
File existingWorkdir = workdirs.remove(id);
if (existingWorkdir != null) {
Stopwatch stopwatch = Stopwatch.createStarted();
try {
SimpleWorkingCopyFactory.ParentAndClone<R, W> reclaimed = workingCopyContext.getReclaimer().reclaim(existingWorkdir);
ParentAndClone<R, W> reclaimed = workingCopyContext.getReclaimer().reclaim(existingWorkdir);
LOG.debug("reclaimed workdir for {} in path {} in {}", workingCopyContext.getScmRepository().getNamespaceAndName(), existingWorkdir, stopwatch.stop());
return reclaimed;
} catch (SimpleWorkingCopyFactory.ReclaimFailedException e) {
@@ -63,13 +63,17 @@ public class CachingAllWorkingCopyPool implements WorkingCopyPool {
deleteWorkdir(existingWorkdir);
}
}
return createNewWorkdir(workingCopyContext);
try {
return createNewWorkdir(workingCopyContext);
} catch (WorkingCopyFailedException e) {
throw new InternalRepositoryException(workingCopyContext.getScmRepository(), "failed to create working copy", e);
}
}
private <R, W> SimpleWorkingCopyFactory.ParentAndClone<R, W> createNewWorkdir(WorkingCopyContext<R, W, ?> workingCopyContext) throws IOException {
private <R, W> ParentAndClone<R, W> createNewWorkdir(WorkingCopyContext<R, W, ?> workingCopyContext) throws WorkingCopyFailedException {
Stopwatch stopwatch = Stopwatch.createStarted();
File newWorkdir = workdirProvider.createNewWorkdir();
SimpleWorkingCopyFactory.ParentAndClone<R, W> parentAndClone = workingCopyContext.getInitializer().initialize(newWorkdir);
ParentAndClone<R, W> parentAndClone = workingCopyContext.getInitializer().initialize(newWorkdir);
LOG.debug("initialized new workdir for {} in path {} in {}", workingCopyContext.getScmRepository().getNamespaceAndName(), newWorkdir, stopwatch.stop());
return parentAndClone;
}

View File

@@ -28,7 +28,6 @@ import sonia.scm.util.IOUtil;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
public class NoneCachingWorkingCopyPool implements WorkingCopyPool {
@@ -40,13 +39,13 @@ public class NoneCachingWorkingCopyPool implements WorkingCopyPool {
}
@Override
public <R, W, C> SimpleWorkingCopyFactory.ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> context) throws IOException {
public <R, W, C> ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> context) throws WorkingCopyFailedException {
return context.getInitializer().initialize(workdirProvider.createNewWorkdir());
}
@Override
public void contextClosed(WorkingCopyContext<?, ?, ?> workingCopyContext, File workdir) throws IOException {
IOUtil.delete(workdir, true);
public void contextClosed(WorkingCopyContext<?, ?, ?> workingCopyContext, File workdir) {
IOUtil.deleteSilently(workdir);
}
@Override

View File

@@ -26,14 +26,12 @@ package sonia.scm.repository.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.File;
import java.io.IOException;
public abstract class SimpleWorkingCopyFactory<R, W, C> implements WorkingCopyFactory<R, W, C>, ServletContextListener {
@@ -49,16 +47,14 @@ public abstract class SimpleWorkingCopyFactory<R, W, C> implements WorkingCopyFa
public WorkingCopy<R, W> createWorkingCopy(C repositoryContext, String initialBranch) {
try {
WorkingCopyContext<R, W, C> workingCopyContext = createWorkingCopyContext(repositoryContext, initialBranch);
ParentAndClone<R, W> parentAndClone = workingCopyPool.getWorkingCopy(workingCopyContext);
WorkingCopyPool.ParentAndClone<R, W> parentAndClone = workingCopyPool.getWorkingCopy(workingCopyContext);
return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), () -> this.close(workingCopyContext, parentAndClone), parentAndClone.getDirectory());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new InternalRepositoryException(getScmRepository(repositoryContext), "could not clone repository in temporary directory", e);
} catch (WorkingCopyFailedException e) {
throw new InternalRepositoryException(getScmRepository(repositoryContext), "could not create working copy for repository in temporary directory", e);
}
}
public WorkingCopyContext<R, W, C> createWorkingCopyContext(C repositoryContext, String initialBranch) {
private WorkingCopyContext<R, W, C> createWorkingCopyContext(C repositoryContext, String initialBranch) {
return new WorkingCopyContext<>(
getScmRepository(repositoryContext),
initialBranch,
@@ -68,7 +64,7 @@ public abstract class SimpleWorkingCopyFactory<R, W, C> implements WorkingCopyFa
);
}
private void close(WorkingCopyContext<R, W, C> workingCopyContext, ParentAndClone<R, W> parentAndClone) {
private void close(WorkingCopyContext<R, W, C> workingCopyContext, WorkingCopyPool.ParentAndClone<R, W> parentAndClone) {
try {
closeRepository(parentAndClone.getParent());
} catch (Exception e) {
@@ -98,12 +94,12 @@ public abstract class SimpleWorkingCopyFactory<R, W, C> implements WorkingCopyFa
@FunctionalInterface
public interface WorkingCopyInitializer<R, W> {
ParentAndClone<R, W> initialize(File target) throws IOException;
WorkingCopyPool.ParentAndClone<R, W> initialize(File target) throws WorkingCopyFailedException;
}
@FunctionalInterface
public interface WorkingCopyReclaimer<R, W> {
ParentAndClone<R, W> reclaim(File target) throws IOException, ReclaimFailedException;
WorkingCopyPool.ParentAndClone<R, W> reclaim(File target) throws ReclaimFailedException;
}
protected abstract Repository getScmRepository(C context);
@@ -115,40 +111,21 @@ public abstract class SimpleWorkingCopyFactory<R, W, C> implements WorkingCopyFa
// We do allow implementations to throw arbitrary exceptions here, so that we can handle them in closeWorkingCopy
protected abstract void closeWorkingCopyInternal(W workingCopy) throws Exception;
protected abstract ParentAndClone<R, W> cloneRepository(C context, File target, String initialBranch) throws IOException;
protected abstract WorkingCopyPool.ParentAndClone<R, W> cloneRepository(C context, File target, String initialBranch) throws WorkingCopyFailedException;
protected abstract ParentAndClone<R, W> reclaimRepository(C context, File target, String initialBranch) throws IOException, ReclaimFailedException;
protected static class ParentAndClone<R, W> {
private final R parent;
private final W clone;
private final File directory;
public ParentAndClone(R parent, W clone, File directory) {
this.parent = parent;
this.clone = clone;
this.directory = directory;
}
public R getParent() {
return parent;
}
public W getClone() {
return clone;
}
public File getDirectory() {
return directory;
}
}
protected abstract WorkingCopyPool.ParentAndClone<R, W> reclaimRepository(C context, File target, String initialBranch) throws ReclaimFailedException;
public static class ReclaimFailedException extends Exception {
public ReclaimFailedException() {
public ReclaimFailedException(String message) {
super(message);
}
public ReclaimFailedException(Throwable cause) {
super(cause);
}
public ReclaimFailedException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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;
public class WorkingCopyFailedException extends Exception {
public WorkingCopyFailedException(String message) {
super(message);
}
public WorkingCopyFailedException(Throwable cause) {
super(cause);
}
public WorkingCopyFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -27,9 +27,33 @@ package sonia.scm.repository.util;
import java.io.File;
public interface WorkingCopyPool {
<R, W, C> SimpleWorkingCopyFactory.ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> context) throws Exception;
<R, W, C> ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> context) throws WorkingCopyFailedException;
void contextClosed(WorkingCopyContext<?, ?, ?> workingCopyContext, File workdir) throws Exception;
void contextClosed(WorkingCopyContext<?, ?, ?> workingCopyContext, File workdir);
void shutdown();
class ParentAndClone<R, W> {
private final R parent;
private final W clone;
private final File directory;
public ParentAndClone(R parent, W clone, File directory) {
this.parent = parent;
this.clone = clone;
this.directory = directory;
}
public R getParent() {
return parent;
}
public W getClone() {
return clone;
}
public File getDirectory() {
return directory;
}
}
}

View File

@@ -34,7 +34,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.Repository;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
@@ -62,34 +61,34 @@ class CachingAllWorkingCopyPoolTest {
SimpleWorkingCopyFactory.WorkingCopyReclaimer<Object, Path> reclaimer;
@BeforeEach
void initContext() throws IOException, SimpleWorkingCopyFactory.ReclaimFailedException {
void initContext() throws SimpleWorkingCopyFactory.ReclaimFailedException, WorkingCopyFailedException {
lenient().when(workingCopyContext.getInitializer()).thenReturn(initializer);
lenient().when(workingCopyContext.getReclaimer()).thenReturn(reclaimer);
lenient().when(initializer.initialize(any()))
.thenAnswer(invocationOnMock -> new SimpleWorkingCopyFactory.ParentAndClone<>(null, null, invocationOnMock.getArgument(0, File.class)));
.thenAnswer(invocationOnMock -> new WorkingCopyPool.ParentAndClone<>(null, null, invocationOnMock.getArgument(0, File.class)));
lenient().when(reclaimer.reclaim(any()))
.thenAnswer(invocationOnMock -> new SimpleWorkingCopyFactory.ParentAndClone<>(null, null, invocationOnMock.getArgument(0, File.class)));
.thenAnswer(invocationOnMock -> new WorkingCopyPool.ParentAndClone<>(null, null, invocationOnMock.getArgument(0, File.class)));
}
@Test
void shouldCreateNewWorkdirForTheFirstRequest(@TempDir Path temp) throws IOException {
void shouldCreateNewWorkdirForTheFirstRequest(@TempDir Path temp) throws WorkingCopyFailedException {
when(workingCopyContext.getScmRepository()).thenReturn(REPOSITORY);
when(workdirProvider.createNewWorkdir()).thenReturn(temp.toFile());
SimpleWorkingCopyFactory.ParentAndClone<?, ?> workdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
WorkingCopyPool.ParentAndClone<?, ?> workdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
verify(initializer).initialize(temp.toFile());
}
@Test
void shouldCreateWorkdirOnlyOnceForTheSameRepository(@TempDir Path temp) throws IOException, SimpleWorkingCopyFactory.ReclaimFailedException {
void shouldCreateWorkdirOnlyOnceForTheSameRepository(@TempDir Path temp) throws SimpleWorkingCopyFactory.ReclaimFailedException, WorkingCopyFailedException {
when(workingCopyContext.getScmRepository()).thenReturn(REPOSITORY);
when(workdirProvider.createNewWorkdir()).thenReturn(temp.toFile());
SimpleWorkingCopyFactory.ParentAndClone<?, ?> firstWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
WorkingCopyPool.ParentAndClone<?, ?> firstWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
cachingAllWorkingCopyPool.contextClosed(workingCopyContext, firstWorkdir.getDirectory());
SimpleWorkingCopyFactory.ParentAndClone<?, ?> secondWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
WorkingCopyPool.ParentAndClone<?, ?> secondWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
verify(initializer).initialize(temp.toFile());
verify(reclaimer).reclaim(temp.toFile());
@@ -97,7 +96,7 @@ class CachingAllWorkingCopyPoolTest {
}
@Test
void shouldCacheOnlyOneWorkdirForRepository(@TempDir Path temp) throws IOException, SimpleWorkingCopyFactory.ReclaimFailedException {
void shouldCacheOnlyOneWorkdirForRepository(@TempDir Path temp) throws SimpleWorkingCopyFactory.ReclaimFailedException, WorkingCopyFailedException {
when(workingCopyContext.getScmRepository()).thenReturn(REPOSITORY);
File firstDirectory = temp.resolve("first").toFile();
firstDirectory.mkdirs();
@@ -107,8 +106,8 @@ class CachingAllWorkingCopyPoolTest {
firstDirectory,
secondDirectory);
SimpleWorkingCopyFactory.ParentAndClone<?, ?> firstWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
SimpleWorkingCopyFactory.ParentAndClone<?, ?> secondWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
WorkingCopyPool.ParentAndClone<?, ?> firstWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
WorkingCopyPool.ParentAndClone<?, ?> secondWorkdir = cachingAllWorkingCopyPool.getWorkingCopy(workingCopyContext);
cachingAllWorkingCopyPool.contextClosed(workingCopyContext, firstWorkdir.getDirectory());
cachingAllWorkingCopyPool.contextClosed(workingCopyContext, secondWorkdir.getDirectory());

View File

@@ -61,15 +61,15 @@ public class SimpleWorkingCopyFactoryTest {
WorkdirProvider workdirProvider = new WorkdirProvider(temporaryFolder.newFolder());
WorkingCopyPool configurableTestWorkingCopyPool = new WorkingCopyPool() {
@Override
public <R, W, C> SimpleWorkingCopyFactory.ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> context) throws IOException {
public <R, W, C> ParentAndClone<R, W> getWorkingCopy(WorkingCopyContext<R, W, C> context) throws WorkingCopyFailedException {
workdir = workdirProvider.createNewWorkdir();
return context.getInitializer().initialize(workdir);
}
@Override
public void contextClosed(WorkingCopyContext<?, ?, ?> createWorkdirContext, File workdir) throws Exception {
public void contextClosed(WorkingCopyContext<?, ?, ?> createWorkdirContext, File workdir) {
if (!workdirIsCached) {
IOUtil.delete(workdir);
IOUtil.deleteSilently(workdir);
}
}
@@ -89,7 +89,7 @@ public class SimpleWorkingCopyFactoryTest {
}
@Override
protected ParentAndClone<Closeable, Closeable> reclaimRepository(Context context, File target, String initialBranch) throws IOException {
protected WorkingCopyPool.ParentAndClone<Closeable, Closeable> reclaimRepository(Context context, File target, String initialBranch) {
throw new UnsupportedOperationException();
}
@@ -99,9 +99,9 @@ public class SimpleWorkingCopyFactoryTest {
}
@Override
protected ParentAndClone<Closeable, Closeable> cloneRepository(Context context, File target, String initialBranch) {
protected WorkingCopyPool.ParentAndClone<Closeable, Closeable> cloneRepository(Context context, File target, String initialBranch) {
initialBranchForLastCloneCall = initialBranch;
return new ParentAndClone<>(parent, clone, target);
return new WorkingCopyPool.ParentAndClone<>(parent, clone, target);
}
};
}

View File

@@ -38,6 +38,7 @@ import sonia.scm.repository.GitWorkingCopyFactory;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.util.WorkingCopyPool;
import sonia.scm.repository.util.SimpleWorkingCopyFactory;
import sonia.scm.repository.util.WorkingCopyPool.ParentAndClone;
import sonia.scm.util.SystemUtil;
import javax.inject.Inject;
@@ -85,10 +86,10 @@ public class SimpleGitWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposi
}
@Override
protected ParentAndClone<Repository, Repository> reclaimRepository(GitContext context, File target, String initialBranch) throws IOException, ReclaimFailedException {
protected ParentAndClone<Repository, Repository> reclaimRepository(GitContext context, File target, String initialBranch) throws ReclaimFailedException {
LOG.trace("reclaim repository {}", context.getRepository().getId());
long start = System.nanoTime();
Repository repo = GitUtil.open(target);
Repository repo = openTarget(target);
try (Git git = Git.open(target)) {
git.reset().setMode(ResetCommand.ResetType.HARD).call();
git.clean().setForce(true).setCleanDirectories(true).call();
@@ -97,7 +98,7 @@ public class SimpleGitWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposi
git.branchDelete().setBranchNames(initialBranch).setForce(true).call();
git.checkout().setName(initialBranch).setCreateBranch(true).call();
return new ParentAndClone<>(null, repo, target);
} catch (GitAPIException e) {
} catch (GitAPIException | IOException e) {
throw new ReclaimFailedException(e);
} finally {
long end = System.nanoTime();
@@ -106,6 +107,14 @@ public class SimpleGitWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposi
}
}
private Repository openTarget(File target) throws ReclaimFailedException {
try {
return GitUtil.open(target);
} catch (IOException e) {
throw new ReclaimFailedException(e);
}
}
String createScmTransportProtocolUri(File bareRepository) {
if (SystemUtil.isWindows()) {
return ScmTransportProtocol.NAME + ":///" + bareRepository.getAbsolutePath().replaceAll("\\\\", "/");

View File

@@ -32,8 +32,10 @@ import com.aragost.javahg.commands.PullCommand;
import com.aragost.javahg.commands.StatusCommand;
import com.aragost.javahg.commands.UpdateCommand;
import com.aragost.javahg.commands.flags.CloneCommandFlags;
import sonia.scm.repository.util.WorkingCopyFailedException;
import sonia.scm.repository.util.WorkingCopyPool;
import sonia.scm.repository.util.SimpleWorkingCopyFactory;
import sonia.scm.repository.util.WorkingCopyPool.ParentAndClone;
import sonia.scm.util.IOUtil;
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
@@ -54,13 +56,17 @@ public class SimpleHgWorkingCopyFactory extends SimpleWorkingCopyFactory<Reposit
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
}
@Override
public ParentAndClone<Repository, Repository> cloneRepository(HgCommandContext context, File target, String initialBranch) throws IOException {
public ParentAndClone<Repository, Repository> cloneRepository(HgCommandContext context, File target, String initialBranch) throws WorkingCopyFailedException {
Repository centralRepository = openCentral(context);
CloneCommand cloneCommand = CloneCommandFlags.on(centralRepository);
if (initialBranch != null) {
cloneCommand.updaterev(initialBranch);
}
cloneCommand.execute(target.getAbsolutePath());
try {
cloneCommand.execute(target.getAbsolutePath());
} catch (IOException e) {
throw new WorkingCopyFailedException(e);
}
BaseRepository clone = Repository.open(target);

View File

@@ -31,13 +31,10 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.rules.TemporaryFolder;
import org.junitpioneer.jupiter.TempDirectory;
import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgTestUtil;
import sonia.scm.repository.util.CachingAllWorkingCopyPool;
import sonia.scm.repository.util.NoneCachingWorkingCopyPool;
import sonia.scm.repository.util.WorkdirProvider;
import sonia.scm.repository.util.WorkingCopy;
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
@@ -50,7 +47,6 @@ import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(TempDirectory.class)
public class SimpleHgWorkingCopyFactoryTest extends AbstractHgCommandTestBase {
@Rule

View File

@@ -37,6 +37,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.SvnWorkingCopyFactory;
import sonia.scm.repository.util.WorkingCopyPool;
import sonia.scm.repository.util.SimpleWorkingCopyFactory;
import sonia.scm.repository.util.WorkingCopyPool.ParentAndClone;
import javax.inject.Inject;
import java.io.File;