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