From 392e743fa86cf17f25a7053cfa1829ec3638e669 Mon Sep 17 00:00:00 2001 From: Antonin Delpeuch Date: Thu, 10 Oct 2024 06:33:36 +0200 Subject: [PATCH] feat: add diff3 support (#498) --- pom.xml | 2 +- src/main/java/se/kth/spork/cli/Cli.java | 28 ++++-- .../spoon/printer/PrinterPreprocessor.java | 43 ++++++--- .../spoon/printer/SporkPrettyPrinter.java | 49 ++++++---- src/main/kotlin/se/kth/spork/spoon/Parser.kt | 8 +- .../se/kth/spork/spoon/Spoon3dmMerge.kt | 6 +- .../spoon/conflict/CommentContentHandler.kt | 7 +- .../spoon/conflict/ContentConflictHandler.kt | 1 + .../kth/spork/spoon/conflict/ContentMerger.kt | 2 + .../spork/spoon/conflict/IsImplicitHandler.kt | 1 + .../spork/spoon/conflict/IsUpperHandler.kt | 1 + .../spork/spoon/conflict/ModifierHandler.kt | 1 + .../spoon/conflict/StructuralConflict.kt | 4 +- .../spoon/pcsinterpreter/PcsInterpreter.kt | 6 +- .../spoon/pcsinterpreter/SpoonTreeBuilder.kt | 4 +- .../spoon/pcsinterpreter/SporkTreeBuilder.kt | 58 +++++++----- .../se/kth/spork/util/LineBasedMerge.kt | 16 +++- .../java/se/kth/spork/LineBasedMergeTest.java | 27 ++++++ src/test/java/se/kth/spork/Util.java | 44 ++++++--- src/test/java/se/kth/spork/cli/CliTest.java | 40 +++++++- .../add_similar_fields/ExpectedDiff3.java | 18 ++++ .../ExpectedDiff3.java | 7 ++ .../ExpectedDiff3.java | 91 +++++++++++++++++++ .../ExpectedDiff3.java | 13 +++ .../ExpectedDiff3.java | 9 ++ .../ExpectedDiff3.java | 38 ++++++++ .../ExpectedDiff3.java | 21 +++++ .../ExpectedDiff3.java | 21 +++++ .../ExpectedDiff3.java | 14 +++ .../ExpectedDiff3.java | 13 +++ .../ExpectedDiff3.java | 9 ++ .../ExpectedDiff3.java | 14 +++ .../ExpectedDiff3.java | 22 +++++ .../simple_delete_delete/ExpectedDiff3.java | 13 +++ .../simple_insert_delete/ExpectedDiff3.java | 11 +++ .../ExpectedDiff3.java | 14 +++ 36 files changed, 584 insertions(+), 92 deletions(-) create mode 100644 src/test/java/se/kth/spork/LineBasedMergeTest.java create mode 100644 src/test/resources/conflict/add_similar_fields/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_class_visibility/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_comment_edits/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_literal_change/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_method_visibility/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_operator_change/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_type_change/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_variable_rename/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/conflicting_wildcard_changes/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/integer_literal_conflict/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/method_visibility_left_empty/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/multiple_conflicting_statements/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/multiple_simple_conflicts/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/simple_delete_delete/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/simple_insert_delete/ExpectedDiff3.java create mode 100644 src/test/resources/conflict/single_conflicting_statement/ExpectedDiff3.java diff --git a/pom.xml b/pom.xml index 7f39599c..fbabbdc0 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ org.eclipse.jgit org.eclipse.jgit - 6.9.0.202403050737-r + 6.10.0.202406032230-r org.jetbrains.kotlin diff --git a/src/main/java/se/kth/spork/cli/Cli.java b/src/main/java/se/kth/spork/cli/Cli.java index a8a1a5ab..08997162 100644 --- a/src/main/java/se/kth/spork/cli/Cli.java +++ b/src/main/java/se/kth/spork/cli/Cli.java @@ -55,7 +55,11 @@ public static String prettyPrint(CtModule spoonRoot) { .map(Object::toString) .map(impStmt -> impStmt.substring("import ".length(), impStmt.length() - 1)) .collect(Collectors.toList()); - new PrinterPreprocessor(importNames, activePackage.getQualifiedName()).scan(spoonRoot); + new PrinterPreprocessor( + importNames, + activePackage.getQualifiedName(), + Spoon3dmMerge.INSTANCE.getDiff3()) + .scan(spoonRoot); StringBuilder sb = new StringBuilder(); @@ -135,6 +139,12 @@ static class Merge implements Callable { "Enable Git compatibility mode. Required to use Spork as a Git merge driver.") boolean gitMode; + @CommandLine.Option( + names = {"--diff3"}, + description = + "In conflicts, show the version at the base revision in addition to the left and right versions.") + boolean diff3; + @CommandLine.Option( names = {"-l", "--logging"}, description = "Enable logging output") @@ -145,6 +155,8 @@ public Integer call() throws IOException { if (logging) { setLogLevel("DEBUG"); } + Parser.INSTANCE.setDiff3(diff3); + Spoon3dmMerge.INSTANCE.setDiff3(diff3); long start = System.nanoTime(); @@ -162,7 +174,7 @@ public Integer call() throws IOException { rightPath.toFile().deleteOnExit(); } - Pair merged = merge(basePath, leftPath, rightPath, exitOnError); + Pair merged = merge(basePath, leftPath, rightPath, exitOnError, diff3); String pretty = merged.getFirst(); int numConflicts = merged.getSecond(); @@ -195,10 +207,11 @@ public Integer call() throws IOException { * @param right Path to right revision. * @param exitOnError Disallow the use of line-based fallback if the structured merge encounters * an error. + * @param diff3 whether to use the diff3 style one or the default one * @return A pair on the form (prettyPrint, numConflicts) */ public static Pair merge( - Path base, Path left, Path right, boolean exitOnError) { + Path base, Path left, Path right, boolean exitOnError, boolean diff3) { try { LOGGER.info(() -> "Parsing input files"); CtModule baseModule = Parser.INSTANCE.parse(base); @@ -221,7 +234,7 @@ public static Pair merge( LOGGER.warn( () -> "Merge contains no types (i.e. classes, interfaces, etc), reverting to line-based merge"); - return lineBasedMerge(base, left, right); + return lineBasedMerge(base, left, right, diff3); } } catch (Exception e) { if (exitOnError) { @@ -234,16 +247,17 @@ public static Pair merge( LOGGER.info( () -> "Spork encountered an error in structured merge. Falling back to line-based merge"); - return lineBasedMerge(base, left, right); + return lineBasedMerge(base, left, right, diff3); } } } - private static Pair lineBasedMerge(Path base, Path left, Path right) { + private static Pair lineBasedMerge( + Path base, Path left, Path right, boolean diff3) { String baseStr = Parser.INSTANCE.read(base); String leftStr = Parser.INSTANCE.read(left); String rightStr = Parser.INSTANCE.read(right); - return LineBasedMergeKt.lineBasedMerge(baseStr, leftStr, rightStr); + return LineBasedMergeKt.lineBasedMerge(baseStr, leftStr, rightStr, diff3); } /** diff --git a/src/main/java/se/kth/spork/spoon/printer/PrinterPreprocessor.java b/src/main/java/se/kth/spork/spoon/printer/PrinterPreprocessor.java index 994fd48d..02ecd30d 100644 --- a/src/main/java/se/kth/spork/spoon/printer/PrinterPreprocessor.java +++ b/src/main/java/se/kth/spork/spoon/printer/PrinterPreprocessor.java @@ -2,6 +2,7 @@ import java.util.*; import kotlin.Pair; +import kotlin.Triple; import se.kth.spork.exception.ConflictException; import se.kth.spork.spoon.conflict.ContentConflict; import se.kth.spork.spoon.conflict.ModifierHandler; @@ -32,20 +33,22 @@ public class PrinterPreprocessor extends CtScanner { private final String activePackage; private final Map> refToPack; + private final boolean diff3; private int currentConflictId; // A mapping with content_conflict_id -> (left_side, right_side) mappings that are valid // in the entire source tree // TODO improve the pretty-printer such that this hack is redundant - private final Map> globalContentConflicts; + private final Map> globalContentConflicts; - public PrinterPreprocessor(List importStatements, String activePackage) { + public PrinterPreprocessor(List importStatements, String activePackage, boolean diff3) { this.importStatements = importStatements; this.activePackage = activePackage; refToPack = new HashMap<>(); currentConflictId = 0; globalContentConflicts = new HashMap<>(); + this.diff3 = diff3; } @Override @@ -117,13 +120,14 @@ private void handleIncorrectExplicitPackages(CtElement element) { private void processConflict(ContentConflict conflict, CtElement element) { Object leftVal = conflict.getLeft().getValue(); Object rightVal = conflict.getRight().getValue(); + Object baseVal = conflict.getBase() == null ? "" : conflict.getBase().getValue(); // The local printer map, unlike the global printer map, is only valid in the scope of the // current CtElement. It contains conflicts for anything that can't be replaced with a // conflict id, // such as operators and modifiers (as these are represented by enums) // TODO improve the pretty-printer such that this hack is redundant - Map> localPrinterMap = new HashMap<>(); + Map> localPrinterMap = new HashMap<>(); switch (conflict.getRole()) { case NAME: @@ -132,7 +136,8 @@ private void processConflict(ContentConflict conflict, CtElement element) { // always scanned separately by the printer (often it just calls `getSimpleName`) String conflictKey = CONTENT_CONFLICT_PREFIX + currentConflictId++; globalContentConflicts.put( - conflictKey, new Pair<>(leftVal.toString(), rightVal.toString())); + conflictKey, + new Triple<>(leftVal.toString(), rightVal.toString(), baseVal.toString())); element.setValueByRole(conflict.getRole(), conflictKey); break; case COMMENT_CONTENT: @@ -141,13 +146,13 @@ private void processConflict(ContentConflict conflict, CtElement element) { String rawRight = (String) conflict.getRight().getMetadata(RoledValue.Key.RAW_CONTENT); String rawBase = - conflict.getBase() == null + conflict.getBase() != null ? (String) conflict.getBase().getMetadata(RoledValue.Key.RAW_CONTENT) : ""; Pair rawConflict = - LineBasedMergeKt.lineBasedMerge(rawBase, rawLeft, rawRight); + LineBasedMergeKt.lineBasedMerge(rawBase, rawLeft, rawRight, diff3); assert rawConflict.getSecond() > 0 : "Comments without conflict should already have been merged"; @@ -155,33 +160,45 @@ private void processConflict(ContentConflict conflict, CtElement element) { break; case IS_UPPER: if (leftVal.equals(true)) { - localPrinterMap.put("extends", new Pair<>("extends", "super")); + localPrinterMap.put("extends", new Triple<>("extends", "super", "")); } else { - localPrinterMap.put("super", new Pair<>("super", "extends")); + localPrinterMap.put("super", new Triple<>("super", "extends", "")); } break; case MODIFIER: Collection leftMods = (Collection) leftVal; Collection rightMods = (Collection) rightVal; + Collection baseMods = (Collection) baseVal; Set leftVisibilities = ModifierHandler.Companion.categorizeModifiers(leftMods).getFirst(); Set rightVisibilities = ModifierHandler.Companion.categorizeModifiers(rightMods).getFirst(); - + Set baseVisibilities = + baseVal == null + ? Collections.emptySet() + : ModifierHandler.Companion.categorizeModifiers(baseMods) + .getFirst(); + + String baseVisStr = + baseVisibilities.isEmpty() + ? "" + : baseVisibilities.iterator().next().toString(); if (leftVisibilities.isEmpty()) { // use the right-hand visibility in actual tree to force something to be printed Collection mods = element.getValueByRole(CtRole.MODIFIER); ModifierKind rightVis = rightVisibilities.iterator().next(); mods.add(rightVis); element.setValueByRole(CtRole.MODIFIER, mods); - localPrinterMap.put(rightVis.toString(), new Pair<>("", rightVis.toString())); + localPrinterMap.put( + rightVis.toString(), new Triple<>("", rightVis.toString(), baseVisStr)); } else { String leftVisStr = leftVisibilities.iterator().next().toString(); String rightVisStr = rightVisibilities.isEmpty() ? "" : rightVisibilities.iterator().next().toString(); - localPrinterMap.put(leftVisStr, new Pair<>(leftVisStr, rightVisStr)); + localPrinterMap.put( + leftVisStr, new Triple<>(leftVisStr, rightVisStr, baseVisStr)); } break; case OPERATOR_KIND: @@ -189,12 +206,14 @@ private void processConflict(ContentConflict conflict, CtElement element) { String leftStr = OperatorHelper.getOperatorText(leftVal); String rightStr = OperatorHelper.getOperatorText(rightVal); + String baseStr = baseVal == null ? "" : OperatorHelper.getOperatorText(baseVal); if (element instanceof CtOperatorAssignment) { leftStr += "="; rightStr += "="; + baseStr += "="; } - localPrinterMap.put(leftStr, new Pair<>(leftStr, rightStr)); + localPrinterMap.put(leftStr, new Triple<>(leftStr, rightStr, baseStr)); break; default: throw new ConflictException("Unhandled conflict: " + leftVal + ", " + rightVal); diff --git a/src/main/java/se/kth/spork/spoon/printer/SporkPrettyPrinter.java b/src/main/java/se/kth/spork/spoon/printer/SporkPrettyPrinter.java index 7d3d33c2..38e2a44e 100644 --- a/src/main/java/se/kth/spork/spoon/printer/SporkPrettyPrinter.java +++ b/src/main/java/se/kth/spork/spoon/printer/SporkPrettyPrinter.java @@ -1,7 +1,7 @@ package se.kth.spork.spoon.printer; import java.util.*; -import kotlin.Pair; +import kotlin.Triple; import se.kth.spork.spoon.conflict.StructuralConflict; import se.kth.spork.spoon.pcsinterpreter.SpoonTreeBuilder; import spoon.compiler.Environment; @@ -18,20 +18,22 @@ public final class SporkPrettyPrinter extends DefaultJavaPrettyPrinter { public static final String START_CONFLICT = "<<<<<<< LEFT"; + public static final String BASE_CONFLICT = "||||||| BASE"; public static final String MID_CONFLICT = "======="; public static final String END_CONFLICT = ">>>>>>> RIGHT"; - private static final Map> DEFAULT_CONFLICT_MAP = + private static final Map> DEFAULT_CONFLICT_MAP = Collections.emptyMap(); private final SporkPrinterHelper printerHelper; private final String lineSeparator = getLineSeparator(); + private final boolean diff3; - private Map> globalContentConflicts; + private Map> globalContentConflicts; - private Deque>>> localContentConflictMaps; + private Deque>>> localContentConflictMaps; - public SporkPrettyPrinter(Environment env) { + public SporkPrettyPrinter(Environment env, boolean diff3) { super(env); printerHelper = new SporkPrinterHelper(env); localContentConflictMaps = new ArrayDeque<>(); @@ -48,6 +50,7 @@ public SporkPrettyPrinter(Environment env) { setIgnoreImplicit(false); globalContentConflicts = DEFAULT_CONFLICT_MAP; + this.diff3 = diff3; } /** Check if the element is a multi declaration (i.e. something like `int a, b, c;`. */ @@ -72,12 +75,12 @@ private static boolean isMultiDeclaration(CtElement e, String declarationSource) protected void enter(CtElement e) { localContentConflictMaps.push( Optional.ofNullable( - (Map>) + (Map>) e.getMetadata(PrinterPreprocessor.LOCAL_CONFLICT_MAP_KEY))); if (globalContentConflicts == DEFAULT_CONFLICT_MAP) { - Map> globals = - (Map>) + Map> globals = + (Map>) e.getMetadata(PrinterPreprocessor.GLOBAL_CONFLICT_MAP_KEY); if (globals != null) { globalContentConflicts = globals; @@ -176,7 +179,11 @@ private void handleStructuralConflict( private void writeStructuralConflict(StructuralConflict structuralConflict) { String leftSource = SourceExtractor.getOriginalSource(structuralConflict.getLeft()); String rightSource = SourceExtractor.getOriginalSource(structuralConflict.getRight()); - printerHelper.writeConflict(leftSource, rightSource); + String baseSource = + structuralConflict.getBase() == null + ? "" + : SourceExtractor.getOriginalSource(structuralConflict.getBase()); + printerHelper.writeConflict(leftSource, rightSource, baseSource); } private class SporkPrinterHelper extends PrinterHelper { @@ -186,10 +193,11 @@ public SporkPrinterHelper(Environment env) { @Override public SporkPrinterHelper write(String s) { - Optional>> localConflictMap = + Optional>> localConflictMap = localContentConflictMaps.peek(); String trimmed = s.trim(); if (trimmed.startsWith(START_CONFLICT) + || trimmed.startsWith(BASE_CONFLICT) || trimmed.startsWith(MID_CONFLICT) || trimmed.startsWith(END_CONFLICT)) { // All we need to do here is the decrease tabs and enter some appropriate whitespace @@ -199,11 +207,12 @@ public SporkPrinterHelper write(String s) { String strippedQuotes = trimmed.replaceAll("\"", ""); if (globalContentConflicts.containsKey(strippedQuotes)) { - Pair conflict = globalContentConflicts.get(strippedQuotes); - writeConflict(conflict.getFirst(), conflict.getSecond()); + Triple conflict = + globalContentConflicts.get(strippedQuotes); + writeConflict(conflict.getFirst(), conflict.getSecond(), conflict.getThird()); } else if (localConflictMap.isPresent() && localConflictMap.get().containsKey(s)) { - Pair conflict = localConflictMap.get().get(s); - writeConflict(conflict.getFirst(), conflict.getSecond()); + Triple conflict = localConflictMap.get().get(s); + writeConflict(conflict.getFirst(), conflict.getSecond(), conflict.getThird()); } else { super.write(s); } @@ -211,12 +220,18 @@ public SporkPrinterHelper write(String s) { return this; } - public SporkPrinterHelper writeConflict(String left, String right) { + public SporkPrinterHelper writeConflict(String left, String right, String base) { writelnIfNotPresent() .writeAtLeftMargin(START_CONFLICT) .writeln() - .writeAtLeftMargin(left) - .writelnIfNotPresent() + .writeAtLeftMargin(left); + if (diff3) { + writelnIfNotPresent() + .writeAtLeftMargin(BASE_CONFLICT) + .writeln() + .writeAtLeftMargin(base); + } + writelnIfNotPresent() .writeAtLeftMargin(MID_CONFLICT) .writeln() .writeAtLeftMargin(right) diff --git a/src/main/kotlin/se/kth/spork/spoon/Parser.kt b/src/main/kotlin/se/kth/spork/spoon/Parser.kt index 45379ff1..e8f6b83f 100644 --- a/src/main/kotlin/se/kth/spork/spoon/Parser.kt +++ b/src/main/kotlin/se/kth/spork/spoon/Parser.kt @@ -26,6 +26,8 @@ object Parser { const val COMPILATION_UNIT_COMMENT = "spork_cu_comment" private val LOGGER = LazyLogger(Parser::class.java) + var diff3 = false + /** * Parse a Java file to a Spoon tree. Any import statements in the file are attached to the returned module's * metadata with the [Parser.IMPORT_STATEMENTS] key. The imports are sorted in ascending lexicographical @@ -55,10 +57,10 @@ object Parser { } } - fun setSporkEnvironment(env: Environment, tabulationSize: Int, useTabs: Boolean) { + fun setSporkEnvironment(env: Environment, tabulationSize: Int, useTabs: Boolean, diff3: Boolean) { env.tabulationSize = tabulationSize env.useTabulations(useTabs) - env.setPrettyPrinterCreator { SporkPrettyPrinter(env) } + env.setPrettyPrinterCreator { SporkPrettyPrinter(env, diff3) } env.noClasspath = true } @@ -69,7 +71,7 @@ object Parser { val indentationGuess = SourceExtractor.guessIndentation(model) val indentationType = if (indentationGuess.second) "tabs" else "spaces" LOGGER.info { "Using indentation: " + indentationGuess.first + " " + indentationType } - setSporkEnvironment(launcher.environment, indentationGuess.first, indentationGuess.second) + setSporkEnvironment(launcher.environment, indentationGuess.first, indentationGuess.second, diff3) val module = model.unnamedModule module.putMetadata(COMPILATION_UNIT_COMMENT, getCuComment(module)) diff --git a/src/main/kotlin/se/kth/spork/spoon/Spoon3dmMerge.kt b/src/main/kotlin/se/kth/spork/spoon/Spoon3dmMerge.kt index 014380ac..31801282 100644 --- a/src/main/kotlin/se/kth/spork/spoon/Spoon3dmMerge.kt +++ b/src/main/kotlin/se/kth/spork/spoon/Spoon3dmMerge.kt @@ -46,6 +46,8 @@ import java.util.HashSet object Spoon3dmMerge { private val LOGGER = LazyLogger(Spoon3dmMerge::class.java) + var diff3 = false + /** * Merge the left and right revisions with an AST-based merge. * @@ -170,11 +172,13 @@ object Spoon3dmMerge { CommentContentHandler(), ) val merge = fromMergedPcs( + t0Star, delta, baseLeft, baseRight, structuralConflictHandlers, contentConflictHandlers, + diff3, ) // we can be certain that the merge tree has the same root type as the three constituents, @@ -314,7 +318,7 @@ object Spoon3dmMerge { val baseComment = getCuComment(base) val leftComment = getCuComment(left) val rightComment = getCuComment(right) - return lineBasedMerge(baseComment, leftComment, rightComment) + return lineBasedMerge(baseComment, leftComment, rightComment, diff3) } private fun getCuComment(mod: CtElement): String = mod.getMetadata(Parser.COMPILATION_UNIT_COMMENT) as String diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/CommentContentHandler.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/CommentContentHandler.kt index 49c71d8b..cbfcf6dc 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/CommentContentHandler.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/CommentContentHandler.kt @@ -20,12 +20,13 @@ class CommentContentHandler : ContentConflictHandler { baseElem: CtElement?, leftElem: CtElement, rightElem: CtElement, + diff3: Boolean, ): Pair { - return Pair(mergeComments(baseVal ?: "", leftVal, rightVal), false) + return Pair(mergeComments(baseVal ?: "", leftVal, rightVal, diff3), false) } - private fun mergeComments(base: Any, left: Any, right: Any): Any? { - val merge = lineBasedMerge(base.toString(), left.toString(), right.toString()) + private fun mergeComments(base: Any, left: Any, right: Any, diff3: Boolean): Any? { + val merge = lineBasedMerge(base.toString(), left.toString(), right.toString(), diff3) return if (merge.second > 0) { null } else { diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/ContentConflictHandler.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/ContentConflictHandler.kt index 889cfcb0..82d5dfee 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/ContentConflictHandler.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/ContentConflictHandler.kt @@ -39,6 +39,7 @@ interface ContentConflictHandler { baseElem: CtElement?, leftElem: CtElement, rightElem: CtElement, + diff3: Boolean, ): Pair /** @return The role that this conflict handler deals with. diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/ContentMerger.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/ContentMerger.kt index eb956d0a..19103468 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/ContentMerger.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/ContentMerger.kt @@ -24,6 +24,7 @@ class ContentMerger(conflictHandlers: List) { */ fun mergedContent( nodeContents: Set>, + diff3: Boolean, ): Pair> { if (nodeContents.size == 1) { return Pair(nodeContents.iterator().next().value, emptyList()) @@ -85,6 +86,7 @@ class ContentMerger(conflictHandlers: List) { baseRoledValues?.element, leftRoledValues.element, rightRoledValues.element, + diff3, ) merged = result.first conflictPresent = result.second diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/IsImplicitHandler.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/IsImplicitHandler.kt index 19e1cbd4..7ef2a28c 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/IsImplicitHandler.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/IsImplicitHandler.kt @@ -20,6 +20,7 @@ class IsImplicitHandler : ContentConflictHandler { baseElem: CtElement?, leftElem: CtElement, rightElem: CtElement, + diff3: Boolean, ): Pair { val mergedValue = if (baseVal != null) { // as there are only two possible values for a boolean, left and right disagreeing must diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/IsUpperHandler.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/IsUpperHandler.kt index 6a18b7ba..a96fc5e4 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/IsUpperHandler.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/IsUpperHandler.kt @@ -22,6 +22,7 @@ class IsUpperHandler : ContentConflictHandler { baseElem: CtElement?, leftElem: CtElement, rightElem: CtElement, + diff3: Boolean, ): Pair { return Pair(mergeIsUpper(baseElem, leftElem, rightElem), false) } diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/ModifierHandler.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/ModifierHandler.kt index 556aeddc..fd58bad1 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/ModifierHandler.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/ModifierHandler.kt @@ -25,6 +25,7 @@ class ModifierHandler : ContentConflictHandler { baseElem: CtElement?, leftElem: CtElement, rightElem: CtElement, + diff3: Boolean, ): Pair { return mergeModifierKinds( (baseVal ?: setOf()) as Set, diff --git a/src/main/kotlin/se/kth/spork/spoon/conflict/StructuralConflict.kt b/src/main/kotlin/se/kth/spork/spoon/conflict/StructuralConflict.kt index 3f9905c5..db81f471 100644 --- a/src/main/kotlin/se/kth/spork/spoon/conflict/StructuralConflict.kt +++ b/src/main/kotlin/se/kth/spork/spoon/conflict/StructuralConflict.kt @@ -21,10 +21,10 @@ class StructuralConflict { * @param left The left part of the conflict. * @param right The right part of the conflict. */ - constructor(left: List, right: List) { + constructor(base: List, left: List, right: List) { this.left = left this.right = right - base = null + this.base = base lineBasedMerge = null } diff --git a/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/PcsInterpreter.kt b/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/PcsInterpreter.kt index 6ce4a78f..7d203b5b 100644 --- a/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/PcsInterpreter.kt +++ b/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/PcsInterpreter.kt @@ -18,13 +18,15 @@ import spoon.reflect.declaration.CtElement * @return A pair on the form (tree, numConflicts). */ fun fromMergedPcs( + base: ChangeSet, delta: ChangeSet, baseLeft: SpoonMapping, baseRight: SpoonMapping, structuralConflictHandlers: List, contentConflictHandlers: List, + diff3: Boolean, ): Pair { - val sporkTreeBuilder = SporkTreeBuilder(delta, baseLeft, baseRight, structuralConflictHandlers) + val sporkTreeBuilder = SporkTreeBuilder(base, delta, baseLeft, baseRight, structuralConflictHandlers, diff3) val sporkTreeRoot = sporkTreeBuilder.buildTree() // this is a bit of a hack, get any used environment such that the SpoonTreeBuilder can copy environment details @@ -34,7 +36,7 @@ fun fromMergedPcs( .element .factory .environment - val spoonTreeBuilder = SpoonTreeBuilder(baseLeft, baseRight, oldEnv, contentConflictHandlers) + val spoonTreeBuilder = SpoonTreeBuilder(baseLeft, baseRight, oldEnv, contentConflictHandlers, diff3) val spoonTreeRoot = spoonTreeBuilder.build(sporkTreeRoot) return Pair( spoonTreeRoot, diff --git a/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.kt b/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.kt index 8909e3af..d1062b25 100644 --- a/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.kt +++ b/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.kt @@ -43,6 +43,7 @@ class SpoonTreeBuilder internal constructor( private val baseRight: SpoonMapping, oldEnv: Environment, contentConflictHandlers: List, + private val diff3: Boolean, ) { var numContentConflicts: Int = 0 private set @@ -115,7 +116,7 @@ class SpoonTreeBuilder internal constructor( mergeTree = originalTree.clone() mergeTree.putMetadata(SINGLE_REVISION_KEY, sporkChild.singleRevision) } else { - val mergedContent = contentMerger.mergedContent(sporkChild.content) + val mergedContent = contentMerger.mergedContent(sporkChild.content, diff3) mergeTree = shallowCopyTree(originalTree, factory) mergedContent .first?.forEach { roledValue -> @@ -360,6 +361,7 @@ class SpoonTreeBuilder internal constructor( factory.environment, oldEnv.tabulationSize, oldEnv.isUsingTabulations, + diff3, ) } } diff --git a/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.kt b/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.kt index 4f7f4e4a..0946b3ea 100644 --- a/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.kt +++ b/src/main/kotlin/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.kt @@ -8,7 +8,6 @@ import se.kth.spork.base3dm.Revision import se.kth.spork.exception.ConflictException import se.kth.spork.spoon.conflict.ConflictType import se.kth.spork.spoon.conflict.StructuralConflict -import se.kth.spork.spoon.conflict.StructuralConflict.Companion.isPredecessorConflict import se.kth.spork.spoon.conflict.StructuralConflict.Companion.isSuccessorConflict import se.kth.spork.spoon.conflict.StructuralConflictHandler import se.kth.spork.spoon.matching.SpoonMapping @@ -18,7 +17,9 @@ import se.kth.spork.spoon.wrappers.SpoonNode import se.kth.spork.util.LazyLogger import se.kth.spork.util.lineBasedMerge import java.lang.NullPointerException +import java.util.* import java.util.stream.Collectors +import kotlin.collections.HashMap /** * Class for building a [SporkTree] from a merged [ChangeSet]. @@ -29,12 +30,15 @@ import java.util.stream.Collectors * @param conflictHandlers All conflict handlers. */ internal class SporkTreeBuilder( + private val base: ChangeSet, private val delta: ChangeSet, private val baseLeft: SpoonMapping, private val baseRight: SpoonMapping, private val conflictHandlers: List, + private val diff3: Boolean, ) { private val rootToChildren: Map>> = buildRootToChildren(delta.pcsSet) + private val baseRootToChildren: Map>> = buildRootToChildren(base.pcsSet) private val contents: Map>> = delta.contents private var numStructuralConflicts: Int = 0 @@ -83,6 +87,7 @@ internal class SporkTreeBuilder( */ fun build(currentRoot: SpoonNode): SporkTree { val children: Map>? = rootToChildren[currentRoot] + val baseChildren: Map>? = baseRootToChildren[currentRoot] val currentContent = contents[currentRoot] ?: emptySet() var tree = SporkTree(currentRoot, currentContent) if (children == null) { @@ -90,7 +95,7 @@ internal class SporkTreeBuilder( return tree } try { - build(currentRoot.startOfChildList, tree, children) + build(currentRoot.startOfChildList, tree, children, baseChildren) for (inconsistent in remainingInconsistencies) { if (inconsistent.root == tree.node) { throw ConflictException("Missed conflict: $inconsistent") @@ -124,7 +129,7 @@ internal class SporkTreeBuilder( return tree } - private fun build(start: SpoonNode, tree: SporkTree, children: Map>?) { + private fun build(start: SpoonNode, tree: SporkTree, children: Map>?, baseChildren: Map>?) { if (children == null) { // leaf node return @@ -137,16 +142,17 @@ internal class SporkTreeBuilder( if (next.isListEdge) { // can still have a conflict at the end of the child list getSuccessorConflict(nextPcs)?.apply { - traverseConflict(nextPcs, this, children, tree) + traverseConflict(nextPcs, this, null, children, baseChildren, tree) // TODO supply base? } break } if (next.isVirtual && !next.isListEdge) { - build(next.startOfChildList, tree, rootToChildren[next]) + build(next.startOfChildList, tree, rootToChildren[next], baseRootToChildren[next]) } else { val successorConflict = getSuccessorConflict(nextPcs) if (successorConflict != null) { - next = traverseConflict(nextPcs, successorConflict, children, tree) + val baseSuccessor = getBaseSuccessor(nextPcs) + next = traverseConflict(nextPcs, successorConflict, baseSuccessor, children, baseChildren, tree) } else { addChild(tree, build(next)) } @@ -166,6 +172,9 @@ internal class SporkTreeBuilder( ) } + private fun getBaseSuccessor(pcs: Pcs): Pcs? = + base.getOtherSuccessors(pcs).firstOrNull() + /** * When a conflict in the child list of a node is not possible to resolve, we approximate the * conflict by finding the node's matches in the left and right revisions and have them make up @@ -200,6 +209,7 @@ internal class SporkTreeBuilder( base.element, left!!.element, right!!.element, + diff3, ) numStructuralConflicts += second return StructuralConflict( @@ -213,7 +223,9 @@ internal class SporkTreeBuilder( private fun traverseConflict( nextPcs: Pcs, conflicting: Pcs, + baseSuccessor: Pcs?, children: Map>, + baseChildren: Map>?, tree: SporkTree, ): SpoonNode { remainingInconsistencies.remove(nextPcs) @@ -221,8 +233,9 @@ internal class SporkTreeBuilder( listOf(Revision.LEFT, Revision.RIGHT).forEach(tree::addRevision) val leftPcs = if (nextPcs.revision === Revision.LEFT) nextPcs else conflicting val rightPcs = if (leftPcs === nextPcs) conflicting else nextPcs - val leftNodes = extractConflictList(leftPcs, children) - val rightNodes = extractConflictList(rightPcs, children) + val leftNodes = extractConflictList(leftPcs, children, null) + val rightNodes = extractConflictList(rightPcs, children, null) + val baseNodes = if (baseSuccessor == null) Collections.emptyList() else extractConflictList(baseSuccessor, children, baseChildren) val resolved = tryResolveConflict(leftNodes, rightNodes) // if nextPcs happens to be the final PCS of a child list, next may be a virtual node @@ -232,6 +245,9 @@ internal class SporkTreeBuilder( } else { numStructuralConflicts++ val conflict = StructuralConflict( + baseNodes.stream() + .map(SpoonNode::element) + .collect(Collectors.toList()), leftNodes.stream() .map(SpoonNode::element) .collect(Collectors.toList()), @@ -263,25 +279,15 @@ internal class SporkTreeBuilder( private fun extractConflictList( pcs: Pcs, siblings: Map>, + baseSiblings: Map>?, ): List { var currentPcs = pcs val nodes: MutableList = mutableListOf() while (true) { - val conflicts = delta.structuralConflicts[currentPcs] - if (conflicts != null && conflicts.isNotEmpty()) { - val finalPcs = currentPcs - val predConflict = conflicts.stream() - .filter { - isPredecessorConflict( - finalPcs, - it, - ) - } - .findFirst() - if (predConflict.isPresent) { - remainingInconsistencies.remove(predConflict.get()) - return nodes - } + val otherPred = delta.getOtherPredecessors(currentPcs).firstOrNull() + if (otherPred != null) { + remainingInconsistencies.remove(otherPred) + return nodes } val nextNode = currentPcs.successor if (nextNode.isEndOfList) { @@ -290,7 +296,11 @@ internal class SporkTreeBuilder( ) } nodes.add(nextNode) - currentPcs = siblings[nextNode]!! + var firstSibling = siblings[nextNode] + if (firstSibling == null && baseSiblings != null) { + firstSibling = baseSiblings[nextNode] + } + currentPcs = firstSibling!! } } diff --git a/src/main/kotlin/se/kth/spork/util/LineBasedMerge.kt b/src/main/kotlin/se/kth/spork/util/LineBasedMerge.kt index d0c1e5a4..9d10b1de 100644 --- a/src/main/kotlin/se/kth/spork/util/LineBasedMerge.kt +++ b/src/main/kotlin/se/kth/spork/util/LineBasedMerge.kt @@ -19,7 +19,7 @@ import java.util.Objects * @param right The right revision. * @return A pair containing the merge and the amount of conflicts. */ -fun lineBasedMerge(base: String, left: String, right: String): Pair { +fun lineBasedMerge(base: String, left: String, right: String, diff3: Boolean): Pair { if (base.isEmpty() && (left.isEmpty() || right.isEmpty())) { // For some reason, this merge implementation reports a conflict on pure additions. // This is an easy fix for that. See #144 for details. @@ -31,6 +31,7 @@ fun lineBasedMerge(base: String, left: String, right: String): Pair val rightRaw = RawText(right.toByteArray()) val merge = MergeAlgorithm() + val res: MergeResult = merge.merge( object : SequenceComparator() { override fun equals(lhs: RawText, lhsIdx: Int, rhs: RawText, rhsIdx: Int) = @@ -66,7 +67,14 @@ fun lineBasedMerge(base: String, left: String, right: String): Pair } else if (chunk.conflictState == MergeChunk.ConflictState.BASE_CONFLICTING_RANGE ) { - continue + if (!diff3) { + continue + } + if (!inConflict) { + lines.add(SporkPrettyPrinter.START_CONFLICT) + inConflict = true + } + lines.add(SporkPrettyPrinter.BASE_CONFLICT) } for (i in chunk.begin until chunk.end) { lines.add(seq.getString(i)) @@ -93,9 +101,9 @@ fun lineBasedMerge(base: String, left: String, right: String): Pair * @param right The right revision. * @return A pair containing the merge and the amount of conflicts. */ -fun lineBasedMerge(base: CtElement, left: CtElement, right: CtElement): Pair { +fun lineBasedMerge(base: CtElement, left: CtElement, right: CtElement, diff3: Boolean): Pair { val baseSource = SourceExtractor.getOriginalSource(base) val leftSource = SourceExtractor.getOriginalSource(left) val rightSource = SourceExtractor.getOriginalSource(right) - return lineBasedMerge(baseSource, leftSource, rightSource) + return lineBasedMerge(baseSource, leftSource, rightSource, diff3) } diff --git a/src/test/java/se/kth/spork/LineBasedMergeTest.java b/src/test/java/se/kth/spork/LineBasedMergeTest.java new file mode 100644 index 00000000..9c14602d --- /dev/null +++ b/src/test/java/se/kth/spork/LineBasedMergeTest.java @@ -0,0 +1,27 @@ +package se.kth.spork; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import kotlin.Pair; +import org.junit.jupiter.api.Test; +import se.kth.spork.util.LineBasedMergeKt; + +public class LineBasedMergeTest { + @Test + public void testSimpleMerge() { + Pair result = + LineBasedMergeKt.lineBasedMerge( + "hello world", "ola mundo", "bonjour le monde", true); + + assertEquals( + result.component1(), + "<<<<<<< LEFT\n" + + "ola mundo\n" + + "||||||| BASE\n" + + "hello world\n" + + "=======\n" + + "bonjour le monde\n" + + ">>>>>>> RIGHT"); + assertEquals(result.component2(), 1); + } +} diff --git a/src/test/java/se/kth/spork/Util.java b/src/test/java/se/kth/spork/Util.java index 8a3cd818..0ab39fec 100644 --- a/src/test/java/se/kth/spork/Util.java +++ b/src/test/java/se/kth/spork/Util.java @@ -65,20 +65,20 @@ public static List parseConflicts(Path path) throws IOException { public static List parseConflicts(String string) { Pattern conflictPattern = - Pattern.compile("(<<<<<<< LEFT.*?=======.*?>>>>>>> RIGHT)", Pattern.DOTALL); + Pattern.compile( + "<<<<<<< LEFT(.*?)(\\|\\|\\|\\|\\|\\|\\| BASE(.*?))?=======(.*?)>>>>>>> RIGHT", + Pattern.DOTALL); Matcher matcher = conflictPattern.matcher(string); List matches = new ArrayList<>(); while (matcher.find()) { - String match = matcher.group().trim(); - String[] parts = match.split("======="); - assert parts.length == 2; - - String left = parts[0].replace(SporkPrettyPrinter.START_CONFLICT, ""); - String right = parts[1].replace(SporkPrettyPrinter.END_CONFLICT, ""); + String left = matcher.group(1); + String right = matcher.group(4); + String base = matcher.group(3); Conflict conf = new Conflict(); conf.left = left.trim(); conf.right = right.trim(); + conf.base = base == null ? null : base.trim(); matches.add(conf); } @@ -94,7 +94,9 @@ public static String keepLeftConflict(Path path) throws IOException { * left revision. */ public static String keepLeftConflict(String string) { - Pattern rightConflictPattern = Pattern.compile("=======.*?>>>>>>> RIGHT", Pattern.DOTALL); + Pattern rightConflictPattern = + Pattern.compile( + "(\\|\\|\\|\\|\\|\\|\\| BASE.*)?=======.*?>>>>>>> RIGHT", Pattern.DOTALL); Matcher rightConflictMatcher = rightConflictPattern.matcher(string); String rightRevStrippend = rightConflictMatcher.replaceAll(""); @@ -166,10 +168,20 @@ public Stream provideArguments(ExtensionContext extensionCo public static class Conflict { String left; String right; + String base; // null if not rendered, "" if empty @Override public String toString() { - return "Conflict{" + "left='" + left + '\'' + ", right='" + right + '\'' + '}'; + return "Conflict{" + + "left='" + + left + + '\'' + + ", right='" + + right + + "\', base='" + + base + + '\'' + + '}'; } @Override @@ -177,12 +189,14 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Conflict conflict = (Conflict) o; - return Objects.equals(left, conflict.left) && Objects.equals(right, conflict.right); + return Objects.equals(left, conflict.left) + && Objects.equals(right, conflict.right) + && Objects.equals(base, conflict.base); } @Override public int hashCode() { - return Objects.hash(left, right); + return Objects.hash(left, right, base); } } @@ -191,12 +205,14 @@ public static class TestSources { public Path left; public Path right; public Path expected; + public Path expectedDiff3; - TestSources(Path base, Path left, Path right, Path expected) { + TestSources(Path base, Path left, Path right, Path expected, Path expectedDiff3) { this.base = base; this.left = left; this.right = right; this.expected = expected; + this.expectedDiff3 = expectedDiff3; } public static TestSources fromTestDirectory(File testDir) { @@ -205,7 +221,8 @@ public static TestSources fromTestDirectory(File testDir) { path.resolve("Base.java"), path.resolve("Left.java"), path.resolve("Right.java"), - path.resolve("Expected.java")); + path.resolve("Expected.java"), + path.resolve("ExpectedDiff3.java")); } public static TestSources fromTestDirectoryWithoutExpected(File testDir) { @@ -214,6 +231,7 @@ public static TestSources fromTestDirectoryWithoutExpected(File testDir) { path.resolve("Base.java"), path.resolve("Left.java"), path.resolve("Right.java"), + null, null); } diff --git a/src/test/java/se/kth/spork/cli/CliTest.java b/src/test/java/se/kth/spork/cli/CliTest.java index e3f11b5a..388d7cb3 100644 --- a/src/test/java/se/kth/spork/cli/CliTest.java +++ b/src/test/java/se/kth/spork/cli/CliTest.java @@ -53,7 +53,13 @@ void merge_shouldThrowOnMissingType_whenGlobalFallbackIsDisabled() { assertThrows( MergeException.class, - () -> Cli.merge(sources.base, sources.left, sources.right, /* exitOnError= */ true), + () -> + Cli.merge( + sources.base, + sources.left, + sources.right, + /* exitOnError= */ true, + /* diff3= */ false), "Merge contained no types and global line-based fallback is disabled"); } @@ -64,7 +70,12 @@ void merge_shouldMexgeCorrectlyOnMissingType_whenGlobalFallbackIsEnabled() { String expected = Parser.INSTANCE.read(sources.expected); Pair merge = - Cli.merge(sources.base, sources.left, sources.right, /* exitOnError= */ false); + Cli.merge( + sources.base, + sources.left, + sources.right, + /* exitOnError= */ false, + /* diff3= */ false); assertEquals(0, merge.getSecond()); assertEquals(expected, merge.getFirst()); @@ -96,6 +107,31 @@ void mergeTreeShouldEqualReParsedPrettyPrent_whenBothRevisionsAreModified( void prettyPrint_shouldContainConflict(Util.TestSources sources) throws IOException { List expectedConflicts = Util.parseConflicts(sources.expected); + Spoon3dmMerge.INSTANCE.setDiff3(false); + Pair merged = + Spoon3dmMerge.INSTANCE.merge(sources.base, sources.left, sources.right); + CtModule mergeTree = merged.getFirst(); + Integer numConflicts = merged.getSecond(); + + String prettyPrint = Cli.prettyPrint(mergeTree); + + List actualConflicts = Util.parseConflicts(prettyPrint); + + System.out.println(prettyPrint); + + assertEquals(expectedConflicts, actualConflicts); + assertTrue(numConflicts > 0); + } + + @ParameterizedTest + @ArgumentsSource(Util.ConflictSourceProvider.class) + void prettyPrint_shouldContainConflictDiff3(Util.TestSources sources) throws IOException { + if (!sources.expectedDiff3.toFile().exists()) { + return; + } + List expectedConflicts = Util.parseConflicts(sources.expectedDiff3); + + Spoon3dmMerge.INSTANCE.setDiff3(true); Pair merged = Spoon3dmMerge.INSTANCE.merge(sources.base, sources.left, sources.right); CtModule mergeTree = merged.getFirst(); diff --git a/src/test/resources/conflict/add_similar_fields/ExpectedDiff3.java b/src/test/resources/conflict/add_similar_fields/ExpectedDiff3.java new file mode 100644 index 00000000..9105426d --- /dev/null +++ b/src/test/resources/conflict/add_similar_fields/ExpectedDiff3.java @@ -0,0 +1,18 @@ +public class Main { + Integer a = 2; + Integer b = 3; +<<<<<<< LEFT +Object +||||||| BASE +======= +Integer +>>>>>>> RIGHT + field = Integer.valueOf( +<<<<<<< LEFT +2 +||||||| BASE +======= +3 +>>>>>>> RIGHT + ); +} diff --git a/src/test/resources/conflict/conflicting_class_visibility/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_class_visibility/ExpectedDiff3.java new file mode 100644 index 00000000..7987a252 --- /dev/null +++ b/src/test/resources/conflict/conflicting_class_visibility/ExpectedDiff3.java @@ -0,0 +1,7 @@ +<<<<<<< LEFT +public +||||||| BASE +======= +private +>>>>>>> RIGHT + static class Cls {} diff --git a/src/test/resources/conflict/conflicting_comment_edits/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_comment_edits/ExpectedDiff3.java new file mode 100644 index 00000000..6475fc55 --- /dev/null +++ b/src/test/resources/conflict/conflicting_comment_edits/ExpectedDiff3.java @@ -0,0 +1,91 @@ +import java.util.EmptyStackException; + +/** + * An array-based implementation of the Stack interface. + * + * @author Simon Larsén + */ +public class ArrayStack implements Stack { + private static final int INITIAL_CAPACITY = 10; + private Object[] elements; + private int size; + + /** +<<<<<<< LEFT + * Create an empty ArrayStack. +||||||| BASE + * Creat an empty ArrayStack. +======= + * Creat an empty ArrayStack with an initial capacity of 10. +>>>>>>> RIGHT + */ + public ArrayStack() { + elements = new Object[INITIAL_CAPACITY]; + } + + @Override + public void push(T element) { + ensureCapacity(size + 1); + elements[size++] = element; + } + + @Override + public T pop() { + T elem = checkedTop(); +<<<<<<< LEFT + // null element to avoid blocking garbage collector +||||||| BASE + // null element to avoid blocking GC +======= + // null element to avoid blocking the garbage collector +>>>>>>> RIGHT + elements[size--] = null; + return elem; + } + + @Override + public T top() { + return checkedTop(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + /** + * Checked fetching of the top element, throws an EmptyStackException if + * the stack is empty. + */ + private T checkedTop() { + if (size == 0) { + throw new EmptyStackException(); + } + + return (T) elements[size - 1]; + } + + /** + * Ensure that the capacity is at least minCapacity. + */ + private void ensureCapacity(int minCapacity) { + if (minCapacity > elements.length) { + grow(); + } + } + + /** + * Replace the current backing array with a larger one and copy over the + * elements to the now array. + */ + private void grow() { + Object[] newElements = new Object[elements.length << 1]; + System.arraycopy(elements, 0, newElements, 0, elements.length); + elements = newElements; + } +} diff --git a/src/test/resources/conflict/conflicting_literal_change/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_literal_change/ExpectedDiff3.java new file mode 100644 index 00000000..1706d397 --- /dev/null +++ b/src/test/resources/conflict/conflicting_literal_change/ExpectedDiff3.java @@ -0,0 +1,13 @@ +public class Adder { + public int add(int a, int b) { + return a + +<<<<<<< LEFT +1 +||||||| BASE +b +======= +2 +>>>>>>> RIGHT + ; + } +} diff --git a/src/test/resources/conflict/conflicting_method_visibility/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_method_visibility/ExpectedDiff3.java new file mode 100644 index 00000000..5c8f2f18 --- /dev/null +++ b/src/test/resources/conflict/conflicting_method_visibility/ExpectedDiff3.java @@ -0,0 +1,9 @@ +class Cls { +<<<<<<< LEFT +public +||||||| BASE +======= +private +>>>>>>> RIGHT + void method() {} +} diff --git a/src/test/resources/conflict/conflicting_operator_change/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_operator_change/ExpectedDiff3.java new file mode 100644 index 00000000..f615bfed --- /dev/null +++ b/src/test/resources/conflict/conflicting_operator_change/ExpectedDiff3.java @@ -0,0 +1,38 @@ +public class Adder { + public int add(int a, int b) { + return a +<<<<<<< LEFT +* +||||||| BASE ++ +======= +/ +>>>>>>> RIGHT + b; + } + + public int otherOps() { + int a = 1; + a +<<<<<<< LEFT +-= +||||||| BASE ++= +======= +*= +>>>>>>> RIGHT; + 1; + + a = +<<<<<<< LEFT ++ +||||||| BASE +~ +======= +- +>>>>>>> RIGHT + a; + + return a; + } +} diff --git a/src/test/resources/conflict/conflicting_type_change/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_type_change/ExpectedDiff3.java new file mode 100644 index 00000000..cf05774e --- /dev/null +++ b/src/test/resources/conflict/conflicting_type_change/ExpectedDiff3.java @@ -0,0 +1,21 @@ +public class Adder { + public + <<<<<<< LEFT + long + ||||||| BASE + int + ======= + double + >>>>>>> RIGHT + add( + <<<<<<< LEFT + long + ||||||| BASE + int + ======= + double + >>>>>>> RIGHT + a, int b) { + return a + b; + } +} diff --git a/src/test/resources/conflict/conflicting_variable_rename/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_variable_rename/ExpectedDiff3.java new file mode 100644 index 00000000..a3657207 --- /dev/null +++ b/src/test/resources/conflict/conflicting_variable_rename/ExpectedDiff3.java @@ -0,0 +1,21 @@ +public class Adder { + public int add(int lhs, int + <<<<<<< LEFT + rhs + ||||||| BASE + b + ======= + r + >>>>>>> RIGHT + ) { + return lhs + + <<<<<<< LEFT + rhs + ||||||| BASE + b + ======= + r + >>>>>>> RIGHT + ; + } +} diff --git a/src/test/resources/conflict/conflicting_wildcard_changes/ExpectedDiff3.java b/src/test/resources/conflict/conflicting_wildcard_changes/ExpectedDiff3.java new file mode 100644 index 00000000..6bae62de --- /dev/null +++ b/src/test/resources/conflict/conflicting_wildcard_changes/ExpectedDiff3.java @@ -0,0 +1,14 @@ +import java.util.List; + +class Cls { + public static void print(List>>>>>> RIGHT + String> list) { + System.out.println(list); + } +} diff --git a/src/test/resources/conflict/integer_literal_conflict/ExpectedDiff3.java b/src/test/resources/conflict/integer_literal_conflict/ExpectedDiff3.java new file mode 100644 index 00000000..958e7c60 --- /dev/null +++ b/src/test/resources/conflict/integer_literal_conflict/ExpectedDiff3.java @@ -0,0 +1,13 @@ +public class Main { + int calcNumber() { + return 1 + 2 + +<<<<<<< LEFT +99 +||||||| BASE +3 +======= +102 +>>>>>>> RIGHT + ; + } +} diff --git a/src/test/resources/conflict/method_visibility_left_empty/ExpectedDiff3.java b/src/test/resources/conflict/method_visibility_left_empty/ExpectedDiff3.java new file mode 100644 index 00000000..0599123b --- /dev/null +++ b/src/test/resources/conflict/method_visibility_left_empty/ExpectedDiff3.java @@ -0,0 +1,9 @@ +class Cls { +<<<<<<< LEFT +||||||| BASE +public +======= +protected +>>>>>>> RIGHT + final static void method() {} +} diff --git a/src/test/resources/conflict/multiple_conflicting_statements/ExpectedDiff3.java b/src/test/resources/conflict/multiple_conflicting_statements/ExpectedDiff3.java new file mode 100644 index 00000000..9790707a --- /dev/null +++ b/src/test/resources/conflict/multiple_conflicting_statements/ExpectedDiff3.java @@ -0,0 +1,14 @@ +public class Declarations { + public void pointlessMethod() { + int a = 1; +<<<<<<< LEFT + int c = 3; + int d = 2; +||||||| BASE +======= + long q = 10; + long x = 20; +>>>>>>> RIGHT + int b = 2; + } +} diff --git a/src/test/resources/conflict/multiple_simple_conflicts/ExpectedDiff3.java b/src/test/resources/conflict/multiple_simple_conflicts/ExpectedDiff3.java new file mode 100644 index 00000000..1baf7790 --- /dev/null +++ b/src/test/resources/conflict/multiple_simple_conflicts/ExpectedDiff3.java @@ -0,0 +1,22 @@ +public class Declarations { + public void pointlessMethod() { + int a = 1; +<<<<<<< LEFT + int c = 3; + int d = 4; +||||||| BASE +======= + long q = 10; + long x = 1; + long y = 1; + long z = 1; +>>>>>>> RIGHT + int b = 2; +<<<<<<< LEFT + int e = 5; +||||||| BASE +======= + long w = 12; +>>>>>>> RIGHT + } +} diff --git a/src/test/resources/conflict/simple_delete_delete/ExpectedDiff3.java b/src/test/resources/conflict/simple_delete_delete/ExpectedDiff3.java new file mode 100644 index 00000000..fb2e2e74 --- /dev/null +++ b/src/test/resources/conflict/simple_delete_delete/ExpectedDiff3.java @@ -0,0 +1,13 @@ +class Main { + public static void main(String[] args) { + int a = 2; +<<<<<<< LEFT + int b = 3; +||||||| BASE + int b = 3; + int c = 4; +======= + int c = 4; +>>>>>>> RIGHT + } +} diff --git a/src/test/resources/conflict/simple_insert_delete/ExpectedDiff3.java b/src/test/resources/conflict/simple_insert_delete/ExpectedDiff3.java new file mode 100644 index 00000000..014fc50e --- /dev/null +++ b/src/test/resources/conflict/simple_insert_delete/ExpectedDiff3.java @@ -0,0 +1,11 @@ +class Main { + public static void main(String[] args) { +<<<<<<< LEFT +||||||| BASE + int a = 2; +======= + int a = 2; + int b = 3; +>>>>>>> RIGHT + } +} diff --git a/src/test/resources/conflict/single_conflicting_statement/ExpectedDiff3.java b/src/test/resources/conflict/single_conflicting_statement/ExpectedDiff3.java new file mode 100644 index 00000000..e99d76a0 --- /dev/null +++ b/src/test/resources/conflict/single_conflicting_statement/ExpectedDiff3.java @@ -0,0 +1,14 @@ +public class Declarations { + public void pointlessMethod() { + int a = 1; +<<<<<<< LEFT + if (a < 2) { + a = 2; + } +||||||| BASE +======= + long q = 10; +>>>>>>> RIGHT + int b = 2; + } +}