diff --git a/scm-core/pom.xml b/scm-core/pom.xml
index f19d50064d..8437b256b2 100644
--- a/scm-core/pom.xml
+++ b/scm-core/pom.xml
@@ -131,6 +131,12 @@
mapstruct-jdk8
+
+ org.mapstruct
+ mapstruct-processor
+ provided
+
+
com.webcohesion.enunciate
@@ -208,6 +214,12 @@
shiro-unit
test
+
+ org.hibernate
+ hibernate-validator
+ 5.3.6.Final
+ compile
+
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java
similarity index 100%
rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java
rename to scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java
index 4ec78c9bdc..33f4a482ac 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java
@@ -68,6 +68,10 @@ public final class BranchCommandBuilder {
return command.branch(request);
}
+ public void delete(String branchName) {
+ command.deleteOrClose(branchName);
+ }
+
private BranchCommand command;
private BranchRequest request = new BranchRequest();
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java
index 917b81391f..2836fc537d 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java
@@ -178,7 +178,7 @@ public final class LogCommandBuilder
logger.debug("get changeset for {} with disabled cache", id);
}
- changeset = logCommand.getChangeset(id);
+ changeset = logCommand.getChangeset(id, request);
}
else
{
@@ -192,7 +192,7 @@ public final class LogCommandBuilder
logger.debug("get changeset for {}", id);
}
- changeset = logCommand.getChangeset(id);
+ changeset = logCommand.getChangeset(id, request);
if (changeset != null)
{
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java
index 0a2267e888..6c4324c259 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java
@@ -6,6 +6,8 @@ import sonia.scm.repository.spi.MergeCommand;
import sonia.scm.repository.spi.MergeCommandRequest;
import sonia.scm.repository.util.AuthorUtil;
+import java.util.Set;
+
/**
* Use this {@link MergeCommandBuilder} to merge two branches of a repository ({@link #executeMerge()}) or to check if
* the branches could be merged without conflicts ({@link #dryRun()}). To do so, you have to specify the name of
@@ -55,6 +57,24 @@ public class MergeCommandBuilder {
this.mergeCommand = mergeCommand;
}
+ /**
+ * Use this to check if merge-strategy is supported by mergeCommand.
+ *
+ * @return boolean.
+ */
+ public boolean isSupported(MergeStrategy strategy) {
+ return mergeCommand.isSupported(strategy);
+ }
+
+ /**
+ * Use this to get a Set of all supported merge strategies by merge command.
+ *
+ * @return boolean.
+ */
+ public Set getSupportedMergeStrategies() {
+ return mergeCommand.getSupportedMergeStrategies();
+ }
+
/**
* Use this to set the branch that should be merged into the target branch.
*
@@ -92,6 +112,21 @@ public class MergeCommandBuilder {
return this;
}
+ /**
+ * Use this to set the strategy of the merge commit manually.
+ *
+ * This is optional and for {@link #executeMerge()} only.
+ *
+ * @return This builder instance.
+ */
+ public MergeCommandBuilder setMergeStrategy(MergeStrategy strategy) {
+ if (!mergeCommand.isSupported(strategy)) {
+ throw new IllegalArgumentException("merge strategy not supported: " + strategy);
+ }
+ request.setMergeStrategy(strategy);
+ return this;
+ }
+
/**
* Use this to set a template for the commit message. If no message is set, a default message will be used.
*
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java
new file mode 100644
index 0000000000..4ccf1f706b
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java
@@ -0,0 +1,7 @@
+package sonia.scm.repository.api;
+
+public enum MergeStrategy {
+ MERGE_COMMIT,
+ FAST_FORWARD_IF_POSSIBLE,
+ SQUASH
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java
new file mode 100644
index 0000000000..ae9fbb08b4
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java
@@ -0,0 +1,27 @@
+package sonia.scm.repository.api;
+
+import sonia.scm.BadRequestException;
+import sonia.scm.repository.Repository;
+
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
+
+@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
+public class MergeStrategyNotSupportedException extends BadRequestException {
+
+ private static final long serialVersionUID = 256498734456613496L;
+
+ private static final String CODE = "6eRhF9gU41";
+
+ public MergeStrategyNotSupportedException(Repository repository, MergeStrategy strategy) {
+ super(entity(repository).build(), createMessage(strategy));
+ }
+
+ @Override
+ public String getCode() {
+ return CODE;
+ }
+
+ private static String createMessage(MergeStrategy strategy) {
+ return "merge strategy " + strategy + " is not supported by this repository";
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java
index c659c7fac0..d5ba7f8dca 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java
@@ -41,4 +41,6 @@ import sonia.scm.repository.api.BranchRequest;
*/
public interface BranchCommand {
Branch branch(BranchRequest name);
+
+ void deleteOrClose(String branchName);
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java b/scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java
new file mode 100644
index 0000000000..0053d5384a
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java
@@ -0,0 +1,19 @@
+package sonia.scm.repository.spi;
+
+import sonia.scm.ContextEntry;
+import sonia.scm.ExceptionWithContext;
+import sonia.scm.repository.Repository;
+
+public class CannotDeleteDefaultBranchException extends ExceptionWithContext {
+
+ public static final String CODE = "78RhWxTIw1";
+
+ public CannotDeleteDefaultBranchException(Repository repository, String branchName) {
+ super(ContextEntry.ContextBuilder.entity("Branch", branchName).in(repository).build(), "default branch cannot be deleted");
+ }
+
+ @Override
+ public String getCode() {
+ return CODE;
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java
index f4babcee72..21d9ece4de 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java
@@ -49,7 +49,7 @@ import java.io.IOException;
*/
public interface LogCommand {
- Changeset getChangeset(String id) throws IOException;
+ Changeset getChangeset(String id, LogCommandRequest request) throws IOException;
ChangesetPagingResult getChangesets(LogCommandRequest request) throws IOException;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java
index 0a3680f6b3..a62e373dca 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java
@@ -2,9 +2,16 @@ package sonia.scm.repository.spi;
import sonia.scm.repository.api.MergeCommandResult;
import sonia.scm.repository.api.MergeDryRunCommandResult;
+import sonia.scm.repository.api.MergeStrategy;
+
+import java.util.Set;
public interface MergeCommand {
MergeCommandResult merge(MergeCommandRequest request);
MergeDryRunCommandResult dryRun(MergeCommandRequest request);
+
+ boolean isSupported(MergeStrategy strategy);
+
+ Set getSupportedMergeStrategies();
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java
index 223cf8c49e..0751920d01 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java
@@ -5,6 +5,7 @@ import com.google.common.base.Objects;
import com.google.common.base.Strings;
import sonia.scm.Validateable;
import sonia.scm.repository.Person;
+import sonia.scm.repository.api.MergeStrategy;
import sonia.scm.repository.util.AuthorUtil.CommandWithAuthor;
import java.io.Serializable;
@@ -17,6 +18,7 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl
private String targetBranch;
private Person author;
private String messageTemplate;
+ private MergeStrategy mergeStrategy;
public String getBranchToMerge() {
return branchToMerge;
@@ -50,6 +52,14 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl
this.messageTemplate = messageTemplate;
}
+ public MergeStrategy getMergeStrategy() {
+ return mergeStrategy;
+ }
+
+ public void setMergeStrategy(MergeStrategy mergeStrategy) {
+ this.mergeStrategy = mergeStrategy;
+ }
+
public boolean isValid() {
return !Strings.isNullOrEmpty(getBranchToMerge())
&& !Strings.isNullOrEmpty(getTargetBranch());
@@ -74,12 +84,13 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl
return Objects.equal(branchToMerge, other.branchToMerge)
&& Objects.equal(targetBranch, other.targetBranch)
- && Objects.equal(author, other.author);
+ && Objects.equal(author, other.author)
+ && Objects.equal(mergeStrategy, other.mergeStrategy);
}
@Override
public int hashCode() {
- return Objects.hashCode(branchToMerge, targetBranch, author);
+ return Objects.hashCode(branchToMerge, targetBranch, author, mergeStrategy);
}
@Override
@@ -88,6 +99,7 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl
.add("branchToMerge", branchToMerge)
.add("targetBranch", targetBranch)
.add("author", author)
+ .add("mergeStrategy", mergeStrategy)
.toString();
}
}
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 a1073e24f2..eaa9487f3c 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
@@ -8,7 +8,7 @@ import sonia.scm.repository.Repository;
import java.io.File;
import java.io.IOException;
-public abstract class SimpleWorkdirFactory implements WorkdirFactory {
+public abstract class SimpleWorkdirFactory implements WorkdirFactory {
private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class);
@@ -19,11 +19,11 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory
}
@Override
- public WorkingCopy createWorkingCopy(C context, String initialBranch) {
+ public WorkingCopy createWorkingCopy(C context, String initialBranch) {
try {
File directory = workdirProvider.createNewWorkdir();
- ParentAndClone parentAndClone = cloneRepository(context, directory, initialBranch);
- return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::close, directory);
+ ParentAndClone parentAndClone = cloneRepository(context, directory, initialBranch);
+ return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::closeWorkdir, this::closeCentral, directory);
} catch (IOException e) {
throw new InternalRepositoryException(getScmRepository(context), "could not clone repository in temporary directory", e);
}
@@ -32,12 +32,15 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory
protected abstract Repository getScmRepository(C context);
@SuppressWarnings("squid:S00112")
- // We do allow implementations to throw arbitrary exceptions here, so that we can handle them in close
+ // We do allow implementations to throw arbitrary exceptions here, so that we can handle them in closeCentral
protected abstract void closeRepository(R repository) throws Exception;
+ @SuppressWarnings("squid:S00112")
+ // We do allow implementations to throw arbitrary exceptions here, so that we can handle them in closeWorkdir
+ protected abstract void closeWorkdirInternal(W workdir) throws Exception;
- protected abstract ParentAndClone cloneRepository(C context, File target, String initialBranch) throws IOException;
+ protected abstract ParentAndClone cloneRepository(C context, File target, String initialBranch) throws IOException;
- private void close(R repository) {
+ private void closeCentral(R repository) {
try {
closeRepository(repository);
} catch (Exception e) {
@@ -45,11 +48,19 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory
}
}
- protected static class ParentAndClone {
- private final R parent;
- private final R clone;
+ private void closeWorkdir(W repository) {
+ try {
+ closeWorkdirInternal(repository);
+ } catch (Exception e) {
+ logger.warn("could not close temporary repository clone", e);
+ }
+ }
- public ParentAndClone(R parent, R clone) {
+ protected static class ParentAndClone {
+ private final R parent;
+ private final W clone;
+
+ public ParentAndClone(R parent, W clone) {
this.parent = parent;
this.clone = clone;
}
@@ -58,7 +69,7 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory
return parent;
}
- public R getClone() {
+ public W getClone() {
return clone;
}
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java
index bddf03adaa..e1df5e99f3 100644
--- a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java
+++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java
@@ -1,5 +1,5 @@
package sonia.scm.repository.util;
-public interface WorkdirFactory {
- WorkingCopy createWorkingCopy(C context, String initialBranch);
+public interface WorkdirFactory {
+ WorkingCopy createWorkingCopy(C context, String initialBranch);
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java
index 3c96184142..12c859fb8c 100644
--- a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java
+++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java
@@ -8,23 +8,25 @@ import java.io.File;
import java.io.IOException;
import java.util.function.Consumer;
-public class WorkingCopy implements AutoCloseable {
+public class WorkingCopy implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class);
private final File directory;
- private final R workingRepository;
+ private final W workingRepository;
private final R centralRepository;
- private final Consumer cleanup;
+ private final Consumer cleanupWorkdir;
+ private final Consumer cleanupCentral;
- public WorkingCopy(R workingRepository, R centralRepository, Consumer cleanup, File directory) {
+ public WorkingCopy(W workingRepository, R centralRepository, Consumer cleanupWorkdir, Consumer cleanupCentral, File directory) {
this.directory = directory;
this.workingRepository = workingRepository;
this.centralRepository = centralRepository;
- this.cleanup = cleanup;
+ this.cleanupCentral = cleanupCentral;
+ this.cleanupWorkdir = cleanupWorkdir;
}
- public R getWorkingRepository() {
+ public W getWorkingRepository() {
return workingRepository;
}
@@ -39,8 +41,8 @@ public class WorkingCopy implements AutoCloseable {
@Override
public void close() {
try {
- cleanup.accept(workingRepository);
- cleanup.accept(centralRepository);
+ cleanupWorkdir.accept(workingRepository);
+ cleanupCentral.accept(centralRepository);
IOUtil.delete(directory);
} catch (IOException e) {
LOG.warn("could not delete temporary workdir '{}'", directory, e);
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 4a1a1a4179..04e7b72202 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
@@ -24,14 +24,14 @@ public class SimpleWorkdirFactoryTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
- private SimpleWorkdirFactory simpleWorkdirFactory;
+ private SimpleWorkdirFactory simpleWorkdirFactory;
private String initialBranchForLastCloneCall;
@Before
public void initFactory() throws IOException {
WorkdirProvider workdirProvider = new WorkdirProvider(temporaryFolder.newFolder());
- simpleWorkdirFactory = new SimpleWorkdirFactory(workdirProvider) {
+ simpleWorkdirFactory = new SimpleWorkdirFactory(workdirProvider) {
@Override
protected Repository getScmRepository(Context context) {
return REPOSITORY;
@@ -43,7 +43,12 @@ public class SimpleWorkdirFactoryTest {
}
@Override
- protected ParentAndClone cloneRepository(Context context, File target, String initialBranch) {
+ protected void closeWorkdirInternal(Closeable workdir) throws Exception {
+ workdir.close();
+ }
+
+ @Override
+ protected ParentAndClone cloneRepository(Context context, File target, String initialBranch) {
initialBranchForLastCloneCall = initialBranch;
return new ParentAndClone<>(parent, clone);
}
@@ -53,7 +58,7 @@ public class SimpleWorkdirFactoryTest {
@Test
public void shouldCreateParentAndClone() {
Context context = new Context();
- try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {
+ try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {
assertThat(workingCopy.getCentralRepository()).isSameAs(parent);
assertThat(workingCopy.getWorkingRepository()).isSameAs(clone);
}
@@ -62,7 +67,7 @@ public class SimpleWorkdirFactoryTest {
@Test
public void shouldCloseParent() throws IOException {
Context context = new Context();
- try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {}
+ try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {}
verify(parent).close();
}
@@ -70,7 +75,7 @@ public class SimpleWorkdirFactoryTest {
@Test
public void shouldCloseClone() throws IOException {
Context context = new Context();
- try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {}
+ try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {}
verify(clone).close();
}
@@ -78,7 +83,7 @@ public class SimpleWorkdirFactoryTest {
@Test
public void shouldPropagateInitialBranch() {
Context context = new Context();
- try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, "some")) {
+ try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, "some")) {
assertThat(initialBranchForLastCloneCall).isEqualTo("some");
}
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java
index d3ed353677..9b2be467e8 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java
@@ -4,5 +4,5 @@ import org.eclipse.jgit.lib.Repository;
import sonia.scm.repository.spi.GitContext;
import sonia.scm.repository.util.WorkdirFactory;
-public interface GitWorkdirFactory extends WorkdirFactory {
+public interface GitWorkdirFactory extends WorkdirFactory {
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
index 2531888a8b..adf7878221 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
@@ -142,7 +142,7 @@ class AbstractGitCommand
}
> R inClone(Function workerSupplier, GitWorkdirFactory workdirFactory, String initialBranch) {
- try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, initialBranch)) {
+ try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, initialBranch)) {
Repository repository = workingCopy.getWorkingRepository();
logger.debug("cloned repository to folder {}", repository.getWorkTree());
return workerSupplier.apply(new Git(repository)).run();
@@ -152,19 +152,28 @@ class AbstractGitCommand
}
ObjectId resolveRevisionOrThrowNotFound(Repository repository, String revision) throws IOException {
+ sonia.scm.repository.Repository scmRepository = context.getRepository();
+ return resolveRevisionOrThrowNotFound(repository, revision, scmRepository);
+ }
+
+ static ObjectId resolveRevisionOrThrowNotFound(Repository repository, String revision, sonia.scm.repository.Repository scmRepository) throws IOException {
ObjectId resolved = repository.resolve(revision);
if (resolved == null) {
- throw notFound(entity("Revision", revision).in(context.getRepository()));
+ throw notFound(entity("Revision", revision).in(scmRepository));
} else {
return resolved;
}
}
- abstract class GitCloneWorker {
+ abstract static class GitCloneWorker {
private final Git clone;
+ private final GitContext context;
+ private final sonia.scm.repository.Repository repository;
- GitCloneWorker(Git clone) {
+ GitCloneWorker(Git clone, GitContext context, sonia.scm.repository.Repository repository) {
this.clone = clone;
+ this.context = context;
+ this.repository = repository;
}
abstract R run() throws IOException;
@@ -173,6 +182,10 @@ class AbstractGitCommand
return clone;
}
+ GitContext getContext() {
+ return context;
+ }
+
void checkOutBranch(String branchName) throws IOException {
try {
clone.checkout().setName(branchName).call();
@@ -199,7 +212,7 @@ class AbstractGitCommand
ObjectId resolveRevision(String revision) throws IOException {
ObjectId resolved = clone.getRepository().resolve(revision);
if (resolved == null) {
- return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision);
+ return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision, context.getRepository());
} else {
return resolved;
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java
index e8675068b9..fd3c25cf39 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java
@@ -33,51 +33,120 @@
package sonia.scm.repository.spi;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.transport.PushResult;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
+import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Branch;
import sonia.scm.repository.GitUtil;
-import sonia.scm.repository.GitWorkdirFactory;
import sonia.scm.repository.InternalRepositoryException;
+import sonia.scm.repository.PostReceiveRepositoryHookEvent;
+import sonia.scm.repository.PreReceiveRepositoryHookEvent;
import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryHookEvent;
+import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.api.BranchRequest;
-import sonia.scm.repository.util.WorkingCopy;
+import sonia.scm.repository.api.HookBranchProvider;
+import sonia.scm.repository.api.HookContext;
+import sonia.scm.repository.api.HookContextFactory;
+import sonia.scm.repository.api.HookFeature;
-import java.util.stream.StreamSupport;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
public class GitBranchCommand extends AbstractGitCommand implements BranchCommand {
- private final GitWorkdirFactory workdirFactory;
+ private final HookContextFactory hookContextFactory;
+ private final ScmEventBus eventBus;
- GitBranchCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) {
+ GitBranchCommand(GitContext context, Repository repository, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
super(context, repository);
- this.workdirFactory = workdirFactory;
+ this.hookContextFactory = hookContextFactory;
+ this.eventBus = eventBus;
}
@Override
public Branch branch(BranchRequest request) {
- try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, request.getParentBranch())) {
- Git clone = new Git(workingCopy.getWorkingRepository());
- Ref ref = clone.branchCreate().setName(request.getNewBranch()).call();
- Iterable call = clone.push().add(request.getNewBranch()).call();
- StreamSupport.stream(call.spliterator(), false)
- .flatMap(pushResult -> pushResult.getRemoteUpdates().stream())
- .filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK)
- .findFirst()
- .ifPresent(r -> this.handlePushError(r, request, context.getRepository()));
+ try (Git git = new Git(context.open())) {
+ RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.createHookEvent(request.getNewBranch()));
+ eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
+ Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call();
+ eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
return Branch.normalBranch(request.getNewBranch(), GitUtil.getId(ref.getObjectId()));
- } catch (GitAPIException ex) {
+ } catch (GitAPIException | IOException ex) {
throw new InternalRepositoryException(repository, "could not create branch " + request.getNewBranch(), ex);
}
}
- private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) {
- if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) {
- // TODO handle failed remote update
- throw new IntegrateChangesFromWorkdirException(repository,
- String.format("Could not push new branch '%s' into central repository", request.getNewBranch()));
+ @Override
+ public void deleteOrClose(String branchName) {
+ try (Git gitRepo = new Git(context.open())) {
+ RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.deleteHookEvent(branchName));
+ eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
+ gitRepo
+ .branchDelete()
+ .setBranchNames(branchName)
+ .setForce(true)
+ .call();
+ eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
+ } catch (CannotDeleteCurrentBranchException e) {
+ throw new CannotDeleteDefaultBranchException(context.getRepository(), branchName);
+ } catch (GitAPIException | IOException ex) {
+ throw new InternalRepositoryException(entity(context.getRepository()), String.format("Could not delete branch: %s", branchName));
+ }
+ }
+
+ private RepositoryHookEvent createBranchHookEvent(BranchHookContextProvider hookEvent) {
+ HookContext context = hookContextFactory.createContext(hookEvent, this.context.getRepository());
+ return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE);
+ }
+
+ private static class BranchHookContextProvider extends HookContextProvider {
+ private final List newBranches;
+ private final List deletedBranches;
+
+ private BranchHookContextProvider(List newBranches, List deletedBranches) {
+ this.newBranches = newBranches;
+ this.deletedBranches = deletedBranches;
+ }
+
+ static BranchHookContextProvider createHookEvent(String newBranch) {
+ return new BranchHookContextProvider(singletonList(newBranch), emptyList());
+ }
+
+ static BranchHookContextProvider deleteHookEvent(String deletedBranch) {
+ return new BranchHookContextProvider(emptyList(), singletonList(deletedBranch));
+ }
+
+ @Override
+ public Set getSupportedFeatures() {
+ return singleton(HookFeature.BRANCH_PROVIDER);
+ }
+
+ @Override
+ public HookBranchProvider getBranchProvider() {
+ return new HookBranchProvider() {
+ @Override
+ public List getCreatedOrModified() {
+ return newBranches;
+ }
+
+ @Override
+ public List getDeletedOrClosed() {
+ return deletedBranches;
+ }
+ };
+ }
+
+ @Override
+ public HookChangesetProvider getChangesetProvider() {
+ return r -> new HookChangesetResponse(emptyList());
}
}
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java
new file mode 100644
index 0000000000..64a20a33cb
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java
@@ -0,0 +1,36 @@
+package sonia.scm.repository.spi;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand;
+import org.eclipse.jgit.api.MergeResult;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.api.MergeCommandResult;
+
+import java.io.IOException;
+
+class GitFastForwardIfPossible extends GitMergeStrategy {
+
+ private GitMergeStrategy fallbackMerge;
+
+ GitFastForwardIfPossible(Git clone, MergeCommandRequest request, GitContext context, Repository repository) {
+ super(clone, request, context, repository);
+ fallbackMerge = new GitMergeCommit(clone, request, context, repository);
+ }
+
+ @Override
+ MergeCommandResult run() throws IOException {
+ MergeResult fastForwardResult = mergeWithFastForwardOnlyMode();
+ if (fastForwardResult.getMergeStatus().isSuccessful()) {
+ push();
+ return MergeCommandResult.success();
+ } else {
+ return fallbackMerge.run();
+ }
+ }
+
+ private MergeResult mergeWithFastForwardOnlyMode() throws IOException {
+ MergeCommand mergeCommand = getClone().merge();
+ mergeCommand.setFastForward(MergeCommand.FastForwardMode.FF_ONLY);
+ return doMergeInClone(mergeCommand);
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
index a42abca5f0..8c44f33f7f 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
@@ -106,7 +106,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
* @return
*/
@Override
- public Changeset getChangeset(String revision)
+ public Changeset getChangeset(String revision, LogCommandRequest request)
{
if (logger.isDebugEnabled())
{
@@ -131,7 +131,18 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
if (commit != null)
{
converter = new GitChangesetConverter(gr, revWalk);
- changeset = converter.createChangeset(commit);
+
+ if (isBranchRequested(request)) {
+ String branch = request.getBranch();
+ if (isMergedIntoBranch(gr, revWalk, commit, branch)) {
+ logger.trace("returning commit {} with branch {}", commit.getId(), branch);
+ changeset = converter.createChangeset(commit, branch);
+ } else {
+ logger.debug("returning null, because commit {} was not merged into branch {}", commit.getId(), branch);
+ }
+ } else {
+ changeset = converter.createChangeset(commit);
+ }
}
else if (logger.isWarnEnabled())
{
@@ -157,6 +168,18 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
return changeset;
}
+ private boolean isMergedIntoBranch(Repository repository, RevWalk revWalk, RevCommit commit, String branchName) throws IOException {
+ return revWalk.isMergedInto(commit, findHeadCommitOfBranch(repository, revWalk, branchName));
+ }
+
+ private boolean isBranchRequested(LogCommandRequest request) {
+ return request != null && !Strings.isNullOrEmpty(request.getBranch());
+ }
+
+ private RevCommit findHeadCommitOfBranch(Repository repository, RevWalk revWalk, String branchName) throws IOException {
+ return revWalk.parseCommit(GitUtil.getCommit(repository, revWalk, repository.findRef(branchName)));
+ }
+
/**
* Method description
*
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java
index 5643c858b5..226dd0f285 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java
@@ -1,36 +1,30 @@
package sonia.scm.repository.spi;
-import com.google.common.base.Strings;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
-import org.eclipse.jgit.api.MergeResult;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.lib.ObjectId;
+import com.google.common.collect.ImmutableSet;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitWorkdirFactory;
import sonia.scm.repository.InternalRepositoryException;
-import sonia.scm.repository.Person;
import sonia.scm.repository.api.MergeCommandResult;
import sonia.scm.repository.api.MergeDryRunCommandResult;
+import sonia.scm.repository.api.MergeStrategy;
+import sonia.scm.repository.api.MergeStrategyNotSupportedException;
import java.io.IOException;
-import java.text.MessageFormat;
+import java.util.Set;
+
+import static org.eclipse.jgit.merge.MergeStrategy.RECURSIVE;
public class GitMergeCommand extends AbstractGitCommand implements MergeCommand {
- private static final Logger logger = LoggerFactory.getLogger(GitMergeCommand.class);
-
- private static final String MERGE_COMMIT_MESSAGE_TEMPLATE = String.join("\n",
- "Merge of branch {0} into {1}",
- "",
- "Automatic merge by SCM-Manager.");
-
private final GitWorkdirFactory workdirFactory;
+ private static final Set STRATEGIES = ImmutableSet.of(
+ MergeStrategy.MERGE_COMMIT,
+ MergeStrategy.FAST_FORWARD_IF_POSSIBLE,
+ MergeStrategy.SQUASH
+ );
+
GitMergeCommand(GitContext context, sonia.scm.repository.Repository repository, GitWorkdirFactory workdirFactory) {
super(context, repository);
this.workdirFactory = workdirFactory;
@@ -38,14 +32,30 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
@Override
public MergeCommandResult merge(MergeCommandRequest request) {
- return inClone(clone -> new MergeWorker(clone, request), workdirFactory, request.getTargetBranch());
+ return mergeWithStrategy(request);
+ }
+
+ private MergeCommandResult mergeWithStrategy(MergeCommandRequest request) {
+ switch(request.getMergeStrategy()) {
+ case SQUASH:
+ return inClone(clone -> new GitMergeWithSquash(clone, request, context, repository), workdirFactory, request.getTargetBranch());
+
+ case FAST_FORWARD_IF_POSSIBLE:
+ return inClone(clone -> new GitFastForwardIfPossible(clone, request, context, repository), workdirFactory, request.getTargetBranch());
+
+ case MERGE_COMMIT:
+ return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workdirFactory, request.getTargetBranch());
+
+ default:
+ throw new MergeStrategyNotSupportedException(repository, request.getMergeStrategy());
+ }
}
@Override
public MergeDryRunCommandResult dryRun(MergeCommandRequest request) {
try {
Repository repository = context.open();
- ResolveMerger merger = (ResolveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+ ResolveMerger merger = (ResolveMerger) RECURSIVE.newMerger(repository, true);
return new MergeDryRunCommandResult(
merger.merge(
resolveRevisionOrThrowNotFound(repository, request.getBranchToMerge()),
@@ -55,64 +65,14 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
}
}
- private class MergeWorker extends GitCloneWorker {
-
- private final String target;
- private final String toMerge;
- private final Person author;
- private final String messageTemplate;
-
- private MergeWorker(Git clone, MergeCommandRequest request) {
- super(clone);
- this.target = request.getTargetBranch();
- this.toMerge = request.getBranchToMerge();
- this.author = request.getAuthor();
- this.messageTemplate = request.getMessageTemplate();
- }
-
- @Override
- MergeCommandResult run() throws IOException {
- MergeResult result = doMergeInClone();
- if (result.getMergeStatus().isSuccessful()) {
- doCommit();
- push();
- return MergeCommandResult.success();
- } else {
- return analyseFailure(result);
- }
- }
-
- private MergeResult doMergeInClone() throws IOException {
- MergeResult result;
- try {
- ObjectId sourceRevision = resolveRevision(toMerge);
- result = getClone().merge()
- .setFastForward(FastForwardMode.NO_FF)
- .setCommit(false) // we want to set the author manually
- .include(toMerge, sourceRevision)
- .call();
- } catch (GitAPIException e) {
- throw new InternalRepositoryException(context.getRepository(), "could not merge branch " + toMerge + " into " + target, e);
- }
- return result;
- }
-
- private void doCommit() {
- logger.debug("merged branch {} into {}", toMerge, target);
- doCommit(MessageFormat.format(determineMessageTemplate(), toMerge, target), author);
- }
-
- private String determineMessageTemplate() {
- if (Strings.isNullOrEmpty(messageTemplate)) {
- return MERGE_COMMIT_MESSAGE_TEMPLATE;
- } else {
- return messageTemplate;
- }
- }
-
- private MergeCommandResult analyseFailure(MergeResult result) {
- logger.info("could not merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet());
- return MergeCommandResult.failure(result.getConflicts().keySet());
- }
+ @Override
+ public boolean isSupported(MergeStrategy strategy) {
+ return STRATEGIES.contains(strategy);
}
+
+ @Override
+ public Set getSupportedMergeStrategies() {
+ return STRATEGIES;
+ }
+
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java
new file mode 100644
index 0000000000..6aa68a0ea8
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java
@@ -0,0 +1,31 @@
+package sonia.scm.repository.spi;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand;
+import org.eclipse.jgit.api.MergeResult;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.api.MergeCommandResult;
+
+import java.io.IOException;
+
+class GitMergeCommit extends GitMergeStrategy {
+
+ GitMergeCommit(Git clone, MergeCommandRequest request, GitContext context, Repository repository) {
+ super(clone, request, context, repository);
+ }
+
+ @Override
+ MergeCommandResult run() throws IOException {
+ MergeCommand mergeCommand = getClone().merge();
+ mergeCommand.setFastForward(MergeCommand.FastForwardMode.NO_FF);
+ MergeResult result = doMergeInClone(mergeCommand);
+
+ if (result.getMergeStatus().isSuccessful()) {
+ doCommit();
+ push();
+ return MergeCommandResult.success();
+ } else {
+ return analyseFailure(result);
+ }
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java
new file mode 100644
index 0000000000..1d53b99c99
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java
@@ -0,0 +1,72 @@
+package sonia.scm.repository.spi;
+
+import com.google.common.base.Strings;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.repository.InternalRepositoryException;
+import sonia.scm.repository.Person;
+import sonia.scm.repository.api.MergeCommandResult;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+abstract class GitMergeStrategy extends AbstractGitCommand.GitCloneWorker {
+
+ private static final Logger logger = LoggerFactory.getLogger(GitMergeStrategy.class);
+
+ private static final String MERGE_COMMIT_MESSAGE_TEMPLATE = String.join("\n",
+ "Merge of branch {0} into {1}",
+ "",
+ "Automatic merge by SCM-Manager.");
+
+ private final String target;
+ private final String toMerge;
+ private final Person author;
+ private final String messageTemplate;
+
+ GitMergeStrategy(Git clone, MergeCommandRequest request, GitContext context, sonia.scm.repository.Repository repository) {
+ super(clone, context, repository);
+ this.target = request.getTargetBranch();
+ this.toMerge = request.getBranchToMerge();
+ this.author = request.getAuthor();
+ this.messageTemplate = request.getMessageTemplate();
+ }
+
+ MergeResult doMergeInClone(MergeCommand mergeCommand) throws IOException {
+ MergeResult result;
+ try {
+ ObjectId sourceRevision = resolveRevision(toMerge);
+ mergeCommand
+ .setCommit(false) // we want to set the author manually
+ .include(toMerge, sourceRevision);
+
+ result = mergeCommand.call();
+ } catch (GitAPIException e) {
+ throw new InternalRepositoryException(getContext().getRepository(), "could not merge branch " + toMerge + " into " + target, e);
+ }
+ return result;
+ }
+
+ void doCommit() {
+ logger.debug("merged branch {} into {}", toMerge, target);
+ doCommit(MessageFormat.format(determineMessageTemplate(), toMerge, target), author);
+ }
+
+ private String determineMessageTemplate() {
+ if (Strings.isNullOrEmpty(messageTemplate)) {
+ return MERGE_COMMIT_MESSAGE_TEMPLATE;
+ } else {
+ return messageTemplate;
+ }
+ }
+
+ MergeCommandResult analyseFailure(MergeResult result) {
+ logger.info("could not merge branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet());
+ return MergeCommandResult.failure(result.getConflicts().keySet());
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java
new file mode 100644
index 0000000000..b688956404
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java
@@ -0,0 +1,31 @@
+package sonia.scm.repository.spi;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeResult;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.api.MergeCommandResult;
+import org.eclipse.jgit.api.MergeCommand;
+
+import java.io.IOException;
+
+class GitMergeWithSquash extends GitMergeStrategy {
+
+ GitMergeWithSquash(Git clone, MergeCommandRequest request, GitContext context, Repository repository) {
+ super(clone, request, context, repository);
+ }
+
+ @Override
+ MergeCommandResult run() throws IOException {
+ MergeCommand mergeCommand = getClone().merge();
+ mergeCommand.setSquash(true);
+ MergeResult result = doMergeInClone(mergeCommand);
+
+ if (result.getMergeStatus().isSuccessful()) {
+ doCommit();
+ push();
+ return MergeCommandResult.success();
+ } else {
+ return analyseFailure(result);
+ }
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java
index a68be0a4da..19234c32e3 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java
@@ -46,7 +46,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
private final ModifyCommandRequest request;
ModifyWorker(Git clone, ModifyCommandRequest request) {
- super(clone);
+ super(clone, context, repository);
this.workDir = clone.getRepository().getWorkTree();
this.request = request;
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java
index dc43b8d9b7..e1ae58ada5 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java
@@ -35,10 +35,12 @@ package sonia.scm.repository.spi;
import com.google.common.collect.ImmutableSet;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
+import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Feature;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
+import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.web.lfs.LfsBlobStoreFactory;
import java.io.IOException;
@@ -64,6 +66,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
Command.DIFF_RESULT,
Command.LOG,
Command.TAGS,
+ Command.BRANCH,
Command.BRANCHES,
Command.INCOMING,
Command.OUTGOING,
@@ -77,10 +80,12 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
//~--- constructors ---------------------------------------------------------
- public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory) {
+ public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
this.handler = handler;
this.repository = repository;
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
+ this.hookContextFactory = hookContextFactory;
+ this.eventBus = eventBus;
this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
}
@@ -133,7 +138,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
@Override
public BranchCommand getBranchCommand()
{
- return new GitBranchCommand(context, repository, handler.getWorkdirFactory());
+ return new GitBranchCommand(context, repository, hookContextFactory, eventBus);
}
/**
@@ -292,4 +297,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
private final Repository repository;
private final LfsBlobStoreFactory lfsBlobStoreFactory;
+
+ private final HookContextFactory hookContextFactory;
+
+ private final ScmEventBus eventBus;
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java
index 547c6b25f8..7fc5fb27c4 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java
@@ -36,9 +36,11 @@ package sonia.scm.repository.spi;
import com.google.inject.Inject;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
+import sonia.scm.event.ScmEventBus;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
+import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.web.lfs.LfsBlobStoreFactory;
/**
@@ -51,12 +53,16 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
private final GitRepositoryHandler handler;
private final GitRepositoryConfigStoreProvider storeProvider;
private final LfsBlobStoreFactory lfsBlobStoreFactory;
+ private final HookContextFactory hookContextFactory;
+ private final ScmEventBus eventBus;
@Inject
- public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory) {
+ public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
this.handler = handler;
this.storeProvider = storeProvider;
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
+ this.hookContextFactory = hookContextFactory;
+ this.eventBus = eventBus;
}
@Override
@@ -64,7 +70,7 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
GitRepositoryServiceProvider provider = null;
if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
- provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory);
+ provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory, hookContextFactory, eventBus);
}
return provider;
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 a0fda7cd3b..f7b0c5567c 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
@@ -18,7 +18,7 @@ import java.io.IOException;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
-public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory {
+public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory {
@Inject
public SimpleGitWorkdirFactory(WorkdirProvider workdirProvider) {
@@ -26,7 +26,7 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory cloneRepository(GitContext context, File target, String initialBranch) {
+ public ParentAndClone cloneRepository(GitContext context, File target, String initialBranch) {
try {
Repository clone = Git.cloneRepository()
.setURI(createScmTransportProtocolUri(context.getDirectory()))
@@ -60,6 +60,13 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory b.getName().equals("new_branch")).isEmpty();
+ assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty();
BranchRequest branchRequest = new BranchRequest();
branchRequest.setNewBranch("new_branch");
- new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).branch(branchRequest);
+ createCommand().branch(branchRequest);
- Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty();
+ assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty();
+ }
+
+ @Test
+ public void shouldDeleteBranch() throws IOException {
+ GitContext context = createContext();
+ String branchToBeDeleted = "squash";
+ createCommand().deleteOrClose(branchToBeDeleted);
+ assertThat(readBranches(context)).filteredOn(b -> b.getName().equals(branchToBeDeleted)).isEmpty();
+ }
+
+ @Test
+ public void shouldThrowExceptionWhenDeletingDefaultBranch() {
+ String branchToBeDeleted = "master";
+ assertThrows(CannotDeleteDefaultBranchException.class, () -> createCommand().deleteOrClose(branchToBeDeleted));
+ }
+
+ private GitBranchCommand createCommand() {
+ return new GitBranchCommand(createContext(), repository, hookContextFactory, eventBus);
}
private List readBranches(GitContext context) throws IOException {
return new GitBranchesCommand(context, repository).getBranches();
}
+
+ @Test
+ public void shouldPostCreateEvents() {
+ ArgumentCaptor