6

I have file a:

This
file
does
have
an error
in it
that
needs
to be
fixed.

and a similar file b:

This
file
does
have
no error
in it
that
needs
to be
fixed.

I can create a unified diff with diff -u a b:

--- a   2018-01-03 14:20:22 +0100
+++ b   2018-01-03 14:20:37 +0100
@@ -2,7 +2,7 @@
 file
 does
 have
-an error
+no error
 in it
 that
 needs

And I can also reduce the context to one line on either side with diff -u1 a b:

--- a   2018-01-03 14:20:22 +0100
+++ b   2018-01-03 14:20:37 +0100
@@ -4,3 +4,3 @@
 have
-an error
+no error
 in it

Both of these patches can be applied cleanly with patch. I did not however find a way to make diff produce a patch with asymmetric context. I am assuming it cannot do that. So I tried removing some context manually, to make a patch with two lines of context before the change and one after:

--- a   2018-01-03 14:20:22 +0100
+++ b   2018-01-03 14:20:37 +0100
@@ -3,4 +3,4 @@
 does
 have
-an error
+no error
 in it

This seems valid to me in the unified format. However, patch complains that it had to resort to fuzzing:

patching file a
Hunk #1 succeeded at 3 with fuzz 1.

Am I doing something wrong or is (GNU) patch actually broken for asymmetric contexts because no-one imagined they would ever be used, since diff cannot make them anyway?

Also interesting is that the patch works if I reverse the asymmetry, that is one line before and two after:

--- a   2018-01-03 14:20:22 +0100
+++ b   2018-01-03 14:20:37 +0100
@@ -4,4 +4,4 @@
 have
-an error
+no error
 in it
 that
3
  • The asymmetry may come from the fact that the patch command counts the number of context lines (and I suspect use the context before to count that, I am not able to prove that at 100% from the source code), so in your first case it sees 2 lines of context, but then only one after, whereas in the second example it computes one line of context, and indeed it has one line of context after (and an extra line, that is ignored because patch tries to ignore "garbage" before or after the content being patched) Jan 4, 2018 at 23:14
  • 1
    Note that your asymmetry case is specifically part of the tests: git.savannah.gnu.org/cgit/patch.git/tree/tests/asymmetric-hunks So at least the result are per design/as expected Jan 4, 2018 at 23:24
  • The last line in the last patch is not ignored. patch actually checks it too and needs fuzzing if it does not match.
    – Karel Vlk
    Jan 5, 2018 at 11:51

2 Answers 2

2

The POSIX standard doesn't provide a way to generate asymmetric contexts. GNU patch is being helpful in one case by accepting a malformed patch.

By the way, the manual page indicated in the question makes an incorrect statement:

At present, only GNU diff can produce this format and only GNU patch can automatically apply diffs in this format. For proper operation, patch typically needs at least three lines of context.

The error is still in the diffutils info file.

POSIX patch handles unified diffs.

2
  • Would the patches above be considered malformed?
    – Karel Vlk
    Jan 4, 2018 at 11:23
  • 1
    sure: the unified diff format is only defined in terms of diff and patch. Since diff won't produce asymmetric diffs (and is the only standardized producer of unified diffs), they're outside the definition. Jan 4, 2018 at 20:43
2

GNU patch just does not like when there is more prefix context than suffix context. A simple but ugly workaround is to convert the first line of context into a no-op change like this:

--- a   2018-01-03 14:20:22 +0100
+++ b   2018-01-03 14:20:37 +0100
@@ -3,4 +3,4 @@
-does
+does
 have
-an error
+no error
 in it

This removes all prefix context. It's OK when there is more suffix context.

I did go through patch's source code and found the offending piece. Without understanding it too deeply, I came up with a simple fix similar to how prefix context is already handled in the code. Here is my output of git diff (a patch for patch):

diff --git a/src/patch.c b/src/patch.c
index bba7e0e..e661af1 100644
--- a/src/patch.c
+++ b/src/patch.c
@@ -1171,7 +1171,7 @@ locate_hunk (lin fuzz)
     else if (prefix_fuzz < 0)
       prefix_fuzz = 0;

-    if (suffix_fuzz < 0)
+    if (suffix_fuzz < 0 && pch_first () + pat_lines > input_lines)
       {
    /* Can only match end of file.  */
    offset = first_guess - (input_lines - pat_lines + 1);
@@ -1184,6 +1184,8 @@ locate_hunk (lin fuzz)
    else
      return 0;
       }
+    else if (suffix_fuzz < 0)
+      suffix_fuzz = 0;

     min_offset = max_pos_offset < 0 ? first_guess - max_where
           : max_neg_offset < 0 ? first_guess - min_where
diff --git a/tests/asymmetric-hunks b/tests/asymmetric-hunks
index d6979d9..86e4ef9 100644
--- a/tests/asymmetric-hunks
+++ b/tests/asymmetric-hunks
@@ -77,5 +77,4 @@ seq 1 5 > a

 check 'patch < a.diff' <<EOF
 patching file a
-Hunk #1 succeeded at 2 with fuzz 1.
 EOF

As @Patrick Mevzek pointed out, there already is a regression test for asymmetric contexts. So that is also corrected to not expect fuzzing. Unfortunately the merge test also fails in one case and I am not sure why. I guess I cannot submit the patch because of this. It would be nice if someone with more insight could come up with a better fix.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.