You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2010/01/28 23:20:33 UTC

svn commit: r904280 - in /subversion/trunk/subversion: include/private/svn_diff_private.h libsvn_client/patch.c libsvn_diff/parse-diff.c tests/cmdline/patch_tests.py

Author: stsp
Date: Thu Jan 28 22:20:32 2010
New Revision: 904280

URL: http://svn.apache.org/viewvc?rev=904280&view=rev
Log:
Fix #3460 - svn patch is not fuzzy when applying unidiffs.

* subversion/include/private/svn_diff_private.h
  (svn_hunk_t): Add fields leading_context and trailing_context. They are
   used for determining if there is enough context to apply a patch
   with fuzz.

* subversion/libsvn_client/patch.c
  (hunk_info_t): Add field FUZZ.
  (match_hunk): Add new parameter FUZZ, specifying how many lines
   at the beginning and end of the hunk will always match.
   Ignore FUZZ if there isn't enough context to do fuzzy matching.
  (scan_for_match): Add new parameter FUZZ. Call match_hunk() with FUZZ.
  (get_hunk_info): Add new parameter FUZZ. Call scan_for_match() with
   FUZZ. Save FUZZ in HI for later use. Set HI->rejected to TRUE
   if no line can be found where the hunk applies.
  (copy_hunk_text): Remove. This interface cannot cope well enough
   with hunks applied with fuzz.
  (reject_hunk): Re-implement parts of copy_hunk_text() inline,
   copying the hunk's diff text to the target's reject stream.
  (apply_one_hunk): Rename to ...
  (apply_hunk): ... this, because it is the brother-in-law of reject_hunk().
   Re-implement parts of copy_hunk_text inline, copying hunk's modified
   text to the patched result. Except lines which matched with fuzz, because
   those must be copied verbatim from the target to retain the local changes
   which made fuzzy matching necessary.
  (apply_one_patch): Call get_hunk_info() repeatedly with increasing
   fuzz factor until either the hunk matches or the maximum fuzz
   factor is reached. Track renaming and removal of functions.

* subversion/libsvn_diff/parse-diff.c
  (parse_next_hunk): Count number of lines of context at start and end
   of hunk and save the information in hunk. Refactored some if
   statements for increased readability.

* subversion/tests/cmdline/patch_tests.py
  (patch_with_fuzz): New.
  (test_list): Add new test.

Patch by: Daniel Näslund <daniel{_AT_}longitudo.com>
          me
Review by: julianfoad (older version)

Modified:
    subversion/trunk/subversion/include/private/svn_diff_private.h
    subversion/trunk/subversion/libsvn_client/patch.c
    subversion/trunk/subversion/libsvn_diff/parse-diff.c
    subversion/trunk/subversion/tests/cmdline/patch_tests.py

Modified: subversion/trunk/subversion/include/private/svn_diff_private.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_diff_private.h?rev=904280&r1=904279&r2=904280&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_diff_private.h (original)
+++ subversion/trunk/subversion/include/private/svn_diff_private.h Thu Jan 28 22:20:32 2010
@@ -81,6 +81,12 @@
   svn_linenum_t original_length;
   svn_linenum_t modified_start;
   svn_linenum_t modified_length;
+
+  /* Number of lines starting with ' ' before first '+' or '-'. */
+  svn_linenum_t leading_context;
+
+  /* Number of lines starting with ' ' after last '+' or '-'. */
+  svn_linenum_t trailing_context;
 } svn_hunk_t;
 
 /* Data type to manage parsing of patches. */

Modified: subversion/trunk/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/patch.c?rev=904280&r1=904279&r2=904280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/patch.c (original)
+++ subversion/trunk/subversion/libsvn_client/patch.c Thu Jan 28 22:20:32 2010
@@ -52,6 +52,10 @@
 
   /* Whether this hunk has been rejected. */
   svn_boolean_t rejected;
+
+  /* The fuzz factor used when matching this hunk, i.e. how many
+   * lines of leading and trailing context to ignore during matching. */
+  int fuzz;
 } hunk_info_t;
 
 typedef struct {
@@ -587,18 +591,19 @@
   return SVN_NO_ERROR;
 }
 
