mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-01 00:38:33 +02:00
Compute files with conflicts in merge dry run
Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com> Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
4
gradle/changelog/merge_conflict.yaml
Normal file
4
gradle/changelog/merge_conflict.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
- type: added
|
||||
description: The result of the merge dry-run now contains the names of the files that have conflicts
|
||||
- type: added
|
||||
description: Method to compute anchors in diff views can be used in plugins
|
||||
@@ -26,10 +26,25 @@ package sonia.scm.repository.api;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
*/
|
||||
@Value
|
||||
public class MergePreventReason {
|
||||
MergePreventReasonType type;
|
||||
|
||||
Collection<String> affectedPaths;
|
||||
|
||||
public MergePreventReason(MergePreventReasonType type) {
|
||||
this(type, emptyList());
|
||||
}
|
||||
|
||||
public MergePreventReason(MergePreventReasonType type, Collection<String> affectedPaths) {
|
||||
this.type = type;
|
||||
this.affectedPaths = affectedPaths;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import jakarta.inject.Inject;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
@@ -35,9 +34,9 @@ import org.eclipse.jgit.lib.IndexDiff;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectReader;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.merge.ResolveMerger;
|
||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.GitWorkingCopyFactory;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
@@ -52,8 +51,11 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static org.eclipse.jgit.merge.MergeStrategy.RECURSIVE;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
@@ -62,7 +64,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
|
||||
private final GitWorkingCopyFactory workingCopyFactory;
|
||||
private final AttributeAnalyzer attributeAnalyzer;
|
||||
private static final Set<MergeStrategy> STRATEGIES = ImmutableSet.of(
|
||||
private static final Set<MergeStrategy> STRATEGIES = Set.of(
|
||||
MergeStrategy.MERGE_COMMIT,
|
||||
MergeStrategy.FAST_FORWARD_IF_POSSIBLE,
|
||||
MergeStrategy.SQUASH,
|
||||
@@ -117,9 +119,8 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
mergePreventReasons.add(new MergePreventReason(MergePreventReasonType.EXTERNAL_MERGE_TOOL));
|
||||
}
|
||||
|
||||
if (!isMergeableWithoutFileConflicts(repository, request.getBranchToMerge(), request.getTargetBranch())) {
|
||||
mergePreventReasons.add(new MergePreventReason(MergePreventReasonType.FILE_CONFLICTS));
|
||||
}
|
||||
checkMergeableWithoutFileConflicts(repository, request.getBranchToMerge(), request.getTargetBranch())
|
||||
.ifPresent(mergePreventReasons::add);
|
||||
|
||||
return new MergeDryRunCommandResult(mergePreventReasons.isEmpty(), mergePreventReasons);
|
||||
} catch (IOException e) {
|
||||
@@ -127,11 +128,16 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMergeableWithoutFileConflicts(Repository repository, String sourceRevision, String targetRevision) throws IOException {
|
||||
return RECURSIVE.newMerger(repository, true).merge(
|
||||
resolveRevisionOrThrowNotFound(repository,sourceRevision),
|
||||
private Optional<MergePreventReason> checkMergeableWithoutFileConflicts(Repository repository, String sourceRevision, String targetRevision) throws IOException {
|
||||
ResolveMerger merger = (ResolveMerger) RECURSIVE.newMerger(repository, true);
|
||||
if (!merger.merge(
|
||||
resolveRevisionOrThrowNotFound(repository, sourceRevision),
|
||||
resolveRevisionOrThrowNotFound(repository, targetRevision)
|
||||
);
|
||||
)) {
|
||||
return of(new MergePreventReason(MergePreventReasonType.FILE_CONFLICTS, merger.getUnmergedPaths()));
|
||||
} else {
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -110,8 +110,10 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
MergeDryRunCommandResult result = command.dryRun(request);
|
||||
|
||||
assertThat(result.isMergeable()).isFalse();
|
||||
assertThat(result.getReasons().size()).isEqualTo(1);
|
||||
assertThat(result.getReasons().stream().toList().get(0).getType()).isEqualTo(MergePreventReasonType.FILE_CONFLICTS);
|
||||
assertThat(result.getReasons()).hasSize(1);
|
||||
MergePreventReason mergePreventReason = result.getReasons().stream().toList().get(0);
|
||||
assertThat(mergePreventReason.getType()).isEqualTo(MergePreventReasonType.FILE_CONFLICTS);
|
||||
assertThat(mergePreventReason.getAffectedPaths()).containsExactly("a.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,7 +129,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
MergeDryRunCommandResult result = command.dryRun(request);
|
||||
|
||||
assertThat(result.isMergeable()).isFalse();
|
||||
assertThat(result.getReasons().size()).isEqualTo(1);
|
||||
assertThat(result.getReasons()).hasSize(1);
|
||||
assertThat(result.getReasons().stream().toList().get(0).getType()).isEqualTo(MergePreventReasonType.EXTERNAL_MERGE_TOOL);
|
||||
}
|
||||
|
||||
@@ -144,7 +146,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
MergeDryRunCommandResult result = command.dryRun(request);
|
||||
|
||||
assertThat(result.isMergeable()).isFalse();
|
||||
assertThat(result.getReasons().size()).isEqualTo(2);
|
||||
assertThat(result.getReasons()).hasSize(2);
|
||||
List<MergePreventReason> reasons = result.getReasons().stream().toList();
|
||||
assertThat(reasons.get(0).getType()).isEqualTo(MergePreventReasonType.EXTERNAL_MERGE_TOOL);
|
||||
assertThat(reasons.get(1).getType()).isEqualTo(MergePreventReasonType.FILE_CONFLICTS);
|
||||
@@ -379,7 +381,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
|
||||
GitModificationsCommand modificationsCommand = new GitModificationsCommand(createContext());
|
||||
List<Added> changes = modificationsCommand.getModifications("master").getAdded();
|
||||
assertThat(changes.size()).isEqualTo(3);
|
||||
assertThat(changes).hasSize(3);
|
||||
}
|
||||
|
||||
|
||||
@@ -468,7 +470,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void shouldHandleNotExistingTargetBranchInDryRun() throws IOException {
|
||||
public void shouldHandleNotExistingTargetBranchInDryRun() {
|
||||
GitMergeCommand command = createCommand();
|
||||
MergeCommandRequest request = new MergeCommandRequest();
|
||||
request.setTargetBranch("not_existing");
|
||||
|
||||
@@ -143,3 +143,5 @@ export { urls };
|
||||
export const getPageFromMatch = urls.getPageFromMatch;
|
||||
|
||||
export { default as useGeneratedId } from "./useGeneratedId";
|
||||
|
||||
export { getAnchorId as getDiffAnchorId } from "./repos/diff/helpers";
|
||||
|
||||
@@ -37,13 +37,17 @@ const Button = styled(Link)`
|
||||
type Props = {
|
||||
link: string;
|
||||
tooltip: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
const JumpToFileButton: FC<Props> = ({ link, tooltip }) => {
|
||||
const JumpToFileButton: FC<Props> = ({ link, tooltip, icon, color }) => {
|
||||
const iconToUse = icon ?? "file-code";
|
||||
const colorToUse = color ?? "inherit";
|
||||
return (
|
||||
<Tooltip message={tooltip} side="top">
|
||||
<Button aria-label={tooltip} className="button is-clickable" to={link}>
|
||||
<Icon name="file-code" color="inherit" alt="" />
|
||||
<Icon name={iconToUse} color={colorToUse} alt="" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user