mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-10-26 08:06:09 +01:00
Add Fast Forward Only Strategy
This commit is contained in:
committed by
Till-André Diegeler
parent
6792328e32
commit
8a8d18c33e
2
gradle/changelog/fast-forward-only.yaml
Normal file
2
gradle/changelog/fast-forward-only.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Merge strategy fast forward only, which either fast forwards the commits if possible or throws an error
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import sonia.scm.BadRequestException;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
public class FastForwardNotPossible extends BadRequestException {
|
||||
|
||||
private static final String CODE = "Re6hF5g14U";
|
||||
|
||||
public FastForwardNotPossible(Repository repository, String source, String target) {
|
||||
super(entity(repository).build(), createMessage(source, target));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return CODE;
|
||||
}
|
||||
|
||||
private static String createMessage(String source, String target) {
|
||||
return String.format("Fast forward not possible with source revision %s and target revision %s", source, target);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package sonia.scm.repository.api;
|
||||
public enum MergeStrategy {
|
||||
MERGE_COMMIT(true),
|
||||
FAST_FORWARD_IF_POSSIBLE(true),
|
||||
FAST_FORWARD_ONLY(false),
|
||||
SQUASH(true),
|
||||
REBASE(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
public enum FastForwardFallbackStrategy {
|
||||
MERGE_COMMIT,
|
||||
THROW_EXCEPTION
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.FastForwardNotPossible;
|
||||
import sonia.scm.repository.api.MergeCommandResult;
|
||||
|
||||
@Slf4j
|
||||
@@ -30,9 +31,11 @@ class GitFastForwardIfPossible {
|
||||
private final GitMergeCommit fallbackMerge;
|
||||
private final CommitHelper commitHelper;
|
||||
private final Repository repository;
|
||||
private final FastForwardFallbackStrategy fastForwardStrategy;
|
||||
|
||||
GitFastForwardIfPossible(MergeCommandRequest request, GitContext context, RepositoryManager repositoryManager, GitRepositoryHookEventFactory eventFactory) {
|
||||
GitFastForwardIfPossible(MergeCommandRequest request, GitContext context, RepositoryManager repositoryManager, GitRepositoryHookEventFactory eventFactory, FastForwardFallbackStrategy fastForwardStrategy) {
|
||||
this.request = request;
|
||||
this.fastForwardStrategy = fastForwardStrategy;
|
||||
this.mergeHelper = new MergeHelper(context, request, repositoryManager, eventFactory);
|
||||
this.fallbackMerge = new GitMergeCommit(request, context, repositoryManager, eventFactory);
|
||||
this.commitHelper = new CommitHelper(context, repositoryManager, eventFactory);
|
||||
@@ -48,9 +51,17 @@ class GitFastForwardIfPossible {
|
||||
log.trace("fast forward branch {} onto {}", request.getBranchToMerge(), request.getTargetBranch());
|
||||
commitHelper.updateBranch(request.getTargetBranch(), sourceRevision, targetRevision);
|
||||
return MergeCommandResult.success(targetRevision.name(), mergeHelper.getRevisionToMerge().name(), sourceRevision.name());
|
||||
} else {
|
||||
log.trace("fast forward is not possible, fallback to merge");
|
||||
return fallbackMerge.run();
|
||||
}
|
||||
|
||||
return switch (fastForwardStrategy) {
|
||||
case MERGE_COMMIT -> {
|
||||
log.trace("fast forward is not possible, fallback to merge");
|
||||
yield fallbackMerge.run();
|
||||
}
|
||||
case THROW_EXCEPTION -> {
|
||||
log.trace("fast forward is not possible, fallback to exception");
|
||||
throw new FastForwardNotPossible(repository, request.getBranchToMerge(), request.getTargetBranch());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
private static final Set<MergeStrategy> STRATEGIES = Set.of(
|
||||
MergeStrategy.MERGE_COMMIT,
|
||||
MergeStrategy.FAST_FORWARD_IF_POSSIBLE,
|
||||
MergeStrategy.FAST_FORWARD_ONLY,
|
||||
MergeStrategy.SQUASH,
|
||||
MergeStrategy.REBASE
|
||||
);
|
||||
@@ -102,7 +103,9 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
return switch (request.getMergeStrategy()) {
|
||||
case SQUASH -> new GitMergeWithSquash(request, context, repositoryManager, eventFactory).run();
|
||||
case FAST_FORWARD_IF_POSSIBLE ->
|
||||
new GitFastForwardIfPossible(request, context, repositoryManager, eventFactory).run();
|
||||
new GitFastForwardIfPossible(request, context, repositoryManager, eventFactory, FastForwardFallbackStrategy.MERGE_COMMIT).run();
|
||||
case FAST_FORWARD_ONLY ->
|
||||
new GitFastForwardIfPossible(request, context, repositoryManager, eventFactory, FastForwardFallbackStrategy.THROW_EXCEPTION).run();
|
||||
case MERGE_COMMIT -> new GitMergeCommit(request, context, repositoryManager, eventFactory).run();
|
||||
case REBASE -> new GitMergeRebase(request, context, repositoryManager, eventFactory).run();
|
||||
default -> throw new MergeStrategyNotSupportedException(repository, request.getMergeStrategy());
|
||||
|
||||
@@ -53,7 +53,7 @@ class GitMergeRebase {
|
||||
this.context = context;
|
||||
this.mergeHelper = new MergeHelper(context, request, repositoryManager, eventFactory);
|
||||
this.commitHelper = new CommitHelper(context, repositoryManager, eventFactory);
|
||||
this.fastForwardMerge = new GitFastForwardIfPossible(request, context, repositoryManager, eventFactory);
|
||||
this.fastForwardMerge = new GitFastForwardIfPossible(request, context, repositoryManager, eventFactory, FastForwardFallbackStrategy.THROW_EXCEPTION);
|
||||
}
|
||||
|
||||
MergeCommandResult run() {
|
||||
|
||||
@@ -45,6 +45,7 @@ import sonia.scm.repository.GitWorkingCopyFactory;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.RepositoryHookEvent;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.FastForwardNotPossible;
|
||||
import sonia.scm.repository.api.MergeCommandResult;
|
||||
import sonia.scm.repository.api.MergeDryRunCommandResult;
|
||||
import sonia.scm.repository.api.MergePreventReason;
|
||||
@@ -445,6 +446,38 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMergeWithFastForwardOnly() throws IOException, GitAPIException {
|
||||
try (GitContext context = createContext(); Repository repository = context.open()) {
|
||||
|
||||
ObjectId featureBranchHead = new Git(repository).log().add(repository.resolve("squash")).setMaxCount(1).call().iterator().next().getId();
|
||||
|
||||
GitMergeCommand command = createCommand();
|
||||
MergeCommandRequest request = new MergeCommandRequest();
|
||||
request.setBranchToMerge("squash");
|
||||
request.setTargetBranch("master");
|
||||
request.setMergeStrategy(MergeStrategy.FAST_FORWARD_ONLY);
|
||||
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||
|
||||
MergeCommandResult mergeCommandResult = command.merge(request);
|
||||
assertThat(mergeCommandResult.getNewHeadRevision()).isEqualTo("35597e9e98fe53167266583848bfef985c2adb27");
|
||||
assertThat(mergeCommandResult.getRevisionToMerge()).isEqualTo("35597e9e98fe53167266583848bfef985c2adb27");
|
||||
assertThat(mergeCommandResult.getTargetRevision()).isEqualTo("fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
|
||||
|
||||
assertThat(mergeCommandResult.isSuccess()).isTrue();
|
||||
|
||||
Iterable<RevCommit> commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call();
|
||||
RevCommit mergeCommit = commits.iterator().next();
|
||||
assertThat(mergeCommit.getParentCount()).isEqualTo(1);
|
||||
PersonIdent mergeAuthor = mergeCommit.getAuthorIdent();
|
||||
PersonIdent mergeCommitter = mergeCommit.getCommitterIdent();
|
||||
|
||||
assertThat(mergeAuthor.getName()).isEqualTo("Philip J Fry");
|
||||
assertThat(mergeCommitter.getName()).isEqualTo("Eduard Heimbuch");
|
||||
assertThat(mergeCommit.getId()).isEqualTo(featureBranchHead);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDoMergeCommitIfFastForwardIsNotPossible() throws IOException, GitAPIException {
|
||||
GitMergeCommand command = createCommand();
|
||||
@@ -470,6 +503,18 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = FastForwardNotPossible.class)
|
||||
public void shouldThrowErrorBecauseFastForwardNotPossible() throws IOException, GitAPIException {
|
||||
GitMergeCommand command = createCommand();
|
||||
MergeCommandRequest request = new MergeCommandRequest();
|
||||
request.setTargetBranch("master");
|
||||
request.setBranchToMerge("mergeable");
|
||||
request.setMergeStrategy(MergeStrategy.FAST_FORWARD_ONLY);
|
||||
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||
|
||||
MergeCommandResult mergeCommandResult = command.merge(request);
|
||||
}
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void shouldHandleNotExistingSourceBranchInMerge() {
|
||||
GitMergeCommand command = createCommand();
|
||||
|
||||
@@ -289,6 +289,10 @@
|
||||
"displayName": "Nicht unterstützte Merge-Strategie",
|
||||
"description": "Die gewählte Merge-Strategie wird von dem Repository nicht unterstützt."
|
||||
},
|
||||
"Re6hF5g14U": {
|
||||
"displayName": "Fast-Forward fehlgeschlagen",
|
||||
"description": "Die beiden Branches können nicht per Fast-Forward miteinander gemerged werden"
|
||||
},
|
||||
"78RhWxTIw1": {
|
||||
"displayName": "Der Default-Branch kann nicht gelöscht werden",
|
||||
"description": "Der Default-Branch kann nicht gelöscht werden. Bitte wählen Sie zuerst einen neuen Default-Branch."
|
||||
|
||||
@@ -289,6 +289,10 @@
|
||||
"displayName": "Merge strategy not supported",
|
||||
"description": "The selected merge strategy is not supported by the repository."
|
||||
},
|
||||
"Re6hF5g14U": {
|
||||
"displayName": "Fast forward failed",
|
||||
"description": "The branches cannot be merged via fast forward"
|
||||
},
|
||||
"78RhWxTIw1": {
|
||||
"displayName": "Default branch cannot be deleted",
|
||||
"description": "The default branch of a repository cannot be deleted. Please select another default branch first."
|
||||
|
||||
Reference in New Issue
Block a user