-/* Indicate in *MATCHED whether the original text of HUNK
- * matches the patch TARGET at its current line.
- * When this function returns, neither TARGET->CURRENT_LINE nor the
- * file offset in the target file will have changed.
- * HUNK->ORIGINAL_TEXT will be reset.
- * Do temporary allocations in POOL. */
+/* Indicate in *MATCHED whether the original text of HUNK matches the patch
+ * TARGET at its current line. Lines within FUZZ lines of the start or end
+ * of HUNK will always match. When this function returns, neither
+ * TARGET->CURRENT_LINE nor the file offset in the target file will have
+ * changed. HUNK->ORIGINAL_TEXT will be reset.  Do temporary allocations in
+ * POOL. */
 static svn_error_t *
 match_hunk(svn_boolean_t *matched, patch_target_t *target,
-           const svn_hunk_t *hunk, apr_pool_t *pool)
+           const svn_hunk_t *hunk, int fuzz, apr_pool_t *pool)
 {
   svn_stringbuf_t *hunk_line;
   const char *target_line;
+  svn_linenum_t lines_read;
   svn_linenum_t saved_line;
   svn_boolean_t hunk_eof;
   svn_boolean_t lines_matched;
@@ -610,6 +615,7 @@
     return SVN_NO_ERROR;
 
   saved_line = target->current_line;
+  lines_read = 0;
   lines_matched = FALSE;
   SVN_ERR(svn_stream_reset(hunk->original_text));
   iterpool = svn_pool_create(pool);
@@ -629,9 +635,18 @@
                                            eol_str, FALSE,
                                            target->keywords, FALSE,
                                            iterpool));
+      lines_read++;
       SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
       if (! hunk_eof)
-        lines_matched = ! strcmp(hunk_line_translated, target_line);
+        {
+          if (lines_read <= fuzz && hunk->leading_context > fuzz)
+            lines_matched = TRUE;
+          else if (lines_read > hunk->original_length - fuzz &&
+                   hunk->trailing_context > fuzz)
+            lines_matched = TRUE;
+          else
+            lines_matched = ! strcmp(hunk_line_translated, target_line);
+        }
     }
   while (lines_matched && ! (hunk_eof || target->eof));
 
@@ -657,7 +672,7 @@
 }
 
 /* Scan lines of TARGET for a match of the original text of HUNK,
- * up to but not including the specified UPPER_LINE.
+ * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
  * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
  * Return the line at which HUNK was matched in *MATCHED_LINE.
  * If the hunk did not match at all, set *MATCHED_LINE to zero.
@@ -669,7 +684,7 @@
 static svn_error_t *
 scan_for_match(svn_linenum_t *matched_line, patch_target_t *target,
                const svn_hunk_t *hunk, svn_boolean_t match_first,
-               svn_linenum_t upper_line, apr_pool_t *pool)
+               svn_linenum_t upper_line, int fuzz, apr_pool_t *pool)
 {
   apr_pool_t *iterpool;
 
@@ -683,7 +698,7 @@
 
       svn_pool_clear(iterpool);
 
-      SVN_ERR(match_hunk(&matched, target, hunk, iterpool));
+      SVN_ERR(match_hunk(&matched, target, hunk, fuzz, iterpool));
       if (matched)
         {
           svn_boolean_t taken = FALSE;
@@ -719,14 +734,14 @@
 
 /* Determine the line at which a HUNK applies to the TARGET file,
  * and return an appropriate hunk_info object in *HI, allocated from
- * RESULT_POOL. If no correct line can be determined,
- * set HI->MATCHED_LINE to zero.
- * When this function returns, neither TARGET->CURRENT_LINE nor the
- * file offset in the target file will have changed.
+ * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
+ * line can be determined, set HI->REJECTED to TRUE.
+ * When this function returns, neither TARGET->CURRENT_LINE nor the file
+ * offset in the target file will have changed.
  * Do temporary allocations in POOL. */
 static svn_error_t *
 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
