diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java index 5088fcad1c..53f712cddc 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java @@ -13,7 +13,6 @@ import static java.util.Collections.unmodifiableCollection; */ public class MergeCommandResult { private final Collection filesWithConflict; - private boolean aborted = false; private MergeCommandResult(Collection filesWithConflict) { this.filesWithConflict = filesWithConflict; @@ -42,12 +41,4 @@ public class MergeCommandResult { public Collection getFilesWithConflict() { return unmodifiableCollection(filesWithConflict); } - - public boolean isAborted() { - return aborted; - } - - public void setAborted(boolean aborted) { - this.aborted = aborted; - } } 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 73159da5c1..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 @@ -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/GitFastForwardIfPossible.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java new file mode 100644 index 0000000000..c7820009a1 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java @@ -0,0 +1,42 @@ +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 { + + GitFastForwardIfPossible(Git clone, MergeCommandRequest request, GitContext context, Repository repository) { + super(clone, request, context, repository); + } + + @Override + MergeCommandResult run() throws IOException { + MergeResult fastForwardResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.FF_ONLY); + if (fastForwardResult.getMergeStatus().isSuccessful()) { + push(); + return MergeCommandResult.success(); + } else { + return mergeWithCommit(); + } + } + + private MergeCommandResult mergeWithCommit() throws IOException { + MergeResult mergeCommitResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.NO_FF); + if (mergeCommitResult.getMergeStatus().isSuccessful()) { + return MergeCommandResult.success(); + } else { + return analyseFailure(mergeCommitResult); + } + } + + private MergeResult mergeWithFastForwardMode(MergeCommand.FastForwardMode fastForwardMode) throws IOException { + MergeCommand mergeCommand = getClone().merge(); + mergeCommand.setFastForward(fastForwardMode); + return doMergeInClone(mergeCommand); + } +} 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 1ef4eacce3..94c29bdce9 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,19 @@ package sonia.scm.repository.spi; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.MergeResult; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; 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 java.io.IOException; -import java.text.MessageFormat; -import java.util.Collections; import java.util.Set; 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( @@ -46,7 +29,16 @@ 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) { + if (request.getMergeStrategy() == MergeStrategy.SQUASH) { + return inClone(clone -> new GitMergeWithSquash(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + } else if (request.getMergeStrategy() == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + return inClone(clone -> new GitFastForwardIfPossible(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + } + return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workdirFactory, request.getTargetBranch()); } @Override @@ -73,86 +65,4 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand return STRATEGIES; } - private class MergeWorker extends GitCloneWorker { - - private final String target; - private final String toMerge; - private final Person author; - private final String messageTemplate; - private final MergeStrategy mergeStrategy; - - private MergeWorker(Git clone, MergeCommandRequest request) { - super(clone); - this.target = request.getTargetBranch(); - this.toMerge = request.getBranchToMerge(); - this.author = request.getAuthor(); - this.messageTemplate = request.getMessageTemplate(); - this.mergeStrategy = request.getMergeStrategy(); - } - - @Override - MergeCommandResult run() throws IOException { - MergeResult result = doMergeInClone(); - - if (result.getMergeStatus().isSuccessful()) { - if (!isFastForward()) { - doCommit(); - } - push(); - return MergeCommandResult.success(); - } else if (isFastForward()) { - MergeCommandResult failure = MergeCommandResult.failure(Collections.emptyList()); - failure.setAborted(true); - return failure; - } else { - return analyseFailure(result); - } - } - - private MergeResult doMergeInClone() throws IOException { - MergeResult result; - try { - ObjectId sourceRevision = resolveRevision(toMerge); - org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); - mergeCommand - .setCommit(false) // we want to set the author manually - .include(toMerge, sourceRevision); - - if (mergeStrategy == MergeStrategy.SQUASH) { - mergeCommand.setSquash(true); - } else if (isFastForward()) { - mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF_ONLY); - } else { - mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF); - } - - result = mergeCommand.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()); - } - - private boolean isFastForward() { - return mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE; - } - } } 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..627efb9584 --- /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 { + org.eclipse.jgit.api.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..43a3145405 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java @@ -0,0 +1,82 @@ +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 sonia.scm.repository.api.MergeStrategy; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collections; + +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; + private final MergeStrategy mergeStrategy; + + 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(); + this.mergeStrategy = request.getMergeStrategy(); + } + + 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 merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet()); + return MergeCommandResult.failure(result.getConflicts().keySet()); + } + + MergeCommandResult aborted() { + MergeCommandResult failure = MergeCommandResult.failure(Collections.emptyList()); + failure.setAborted(true); + return failure; + } +} 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..dfbd31fe32 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java @@ -0,0 +1,30 @@ +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 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 { + org.eclipse.jgit.api.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; }