From 8bb4a9a8b1209f461b320a16150fc738107701ae Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 8 Jul 2021 21:01:10 +0200 Subject: [PATCH 1/8] first implementation of meyers diff with linear space --- .../myers/MeyersDiffWithLinearSpace.java | 195 ++++++++++++++++++ .../difflib/algorithm/myers/MyersDiff.java | 3 +- .../myers/MeyersDiffWithLinearSpaceTest.java | 73 +++++++ .../algorithm/myers/MyersDiffTest.java | 1 - ...ithMeyersDiffWithLinearSpacePatchTest.java | 104 ++++++++++ .../unifieddiff/UnifiedDiffReaderTest.java | 20 ++ .../unifieddiff/problem_diff_issue122.diff | 26 +++ .../unifieddiff/problem_diff_issue123.diff | 94 +++++++++ .../src/test/resources/logging.properties | 2 +- 9 files changed, 514 insertions(+), 4 deletions(-) create mode 100644 java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java create mode 100644 java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java create mode 100644 java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java new file mode 100644 index 0000000..1dcae64 --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -0,0 +1,195 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm.myers; + +import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.DiffAlgorithmI; +import com.github.difflib.algorithm.DiffAlgorithmListener; +import com.github.difflib.patch.DeltaType; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiPredicate; + +/** + * + * @author tw + */ +public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { + + private final BiPredicate equalizer; + + public MeyersDiffWithLinearSpace() { + equalizer = Object::equals; + } + + public MeyersDiffWithLinearSpace(final BiPredicate equalizer) { + Objects.requireNonNull(equalizer, "equalizer must not be null"); + this.equalizer = equalizer; + } + + @Override + public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + DiffData data = new DiffData(source, target); + //shouldn't it be source.size() - 1? + buildScript(data, 0, source.size(), 0, target.size()); + return data.script; + } + + private void buildScript(DiffData data, int start1, int end1, int start2, int end2) { + final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); + if (middle == null + || middle.start == end1 && middle.diag == end1 - end2 + || middle.end == start1 && middle.diag == start1 - start2) { + int i = start1; + int j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) { + //script.append(new KeepCommand<>(left.charAt(i))); + ++i; + ++j; + } else { + //TODO: compress these commands. + if (end1 - start1 > end2 - start2) { + //script.append(new DeleteCommand<>(left.charAt(i))); + data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + ++i; + } else { + //script.append(new InsertCommand<>(right.charAt(j))); + data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + ++j; + } + } + } + } else { + buildScript(data, start1, middle.start, start2, middle.start - middle.diag); +// for (int i = middle.getStart(); i < middle.getEnd(); ++i) { +// script.append(new KeepCommand<>(left.charAt(i))); +// } + buildScript(data, middle.end, end1, middle.end - middle.diag, end2); + } + } + + private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) { + final int m = end1 - start1; + final int n = end2 - start2; + if (m == 0 || n == 0) { + return null; + } + + final int delta = m - n; + final int sum = n + m; + final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; + data.vDown[1 + offset] = start1; + data.vUp[1 + offset] = end1 + 1; + + for (int d = 0; d <= offset; ++d) { + // Down + for (int k = -d; k <= d; k += 2) { + // First step + + final int i = k + offset; + if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) { + data.vDown[i] = data.vDown[i + 1]; + } else { + data.vDown[i] = data.vDown[i - 1] + 1; + } + + int x = data.vDown[i]; + int y = x - start1 + start2 - k; + + while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { + if (data.vUp[i - delta] <= data.vDown[i]) { + return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2); + } + } + } + + // Up + for (int k = delta - d; k <= delta + d; k += 2) { + // First step + final int i = k + offset - delta; + if (k == delta - d + || k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) { + data.vUp[i] = data.vUp[i + 1] - 1; + } else { + data.vUp[i] = data.vUp[i - 1]; + } + + int x = data.vUp[i] - 1; + int y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 == 0 && -d <= k && k <= d) { + if (data.vUp[i] <= data.vDown[i + delta]) { + return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2); + } + } + } + } + + // According to Myers, this cannot happen + throw new IllegalStateException("could not find a diff path"); + } + + private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) { + int end = start; + while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) { + ++end; + } + return new Snake(start, end, diag); + } + + class DiffData { + + final int size; + final int[] vDown; + final int[] vUp; + final List script; + final List source; + final List target; + + public DiffData(List source, List target) { + this.source = source; + this.target = target; + size = source.size() + target.size() + 2; + vDown = new int[size]; + vUp = new int[size]; + script = new ArrayList<>(); + } + } + + class Snake { + + final int start; + final int end; + final int diag; + + public Snake(final int start, final int end, final int diag) { + this.start = start; + this.end = end; + this.diag = diag; + } + } +} diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 631bc38..f134557 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -30,11 +30,10 @@ import java.util.function.BiPredicate; */ public final class MyersDiff implements DiffAlgorithmI { - private final BiPredicate DEFAULT_EQUALIZER = Object::equals; private final BiPredicate equalizer; public MyersDiff() { - equalizer = DEFAULT_EQUALIZER; + equalizer = Object::equals; } public MyersDiff(final BiPredicate equalizer) { diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java new file mode 100644 index 0000000..5462eb4 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm.myers; + +import com.github.difflib.algorithm.DiffAlgorithmListener; +import com.github.difflib.patch.Patch; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * @author tw + */ +public class MeyersDiffWithLinearSpaceTest { + + @Test + public void testDiffMyersExample1Forward() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = Patch.generate(original, revised, new MeyersDiffWithLinearSpace().computeDiff(original, revised, null)); + assertNotNull(patch); + System.out.println(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + } + + @Test + public void testDiffMyersExample1ForwardWithListener() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate(original, revised, + new MeyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } + + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } + + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + assertNotNull(patch); + System.out.println(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + System.out.println(logdata); + assertEquals(8, logdata.size()); + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 5b8348e..1e233a8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -69,5 +69,4 @@ public class MyersDiffTest { System.out.println(logdata); assertEquals(8, logdata.size()); } - } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java new file mode 100644 index 0000000..2b06001 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -0,0 +1,104 @@ +package com.github.difflib.algorithm.myers; + +import com.github.difflib.patch.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.github.difflib.DiffUtils; + +public class WithMeyersDiffWithLinearSpacePatchTest { + + @Test + public void testPatch_Insert() { + final List insertTest_from = Arrays.asList("hhh"); + final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to, new MeyersDiffWithLinearSpace()); + try { + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Delete() { + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + final List deleteTest_to = Arrays.asList("ggg"); + + final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to, new MeyersDiffWithLinearSpace()); + try { + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Change() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Serializable() throws IOException, ClassNotFoundException { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(patch); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Patch result = (Patch) in.readObject(); + in.close(); + + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + + } + + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(9, data.size()); + + assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index fdc80fd..02e7c4d 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -364,4 +364,24 @@ public class UnifiedDiffReaderTest { // }); // }); } + + @Test + public void testParseIssue122() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } + + @Test + public void testParseIssue123() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff new file mode 100644 index 0000000..a2a382a --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff @@ -0,0 +1,26 @@ + +diff -r fcd3ed3394f6 -r 135bdcb88b8d coders/wpg.c +--- a/coders/wpg.c Sun Nov 05 01:11:09 2017 +0100 ++++ b/coders/wpg.c Sun Nov 05 01:35:28 2017 +0100 +@@ -340,12 +340,15 @@ + + + if(RetVal==MagickFail) ++ { + (void) LogMagickEvent(CoderEvent,GetMagickModule(),"ImportImagePixelArea failed for row: %ld, bpp: %d", y, bpp); ++ return MagickFail; ++ } + +- if (!SyncImagePixels(image)) ++ if(!SyncImagePixels(image)) + { + (void) LogMagickEvent(CoderEvent,GetMagickModule(),"SyncImagePixels failed for row: %ld, bpp: %d", y, bpp); +- RetVal = MagickFail; ++ return MagickFail; + } + + return RetVal; + + + + diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff new file mode 100644 index 0000000..2228fc8 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff @@ -0,0 +1,94 @@ +Index: src/java/test/org/apache/zookeeper/test/ACLTest.java +=================================================================== +--- src/java/test/org/apache/zookeeper/test/ACLTest.java (revision 1510080) ++++ src/java/test/org/apache/zookeeper/test/ACLTest.java (working copy) +@@ -28,6 +28,7 @@ + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.apache.zookeeper.CreateMode; ++import org.apache.zookeeper.KeeperException; + import org.apache.zookeeper.PortAssignment; + import org.apache.zookeeper.WatchedEvent; + import org.apache.zookeeper.Watcher; +@@ -35,8 +36,10 @@ + import org.apache.zookeeper.ZooKeeper; + import org.apache.zookeeper.Watcher.Event.KeeperState; + import org.apache.zookeeper.ZooDefs.Ids; ++import org.apache.zookeeper.ZooDefs.Perms; + import org.apache.zookeeper.data.ACL; + import org.apache.zookeeper.data.Id; ++import org.apache.zookeeper.data.Stat; + import org.apache.zookeeper.server.ServerCnxnFactory; + import org.apache.zookeeper.server.SyncRequestProcessor; + import org.apache.zookeeper.server.ZooKeeperServer; +@@ -77,6 +80,48 @@ + ClientBase.CONNECTION_TIMEOUT)); + } + } ++ ++ /** ++ * Verify that getAcl should fail when there is not ++ * read permission to that node ++ */ ++ @Test ++ public void testAclReadPermission() throws Exception { ++ File tmpDir = ClientBase.createTmpDir(); ++ ClientBase.setupTestEnv(); ++ ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); ++ SyncRequestProcessor.setSnapCount(1000); ++ final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); ++ ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); ++ f.startup(zks); ++ ZooKeeper zk; ++ String path = "/node1"; ++ boolean readPermLimitWorks = false; ++ try { ++ LOG.info("starting up the zookeeper server .. waiting"); ++ Assert.assertTrue("waiting for server being up", ++ ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); ++ zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); ++ Id id = new Id("ip", "127.0.0.1"); ++ ArrayList acl = new ArrayList(); // Not set read permission ++ acl.add(new ACL(Perms.CREATE, id)); ++ acl.add(new ACL(Perms.DELETE, id)); ++ acl.add(new ACL(Perms.WRITE, id)); ++ acl.add(new ACL(Perms.ADMIN, id)); ++ zk.create(path, path.getBytes(), acl, CreateMode.PERSISTENT); ++ Stat stat = new Stat(); ++ zk.getACL(path, stat); // Should cause exception without read permission ++ } catch (KeeperException.NoAuthException e) { ++ readPermLimitWorks = true; ++ } finally { ++ f.shutdown(); ++ Assert.assertTrue("waiting for server down", ++ ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); ++ } ++ if (!readPermLimitWorks) { ++ Assert.fail("Should not reach here as ACL has no read permission"); ++ } ++ } + + /** + * Verify that acl optimization of storing just +Index: src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java +=================================================================== +--- src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java (revision 1510080) ++++ src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java (working copy) +@@ -324,6 +324,17 @@ + GetACLRequest getACLRequest = new GetACLRequest(); + ByteBufferInputStream.byteBuffer2Record(request.request, + getACLRequest); ++ DataNode n = zks.getZKDatabase().getNode(getACLRequest.getPath()); ++ if (n == null) { ++ throw new KeeperException.NoNodeException(); ++ } ++ Long aclL; ++ synchronized(n) { ++ aclL = n.acl; ++ } ++ PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().convertLong(aclL), ++ ZooDefs.Perms.READ, ++ request.authInfo); + Stat stat = new Stat(); + List acl = + zks.getZKDatabase().getACL(getACLRequest.getPath(), stat); \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/logging.properties b/java-diff-utils/src/test/resources/logging.properties index 4c1655b..bc5042e 100644 --- a/java-diff-utils/src/test/resources/logging.properties +++ b/java-diff-utils/src/test/resources/logging.properties @@ -1,7 +1,7 @@ handlers=java.util.logging.ConsoleHandler .level=INFO -com.github.difflib.unifieddiff.level=FINE +com.github.difflib.unifieddiff.level=INFO java.util.logging.ConsoleHandler.level=INFO #java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter From 976505222683234b11505191394f9e046c7cfbdd Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 8 Jul 2021 22:42:53 +0200 Subject: [PATCH 2/8] first implementation of meyers diff with linear space --- .../com/github/difflib/algorithm/Change.java | 8 ++++++++ .../myers/MeyersDiffWithLinearSpace.java | 17 ++++++++++++++--- .../WithMeyersDiffWithLinearSpacePatchTest.java | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java index 57fbb78..9b6f1df 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java @@ -36,4 +36,12 @@ public class Change { this.startRevised = startRevised; this.endRevised = endRevised; } + + public Change withEndOriginal(int endOriginal) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } + + public Change withEndRevised(int endRevised) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index 1dcae64..98a49dc 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -65,11 +65,22 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { //TODO: compress these commands. if (end1 - start1 > end2 - start2) { //script.append(new DeleteCommand<>(left.charAt(i))); - data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endOriginal != i + || data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) { + data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + } else { + data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndOriginal(i + 1)); + } ++i; } else { - //script.append(new InsertCommand<>(right.charAt(j))); - data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endRevised != j + || data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) { + data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + } else { + data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndRevised(j + 1)); + } ++j; } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java index 2b06001..d2a02ca 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -93,7 +93,7 @@ public class WithMeyersDiffWithLinearSpacePatchTest { try { List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(9, data.size()); + assertEquals(11, data.size()); assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); From 24c3df1f39d67e3de20b31b4de2214e2000f0e38 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Fri, 9 Jul 2021 18:27:52 +0200 Subject: [PATCH 3/8] --- .../myers/MeyersDiffWithLinearSpace.java | 3 --- .../myers/MeyersDiffWithLinearSpaceTest.java | 18 ++++++++++++++++++ ...WithMeyersDiffWithLinearSpacePatchTest.java | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index 98a49dc..e7678be 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -87,9 +87,6 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { } } else { buildScript(data, start1, middle.start, start2, middle.start - middle.diag); -// for (int i = middle.getStart(); i < middle.getEnd(); ++i) { -// script.append(new KeepCommand<>(left.charAt(i))); -// } buildScript(data, middle.end, end1, middle.end - middle.diag, end2); } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java index 5462eb4..fc39757 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -15,11 +15,14 @@ */ package com.github.difflib.algorithm.myers; +import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static java.util.stream.Collectors.toList; +import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -70,4 +73,19 @@ public class MeyersDiffWithLinearSpaceTest { System.out.println(logdata); assertEquals(8, logdata.size()); } + + + @Test + public void testPerformanceProblemsIssue124() { + List old = Arrays.asList("abcd"); + List newl = IntStream.range(0, 90000) + .boxed() + .map(i -> i.toString()) + .collect(toList()); + + long start = System.currentTimeMillis(); + Patch diff = DiffUtils.diff(old, newl, new MeyersDiffWithLinearSpace()); + long end = System.currentTimeMillis(); + System.out.println("Finished in " + (end - start) + "ms and resulted " + diff.getDeltas().size() + " deltas"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java index d2a02ca..82aa04d 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -95,7 +95,7 @@ public class WithMeyersDiffWithLinearSpacePatchTest { List data = DiffUtils.patch(changeTest_from, patch); assertEquals(11, data.size()); - assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); } catch (PatchFailedException e) { fail(e.getMessage()); From c765616957fc690553e34d4007eca7f038937f10 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 18 Jul 2021 01:20:25 +0200 Subject: [PATCH 4/8] attached logging to new meyer diff algorithm --- .../java/com/github/difflib/DiffUtils.java | 71 +++++++++++-------- .../algorithm/DiffAlgorithmFactory.java | 29 ++++++++ .../myers/{MyersDiff.java => MeyersDiff.java} | 37 +++++++--- .../myers/MeyersDiffWithLinearSpace.java | 35 +++++++-- .../myers/MeyersDiffWithLinearSpaceTest.java | 8 +-- .../algorithm/myers/MyersDiffTest.java | 4 +- 6 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java rename java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/{MyersDiff.java => MeyersDiff.java} (85%) diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index f85b617..44a8cff 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -15,9 +15,10 @@ */ package com.github.difflib; +import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; -import com.github.difflib.algorithm.myers.MyersDiff; +import com.github.difflib.algorithm.myers.MeyersDiff; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; @@ -34,26 +35,35 @@ import java.util.function.BiPredicate; public final class DiffUtils { /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * This factory generates the DEFAULT_DIFF algorithm for all these routines. + */ + static DiffAlgorithmFactory DEFAULT_DIFF = MeyersDiff.factory(); + + public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { + DEFAULT_DIFF = factory; + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param types to be diffed * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param progress progress listener - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { - return DiffUtils.diff(original, revised, new MyersDiff<>(), progress); + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); } public static Patch diff(List original, List revised) { - return DiffUtils.diff(original, revised, new MyersDiff<>(), null); + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); } - + public static Patch diff(List original, List revised, boolean includeEqualParts) { - return DiffUtils.diff(original, revised, new MyersDiff<>(), null, includeEqualParts); + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); } /** @@ -67,45 +77,46 @@ public final class DiffUtils { } /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param source The original text. Must not be {@code null}. * @param target The revised text. Must not be {@code null}. * - * @param equalizer the equalizer object to replace the default compare algorithm - * (Object.equals). If {@code null} the default equalizer of the default algorithm is used.. - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @param equalizer the equalizer object to replace the default compare + * algorithm (Object.equals). If {@code null} the default equalizer of the + * default algorithm is used.. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List source, List target, BiPredicate equalizer) { if (equalizer != null) { return DiffUtils.diff(source, target, - new MyersDiff<>(equalizer)); + DEFAULT_DIFF.create(equalizer)); } - return DiffUtils.diff(source, target, new MyersDiff<>()); + return DiffUtils.diff(source, target, new MeyersDiff<>()); } public static Patch diff(List original, List revised, DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { return diff(original, revised, algorithm, progress, false); } - + /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param algorithm The diff algorithm. Must not be {@code null}. * @param progress The diff algorithm listener. * @param includeEqualParts Include equal data parts into the patch. - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress, + DiffAlgorithmI algorithm, DiffAlgorithmListener progress, boolean includeEqualParts) { Objects.requireNonNull(original, "original must not be null"); Objects.requireNonNull(revised, "revised must not be null"); @@ -115,23 +126,23 @@ public final class DiffUtils { } /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param algorithm The diff algorithm. Must not be {@code null}. - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, DiffAlgorithmI algorithm) { return diff(original, revised, algorithm, null); } /** - * Computes the difference between the given texts inline. This one uses the "trick" to make out - * of texts lists of characters, like DiffRowGenerator does and merges those changes at the end - * together again. + * Computes the difference between the given texts inline. This one uses the + * "trick" to make out of texts lists of characters, like DiffRowGenerator + * does and merges those changes at the end together again. * * @param original * @param revised diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java new file mode 100644 index 0000000..7e5205c --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm; + +import java.util.function.BiPredicate; + +/** + * Tool to create new instances of a diff algorithm. This one is only needed at the moment to + * set DiffUtils default diff algorithm. + * @author tw + */ +public interface DiffAlgorithmFactory { + DiffAlgorithmI create(); + + DiffAlgorithmI create(BiPredicate equalizer); +} diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java similarity index 85% rename from java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java rename to java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java index f134557..7daa91d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java @@ -16,6 +16,7 @@ package com.github.difflib.algorithm.myers; import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.DeltaType; @@ -26,17 +27,17 @@ import java.util.Objects; import java.util.function.BiPredicate; /** - * A clean-room implementation of Eugene Myers greedy differencing algorithm. + * A clean-room implementation of Eugene Meyers greedy differencing algorithm. */ -public final class MyersDiff implements DiffAlgorithmI { +public final class MeyersDiff implements DiffAlgorithmI { private final BiPredicate equalizer; - public MyersDiff() { + public MeyersDiff() { equalizer = Object::equals; } - public MyersDiff(final BiPredicate equalizer) { + public MeyersDiff(final BiPredicate equalizer) { Objects.requireNonNull(equalizer, "equalizer must not be null"); this.equalizer = equalizer; } @@ -63,8 +64,9 @@ public final class MyersDiff implements DiffAlgorithmI { } /** - * Computes the minimum diffpath that expresses de differences between the original and revised - * sequences, according to Gene Myers differencing algorithm. + * Computes the minimum diffpath that expresses de differences between the + * original and revised sequences, according to Gene Myers differencing + * algorithm. * * @param orig The original sequence. * @param rev The revised sequence. @@ -138,8 +140,8 @@ public final class MyersDiff implements DiffAlgorithmI { * @param orig The original sequence. * @param rev The revised sequence. * @return A {@link Patch} script corresponding to the path. - * @throws DifferentiationFailedException if a {@link Patch} could not be built from the given - * path. + * @throws DifferentiationFailedException if a {@link Patch} could not be + * built from the given path. */ private List buildRevision(PathNode actualPath, List orig, List rev) { Objects.requireNonNull(actualPath, "path is null"); @@ -176,4 +178,23 @@ public final class MyersDiff implements DiffAlgorithmI { } return changes; } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI + create() { + return new MeyersDiff(); + } + + @Override + public DiffAlgorithmI + create(BiPredicate < T, T > equalizer) { + return new MeyersDiff(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index e7678be..ebcc02f 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.BiPredicate; +import java.util.function.Consumer; /** * @@ -43,13 +44,33 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { @Override public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + + if (progress != null) { + progress.diffStart(); + } + DiffData data = new DiffData(source, target); - //shouldn't it be source.size() - 1? - buildScript(data, 0, source.size(), 0, target.size()); + + int maxIdx = source.size() + target.size(); + + buildScript(data, 0, source.size(), 0, target.size(), idx -> { + if (progress != null) { + progress.diffStep(idx, maxIdx); + } + }); + + if (progress != null) { + progress.diffEnd(); + } return data.script; } - private void buildScript(DiffData data, int start1, int end1, int start2, int end2) { + private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { + if (progress != null) { + progress.accept(start1); + } final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); if (middle == null || middle.start == end1 && middle.diag == end1 - end2 @@ -86,8 +107,8 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { } } } else { - buildScript(data, start1, middle.start, start2, middle.start - middle.diag); - buildScript(data, middle.end, end1, middle.end - middle.diag, end2); + buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress); + buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress); } } @@ -169,7 +190,7 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { return new Snake(start, end, diag); } - class DiffData { + private class DiffData { final int size; final int[] vDown; @@ -188,7 +209,7 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { } } - class Snake { + private class Snake { final int start; final int end; diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java index fc39757..3608140 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -39,8 +39,8 @@ public class MeyersDiffWithLinearSpaceTest { final Patch patch = Patch.generate(original, revised, new MeyersDiffWithLinearSpace().computeDiff(original, revised, null)); assertNotNull(patch); System.out.println(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + assertEquals(5, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); } @Test @@ -68,8 +68,8 @@ public class MeyersDiffWithLinearSpaceTest { })); assertNotNull(patch); System.out.println(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + assertEquals(5, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); System.out.println(logdata); assertEquals(8, logdata.size()); } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 1e233a8..10db0e4 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -34,7 +34,7 @@ public class MyersDiffTest { public void testDiffMyersExample1Forward() { List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); + final Patch patch = Patch.generate(original, revised, new MeyersDiff().computeDiff(original, revised, null)); assertNotNull(patch); assertEquals(4, patch.getDeltas().size()); assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); @@ -47,7 +47,7 @@ public class MyersDiffTest { List logdata = new ArrayList<>(); final Patch patch = Patch.generate(original, revised, - new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { + new MeyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { @Override public void diffStart() { logdata.add("start"); From e413d79ecd0af293fdc0f2474b8b097dc360d78d Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 18 Jul 2021 01:30:44 +0200 Subject: [PATCH 5/8] attached logging to new meyer diff algorithm --- .../difflib/algorithm/myers/MeyersDiffWithLinearSpace.java | 2 +- .../difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index ebcc02f..3fec0fd 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -69,7 +69,7 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { if (progress != null) { - progress.accept(start1); + progress.accept((end1 - start1) / 2 + (end2 - start2) / 2); } final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); if (middle == null diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java index 3608140..7a10f1f 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -71,7 +71,7 @@ public class MeyersDiffWithLinearSpaceTest { assertEquals(5, patch.getDeltas().size()); assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); System.out.println(logdata); - assertEquals(8, logdata.size()); + assertEquals(11, logdata.size()); } From 7203d4890f088771161d14e7597682e872e0765a Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Mon, 19 Jul 2021 00:15:39 +0200 Subject: [PATCH 6/8] mmoved header to inner processing to allow a header for each diff block --- .../unifieddiff/UnifiedDiffReader.java | 70 ++++++++++++++----- .../unifieddiff/UnifiedDiffReaderTest.java | 8 +-- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index da75e6f..181b41a 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -72,26 +72,45 @@ public final class UnifiedDiffReader { // [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], // [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]]; private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { - String headerTxt = ""; - LOG.log(Level.FINE, "header parsing"); - String line = null; - while (READER.ready()) { - line = READER.readLine(); - LOG.log(Level.FINE, "parsing line {0}", line); - if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) - || FROM_FILE.validLine(line) || TO_FILE.validLine(line) - || NEW_FILE_MODE.validLine(line)) { - break; - } else { - headerTxt += line + "\n"; - } - } - if (!"".equals(headerTxt)) { - data.setHeader(headerTxt); - } +// String headerTxt = ""; +// LOG.log(Level.FINE, "header parsing"); +// String line = null; +// while (READER.ready()) { +// line = READER.readLine(); +// LOG.log(Level.FINE, "parsing line {0}", line); +// if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) +// || FROM_FILE.validLine(line) || TO_FILE.validLine(line) +// || NEW_FILE_MODE.validLine(line)) { +// break; +// } else { +// headerTxt += line + "\n"; +// } +// } +// if (!"".equals(headerTxt)) { +// data.setHeader(headerTxt); +// } + String line = READER.readLine(); while (line != null) { - if (!CHUNK.validLine(line)) { + String headerTxt = ""; + LOG.log(Level.FINE, "header parsing"); + while (line != null) { + LOG.log(Level.FINE, "parsing line {0}", line); + if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, + FROM_FILE, TO_FILE, + RENAME_FROM, RENAME_TO, + NEW_FILE_MODE, DELETED_FILE_MODE, + CHUNK)) { + break; + } else { + headerTxt += line + "\n"; + } + line = READER.readLine(); + } + if (!"".equals(headerTxt)) { + data.setHeader(headerTxt); + } + if (line != null && !CHUNK.validLine(line)) { initFileIfNecessary(); while (line != null && !CHUNK.validLine(line)) { if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, @@ -107,7 +126,7 @@ public final class UnifiedDiffReader { processLine(line, CHUNK); while ((line = READER.readLine()) != null) { line = checkForNoNewLineAtTheEndOfTheFile(line); - + if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { throw new UnifiedDiffParserException("expected data line not found"); } @@ -186,6 +205,19 @@ public final class UnifiedDiffReader { return false; //throw new UnifiedDiffParserException("parsing error at line " + line); } + + private boolean validLine(String line, UnifiedDiffLine ... rules) { + if (line == null) { + return false; + } + for (UnifiedDiffLine rule : rules) { + if (rule.validLine(line)) { + LOG.fine(" >>> accepted rule " + rule.toString()); + return true; + } + } + return false; + } private void initFileIfNecessary() { if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 02e7c4d..912b72f 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -370,9 +370,9 @@ public class UnifiedDiffReaderTest { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); - assertThat(diff.getFiles().size()).isEqualTo(22); + assertThat(diff.getFiles().size()).isEqualTo(1); - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("coders/wpg.c"); } @Test @@ -380,8 +380,8 @@ public class UnifiedDiffReaderTest { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); - assertThat(diff.getFiles().size()).isEqualTo(22); + assertThat(diff.getFiles().size()).isEqualTo(2); - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); } } From 2294c5be7b70b79df932e6a046825e4dff609dd6 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 15 Aug 2021 00:51:24 +0200 Subject: [PATCH 7/8] implemented multi algorithm patch test --- .../myers/MeyersDiffWithLinearSpace.java | 20 ++++++ ...va => PatchWithAllDiffAlgorithmsTest.java} | 61 +++++++++++++---- .../difflib/patch/PatchWithMeyerDiffTest.java | 51 ++++++++++++++ ...PatchWithMeyerDiffWithLinearSpaceTest.java | 66 +++++++++++++++++++ 4 files changed, 184 insertions(+), 14 deletions(-) rename java-diff-utils/src/test/java/com/github/difflib/patch/{PatchTest.java => PatchWithAllDiffAlgorithmsTest.java} (64%) create mode 100644 java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java create mode 100644 java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index 3fec0fd..bb3577f 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -16,6 +16,7 @@ package com.github.difflib.algorithm.myers; import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.DeltaType; @@ -221,4 +222,23 @@ public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { this.diag = diag; } } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI + create() { + return new MeyersDiffWithLinearSpace(); + } + + @Override + public DiffAlgorithmI + create(BiPredicate < T, T > equalizer) { + return new MeyersDiffWithLinearSpace(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java similarity index 64% rename from java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java rename to java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index 91d514a..117055a 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -11,14 +11,35 @@ import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.List; -import org.junit.jupiter.api.Test; import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.DiffAlgorithmFactory; +import com.github.difflib.algorithm.myers.MeyersDiff; +import com.github.difflib.algorithm.myers.MeyersDiffWithLinearSpace; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -public class PatchTest { +public class PatchWithAllDiffAlgorithmsTest { - @Test - public void testPatch_Insert() { + private static Stream provideAlgorithms() { + return Stream.of( + Arguments.of(MeyersDiff.factory()), + Arguments.of(MeyersDiffWithLinearSpace.factory())); + } + + @AfterAll + public static void afterAll() { + DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiff.factory()); + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Insert(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List insertTest_from = Arrays.asList("hhh"); final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); @@ -30,8 +51,11 @@ public class PatchTest { } } - @Test - public void testPatch_Delete() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Delete(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); final List deleteTest_to = Arrays.asList("ggg"); @@ -43,8 +67,11 @@ public class PatchTest { } } - @Test - public void testPatch_Change() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Change(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); @@ -56,8 +83,11 @@ public class PatchTest { } } - @Test - public void testPatch_Serializable() throws IOException, ClassNotFoundException { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOException, ClassNotFoundException { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); @@ -79,8 +109,11 @@ public class PatchTest { } - @Test - public void testPatch_Change_withExceptionProcessor() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Change_withExceptionProcessor(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); @@ -93,9 +126,9 @@ public class PatchTest { try { List data = DiffUtils.patch(changeTest_from, patch); assertEquals(9, data.size()); - + assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); - + } catch (PatchFailedException e) { fail(e.getMessage()); } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java new file mode 100644 index 0000000..d7bf279 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.patch; + +import com.github.difflib.DiffUtils; +import java.util.Arrays; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + +/** + * + * @author tw + */ +public class PatchWithMeyerDiffTest { + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(9, data.size()); + + assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java new file mode 100644 index 0000000..8a62992 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.patch; + +import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.myers.MeyersDiff; +import com.github.difflib.algorithm.myers.MeyersDiffWithLinearSpace; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * + * @author tw + */ +public class PatchWithMeyerDiffWithLinearSpaceTest { + + @BeforeAll + public static void setupClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiffWithLinearSpace.factory()); + } + + @AfterAll + public static void resetClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiff.factory()); + } + + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(11, data.size()); + + assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } +} From 894b8ba300e85d4a502f4f48a7029bc48bed032c Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 15 Aug 2021 00:52:08 +0200 Subject: [PATCH 8/8] implemented multi algorithm patch test --- .../patch/PatchWithAllDiffAlgorithmsTest.java | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index 117055a..82e7229 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -107,30 +107,5 @@ public class PatchWithAllDiffAlgorithmsTest { fail(e.getMessage()); } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Change_withExceptionProcessor(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - - changeTest_from.set(2, "CDC"); - - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(9, data.size()); - - assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); - - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + } }