From baa4f6a91731ffb77d8b71522c4be4ee0d084dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 28 Aug 2019 13:53:24 +0200 Subject: [PATCH] Make modify command available --- .../repository/api/ModifyCommandBuilder.java | 160 +++++++++++++----- .../scm/repository/api/RepositoryService.java | 31 +++- .../api/RepositoryServiceFactory.java | 5 +- .../spi/RepositoryServiceProvider.java | 8 + .../repository/api/RepositoryServiceTest.java | 6 +- 5 files changed, 162 insertions(+), 48 deletions(-) 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 index dc3ed4a331..501a2ac3b0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java @@ -15,6 +15,29 @@ 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 final ModifyCommand command; @@ -27,37 +50,127 @@ public class ModifyCommandBuilder { this.workdir = workdirProvider.createNewWorkdir(); } - ModifyCommandBuilder setBranchToModify(String branchToModify) { + /** + * 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; } - ContentLoader createFile(String path) { + /** + * 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)) ); } - ContentLoader modifyFile(String path) { + /** + * 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)) ); } - ModifyCommandBuilder deleteFile(String path) { + /** + * 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; } - ModifyCommandBuilder moveFile(String sourcePath, String targetPath) { + /** + * 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; } - String execute() { + /** + * Apply the changes and create a new commit with the given message and author. + * @return The revision of the new commit. + */ + public String execute() { Preconditions.checkArgument(request.isValid(), "commit message, author and branch are required"); return command.execute(request); } + /** + * 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; + } + } + private File loadData(ByteSource data) throws IOException { File file = createTemporaryFile(); data.copyTo(Files.asByteSink(file)); @@ -75,39 +188,4 @@ public class ModifyCommandBuilder { private File createTemporaryFile() throws IOException { return File.createTempFile("upload-", "", workdir); } - - public ModifyCommandBuilder setCommitMessage(String message) { - request.setCommitMessage(message); - return this; - } - - public ModifyCommandBuilder setAuthor(Person author) { - request.setAuthor(author); - return this; - } - - public ModifyCommandBuilder setBranch(String branch) { - request.setBranch(branch); - return this; - } - - public class ContentLoader { - - private final Consumer contentConsumer; - - private ContentLoader(Consumer contentConsumer) { - this.contentConsumer = contentConsumer; - } - - public ModifyCommandBuilder withData(ByteSource data) throws IOException { - File content = loadData(data); - contentConsumer.accept(content); - return ModifyCommandBuilder.this; - } - public ModifyCommandBuilder withData(InputStream data) throws IOException { - File content = loadData(data); - contentConsumer.accept(content); - return ModifyCommandBuilder.this; - } - } } 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/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/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);