diff --git a/scm-core/src/main/java/sonia/scm/repository/api/Command.java b/scm-core/src/main/java/sonia/scm/repository/api/Command.java index 3249e54ec3..8e95e314bb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/Command.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/Command.java @@ -66,5 +66,5 @@ public enum Command /** * @since 2.0 */ - MODIFICATIONS, MERGE, DIFF_RESULT, BRANCH; + MODIFICATIONS, MERGE, DIFF_RESULT, BRANCH, MODIFY; } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java new file mode 100644 index 0000000000..28adca18ae --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java @@ -0,0 +1,206 @@ +package sonia.scm.repository.api; + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteSource; +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.Person; +import sonia.scm.repository.spi.ModifyCommand; +import sonia.scm.repository.spi.ModifyCommandRequest; +import sonia.scm.repository.util.WorkdirProvider; +import sonia.scm.util.IOUtil; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.function.Consumer; + +/** + * Use this {@link ModifyCommandBuilder} to make file changes to the head of a branch. You can + * + * + * You can collect multiple changes before they are executed with a call to {@link #execute()}. + * + *

Example: + *

+ * commandBuilder
+ *     .setBranch("feature/branch")
+ *     .setCommitMessage("make some changes")
+ *     .setAuthor(new Person())
+ *     .createFile("file/to/create").withData(inputStream)
+ *     .deleteFile("old/file/to/delete")
+ *     .execute();
+ * 
+ *

+ */ +public class ModifyCommandBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(ModifyCommandBuilder.class); + + private final ModifyCommand command; + private final File workdir; + + private final ModifyCommandRequest request = new ModifyCommandRequest(); + + ModifyCommandBuilder(ModifyCommand command, WorkdirProvider workdirProvider) { + this.command = command; + this.workdir = workdirProvider.createNewWorkdir(); + } + + /** + * Set the branch that should be modified. The new commit will be made for this branch. + * @param branchToModify The branch to modify. + * @return This builder instance. + */ + public ModifyCommandBuilder setBranchToModify(String branchToModify) { + return this; + } + + /** + * Create a new file. The content of the file will be specified in a subsequent call to + * {@link ContentLoader#withData(ByteSource)} or {@link ContentLoader#withData(InputStream)}. + * @param path The path and the name of the file that should be created. + * @return The loader to specify the content of the new file. + */ + public ContentLoader createFile(String path) { + return new ContentLoader( + content -> request.addRequest(new ModifyCommandRequest.CreateFileRequest(path, content)) + ); + } + + /** + * Modify an existing file. The new content of the file will be specified in a subsequent call to + * {@link ContentLoader#withData(ByteSource)} or {@link ContentLoader#withData(InputStream)}. + * @param path The path and the name of the file that should be modified. + * @return The loader to specify the new content of the file. + */ + public ContentLoader modifyFile(String path) { + return new ContentLoader( + content -> request.addRequest(new ModifyCommandRequest.ModifyFileRequest(path, content)) + ); + } + + /** + * Delete an existing file. + * @param path The path and the name of the file that should be deleted. + * @return This builder instance. + */ + public ModifyCommandBuilder deleteFile(String path) { + request.addRequest(new ModifyCommandRequest.DeleteFileRequest(path)); + return this; + } + + /** + * Move an existing file. + * @param sourcePath The path and the name of the file that should be moved. + * @param targetPath The new path and name the file should be moved to. + * @return This builder instance. + */ + public ModifyCommandBuilder moveFile(String sourcePath, String targetPath) { + request.addRequest(new ModifyCommandRequest.MoveFileRequest(sourcePath, targetPath)); + return this; + } + + /** + * Apply the changes and create a new commit with the given message and author. + * @return The revision of the new commit. + */ + public String execute() { + try { + Preconditions.checkArgument(request.isValid(), "commit message, author and branch are required"); + return command.execute(request); + } finally { + try { + IOUtil.delete(workdir); + } catch (IOException e) { + LOG.warn("could not delete temporary workdir '{}'", workdir, e); + } + } + } + + /** + * Set the commit message for the new commit. + * @return This builder instance. + */ + public ModifyCommandBuilder setCommitMessage(String message) { + request.setCommitMessage(message); + return this; + } + + /** + * Set the author for the new commit. + * @return This builder instance. + */ + public ModifyCommandBuilder setAuthor(Person author) { + request.setAuthor(author); + return this; + } + + /** + * Set the branch the changes should be made upon. + * @return This builder instance. + */ + public ModifyCommandBuilder setBranch(String branch) { + request.setBranch(branch); + return this; + } + + public class ContentLoader { + + private final Consumer contentConsumer; + + private ContentLoader(Consumer contentConsumer) { + this.contentConsumer = contentConsumer; + } + + /** + * Specify the data of the file using a {@link ByteSource}. + * @return The builder instance. + * @throws IOException If the data could not be read. + */ + public ModifyCommandBuilder withData(ByteSource data) throws IOException { + File content = loadData(data); + contentConsumer.accept(content); + return ModifyCommandBuilder.this; + } + + /** + * Specify the data of the file using an {@link InputStream}. + * @return The builder instance. + * @throws IOException If the data could not be read. + */ + public ModifyCommandBuilder withData(InputStream data) throws IOException { + File content = loadData(data); + contentConsumer.accept(content); + return ModifyCommandBuilder.this; + } + } + + @SuppressWarnings("UnstableApiUsage") // Files only used internal + private File loadData(ByteSource data) throws IOException { + File file = createTemporaryFile(); + data.copyTo(Files.asByteSink(file)); + return file; + } + + @SuppressWarnings("UnstableApiUsage") // Files and ByteStreams only used internal + private File loadData(InputStream data) throws IOException { + File file = createTemporaryFile(); + try (OutputStream out = Files.asByteSink(file).openBufferedStream()) { + ByteStreams.copy(data, out); + } + return file; + } + + private File createTemporaryFile() throws IOException { + return File.createTempFile("upload-", "", workdir); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index 5807ffa998..080d66ebf2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -40,6 +40,7 @@ import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.RepositoryServiceProvider; +import sonia.scm.repository.util.WorkdirProvider; import java.io.Closeable; import java.io.IOException; @@ -91,22 +92,25 @@ public final class RepositoryService implements Closeable { private final RepositoryServiceProvider provider; private final Repository repository; private final Set protocolProviders; + private final WorkdirProvider workdirProvider; /** * Constructs a new {@link RepositoryService}. This constructor should only * be called from the {@link RepositoryServiceFactory}. - * @param cacheManager cache manager + * @param cacheManager cache manager * @param provider implementation for {@link RepositoryServiceProvider} * @param repository the repository + * @param workdirProvider */ RepositoryService(CacheManager cacheManager, - RepositoryServiceProvider provider, Repository repository, - PreProcessorUtil preProcessorUtil, Set protocolProviders) { + RepositoryServiceProvider provider, Repository repository, + PreProcessorUtil preProcessorUtil, Set protocolProviders, WorkdirProvider workdirProvider) { this.cacheManager = cacheManager; this.provider = provider; this.repository = repository; this.preProcessorUtil = preProcessorUtil; this.protocolProviders = protocolProviders; + this.workdirProvider = workdirProvider; } /** @@ -399,6 +403,27 @@ public final class RepositoryService implements Closeable { return new MergeCommandBuilder(provider.getMergeCommand()); } + /** + * The modify command makes changes to the head of a branch. It is possible to + *
    + *
  • create new files
  • + *
  • delete existing files
  • + *
  • modify/replace files
  • + *
  • move files
  • + *
