diff --git a/Makefile.am b/Makefile.am
index 8ab0f104..6e6f0c75 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -194,6 +194,7 @@ TESTS = tests/newline1/run-test \
tests/grepdiff7/run-test \
tests/grepdiff8/run-test \
tests/grepdiff9/run-test \
+ tests/grepdiff-original-line-numbers/run-test \
tests/number1/run-test \
tests/number2/run-test \
tests/number3/run-test \
diff --git a/NEWS b/NEWS
index e88baa6c..e42dc1a6 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,11 @@ Patchutils news
warnings, unused result warnings, and locale-related test failures.
Updated documentation and example spec file.
+ Added new --as-numbered-lines options: original-before and original-after.
+ These preserve original line numbers from the diff context, useful for
+ CI/CD systems that need to report errors on exact line numbers from the
+ original diff. Addresses GitHub issue #55.
+
0.4.2 (stable)
Build system improvements: only run xmlto once during documentation
diff --git a/bash-completion-patchutils b/bash-completion-patchutils
index a344dc51..388c6fbc 100644
--- a/bash-completion-patchutils
+++ b/bash-completion-patchutils
@@ -51,7 +51,7 @@ _filterdiff() {
return 0
;;
--as-numbered-lines)
- COMPREPLY=($(compgen -W "before after" -- "$cur"))
+ COMPREPLY=($(compgen -W "before after original-before original-after" -- "$cur"))
return 0
;;
--format)
@@ -141,7 +141,7 @@ _grepdiff() {
return 0
;;
--as-numbered-lines)
- COMPREPLY=($(compgen -W "before after" -- "$cur"))
+ COMPREPLY=($(compgen -W "before after original-before original-after" -- "$cur"))
return 0
;;
--format)
diff --git a/doc/patchutils.xml b/doc/patchutils.xml
index 6a47e59d..7ed44167 100644
--- a/doc/patchutils.xml
+++ b/doc/patchutils.xml
@@ -888,7 +888,7 @@
- =before|after
+ =before|after|original-before|original-afterInstead of a patch fragment, display the lines of
the selected hunks with the line number of the file
@@ -897,6 +897,13 @@
beginning of each line. Each hunk except the first
will have a line consisting of ...
before it.
+ The before and after
+ options show line numbers adjusted for any skipped hunks.
+ The original-before and original-after
+ options show line numbers as they appear in the original diff,
+ preserving the original line number context. This is useful for
+ CI/CD systems that need to report errors on the exact line numbers
+ from the original diff.
@@ -1022,6 +1029,12 @@
+ For CI/CD systems that need line numbers matching the original
+ diff context (useful for error reporting), use the original-* variants:
+
+
+
Filterdiff can also be used to convert between unified
and context format diffs:
@@ -2458,7 +2471,7 @@ will pipe patch of file #2 to vim - -R
- =before|after
+ =before|after|original-before|original-afterInstead of a patch fragment, display the lines of
the selected hunks with the line number of the file
@@ -2467,6 +2480,13 @@ will pipe patch of file #2 to vim - -R
beginning of each line. Each hunk except the first
will have a line consisting of ...
before it.
+ The before and after
+ options show line numbers adjusted for any skipped hunks.
+ The original-before and original-after
+ options show line numbers as they appear in the original diff,
+ preserving the original line number context. This is useful for
+ CI/CD systems that need to report errors on the exact line numbers
+ from the original diff.
diff --git a/src/filterdiff.c b/src/filterdiff.c
index ddb4472d..b616f71d 100644
--- a/src/filterdiff.c
+++ b/src/filterdiff.c
@@ -57,7 +57,9 @@ struct range {
enum line_numbering {
None = 0,
Before,
- After
+ After,
+ OriginalBefore,
+ OriginalAfter
};
enum {
@@ -443,9 +445,9 @@ do_unified (FILE *f, char **header, unsigned int num_headers,
unsigned int i;
for (i = 0; i < num_headers - 2; i++)
output_header_line (header[i]);
- if (number_lines != After)
+ if (number_lines != After && number_lines != OriginalAfter)
output_header_line (header[num_headers - 2]);
- if (number_lines != Before)
+ if (number_lines != Before && number_lines != OriginalBefore)
output_header_line (header[num_headers - 1]);
header_displayed = 1;
}
@@ -497,6 +499,22 @@ do_unified (FILE *f, char **header, unsigned int num_headers,
hunknum > 1))
fputs ("...\n", output_to);
break;
+ case OriginalBefore:
+ // Note the initial line number (original, no munge)
+ track_linenum = orig_offset;
+ if (!first_hunk ||
+ (output_matching == output_file &&
+ hunknum > 1))
+ fputs ("...\n", output_to);
+ break;
+ case OriginalAfter:
+ // Note the initial line number (original, no munge)
+ track_linenum = new_offset;
+ if (!first_hunk ||
+ (output_matching == output_file &&
+ hunknum > 1))
+ fputs ("...\n", output_to);
+ break;
}
} else if (mode == mode_filter)
// We are missing this hunk out, but
@@ -542,9 +560,9 @@ do_unified (FILE *f, char **header, unsigned int num_headers,
unsigned int i;
for (i = 0; i < num_headers - 2; i++)
output_header_line (header[i]);
- if (number_lines != After)
+ if (number_lines != After && number_lines != OriginalAfter)
output_header_line (header[num_headers - 2]);
- if (number_lines != Before)
+ if (number_lines != Before && number_lines != OriginalBefore)
output_header_line (header[num_headers - 1]);
header_displayed = 1;
}
@@ -573,7 +591,9 @@ do_unified (FILE *f, char **header, unsigned int num_headers,
// Just display each line.
fwrite (*line, (size_t) got, 1, output_to);
else if ((number_lines == Before && **line != '+') ||
- (number_lines == After && **line != '-')) {
+ (number_lines == After && **line != '-') ||
+ (number_lines == OriginalBefore && **line != '+') ||
+ (number_lines == OriginalAfter && **line != '-')) {
// Numbered line.
const char *rest = *line;
if (rest[0] != '\n')
@@ -759,9 +779,9 @@ do_context (FILE *f, char **header, unsigned int num_headers,
unsigned int i;
for (i = 0; i < num_headers - 2; i++)
output_header_line (header[i]);
- if (number_lines != After)
+ if (number_lines != After && number_lines != OriginalAfter)
output_header_line (header[num_headers - 2]);
- if (number_lines != Before)
+ if (number_lines != Before && number_lines != OriginalBefore)
output_header_line (header[num_headers - 1]);
header_displayed = 1;
}
@@ -820,6 +840,30 @@ do_context (FILE *f, char **header, unsigned int num_headers,
hunknum > 1))
fputs ("...\n", output_to);
break;
+
+ case OriginalBefore:
+ if (i != 0)
+ break;
+
+ // Note the initial line number (original, no munge).
+ track_linenum = line_start;
+ if (!first_hunk ||
+ (output_matching == output_file &&
+ hunknum > 1))
+ fputs ("...\n", output_to);
+ break;
+
+ case OriginalAfter:
+ if (i != 1)
+ break;
+
+ // Note the initial line number (original, no munge).
+ track_linenum = line_start;
+ if (!first_hunk ||
+ (output_matching == output_file &&
+ hunknum > 1))
+ fputs ("...\n", output_to);
+ break;
}
}
@@ -866,9 +910,9 @@ do_context (FILE *f, char **header, unsigned int num_headers,
unsigned int i;
for (i = 0; i < num_headers - 2; i++)
output_header_line (header[i]);
- if (number_lines != After)
+ if (number_lines != After && number_lines != OriginalAfter)
output_header_line (header[num_headers - 2]);
- if (number_lines != Before)
+ if (number_lines != Before && number_lines != OriginalBefore)
output_header_line (header[num_headers - 1]);
header_displayed = 1;
}
@@ -927,7 +971,9 @@ do_context (FILE *f, char **header, unsigned int num_headers,
fwrite (*line, (size_t) got,
1, output_to);
else if ((number_lines == Before && !i) ||
- (number_lines == After && i)) {
+ (number_lines == After && i) ||
+ (number_lines == OriginalBefore && !i) ||
+ (number_lines == OriginalAfter && i)) {
fprintf (output_to, "%lu\t:",
track_linenum++);
fwrite (2 + *line, (size_t) got - 2,
@@ -1217,9 +1263,9 @@ const char * syntax_str =
" include only files in range F, if range begins with x show all excluding range F\n"
" --annotate (filterdiff, patchview, grepdiff)\n"
" annotate each hunk with the filename and hunk number (filterdiff, patchview, grepdiff)\n"
-" --as-numbered-lines=before|after (filterdiff, patchview, grepdiff)\n"
-" display lines as they would look before, or after, the (filterdiff, patchview, grepdiff)\n"
-" patch is applied (filterdiff, patchview, grepdiff)\n"
+" --as-numbered-lines=before|after|original-before|original-after (filterdiff, patchview, grepdiff)\n"
+" display lines as they would look before, or after, the patch is applied;\n"
+" or with original line numbers from the diff (original-before/original-after)\n"
" --format=context|unified (filterdiff, patchview, grepdiff)\n"
" set output format (filterdiff, patchview, grepdiff)\n"
" --output-matching=hunk|file (grepdiff)\n"
@@ -1632,6 +1678,10 @@ int main (int argc, char *argv[])
number_lines = Before;
else if (!strcmp (optarg, "after"))
number_lines = After;
+ else if (!strcmp (optarg, "original-before"))
+ number_lines = OriginalBefore;
+ else if (!strcmp (optarg, "original-after"))
+ number_lines = OriginalAfter;
else syntax (1);
break;
case 1000 + 'a':
diff --git a/tests/grepdiff-original-line-numbers/run-test b/tests/grepdiff-original-line-numbers/run-test
new file mode 100755
index 00000000..4303a8d0
--- /dev/null
+++ b/tests/grepdiff-original-line-numbers/run-test
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+# Test for grepdiff --as-numbered-lines=original-* options
+# Tests the new original-before and original-after options that preserve
+# original line numbers from the diff (useful for CI/CD error reporting)
+# Addresses GitHub issue #55
+
+. ${top_srcdir-.}/tests/common.sh
+
+# Create a test diff that reproduces the issue from the GitHub issue
+cat << EOF > diff
+diff --git a/foobar.txt b/foobar.txt
+index 8a19eed..ae745cf 100644
+--- a/foobar.txt
++++ b/foobar.txt
+@@ -0,0 +1,4 @@
++# pre-comment
++# spread over
++# three lines
++
+@@ -8 +12 @@
+-foo()
++foo(bar)
+EOF
+
+# Test --output-matching=hunk (shows adjusted line numbers in hunk headers)
+${GREPDIFF} foo --output-matching=hunk diff 2>errors >hunk_output || exit 1
+[ -s errors ] && exit 1
+
+# The hunk output shows adjusted line numbers (this is the existing behavior)
+cat << EOF | cmp - hunk_output || exit 1
+diff --git a/foobar.txt b/foobar.txt
+index 8a19eed..ae745cf 100644
+--- a/foobar.txt
++++ b/foobar.txt
+@@ -8 +8 @@
+-foo()
++foo(bar)
+EOF
+
+# Test --as-numbered-lines=original-after (the new option for this use case)
+${GREPDIFF} foo --output-matching=hunk --only-match=additions --as-numbered-lines=original-after diff 2>errors2 >numbered_output || exit 1
+[ -s errors2 ] && exit 1
+
+# The expected output should show line 12 (original line number from diff)
+cat << EOF | cmp - numbered_output || exit 1
+diff --git a/foobar.txt b/foobar.txt
+index 8a19eed..ae745cf 100644
++++ b/foobar.txt
+12 :foo(bar)
+EOF
+
+# Test --as-numbered-lines=original-before
+${GREPDIFF} foo --output-matching=hunk --only-match=additions --as-numbered-lines=original-before diff 2>errors3 >numbered_before_output || exit 1
+[ -s errors3 ] && exit 1
+
+# This should show line 8 for the "before" version (original line number from diff)
+cat << EOF | cmp - numbered_before_output || exit 1
+diff --git a/foobar.txt b/foobar.txt
+index 8a19eed..ae745cf 100644
+--- a/foobar.txt
+8 :foo()
+EOF
+
+# Test that regular --as-numbered-lines=after still works with adjusted line numbers
+${GREPDIFF} foo --output-matching=hunk --only-match=additions --as-numbered-lines=after diff 2>errors4 >numbered_adjusted_output || exit 1
+[ -s errors4 ] && exit 1
+
+# This should show line 8 (adjusted line number, old behavior)
+cat << EOF | cmp - numbered_adjusted_output || exit 1
+diff --git a/foobar.txt b/foobar.txt
+index 8a19eed..ae745cf 100644
++++ b/foobar.txt
+8 :foo(bar)
+EOF