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 6c4324c259..5bf93168a2 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 @@ -4,6 +4,7 @@ import com.google.common.base.Preconditions; import sonia.scm.repository.Person; import sonia.scm.repository.spi.MergeCommand; import sonia.scm.repository.spi.MergeCommandRequest; +import sonia.scm.repository.spi.MergeConflictResult; import sonia.scm.repository.util.AuthorUtil; import java.util.Set; @@ -168,7 +169,7 @@ public class MergeCommandBuilder { } /** - * Use this to check whether the given branches can be merged autmatically. If this is possible, + * Use this to check whether the given branches can be merged automatically. If this is possible, * {@link MergeDryRunCommandResult#isMergeable()} will return true. * * @return The result whether the given branches can be merged automatically. @@ -177,4 +178,14 @@ public class MergeCommandBuilder { Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required"); return mergeCommand.dryRun(request); } + + /** + * Use this to compute concrete conflicts for a merge. + * + * @return A result containing all conflicts for the merge. + */ + public MergeConflictResult conflicts() { + Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required"); + return mergeCommand.computeConflicts(request); + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java index e26b2eb5aa..7321718581 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java @@ -101,7 +101,7 @@ public final class DiffCommandRequest extends FileBaseCommandRequest * * * @param format format of the diff output - * + * * @since 1.34 */ public void setFormat(DiffFormat format) @@ -119,7 +119,7 @@ public final class DiffCommandRequest extends FileBaseCommandRequest * * * @return output format - * + * * @since 1.34 */ public DiffFormat getFormat() 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 79d6b12c25..176d1772cf 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 @@ -17,6 +17,8 @@ public interface MergeCommand { MergeDryRunCommandResult dryRun(MergeCommandRequest request); + MergeConflictResult computeConflicts(MergeCommandRequest request); + boolean isSupported(MergeStrategy strategy); Set getSupportedMergeStrategies(); diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeConflictResult.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeConflictResult.java new file mode 100644 index 0000000000..206cf7e7dc --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeConflictResult.java @@ -0,0 +1,63 @@ +package sonia.scm.repository.spi; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.ADDED_BY_BOTH; +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.BOTH_MODIFIED; +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.DELETED_BY_THEM; +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.DELETED_BY_US; + +public class MergeConflictResult { + + private final List conflicts = new LinkedList<>(); + + public List getConflicts() { + return Collections.unmodifiableList(conflicts); + } + + public void addBothModified(String path, String diff) { + conflicts.add(new SingleMergeConflict(BOTH_MODIFIED, path, diff)); + } + + public void addDeletedByThem(String path) { + conflicts.add(new SingleMergeConflict(DELETED_BY_THEM, path, null)); + } + + public void addDeletedByUs(String path) { + conflicts.add(new SingleMergeConflict(DELETED_BY_US, path, null)); + } + + public void addAddedByBoth(String path) { + conflicts.add(new SingleMergeConflict(ADDED_BY_BOTH, path, null)); + } + + public static class SingleMergeConflict { + private final ConflictTypes type; + private final String path; + private final String diff; + + private SingleMergeConflict(ConflictTypes type, String path, String diff) { + this.type = type; + this.path = path; + this.diff = diff; + } + + public ConflictTypes getType() { + return type; + } + + public String getPath() { + return path; + } + + public String getDiff() { + return diff; + } + } + + public enum ConflictTypes { + BOTH_MODIFIED, DELETED_BY_THEM, DELETED_BY_US, ADDED_BY_BOTH + } +} 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 226dd0f285..69eb8b0748 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,8 +1,17 @@ package sonia.scm.repository.spi; import com.google.common.collect.ImmutableSet; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeResult; +import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.errors.GitAPIException; +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.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.MergeCommandResult; @@ -10,10 +19,13 @@ import sonia.scm.repository.api.MergeDryRunCommandResult; import sonia.scm.repository.api.MergeStrategy; import sonia.scm.repository.api.MergeStrategyNotSupportedException; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Set; import static org.eclipse.jgit.merge.MergeStrategy.RECURSIVE; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; public class GitMergeCommand extends AbstractGitCommand implements MergeCommand { @@ -35,6 +47,11 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand return mergeWithStrategy(request); } + @Override + public MergeConflictResult computeConflicts(MergeCommandRequest request) { + return inClone(git -> new ConflictWorker(git, request), workdirFactory, request.getTargetBranch()); + } + private MergeCommandResult mergeWithStrategy(MergeCommandRequest request) { switch(request.getMergeStrategy()) { case SQUASH: @@ -75,4 +92,91 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand return STRATEGIES; } + private class ConflictWorker extends GitCloneWorker { + private final String theirs; + private final String ours; + private final CanonicalTreeParser treeParser; + private final ObjectId treeId; + private final ByteArrayOutputStream diffBuffer; + + private final MergeConflictResult result = new MergeConflictResult(); + + + private ConflictWorker(Git git, MergeCommandRequest request) { + super(git, context, repository); + theirs = request.getBranchToMerge(); + ours = request.getTargetBranch(); + + treeParser = new CanonicalTreeParser(); + diffBuffer = new ByteArrayOutputStream(); + try { + treeId = git.getRepository().resolve(ours + "^{tree}"); + } catch (IOException e) { + throw notFound(entity("branch", ours).in(repository)); + } + } + + @Override + MergeConflictResult run() throws IOException { + MergeResult mergeResult = doTemporaryMerge(); + if (mergeResult.getConflicts() != null) { + getStatus().getConflictingStageState().forEach(this::computeConflict); + } + return result; + } + + private void computeConflict(String path, IndexDiff.StageState stageState) { + switch (stageState) { + case BOTH_MODIFIED: + diffBuffer.reset(); + try (ObjectReader reader = getClone().getRepository().newObjectReader()) { + treeParser.reset(reader, treeId); + getClone() + .diff() + .setOldTree(treeParser) + .setPathFilter(PathFilter.create(path)) + .setOutputStream(diffBuffer) + .call(); + result.addBothModified(path, diffBuffer.toString()); + } catch (GitAPIException | IOException e) { + throw new InternalRepositoryException(repository, "could not calculate diff for path " + path, e); + } + break; + case BOTH_ADDED: + result.addAddedByBoth(path); + break; + case DELETED_BY_THEM: + result.addDeletedByUs(path); + break; + case DELETED_BY_US: + result.addDeletedByThem(path); + break; + default: + throw new InternalRepositoryException(context.getRepository(), "unexpected conflict type: " + stageState); + } + } + + private MergeResult doTemporaryMerge() throws IOException { + ObjectId sourceRevision = resolveRevision(theirs); + try { + return getClone().merge() + .setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF) + .setCommit(false) + .include(theirs, sourceRevision) + .call(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not merge branch " + theirs + " into " + ours, e); + } + } + + private Status getStatus() { + Status status; + try { + status = getClone().status().call(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not get status", e); + } + return status; + } + } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java new file mode 100644 index 0000000000..66c3e9f85e --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommand_Conflict_Test.java @@ -0,0 +1,81 @@ +package sonia.scm.repository.spi; + +import org.junit.Rule; +import org.junit.Test; +import sonia.scm.repository.spi.MergeConflictResult.SingleMergeConflict; +import sonia.scm.repository.util.WorkdirProvider; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.BOTH_MODIFIED; +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.DELETED_BY_THEM; +import static sonia.scm.repository.spi.MergeConflictResult.ConflictTypes.DELETED_BY_US; + +public class GitMergeCommand_Conflict_Test extends AbstractGitCommandTestBase { + + static final String DIFF_HEADER = "diff --git a/Main.java b/Main.java"; + static final String DIFF_FILE_CONFLICT = "--- a/Main.java\n" + + "+++ b/Main.java\n" + + "@@ -1,6 +1,13 @@\n" + + "+import java.util.Arrays;\n" + + "+\n" + + " class Main {\n" + + " public static void main(String[] args) {\n" + + " System.out.println(\"Expect nothing more to happen.\");\n" + + "+<<<<<<< HEAD\n" + + " System.out.println(\"This is for demonstration, only.\");\n" + + "+=======\n" + + "+ System.out.println(\"Parameters:\");\n" + + "+ Arrays.stream(args).map(arg -> \"- \" + arg).forEach(System.out::println);\n" + + "+>>>>>>> feature/print_args\n" + + " }\n" + + " }"; + + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); + + @Test + public void diffBetweenTwoBranchesWithoutConflict() throws IOException { + MergeConflictResult result = computeMergeConflictResult("feature/rename_variable", "integration"); + assertThat(result.getConflicts()).isEmpty(); + } + + @Test + public void diffBetweenTwoBranchesWithSimpleConflict() throws IOException { + MergeConflictResult result = computeMergeConflictResult("feature/print_args", "integration"); + SingleMergeConflict conflict = result.getConflicts().get(0); + assertThat(conflict.getType()).isEqualTo(BOTH_MODIFIED); + assertThat(conflict.getPath()).isEqualTo("Main.java"); + assertThat(conflict.getDiff()).contains(DIFF_HEADER, DIFF_FILE_CONFLICT); + } + + @Test + public void diffBetweenTwoBranchesWithDeletedByUs() throws IOException { + MergeConflictResult result = computeMergeConflictResult("feature/remove_class", "integration"); + SingleMergeConflict conflict = result.getConflicts().get(0); + assertThat(conflict.getType()).isEqualTo(DELETED_BY_US); + assertThat(conflict.getPath()).isEqualTo("Main.java"); + } + + @Test + public void diffBetweenTwoBranchesWithDeletedByThem() throws IOException { + MergeConflictResult result = computeMergeConflictResult("integration", "feature/remove_class"); + SingleMergeConflict conflict = result.getConflicts().get(0); + assertThat(conflict.getType()).isEqualTo(DELETED_BY_THEM); + assertThat(conflict.getPath()).isEqualTo("Main.java"); + } + + private MergeConflictResult computeMergeConflictResult(String branchToMerge, String targetBranch) { + GitMergeCommand gitMergeCommand = new GitMergeCommand(createContext(), repository, new SimpleGitWorkdirFactory(new WorkdirProvider())); + MergeCommandRequest mergeCommandRequest = new MergeCommandRequest(); + mergeCommandRequest.setBranchToMerge(branchToMerge); + mergeCommandRequest.setTargetBranch(targetBranch); + return gitMergeCommand.computeConflicts(mergeCommandRequest); + } + + @Override + protected String getZippedRepositoryResource() { + return "sonia/scm/repository/spi/scm-git-spi-merge-diff-test.zip"; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-merge-diff-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-merge-diff-test.zip new file mode 100644 index 0000000000..9d957ef6f5 Binary files /dev/null and b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-merge-diff-test.zip differ diff --git a/scm-ui/ui-components/src/__resources__/Diff.simple.ts b/scm-ui/ui-components/src/__resources__/Diff.simple.ts index a08aa7cdb0..39d30be8ad 100644 --- a/scm-ui/ui-components/src/__resources__/Diff.simple.ts +++ b/scm-ui/ui-components/src/__resources__/Diff.simple.ts @@ -145,4 +145,27 @@ index 889cc49..d5a4811 100644 } @Test +diff --git a/Main.java b/Main.java +index e77e6da..f183b7c 100644 +--- a/Main.java ++++ b/Main.java +@@ -1,9 +1,18 @@ ++import java.io.PrintStream; + import java.util.Arrays; + + class Main { ++ private static final PrintStream OUT = System.out; ++ + public static void main(String[] args) { ++<<<<<<< HEAD + System.out.println("Expect nothing more to happen."); + System.out.println("The command line parameters are:"); + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); ++======= ++ OUT.println("Expect nothing more to happen."); ++ OUT.println("Parameters:"); ++ Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); ++>>>>>>> feature/use_constant + } + } `; diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 71aec23d5a..c7ab6a81b6 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -935,6 +935,66 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
, ] `; @@ -4333,6 +4393,532 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + 1 + + import java.io.PrintStream; +
+ 1 + + 2 + + import java.util.Arrays; +
+ 2 + + 3 + + +
+ 3 + + 4 + + class Main { +
+ + 5 + + private static final PrintStream OUT = System.out; +
+ + 6 + + +
+ 4 + + 7 + + public static void main(String[] args) { +
+ + 8 + + <<<<<<< HEAD +
+ 5 + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + 12 + + ======= +
+ + 13 + + OUT.println("Expect nothing more to happen."); +
+ + 14 + + OUT.println("Parameters:"); +
+ + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + 16 + + >>>>>>> feature/use_constant +
+ 8 + + 17 + + } +
+ 9 + + 18 + + } +
+
+
, ] `; @@ -7751,6 +8337,536 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
+

+ Custom File annotation for + Main.java +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + 1 + + import java.io.PrintStream; +
+ 1 + + 2 + + import java.util.Arrays; +
+ 2 + + 3 + + +
+ 3 + + 4 + + class Main { +
+ + 5 + + private static final PrintStream OUT = System.out; +
+ + 6 + + +
+ 4 + + 7 + + public static void main(String[] args) { +
+ + 8 + + <<<<<<< HEAD +
+ 5 + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + 12 + + ======= +
+ + 13 + + OUT.println("Expect nothing more to happen."); +
+ + 14 + + OUT.println("Parameters:"); +
+ + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + 16 + + >>>>>>> feature/use_constant +
+ 8 + + 17 + + } +
+ 9 + + 18 + + } +
+
+
, ] `; @@ -11209,6 +12325,544 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + 1 + + import java.io.PrintStream; +
+ 1 + + 2 + + import java.util.Arrays; +
+ 2 + + 3 + + +
+ 3 + + 4 + + class Main { +
+ + 5 + + private static final PrintStream OUT = System.out; +
+ + 6 + + +
+ 4 + + 7 + + public static void main(String[] args) { +
+ + 8 + + <<<<<<< HEAD +
+ 5 + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + 12 + + ======= +
+ + 13 + + OUT.println("Expect nothing more to happen."); +
+ + 14 + + OUT.println("Parameters:"); +
+ + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + 16 + + >>>>>>> feature/use_constant +
+ 8 + + 17 + + } +
+ 9 + + 18 + + } +
+
+
, ] `; @@ -15467,6 +17121,544 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + 1 + + import java.io.PrintStream; +
+ 1 + + 2 + + import java.util.Arrays; +
+ 2 + + 3 + + +
+

+ Line Annotation +

+
+ 3 + + 4 + + class Main { +
+ + 5 + + private static final PrintStream OUT = System.out; +
+ + 6 + + +
+ 4 + + 7 + + public static void main(String[] args) { +
+ + 8 + + <<<<<<< HEAD +
+ 5 + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + 12 + + ======= +
+ + 13 + + OUT.println("Expect nothing more to happen."); +
+ + 14 + + OUT.println("Parameters:"); +
+ + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + 16 + + >>>>>>> feature/use_constant +
+ 8 + + 17 + + } +
+ 9 + + 18 + + } +
+
+
, ] `; @@ -19103,6 +21295,568 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + 1 + + import java.io.PrintStream; +
+ 1 + + 2 + + import java.util.Arrays; +
+ 2 + + 3 + + +
+ 3 + + 4 + + class Main { +
+ + 5 + + private static final PrintStream OUT = System.out; +
+ + 6 + + +
+ 4 + + 7 + + public static void main(String[] args) { +
+ + 8 + + <<<<<<< HEAD +
+ 5 + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + 12 + + ======= +
+ + 13 + + OUT.println("Expect nothing more to happen."); +
+ + 14 + + OUT.println("Parameters:"); +
+ + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + 16 + + >>>>>>> feature/use_constant +
+ 8 + + 17 + + } +
+ 9 + + 18 + + } +
+
+
, ] `; @@ -22957,6 +25711,605 @@ Array [ , +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + 1 + + import java.io.PrintStream; +
+ 1 + + import java.util.Arrays; + + 2 + + import java.util.Arrays; +
+ 2 + + + + 3 + + +
+ 3 + + class Main { + + 4 + + class Main { +
+ + + 5 + + private static final PrintStream OUT = System.out; +
+ + + 6 + + +
+ 4 + + public static void main(String[] args) { + + 7 + + public static void main(String[] args) { +
+ + + 8 + + <<<<<<< HEAD +
+ 5 + + System.out.println("Expect nothing more to happen."); + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + System.out.println("The command line parameters are:"); + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + + 12 + + ======= +
+ + + 13 + + OUT.println("Expect nothing more to happen."); +
+ + + 14 + + OUT.println("Parameters:"); +
+ + + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + + 16 + + >>>>>>> feature/use_constant +
+ 8 + + } + + 17 + + } +
+ 9 + + } + + 18 + + } +
+
+
, ] `; diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index 942d789f99..634376626d 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -86,7 +86,8 @@ const ModifiedDiffComponent = styled(DiffComponent)` class DiffFile extends React.Component { static defaultProps: Partial = { - defaultCollapse: false + defaultCollapse: false, + markConflicts: true }; constructor(props: Props) { @@ -173,6 +174,9 @@ class DiffFile extends React.Component { }; renderHunk = (hunk: HunkType, i: number) => { + if (this.props.markConflicts && hunk.changes) { + this.markConflicts(hunk); + } return [ {this.createHunkHeader(hunk, i)}, { ]; }; + markConflicts = (hunk: HunkType) => { + let inConflict = false; + for (let i = 0; i < hunk.changes.length; ++i) { + if (hunk.changes[i].content === "<<<<<<< HEAD") { + inConflict = true; + } + if (inConflict) { + hunk.changes[i].type = "conflict"; + } + if (hunk.changes[i].content.startsWith(">>>>>>>")) { + inConflict = false; + } + } + }; + renderFileTitle = (file: File) => { if (file.oldPath !== file.newPath && (file.type === "copy" || file.type === "rename")) { return ( diff --git a/scm-ui/ui-components/src/repos/DiffTypes.ts b/scm-ui/ui-components/src/repos/DiffTypes.ts index 635d5e93d8..49e9785d29 100644 --- a/scm-ui/ui-components/src/repos/DiffTypes.ts +++ b/scm-ui/ui-components/src/repos/DiffTypes.ts @@ -27,7 +27,7 @@ export type Hunk = { content: string; }; -export type ChangeType = "insert" | "delete" | "normal"; +export type ChangeType = "insert" | "delete" | "normal" | "conflict"; export type Change = { content: string; @@ -75,4 +75,5 @@ export type DiffObjectProps = { fileControlFactory?: FileControlFactory; fileAnnotationFactory?: FileAnnotationFactory; annotationFactory?: AnnotationFactory; + markConflicts?: boolean; }; diff --git a/scm-ui/ui-styles/src/scm.scss b/scm-ui/ui-styles/src/scm.scss index 9f6a086541..bcb1fd4aa4 100644 --- a/scm-ui/ui-styles/src/scm.scss +++ b/scm-ui/ui-styles/src/scm.scss @@ -829,4 +829,12 @@ form .field:not(.is-grouped) { font-weight: 500 !important; } +.diff-gutter-conflict { + background: $warning-50; +} + +.diff-code-conflict { + background: $warning-25; +} + @import "bulma-popover/css/bulma-popover";