-              const svn_hunk_t *hunk, apr_pool_t *result_pool,
+              const svn_hunk_t *hunk, int fuzz, apr_pool_t *result_pool,
               apr_pool_t *scratch_pool)
 {
   svn_linenum_t matched_line;
@@ -745,7 +760,7 @@
        * should be going. */
       SVN_ERR(seek_to_line(target, hunk->original_start, scratch_pool));
       SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
-                             hunk->original_start + 1, scratch_pool));
+                             hunk->original_start + 1, fuzz, scratch_pool));
       if (matched_line != hunk->original_start)
         {
           /* Scan the whole file again from the start. */
@@ -754,7 +769,7 @@
           /* Scan forward towards the hunk's line and look for a line
            * where the hunk matches. */
           SVN_ERR(scan_for_match(&matched_line, target, hunk, FALSE,
-                                 hunk->original_start, scratch_pool));
+                                 hunk->original_start, fuzz, scratch_pool));
 
           /* In tie-break situations, we arbitrarily prefer early matches
            * to save us from scanning the rest of the file. */
@@ -763,7 +778,7 @@
               /* Scan forward towards the end of the file and look
                * for a line where the hunk matches. */
               SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE, 0,
-                                     scratch_pool));
+                                     fuzz, scratch_pool));
             }
         }
 
@@ -774,7 +789,8 @@
   (*hi) = apr_palloc(result_pool, sizeof(hunk_info_t));
   (*hi)->hunk = hunk;
   (*hi)->matched_line = matched_line;
-  (*hi)->rejected = FALSE;
+  (*hi)->rejected = (matched_line == 0);
+  (*hi)->fuzz = fuzz;
 
   return SVN_NO_ERROR;
 }
@@ -827,96 +843,109 @@
   return SVN_NO_ERROR;
 }
 
-/* Copy HUNK_TEXT into TARGET, line by line, such that the line filter
- * and transformation callbacks set on HUNK_TEXT by the diff parsing
- * code in libsvn_diff will trigger. ABSPATH is the absolute path to the
- * file underlying TARGET. */
+/* Write the diff text of the hunk described by HI to the
+ * reject stream of TARGET, and mark TARGET as having had rejects.
+ * Do temporary allocations in POOL. */
 static svn_error_t *
-copy_hunk_text(svn_stream_t *hunk_text, svn_stream_t *target,
-               const char *abspath, apr_pool_t *scratch_pool)
+reject_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
 {
+  const char *hunk_header;
+  apr_size_t len;
   svn_boolean_t eof;
   apr_pool_t *iterpool;
 
-  iterpool = svn_pool_create(scratch_pool);
+  hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
+                             hi->hunk->original_start,
+                             hi->hunk->original_length,
+                             hi->hunk->modified_start,
+                             hi->hunk->modified_length,
+                             target->eol_str);
+  len = strlen(hunk_header);
+  SVN_ERR(svn_stream_write(target->reject, hunk_header, &len));
+
+  iterpool = svn_pool_create(pool);
   do
     {
-      svn_stringbuf_t *line;
+      svn_stringbuf_t *hunk_line;
       const char *eol_str;
 
       svn_pool_clear(iterpool);
 
-      SVN_ERR(svn_stream_readline_detect_eol(hunk_text, &line, &eol_str,
-                                             &eof, iterpool));
+      SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->diff_text, &hunk_line,
+                                             &eol_str, &eof, iterpool));
       if (! eof)
         {
-          if (line->len >= 1)
-            SVN_ERR(try_stream_write(target, abspath, line->data, line->len,
+          if (hunk_line->len >= 1)
+            SVN_ERR(try_stream_write(target->reject, target->reject_path,
+                                     hunk_line->data, hunk_line->len,
                                      iterpool));
           if (eol_str)
-            SVN_ERR(try_stream_write(target, abspath, eol_str, strlen(eol_str),
-                                     iterpool));
+            SVN_ERR(try_stream_write(target->reject, target->reject_path,
+                                     eol_str, strlen(eol_str), iterpool));
         }
     }
   while (! eof);
   svn_pool_destroy(iterpool);
 
-  return SVN_NO_ERROR;
-}
-
-
-/* Write a hunk described by hunk info HI to the reject stream of TARGET,
- * mark the hunk as rejected, and mark TARGET as having had rejects.
- * Do all allocations in POOL. */
-static svn_error_t *
-reject_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
-{
-  const char *hunk_header;
-  apr_size_t len;
-
-  hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
-                             hi->hunk->original_start,
-                             hi->hunk->original_length,
-                             hi->hunk->modified_start,
-                             hi->hunk->modified_length,
-                             target->eol_str);
-  len = strlen(hunk_header);
-  SVN_ERR(svn_stream_write(target->reject, hunk_header, &len));
-
-  SVN_ERR(copy_hunk_text(hi->hunk->diff_text, target->reject,
-                         target->reject_path, pool));
-
   target->had_rejects = TRUE;