+ * + * @return instance of {@link ModifyCommandBuilder} + * @throws CommandNotSupportedException if the command is not supported + * by the implementation of the repository service provider. + * @since 2.0.0 + */ + public ModifyCommandBuilder getModifyCommand() { + LOG.debug("create modify command for repository {}", + repository.getNamespaceAndName()); + + return new ModifyCommandBuilder(provider.getModifyCommand(), workdirProvider); + } + /** * Returns true if the command is supported by the repository service. * diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java index 8db3ab7546..fe2af9f9e1 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java @@ -61,6 +61,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceResolver; +import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.security.ScmSecurityException; import java.util.Set; @@ -256,7 +257,7 @@ public final class RepositoryServiceFactory } service = new RepositoryService(cacheManager, provider, repository, - preProcessorUtil, protocolProviders); + preProcessorUtil, protocolProviders, workdirProvider); break; } @@ -373,4 +374,6 @@ public final class RepositoryServiceFactory private final Set resolvers; private Set protocolProviders; + + private WorkdirProvider workdirProvider; } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java new file mode 100644 index 0000000000..07b29e8d21 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java @@ -0,0 +1,18 @@ +package sonia.scm.repository.spi; + +import java.io.File; + +public interface ModifyCommand { + + String execute(ModifyCommandRequest request); + + interface Worker { + void delete(String toBeDeleted); + + void create(String toBeCreated, File file); + + void modify(String path, File file); + + void move(String sourcePath, String targetPath); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java new file mode 100644 index 0000000000..e8f2223bdd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java @@ -0,0 +1,155 @@ +package sonia.scm.repository.spi; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.Validateable; +import sonia.scm.repository.Person; +import sonia.scm.util.IOUtil; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ModifyCommandRequest implements Resetable, Validateable { + + private static final Logger LOG = LoggerFactory.getLogger(ModifyCommandRequest.class); + + private final List requests = new ArrayList<>(); + + private Person author; + private String commitMessage; + private String branch; + + @Override + public void reset() { + requests.clear(); + author = null; + commitMessage = null; + branch = null; + } + + public void addRequest(PartialRequest request) { + this.requests.add(request); + } + + public void setAuthor(Person author) { + this.author = author; + } + + public void setCommitMessage(String commitMessage) { + this.commitMessage = commitMessage; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public List getRequests() { + return Collections.unmodifiableList(requests); + } + + public Person getAuthor() { + return author; + } + + public String getCommitMessage() { + return commitMessage; + } + + public String getBranch() { + return branch; + } + + @Override + public boolean isValid() { + return StringUtils.isNotEmpty(commitMessage) && StringUtils.isNotEmpty(branch) && author != null && !requests.isEmpty(); + } + + public interface PartialRequest { + void execute(ModifyCommand.Worker worker); + } + + public static class DeleteFileRequest implements PartialRequest { + private final String path; + + public DeleteFileRequest(String path) { + this.path = path; + } + + @Override + public void execute(ModifyCommand.Worker worker) { + worker.delete(path); + } + } + + public static class MoveFileRequest implements PartialRequest { + private final String sourcePath; + private final String targetPath; + + public MoveFileRequest(String sourcePath, String targetPath) { + this.sourcePath = sourcePath; + this.targetPath = targetPath; + } + + @Override + public void execute(ModifyCommand.Worker worker) { + worker.move(sourcePath, targetPath); + } + } + + private abstract static class ContentModificationRequest implements PartialRequest { + + private final File content; + + ContentModificationRequest(File content) { + this.content = content; + } + + File getContent() { + return content; + } + + void cleanup() { + try { + IOUtil.delete(content); + } catch (IOException e) { + LOG.warn("could not delete temporary file {}", content, e); + } + } + } + + public static class CreateFileRequest extends ContentModificationRequest { + + private final String path; + + public CreateFileRequest(String path, File content) { + super(content); + this.path = path; + } + + @Override + public void execute(ModifyCommand.Worker worker) { + worker.create(path, getContent()); + cleanup(); + } + } + + public static class ModifyFileRequest extends ContentModificationRequest { + + private final String path; + + public ModifyFileRequest(String path, File content) { + super(content); + this.path = path; + } + + @Override + public void execute(ModifyCommand.Worker worker) { + worker.modify(path, getContent()); + cleanup(); + } + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java index bf9cdf6a25..cdd0417cf7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java @@ -275,4 +275,12 @@ public abstract class RepositoryServiceProvider implements Closeable { throw new CommandNotSupportedException(Command.MERGE); } + + /** + * @since 2.0 + */ + public ModifyCommand getModifyCommand() + { + throw new CommandNotSupportedException(Command.MODIFY); + } } 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 0872242612..7236e0c3fe 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 @@ -7,29 +7,21 @@ import sonia.scm.repository.Repository; import java.io.File; import java.io.IOException; -import java.nio.file.Files; public abstract class SimpleWorkdirFactory implements WorkdirFactory { private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); - private final File poolDirectory; + private final WorkdirProvider workdirProvider; - public SimpleWorkdirFactory() { - this(new File(System.getProperty("scm.workdir" , System.getProperty("java.io.tmpdir")), "scm-work")); - } - - public SimpleWorkdirFactory(File poolDirectory) { - this.poolDirectory = poolDirectory; - if (!poolDirectory.exists() && !poolDirectory.mkdirs()) { - throw new IllegalStateException("could not create pool directory " + poolDirectory); - } + public SimpleWorkdirFactory(WorkdirProvider workdirProvider) { + this.workdirProvider = workdirProvider; } @Override public WorkingCopy createWorkingCopy(C context) { try { - File directory = createNewWorkdir(); + File directory = workdirProvider.createNewWorkdir(); ParentAndClone parentAndClone = cloneRepository(context, directory); return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::close, directory); } catch (IOException e) { @@ -45,10 +37,6 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory protected abstract ParentAndClone cloneRepository(C context, File target) throws IOException; - private File createNewWorkdir() throws IOException { - return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); - } - private void close(R repository) { try { closeRepository(repository); diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirProvider.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirProvider.java new file mode 100644 index 0000000000..35dae56faa --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirProvider.java @@ -0,0 +1,29 @@ +package sonia.scm.repository.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class WorkdirProvider { + + private final File poolDirectory; + + public WorkdirProvider() { + this(new File(System.getProperty("scm.workdir" , System.getProperty("java.io.tmpdir")), "scm-work")); + } + + public WorkdirProvider(File poolDirectory) { + this.poolDirectory = poolDirectory; + if (!poolDirectory.exists() && !poolDirectory.mkdirs()) { + throw new IllegalStateException("could not create pool directory " + poolDirectory); + } + } + + public File createNewWorkdir() { + try { + return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); + } catch (IOException e) { + throw new RuntimeException("could not create temporary workdir", e); + } + } +} diff --git a/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java b/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java new file mode 100644 index 0000000000..7d4d96642a --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java @@ -0,0 +1,184 @@ +package sonia.scm.repository.api; + +import com.google.common.io.ByteSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; +import sonia.scm.repository.Person; +import sonia.scm.repository.spi.ModifyCommand; +import sonia.scm.repository.spi.ModifyCommandRequest; +import sonia.scm.repository.util.WorkdirProvider; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@ExtendWith(TempDirectory.class) +class ModifyCommandBuilderTest { + + @Mock + ModifyCommand command; + @Mock + WorkdirProvider workdirProvider; + @Mock + ModifyCommand.Worker worker; + + ModifyCommandBuilder commandBuilder; + Path workdir; + + @BeforeEach + void initWorkdir(@TempDirectory.TempDir Path temp) throws IOException { + workdir = Files.createDirectory(temp.resolve("workdir")); + lenient().when(workdirProvider.createNewWorkdir()).thenReturn(workdir.toFile()); + commandBuilder = new ModifyCommandBuilder(command, workdirProvider); + } + + @BeforeEach + void initRequestCaptor() { + when(command.execute(any())).thenAnswer( + invocation -> { + ModifyCommandRequest request = invocation.getArgument(0); + request.getRequests().forEach(r -> r.execute(worker)); + return "target"; + } + ); + } + + @Test + void shouldReturnTargetRevisionFromCommit() { + String targetRevision = initCommand() + .deleteFile("toBeDeleted") + .execute(); + + assertThat(targetRevision).isEqualTo("target"); + } + + @Test + void shouldExecuteDelete() { + initCommand() + .deleteFile("toBeDeleted") + .execute(); + + verify(worker).delete("toBeDeleted"); + } + + @Test + void shouldExecuteMove() { + initCommand() + .moveFile("source", "target") + .execute(); + + verify(worker).move("source", "target"); + } + + @Test + void shouldExecuteCreateWithByteSourceContent() throws IOException { + ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); + List contentCaptor = new ArrayList<>(); + doAnswer(new ExtractContent(contentCaptor)).when(worker).create(nameCaptor.capture(), any()); + + initCommand() + .createFile("toBeCreated").withData(ByteSource.wrap("content".getBytes())) + .execute(); + + assertThat(nameCaptor.getValue()).isEqualTo("toBeCreated"); + assertThat(contentCaptor).contains("content"); + } + + @Test + void shouldExecuteCreateWithInputStreamContent() throws IOException { + ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); + List contentCaptor = new ArrayList<>(); + doAnswer(new ExtractContent(contentCaptor)).when(worker).create(nameCaptor.capture(), any()); + + initCommand() + .createFile("toBeCreated").withData(new ByteArrayInputStream("content".getBytes())) + .execute(); + + assertThat(nameCaptor.getValue()).isEqualTo("toBeCreated"); + assertThat(contentCaptor).contains("content"); + } + + @Test + void shouldExecuteCreateMultipleTimes() throws IOException { + ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); + List contentCaptor = new ArrayList<>(); + doAnswer(new ExtractContent(contentCaptor)).when(worker).create(nameCaptor.capture(), any()); + + initCommand() + .createFile("toBeCreated_1").withData(new ByteArrayInputStream("content_1".getBytes())) + .createFile("toBeCreated_2").withData(new ByteArrayInputStream("content_2".getBytes())) + .execute(); + + List createdNames = nameCaptor.getAllValues(); + assertThat(createdNames.get(0)).isEqualTo("toBeCreated_1"); + assertThat(createdNames.get(1)).isEqualTo("toBeCreated_2"); + assertThat(contentCaptor).contains("content_1", "content_2"); + } + + @Test + void shouldExecuteModify() throws IOException { + ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); + List contentCaptor = new ArrayList<>(); + doAnswer(new ExtractContent(contentCaptor)).when(worker).modify(nameCaptor.capture(), any()); + + initCommand() + .modifyFile("toBeModified").withData(ByteSource.wrap("content".getBytes())) + .execute(); + + assertThat(nameCaptor.getValue()).isEqualTo("toBeModified"); + assertThat(contentCaptor).contains("content"); + } + + private ModifyCommandBuilder initCommand() { + return commandBuilder + .setBranch("branch") + .setCommitMessage("message") + .setAuthor(new Person()); + } + + @Test + void shouldDeleteTemporaryFiles(@TempDirectory.TempDir Path temp) throws IOException { + ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + doNothing().when(worker).modify(nameCaptor.capture(), fileCaptor.capture()); + + initCommand() + .modifyFile("toBeModified").withData(ByteSource.wrap("content".getBytes())) + .execute(); + + assertThat(Files.list(temp)).isEmpty(); + } + + private static class ExtractContent implements Answer { + private final List contentCaptor; + + public ExtractContent(List contentCaptor) { + this.contentCaptor = contentCaptor; + } + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return contentCaptor.add(Files.readAllLines(((File) invocation.getArgument(1)).toPath()).get(0)); + } + } +} diff --git a/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java b/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java index 2ceafc19bb..330a4c956a 100644 --- a/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java @@ -24,7 +24,7 @@ public class RepositoryServiceTest { @Test public void shouldReturnMatchingProtocolsFromProvider() { - RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider())); + RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null); Stream supportedProtocols = repositoryService.getSupportedProtocols(); assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1); @@ -32,7 +32,7 @@ public class RepositoryServiceTest { @Test public void shouldFindKnownProtocol() { - RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider())); + RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null); HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class); @@ -41,7 +41,7 @@ public class RepositoryServiceTest { @Test public void shouldFailForUnknownProtocol() { - RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider())); + RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null); assertThrows(IllegalArgumentException.class, () -> { repositoryService.getProtocol(UnknownScmProtocol.class); 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 2d3c2ed59e..31aa11c604 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 @@ -28,7 +28,8 @@ public class SimpleWorkdirFactoryTest { @Before public void initFactory() throws IOException { - simpleWorkdirFactory = new SimpleWorkdirFactory(temporaryFolder.newFolder()) { + WorkdirProvider workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); + simpleWorkdirFactory = new SimpleWorkdirFactory(workdirProvider) { @Override protected Repository getScmRepository(Context context) { return REPOSITORY; 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 c30ff59afb..9edcf0c0ea 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 @@ -7,16 +7,16 @@ import org.eclipse.jgit.transport.ScmTransportProtocol; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.repository.util.WorkdirProvider; +import javax.inject.Inject; import java.io.File; public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { - public SimpleGitWorkdirFactory() { - } - - SimpleGitWorkdirFactory(File poolDirectory) { - super(poolDirectory); + @Inject + public SimpleGitWorkdirFactory(WorkdirProvider workdirProvider) { + super(workdirProvider); } @Override 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 e429aa5a42..aa5e641b31 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 @@ -5,6 +5,7 @@ import org.junit.Rule; import org.junit.Test; import sonia.scm.repository.Branch; import sonia.scm.repository.api.BranchRequest; +import sonia.scm.repository.util.WorkdirProvider; import java.io.IOException; import java.util.List; @@ -25,7 +26,7 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { branchRequest.setParentBranch(source.getName()); branchRequest.setNewBranch("new_branch"); - new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch(branchRequest); + new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).branch(branchRequest); Branch newBranch = findBranch(context, "new_branch"); Assertions.assertThat(newBranch.getRevision()).isEqualTo(source.getRevision()); @@ -45,7 +46,7 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { BranchRequest branchRequest = new BranchRequest(); branchRequest.setNewBranch("new_branch"); - new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch(branchRequest); + new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).branch(branchRequest); 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 e8a62f5b86..8b3b4548d9 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 @@ -24,6 +24,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; +import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.user.User; import java.io.IOException; @@ -244,6 +245,6 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { } private GitMergeCommand createCommand() { - return new GitMergeCommand(createContext(), repository, new SimpleGitWorkdirFactory()); + return new GitMergeCommand(createContext(), repository, new SimpleGitWorkdirFactory(new WorkdirProvider())); } } 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 616df5e68e..1b3c730ef1 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 @@ -11,6 +11,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.WorkdirProvider; import sonia.scm.repository.util.WorkingCopy; import java.io.File; @@ -29,19 +30,21 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { // keep this so that it will not be garbage collected (Transport keeps this in a week reference) private ScmTransportProtocol proto; + private WorkdirProvider workdirProvider; @Before - public void bindScmProtocol() { + public void bindScmProtocol() throws IOException { HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); HookEventFacade hookEventFacade = new HookEventFacade(of(mock(RepositoryManager.class)), hookContextFactory); GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); proto = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); Transport.register(proto); + workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); } @Test public void emptyPoolShouldCreateNewWorkdir() throws IOException { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); File masterRepo = createRepositoryDirectory(); try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { @@ -59,7 +62,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void cloneFromPoolShouldNotBeReused() throws IOException { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); File firstDirectory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { @@ -73,7 +76,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { @Test public void cloneFromPoolShouldBeDeletedOnClose() throws IOException { - SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); + SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); File directory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { 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 9565f672d5..b9194145e6 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 @@ -4,6 +4,7 @@ import com.aragost.javahg.Repository; import com.aragost.javahg.commands.CloneCommand; import com.aragost.javahg.commands.PullCommand; import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.HgRepositoryEnvironmentBuilder; import javax.inject.Inject; @@ -18,7 +19,8 @@ public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory hgRepositoryEnvironmentBuilder; @Inject - public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder) { + public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder, WorkdirProvider workdirProvider) { + super(workdirProvider); this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; } @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 c015aa0f62..7976af8b3b 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 @@ -7,6 +7,7 @@ import org.junit.Test; import sonia.scm.repository.Branch; import sonia.scm.repository.HgTestUtil; import sonia.scm.repository.api.BranchRequest; +import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.HgRepositoryEnvironmentBuilder; import java.io.IOException; @@ -20,7 +21,7 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager()); - SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder)) { + SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), new WorkdirProvider()) { @Override public void configure(PullCommand pullCommand) { // we do not want to configure http hooks in this unit test