Skip to content

Commit 3a778b5

Browse files
committed
fix: use the correct child order in line graph exports
When importing line graphs, we consider the order of the edge lines as the definite child ordering even if we export the child order using `ChildOrderEdgeFormat`. However, the previous algorithm blindly exports all `DiffType.NON` edges disregarding the fact the other edges might need to be exported first. As the order of edge lines acts as a diff (before edges act as deletions and after edges as insertions), we need to compute a line diff of the child orders at both times to know which edges can be exported as existing at both times (unchanged). Note that some edges will be exported as inserted and deleted instead of unmodified although both the child and the parent are the same because line diffs cannot encode moves. Consumers of line graphs should not depend on the fact which edges are exported as unchanged because the diffing algorithm needs to apply heuristics to select these edges.
1 parent 467a523 commit 3a778b5

74 files changed

Lines changed: 1776 additions & 1693 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/org/variantsync/diffdetective/variation/diff/serialize/Format.java

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package org.variantsync.diffdetective.variation.diff.serialize;
22

3+
import org.eclipse.jgit.diff.DiffAlgorithm;
4+
import org.eclipse.jgit.diff.EditList;
5+
import org.eclipse.jgit.diff.Sequence;
6+
import org.eclipse.jgit.diff.SequenceComparator;
37
import org.variantsync.diffdetective.variation.Label;
48
import org.variantsync.diffdetective.variation.diff.DiffNode;
59
import org.variantsync.diffdetective.variation.diff.VariationDiff;
610
import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.EdgeLabelFormat;
711
import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.DiffNodeLabelFormat;
812

13+
import java.util.List;
914
import java.util.function.Consumer;
1015

