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
+ *
+ * - create new files ({@link #createFile(String)}
+ * - modify existing files ({@link #modifyFile(String)}
+ * - delete existing files ({@link #deleteFile(String)}
+ * - move/rename existing files ({@link #moveFile(String, String)}
+ *
+ *
+ * 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);