Skip to content

Commit 1083d88

Browse files
youdie006wumpz
andauthored
Read and write empty hunk ranges the way diff and git do (#236)
In a unified diff a range with a count of 0 names the line before it, so "@@ -2,0 +3 @@" inserts after line 2. Both parsers took the start as the first affected line and both writers wrote it that way, so a zero-context diff from GNU diff or git was applied one line early, and GNU patch applied ours one line late. Co-authored-by: Tobias <t.warneke@gmx.net>
1 parent 92c90c2 commit 1083d88

6 files changed

Lines changed: 94 additions & 0 deletions

File tree

‎java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ public static Patch<String> parseUnifiedDiff(List<String> diff) {
6969
old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1));
7070
new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
7171

72+
// an empty range names the line before it
73+
if ("0".equals(m.group(2))) {
74+
old_ln++;
75+
}
76+
if ("0".equals(m.group(4))) {
77+
new_ln++;
78+
}
7279
if (old_ln == 0) {
7380
old_ln = 1;
7481
}
@@ -278,6 +285,14 @@ private static List<String> processDeltas(
278285
revTotal++;
279286
}
280287

288+
// an empty range names the line before it
289+
if (origTotal == 0 && origStart > 0) {
290+
origStart--;
291+
}
292+
if (revTotal == 0) {
293+
revStart--;
294+
}
295+
281296
// Create and insert the block header, conforming to the Unified Diff
282297
// standard
283298
StringBuilder header = new StringBuilder();

‎java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,13 @@ private void processChunk(MatchResult match, String chunkStart) {
366366
old_size = toInteger(match, 2, 1);
367367
new_ln = toInteger(match, 3, 1);
368368
new_size = toInteger(match, 4, 1);
369+
// an empty range names the line before it
370+
if (old_size == 0) {
371+
old_ln++;
372+
}
373+
if (new_size == 0) {
374+
new_ln++;
375+
}
369376
if (old_ln == 0) {
370377
old_ln = 1;
371378
}

‎java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ private static void processDeltas(
196196
revTotal++;
197197
}
198198

199+
// an empty range names the line before it
200+
if (origTotal == 0 && origStart > 0) {
201+
origStart--;
202+
}
203+
if (revTotal == 0) {
204+
revStart--;
205+
}
206+
199207
// Create and insert the block header, conforming to the Unified Diff
200208
// standard
201209
writer.accept("@@ -" + origStart + "," + origTotal + " +" + revStart + "," + revTotal + " @@");

‎java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,35 @@ public void testNewFileCreation() {
138138
UnifiedDiffUtils.parseUnifiedDiff(udiff);
139139
}
140140

141+
@Test
142+
public void testZeroContextEmptyRangeHeader() throws PatchFailedException {
143+
List<String> original = Arrays.asList("a", "b", "c");
144+
List<String> revised = Arrays.asList("a", "b", "X", "c");
145+
146+
Patch<String> patch = DiffUtils.diff(original, revised);
147+
List<String> udiff = UnifiedDiffUtils.generateUnifiedDiff("original", "revised", original, patch, 0);
148+
149+
assertEquals("@@ -2,0 +3,1 @@", udiff.get(2));
150+
assertEquals(revised, UnifiedDiffUtils.parseUnifiedDiff(udiff).applyTo(original));
151+
}
152+
153+
@Test
154+
public void testParseZeroContextEmptyRanges() throws PatchFailedException {
155+
// as written by diff -U0 and git diff -U0
156+
List<String> insert = Arrays.asList("--- original", "+++ revised", "@@ -2,0 +3 @@", "+X");
157+
assertEquals(
158+
Arrays.asList("a", "b", "X", "c"),
159+
UnifiedDiffUtils.parseUnifiedDiff(insert).applyTo(Arrays.asList("a", "b", "c")));
160+
161+
List<String> delete = Arrays.asList("--- original", "+++ revised", "@@ -2 +1,0 @@", "-b");
162+
assertEquals(
163+
Arrays.asList("a", "c"),
164+
UnifiedDiffUtils.parseUnifiedDiff(delete).applyTo(Arrays.asList("a", "b", "c")));
165+
assertEquals(
166+
Arrays.asList("a", "b", "c"),
167+
UnifiedDiffUtils.parseUnifiedDiff(delete).restore(Arrays.asList("a", "c")));
168+
}
169+
141170
/**
142171
* Issue 89
143172
*/

‎java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
import static org.junit.jupiter.api.Assertions.assertTrue;
2222

2323
import com.github.difflib.patch.AbstractDelta;
24+
import com.github.difflib.patch.Patch;
25+
import com.github.difflib.patch.PatchFailedException;
26+
import java.io.ByteArrayInputStream;
2427
import java.io.IOException;
28+
import java.nio.charset.StandardCharsets;
29+
import java.util.Arrays;
2530
import java.util.regex.Matcher;
2631
import java.util.regex.Pattern;
2732
import org.junit.jupiter.api.Test;
@@ -503,4 +508,16 @@ public void testParseIssue193Copy() throws IOException {
503508
assertThat(file1.getCopyTo())
504509
.isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf");
505510
}
511+
512+
@Test
513+
public void testParseZeroContextEmptyRanges() throws IOException, PatchFailedException {
514+
// as written by diff -U0 and git diff -U0
515+
String text = "--- a/f\n+++ b/f\n@@ -2,0 +3 @@\n+X\n@@ -4 +4,0 @@\n-d\n";
516+
UnifiedDiff diff =
517+
UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
518+
519+
Patch<String> patch = diff.getFiles().get(0).getPatch();
520+
assertThat(patch.applyTo(Arrays.asList("a", "b", "c", "d"))).containsExactly("a", "b", "X", "c");
521+
assertThat(patch.restore(Arrays.asList("a", "b", "X", "c"))).containsExactly("a", "b", "c", "d");
522+
}
506523
}

‎java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.nio.file.Files;
3232
import java.nio.file.Paths;
3333
import java.util.ArrayList;
34+
import java.util.Arrays;
3435
import java.util.Collections;
3536
import java.util.List;
3637
import org.junit.jupiter.api.Test;
@@ -86,6 +87,23 @@ public void testWriteWithNewFile() throws URISyntaxException, IOException {
8687
assertEquals("@@ -0,0 +1,2 @@", lines[2]);
8788
}
8889

90+
@Test
91+
public void testWriteZeroContextEmptyRange() throws IOException {
92+
List<String> original = Arrays.asList("a", "b", "c");
93+
List<String> revised = Arrays.asList("a", "b", "X", "c");
94+
95+
Patch<String> patch = DiffUtils.diff(original, revised);
96+
UnifiedDiff diff = new UnifiedDiff();
97+
diff.addFile(UnifiedDiffFile.from("original", "revised", patch));
98+
99+
StringWriter writer = new StringWriter();
100+
UnifiedDiffWriter.write(diff, f -> original, writer, 0);
101+
102+
String[] lines = writer.toString().split("\\n");
103+
104+
assertEquals("@@ -2,0 +3,1 @@", lines[2]);
105+
}
106+
89107
@ParameterizedTest
90108
@ValueSource(ints = {1, 2, 9, 16})
91109
public void testPropagateWriterFailures(int failOnWrite) {

0 commit comments

Comments
 (0)