1116
import static org.variantsync.diffdetective.variation.diff.Time.AFTER;
@@ -69,20 +74,55 @@ public <La extends L> void forEachNode(VariationDiff<La> variationDiff, Consumer
6974
*/
7075
public <La extends L> void forEachEdge(VariationDiff<La> variationDiff, Consumer<StyledEdge<La>> callback) {
7176
variationDiff.forAll((node) -> {
72-
var beforeParent = node.getParent(BEFORE);
73-
var afterParent = node.getParent(AFTER);
74-
75-
// Are both parent edges the same?
76-
if (beforeParent != null && afterParent != null && beforeParent == afterParent) {
77-
sortedEdgeWithLabel(node, node.getParent(BEFORE), StyledEdge.ALWAYS, callback);
78-
} else {
79-
if (beforeParent != null) {
80-
sortedEdgeWithLabel(node, node.getParent(BEFORE), StyledEdge.BEFORE, callback);
77+
List<DiffNode<La>> beforeChildren = node.getChildOrder(BEFORE);
78+
List<DiffNode<La>> afterChildren = node.getChildOrder(AFTER);
79+
80+
class ListSequence<E> extends Sequence {
81+
public final List<E> list;
82+
83+
public ListSequence(List<E> list) {
84+
this.list = list;
85+
}
86+
87+
@Override
88+
public int size() {
89+
return list.size();
8190
}
82-
if (afterParent != null) {
83-
sortedEdgeWithLabel(node, node.getParent(AFTER), StyledEdge.AFTER, callback);
91+
}
92+
93+
class ListSequenceComparator<E> extends SequenceComparator<ListSequence<E>> {
94+
@Override
95+
public boolean equals(ListSequence<E> sequence1, int index1, ListSequence<E> sequence2, int index2) {
96+
return sequence1.list.get(index1) == sequence2.list.get(index2);
97+
}
98+
99+
@Override
100+
public int hash(ListSequence<E> sequence, int index) {
101+
return System.identityHashCode(sequence.list.get(index));
84102
}
85103
}
104+
105+
final EditList editList = DiffAlgorithm.getAlgorithm(DiffAlgorithm.SupportedAlgorithm.MYERS).diff(
106+
new ListSequenceComparator<DiffNode<La>>(),
107+
new ListSequence<>(beforeChildren),
108+
new ListSequence<>(afterChildren)
109+
);
110+
111+
int beforeIndex = 0;
112+
for (var edit : editList) {
113+
for (; beforeIndex < edit.getBeginA(); ++beforeIndex) {
114+
sortedEdgeWithLabel(beforeChildren.get(beforeIndex), node, StyledEdge.ALWAYS, callback);
115+
}
116+
for (; beforeIndex < edit.getEndA(); ++beforeIndex) {
117+
sortedEdgeWithLabel(beforeChildren.get(beforeIndex), node, StyledEdge.BEFORE, callback);
118+
}
119+
for (int afterIndex = edit.getBeginB(); afterIndex < edit.getEndB(); ++afterIndex) {
120+
sortedEdgeWithLabel(afterChildren.get(afterIndex), node, StyledEdge.AFTER, callback);
121+
}
122+
}
123+
for (; beforeIndex < beforeChildren.size(); ++beforeIndex) {
124+
sortedEdgeWithLabel(beforeChildren.get(beforeIndex), node, StyledEdge.ALWAYS, callback);
125+
}
86126
});
87127
}
88128

src/main/java/org/variantsync/diffdetective/variation/diff/serialize/edgeformat/ChildOrderEdgeFormat.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
* This index is encoded into decimal and delimited by a semicolon from the previous value.
1717
*
18-
* This format is mainly useful to equivalence of two {@link VariationDiff}s, for example in tests.
18+
* This format is mainly useful to verify the equivalence of two {@link VariationDiff}s, for example in tests.
1919
*
2020
* @author Benjamin Moosherr
2121
*/

src/test/java/LineGraphTest.java

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
import org.apache.commons.io.IOUtils;
2+
import org.apache.commons.lang3.function.FailableConsumer;
3+
import org.junit.jupiter.api.Test;
24
import org.junit.jupiter.params.ParameterizedTest;
35
import org.junit.jupiter.params.provider.MethodSource;
6+
import org.variantsync.diffdetective.diff.result.DiffParseException;
7+
import org.variantsync.diffdetective.diff.text.DiffLineNumber;
8+
import org.variantsync.diffdetective.util.IO;
49
import org.variantsync.diffdetective.variation.DiffLinesLabel;
10+
import org.variantsync.diffdetective.variation.diff.DiffNode;
11+
import org.variantsync.diffdetective.variation.diff.DiffType;
12+
import org.variantsync.diffdetective.variation.diff.Time;
513
import org.variantsync.diffdetective.variation.diff.VariationDiff;
614
import org.variantsync.diffdetective.variation.diff.serialize.*;
715
import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.DefaultEdgeLabelFormat;
816
import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.LabelOnlyDiffNodeFormat;
917
import org.variantsync.diffdetective.variation.diff.serialize.treeformat.CommitDiffVariationDiffLabelFormat;
10-
import org.variantsync.diffdetective.util.IO;
11-
12-
import static org.junit.jupiter.api.Assertions.fail;
18+
import org.variantsync.diffdetective.variation.diff.source.CommitDiffVariationDiffSource;
1319

20+
import java.io.BufferedOutputStream;
1421
import java.io.BufferedReader;
1522
import java.io.IOException;
1623
import java.nio.file.Files;
1724
import java.nio.file.Path;
18-
import java.nio.file.Paths;
1925
import java.util.List;
2026
import java.util.stream.Stream;
2127

28+
import static org.junit.jupiter.api.Assertions.fail;
29+
2230
/**
2331
* For testing the import of a line graph.
2432
*/
@@ -34,7 +42,9 @@ public class LineGraphTest {
3442
);
3543

3644
public static Stream<Path> testCases() throws IOException {
37-
return Files.list(Paths.get("src/test/resources/line_graph"));
45+
return Files
46+
.list(Constants.RESOURCE_DIR.resolve("line_graph"))
47+
.filter(file -> file.getFileName().toString().endsWith(".lg"));
3848
}
3949

4050
/**
@@ -49,22 +59,26 @@ public void idempotentReadWrite(Path testFile) throws IOException {
4959
}
5060
assertConsistencyForAll(variationDiffs);
5161

52-
Path actualPath = testFile.getParent().resolve(testFile.getFileName().toString() + ".actual");
53-
try (var output = IO.newBufferedOutputStream(actualPath)) {
54-
LineGraphExport.toLineGraphFormat(variationDiffs, EXPORT_OPTIONS, output);
55-
}
62+
assertEqualsFile(testFile, output -> LineGraphExport.toLineGraphFormat(variationDiffs, EXPORT_OPTIONS, output));
63+
}
5664

57-
try (
58-
var expectedFile = Files.newBufferedReader(testFile);
59-
var actualFile = Files.newBufferedReader(actualPath);
60-
) {
61-
if (!IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) {
62-
fail("The file " + testFile + " couldn't be exported or imported without modifications");
63-
} else {
64-
// Only keep output file on errors
65-
Files.delete(actualPath);
66-
}
67-
}
65+
@Test
66+
public void testChildOrder() throws IOException, DiffParseException {
67+
Path expectedPath = Constants.RESOURCE_DIR.resolve("line_graph").resolve("childOrder.lg");
68+
69+
// Note that this variation diff doesn't have a line diff representation because it
70+
// represents a move (it could be parseable using a tree differ).
71+
var root = DiffNode.createRoot(new DiffLinesLabel());
72+
var A = DiffNode.createArtifact(DiffType.NON, new DiffLineNumber(1, 1, 2), new DiffLineNumber(1, 1, 2), new DiffLinesLabel(List.of(new DiffLinesLabel.Line("A", new DiffLineNumber(1, DiffLineNumber.InvalidLineNumber, 2)))));
73+
var B = DiffNode.createArtifact(DiffType.NON, new DiffLineNumber(2, 2, 1), new DiffLineNumber(2, 2, 1), new DiffLinesLabel(List.of(new DiffLinesLabel.Line("B", new DiffLineNumber(2, DiffLineNumber.InvalidLineNumber, 1)))));
74+
root.addChild(A, Time.BEFORE);
75+
root.addChild(B, Time.BEFORE);
76+
root.addChild(B, Time.AFTER);
77+
root.addChild(A, Time.AFTER);
78+
79+
final var variationDiff = new VariationDiff<DiffLinesLabel>(root, new CommitDiffVariationDiffSource(Path.of("fileName"), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
80+
81+
assertEqualsFile(expectedPath, output -> LineGraphExport.toLineGraphFormat(List.of(variationDiff), EXPORT_OPTIONS, output));
6882
}
6983

7084
/**
@@ -78,4 +92,23 @@ private static void assertConsistencyForAll(final List<VariationDiff<DiffLinesLa
7892
// }
7993
treeList.forEach(VariationDiff::assertConsistency);
8094
}
95+
96+
private static void assertEqualsFile(Path expectedPath, FailableConsumer<BufferedOutputStream, IOException> actualOutput) throws IOException {
97+
Path actualPath = expectedPath.getParent().resolve(expectedPath.getFileName().toString() + ".actual");
98+
try (var output = IO.newBufferedOutputStream(actualPath)) {
99+
actualOutput.accept(output);
100+
}
101+
102+
try (
103+
var expectedFile = Files.newBufferedReader(expectedPath);
104+
var actualFile = Files.newBufferedReader(actualPath);
105+
) {
106+
if (!IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) {
107+
fail("Expected the content of " + expectedPath + " but got the content of " + actualPath);
108+
} else {
109+
// Only keep output file on errors
110+
Files.delete(actualPath);
111+
}
112+
}
113+
}
81114
}

src/test/resources/diffs/differ/4a4c1db1b192e221d8e25460d6d1128d1bdd0c0d.lg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ v 339 NON;ARTIFACT;(old: 3, diff: 4, new: 3);(old: 4, diff: 5, new: 4);;Another
55
v 259 ADD;ARTIFACT;(old: -1, diff: 3, new: 2);(old: -1, diff: 4, new: 3);;Hallo
66
e 147 16 ba;0,0
77
e 203 16 b;1,-1
8-
e 339 16 ba;2,2
98
e 259 16 a;-1,1
9+
e 339 16 ba;2,2

src/test/resources/diffs/differ/a032346092e47048becb36a7cb183b4739547370.lg

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@ e 336 16 ba;3,3
2929
e 403 336 ba;0,0
3030
e 467 336 ba;1,1
3131
e 528 336 ba;2,2
32-
e 595 528 ba;0,0
33-
e 657 528 ba;1,1
34-
e 723 657 ba;0,0
3532
e 851 336 ba;3,3
3633
e 915 336 ba;4,4
3734
e 979 336 ba;5,5
3835
e 1043 336 ba;6,6
3936
e 1104 336 ba;7,7
40-
e 1299 1104 ba;0,0
41-
e 1363 1104 ba;1,1
4237
e 1619 336 ba;8,8
4338
e 1680 336 ba;9,9
44-
e 1811 1680 ba;0,0
4539
e 1939 336 ba;10,10
4640
e 2000 336 ba;11,11
41+
e 595 528 ba;0,0
42+
e 657 528 ba;1,1
43+
e 723 657 ba;0,0
44+
e 1299 1104 ba;0,0
45+
e 1363 1104 ba;1,1
46+
e 1811 1680 ba;0,0
4747
e 2067 2000 ba;0,0

src/test/resources/diffs/jpp/basic_jpp_expected.lg

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ e 131 16 a;-1,0
2020
e 195 16 a;-1,1
2121
e 259 16 a;-1,2
2222
e 320 16 a;-1,3
23-
e 387 320 a;-1,0
24-
e 451 320 a;-1,1
25-
e 515 320 a;-1,2
2623
e 643 16 a;-1,4
2724
e 707 16 a;-1,5
2825
e 768 16 a;-1,6
29-
e 835 768 a;-1,0
30-
e 899 768 a;-1,1
31-
e 963 768 a;-1,2
3226
e 1091 16 a;-1,7
3327
e 1155 16 a;-1,8
3428
e 1216 16 a;-1,9
29+
e 387 320 a;-1,0
30+
e 451 320 a;-1,1
31+
e 515 320 a;-1,2
32+
e 835 768 a;-1,0
33+
e 899 768 a;-1,1
34+
e 963 768 a;-1,2
3535
e 1283 1216 a;-1,0

src/test/resources/diffs/parser/02_expected.lg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 3, new: -1);A;#if A
33
v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 1);(old: 3, diff: 3, new: 2);; Code
44
v 323 ADD;ARTIFACT;(old: -1, diff: 4, new: 2);(old: -1, diff: 5, new: 3);; // Hehe
55
e 136 16 b;0,-1
6-
e 211 136 b;0,-1
76
e 211 16 a;-1,0
87
e 323 16 a;-1,1
8+
e 211 136 b;0,-1

src/test/resources/diffs/parser/03_expected.lg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ v 595 NON;ARTIFACT;(old: 6, diff: 8, new: 5);(old: 7, diff: 9, new: 6);;Code
55
v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 10, new: 6);A | B | C;#if A \; || B \; || C \; // Foo
66
v 771 ADD;ARTIFACT;(old: -1, diff: 11, new: 7);(old: -1, diff: 12, new: 8);; // Bar
77
e 136 16 b;0,-1
8+
e 128 16 a;-1,0
9+
e 771 16 a;-1,1
810
e 523 136 b;0,-1
911
e 595 136 b;1,-1
1012
e 595 128 a;-1,0
11-
e 128 16 a;-1,0
12-
e 771 16 a;-1,1

src/test/resources/diffs/parser/08_expected.lg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 4, new: -1);defined(A);#i
33
v 275 NON;ARTIFACT;(old: 2, diff: 3, new: 2);(old: 3, diff: 4, new: 3);;Code
44
v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 4, new: 3);defined(B);#ifdef B
55
e 136 16 b;0,-1
6+
e 192 16 a;-1,0
67
e 275 136 b;0,-1
78
e 275 192 a;-1,0
8-
e 192 16 a;-1,0

src/test/resources/diffs/parser/09_expected.lg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 3, new: 3);;Code 1
44
v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 3);(old: 5, diff: 5, new: 4);;Code 2
55
v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 5, new: 4);defined(A);#ifdef A
66
e 136 16 b;0,-1
7+
e 339 16 b;1,-1
8+
e 128 16 a;-1,0
79
e 211 136 b;0,-1
810
e 211 128 a;-1,0
9-
e 339 16 b;1,-1
1011
e 339 128 a;-1,1
11-
e 128 16 a;-1,0

0 commit comments

Comments
 (0)