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-after Instead 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-after Instead 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