-  hi->rejected = TRUE;
 
   return SVN_NO_ERROR;
 }
 
-/* Apply a hunk described by hunk info HI to a patch TARGET.
- * Do all allocations in POOL. */
+/* Write the modified text of hunk described by HI to the patched
+ * stream of TARGET. Do temporary allocations in POOL. */
 static svn_error_t *
-apply_one_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
+apply_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
 {
+  svn_linenum_t lines_read;
+  svn_boolean_t eof;
+  apr_pool_t *iterpool;
+
   if (target->kind == svn_node_file)
     {
-      /* Move forward to the hunk's line, copying data as we go. */
-      SVN_ERR(copy_lines_to_target(target, hi->matched_line, pool));
-      if (target->eof)
-        {
-          /* File is shorter than it should be. */
-          SVN_ERR(reject_hunk(target, hi, pool));
-          return SVN_NO_ERROR;
-        }
-
-      /* Skip the target's version of the hunk. */
-      SVN_ERR(seek_to_line(target,
-                           target->current_line + hi->hunk->original_length,
+      /* Move forward to the hunk's line, copying data as we go.
+       * Also copy leading lines of context which matched with fuzz.
+       * The target has changed on the fuzzy-matched lines,
+       * so we should retain the target's version of those lines. */
+      SVN_ERR(copy_lines_to_target(target, hi->matched_line + hi->fuzz,
+                                   pool));
+
+      /* Skip the target's version of the hunk.
+       * Don't skip trailing lines which matched with fuzz. */
+      SVN_ERR(seek_to_line(target, target->current_line +
+                             hi->hunk->original_length - (2 * hi->fuzz),
                            pool));
     }
 
-  /* Copy the patched hunk text into the patched stream. */
-  SVN_ERR(copy_hunk_text(hi->hunk->modified_text, target->patched,
-                         target->patched_path, pool));
+  /* Write the hunk's version to the patched result.
+   * Don't write the lines which matched with fuzz. */
+  lines_read = 0;
+  iterpool = svn_pool_create(pool);
+  do
+    {
+      svn_stringbuf_t *hunk_line;
+      const char *eol_str;
+
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->modified_text,
+                                             &hunk_line, &eol_str,
+                                             &eof, iterpool));
+      lines_read++;
+      if (! eof && lines_read > hi->fuzz &&
+          lines_read <= hi->hunk->modified_length - hi->fuzz)
+        {
+          if (hunk_line->len >= 1)
+            SVN_ERR(try_stream_write(target->patched, target->patched_path,
+                                     hunk_line->data, hunk_line->len,
+                                     iterpool));
+          if (eol_str)
+            SVN_ERR(try_stream_write(target->patched, target->patched_path,
+                                     eol_str, strlen(eol_str), iterpool));
+        }
+    }
+  while (! eof);
+  svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
@@ -1023,6 +1052,7 @@
   apr_finfo_t working_file;
   apr_finfo_t patched_file;
   int i;
+  static const int MAX_FUZZ = 2;
 
   SVN_ERR(init_patch_target(&target, patch, abs_wc_path, ctx, strip_count,
                             pool, pool));
@@ -1041,22 +1071,25 @@
     {
       svn_hunk_t *hunk;
       hunk_info_t *hi;
+      int fuzz = 0;
 
       svn_pool_clear(iterpool);
 
       hunk = APR_ARRAY_IDX(patch->hunks, i, svn_hunk_t *);
 
-      /* Determine the line the hunk should be applied at. */
-      SVN_ERR(get_hunk_info(&hi, target, hunk, pool, iterpool));
-      if (hi->matched_line == 0)
+      /* Determine the line the hunk should be applied at.
+       * If no match is found initially, try with fuzz. */
+      do 
         {
-          /* The hunk does not apply, reject it. */
-          SVN_ERR(reject_hunk(target, hi, iterpool));
+          SVN_ERR(get_hunk_info(&hi, target, hunk, fuzz, pool, iterpool));
+          fuzz++;
         }
+      while (hi->rejected && fuzz <= MAX_FUZZ);
+
       APR_ARRAY_PUSH(target->hunks, hunk_info_t *) = hi;
     }
 
-  /* Apply hunks. */
+  /* Apply or reject hunks. */
   for (i = 0; i < target->hunks->nelts; i++)
     {
       hunk_info_t *hi;
@@ -1064,8 +1097,10 @@
       svn_pool_clear(iterpool);
 
       hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
-      if (! hi->rejected)
-        SVN_ERR(apply_one_hunk(target, hi, iterpool));
+      if (hi->rejected)
+        SVN_ERR(reject_hunk(target, hi, iterpool));
+      else
+        SVN_ERR(apply_hunk(target, hi, iterpool));
     }
   svn_pool_destroy(iterpool);
 

Modified: subversion/trunk/subversion/libsvn_diff/parse-diff.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_diff/parse-diff.c?rev=904280&r1=904279&r2=904280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_diff/parse-diff.c (original)
+++ subversion/trunk/subversion/libsvn_diff/parse-diff.c Thu Jan 28 22:20:32 2010
@@ -224,6 +224,9 @@
   svn_stream_t *original_text;
   svn_stream_t *modified_text;
   svn_linenum_t original_lines;
+  svn_linenum_t leading_context;
+  svn_linenum_t trailing_context;
+  svn_boolean_t changed_line_seen;
   apr_pool_t *iterpool;
 
   if (apr_file_eof(patch->patch_file) == APR_EOF)
@@ -235,6 +238,9 @@
 
   in_hunk = FALSE;
   hunk_seen = FALSE;
+  leading_context = 0;
+  trailing_context = 0;
+  changed_line_seen = FALSE;
   *hunk = apr_pcalloc(result_pool, sizeof(**hunk));
 
   /* Get current seek position -- APR has no ftell() :( */
@@ -278,20 +284,28 @@
             }
 
           c = line->data[0];
