use strategy pattern for mergeStrategies

This commit is contained in:
Eduard Heimbuch
2019-11-07 11:50:28 +01:00
parent de09097203
commit ec6b4493c8
8 changed files with 213 additions and 114 deletions

View File

@@ -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<R> {
abstract static class GitCloneWorker<R> {
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;
}

View File

@@ -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);
}
}

View File

@@ -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<MergeStrategy> 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<MergeCommandResult> {
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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<MergeCommandResult> {
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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}