-          if (original_lines > 0 && (c == ' ' || c == '-'))
+          /* Tolerate chopped leading spaces on empty lines. */
+          if (original_lines > 0 && (c == ' ' || (! eof && line->len == 0)))
             {
               hunk_seen = TRUE;
               original_lines--;
+              if (changed_line_seen)
+                trailing_context++;
+              else
+                leading_context++;
             }
-          else if (c == '+')
-            {
-              hunk_seen = TRUE;
-            }
-          /* Tolerate chopped leading spaces on empty lines. */
-          else if (original_lines > 0 && ! eof && line->len == 0)
+          else if (c == '+' || c == '-')
             {
               hunk_seen = TRUE;
-              original_lines--;
+              changed_line_seen = TRUE;
+
+              /* A hunk may have context in the middle. We only want the
+                 last lines of context. */
+              if (trailing_context > 0)
+                trailing_context = 0;
+
+              if (original_lines > 0 && c == '-')
+                original_lines--;
             }
           else
             {
@@ -363,6 +377,8 @@
       (*hunk)->diff_text = diff_text;
       (*hunk)->original_text = original_text;
       (*hunk)->modified_text = modified_text;
+      (*hunk)->leading_context = leading_context;
+      (*hunk)->trailing_context = trailing_context;
     }
   else
     /* Something went wrong, just discard the result. */

Modified: subversion/trunk/subversion/tests/cmdline/patch_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/patch_tests.py?rev=904280&r1=904279&r2=904280&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/patch_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/patch_tests.py Thu Jan 28 22:20:32 2010
@@ -1029,6 +1029,137 @@
                                        1, # check-props
                                        1) # dry-run
 
+def patch_with_fuzz(sbox):
+  "apply a patch with fuzz"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  patch_file_path = tempfile.mkstemp(dir=os.path.abspath(svntest.main.temp_dir))[1]
+
+  mu_path = os.path.join(wc_dir, 'A', 'mu')
+
+  # We have replaced a couple of lines to cause fuzz. Those lines contains
+  # the word fuzz
+  mu_contents = [
+    "Line replaced for fuzz = 1\n",
+    "\n",
+    "We wish to congratulate you over your email success in our computer\n",
+    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
+    "in which email addresses were used. All participants were selected\n",
+    "through a computer ballot system drawn from over 100,000 company\n",
+    "and 50,000,000 individual email addresses from all over the world.\n",
+    "Line replaced for fuzz = 2 with only the second context line changed\n",
+    "Your email address drew and have won the sum of  750,000 Euros\n",
+    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
+    "file with\n",
+    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
+    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
+    "    BATCH NUMBERS :\n",
+    "    EULO/1007/444/606/08;\n",
+    "    SERIAL NUMBER: 45327\n",
+    "and PROMOTION DATE: 13th June. 2009\n",
+    "\n",
+    "To claim your winning prize, you are to contact the appointed\n",
+    "agent below as soon as possible for the immediate release of your\n",
+    "winnings with the below details.\n",
+    "\n",
+    "Line replaced for fuzz = 2\n",
+    "Line replaced for fuzz = 2\n",
+  ]
+
+  # Set mu contents
+  svntest.main.file_write(mu_path, ''.join(mu_contents))
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/mu'       : Item(verb='Sending'),
+    })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/mu', wc_rev=2)
+  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
+                                      expected_status, None, wc_dir)
+
+  unidiff_patch = [
+    "Index: mu\n",
+    "===================================================================\n",
+    "--- A/mu\t(revision 0)\n",
+    "+++ A/mu\t(revision 0)\n",
+    "@@ -1,6 +1,7 @@\n",
+    " Dear internet user,\n",
+    " \n",
+    " We wish to congratulate you over your email success in our computer\n",
+    "+A new line here\n",
+    " Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
+    " in which email addresses were used. All participants were selected\n",
+    " through a computer ballot system drawn from over 100,000 company\n",
+    "@@ -7,6 +8,7 @@\n",
+    " and 50,000,000 individual email addresses from all over the world.\n",
+    " \n",
+    " Your email address drew and have won the sum of  750,000 Euros\n",
+    "+Another new line\n",
+    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
+    " file with\n",
+    "     REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
+    "@@ -19,6 +20,7 @@\n",
+    " To claim your winning prize, you are to contact the appointed\n",
+    " agent below as soon as possible for the immediate release of your\n",
+    " winnings with the below details.\n",
+    "+A third new line\n",
+    " \n",
+    " Again, we wish to congratulate you over your email success in our\n"
+    " computer Balloting.\n"
+  ]
+
+  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
+
+  mu_contents = [
+    "Line replaced for fuzz = 1\n",
+    "\n",
+    "We wish to congratulate you over your email success in our computer\n",
+    "A new line here\n",
+    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
+    "in which email addresses were used. All participants were selected\n",
+    "through a computer ballot system drawn from over 100,000 company\n",
+    "and 50,000,000 individual email addresses from all over the world.\n",
+    "Line replaced for fuzz = 2 with only the second context line changed\n",
+    "Your email address drew and have won the sum of  750,000 Euros\n",
+    "Another new line\n",
+    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
+    "file with\n",
+    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
+    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
+    "    BATCH NUMBERS :\n",
+    "    EULO/1007/444/606/08;\n",
+    "    SERIAL NUMBER: 45327\n",
+    "and PROMOTION DATE: 13th June. 2009\n",
+    "\n",
+    "To claim your winning prize, you are to contact the appointed\n",
+    "agent below as soon as possible for the immediate release of your\n",
+    "winnings with the below details.\n",
+    "A third new line\n",
+    "\n",
+    "Line replaced for fuzz = 2\n",
+    "Line replaced for fuzz = 2\n",
+  ]
+
+  expected_output = [
+    'U         %s\n' % os.path.join(wc_dir, 'A', 'mu'),
+  ]
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/mu', status='M ', wc_rev=2)
+
+  expected_skip = wc.State('', { })
+
+  svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+                                       expected_output,
+                                       expected_disk,
+                                       expected_status,
+                                       expected_skip,
+                                       None, # expected err
+                                       1, # check-props
+                                       1) # dry-run
+
 ########################################################################
 #Run the tests
 
@@ -1042,6 +1173,7 @@
               patch_add_new_dir,
               patch_reject,
               patch_keywords,
+              patch_with_fuzz,
             ]
 
 if __name__ == '__main__':