Hi again Stefan and Julian :-)!

On Thu, Jan 28, 2010 at 12:53:08PM +0000, Julian Foad wrote:
> > +/* Indicate in *MATCHED whether the original text of HUNK matches the patch
> > + * TARGET at its current line. Lines at FUZZ offset from start or end of 
> > the
> 
> "Lines within FUZZ lines of the start or end ..."

Fixed! For all linguistic issues I trust everyone but me.

> > + * 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)
> 
> 
> >  /* Scan lines of TARGET for a match of the original text of HUNK,
> >   * up to but not including the specified UPPER_LINE.
> > + * Use fuzz factor FUZZ everywhere if possible.
> 
> I would drop the "if possible". It implies that this function decides
> not to use the fuzz factor in some cases.

Fixed!

> >  /* 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. */
> > + * file underlying TARGET. Do not copy the lines that is within FUZZ offset
> > + * from the beginning or end of hunk unless NR_OF_LINES is set to 0. If
> > + * NR_OF_LINES is non-zero, it represents the number of lines in 
> > HUNK_TEXT. */
> >  static svn_error_t *
> >  copy_hunk_text(svn_stream_t *hunk_text, svn_stream_t *target,
> > -               const char *abspath, apr_pool_t *scratch_pool)
> > +               const char *abspath, int fuzz, int nr_of_lines,
> > +               apr_pool_t *scratch_pool)
> 
> This looks odd. Before now, the function worked without needing to be
> told the number of lines in the hunk, and now it needs to be told, but
> that means it now has two ways of knowing. Looking at the
> implementation, I can see why you want this, but from an interface point
> of view it would be better if it could still work without being told. (I
> can envisage a way of doing that - by keeping the last FUZZ lines that
> have been read in a circular buffer before writing them out, so that at
> the end of the input you can discard the FUZZ lines that are in the
> buffer.)

That's a nice solution. Maybe I'll do it later but for now I'll do it as
simple as possible.
 
> But let's say you want to keep the simple implementation that needs to
> know NR_OF_LINES in advance. Why have two ways to request non-fuzzy
> operation: the obvious way (fuzz=0) or the special way (nr_of_lines=0)?
> I don't think you need that special exception "unless NR_OF_LINES is 0".
> Can you keep the rules simple and delete that special case?
 
So obvious... If no fuzz, then  we can copy all lines. Fixed!
 
> > Index: subversion/libsvn_diff/parse-diff.c
> > ===================================================================
> > --- subversion/libsvn_diff/parse-diff.c (revision 903978)
> > +++ subversion/libsvn_diff/parse-diff.c (arbetskopia)
> > @@ -278,20 +281,45 @@
> >              }
> > 
> >            c = line->data[0];
> > -          if (original_lines > 0 && (c == ' ' || c == '-'))
> > +          if (original_lines > 0 && c == ' ' )
> >              {
> >                hunk_seen = TRUE;
> >                original_lines--;
> > +              if (changed_line_seen)
> > +                trailing_context++;
> > +              else
> > +                leading_context++;
> >              }
> > +          else if (original_lines > 0 && 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;
> > +            }
> >            else if (c == '+')
> >              {
> >                hunk_seen = TRUE;
> > +              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;
> >              }
> >            /* Tolerate chopped leading spaces on empty lines. */
> >            else if (original_lines > 0 && ! eof && line->len == 0)
> >              {
> >                hunk_seen = TRUE;
> >                original_lines--;
> > +              if (changed_line_seen)
> > +                trailing_context++;
> > +              else
> > +                leading_context++;
> >              }
> 
> This fourth block would neatly combine with the first block:
> 
> if (original_lines > 0 && (c == ' ' || (! eof && line->len == 0))
>   { ... }

Yeah, that piece of code had some duplicate code parts. I grouped the
'-' and '+' cases together too. That increased readability a bit.

And now Stefans suggestions.

On Thu, Jan 28, 2010 at 01:46:56PM +0100, Stefan Sperling wrote:
> On Thu, Jan 28, 2010 at 01:55:08PM +0100, Daniel Näslund wrote:
> > Index: subversion/libsvn_diff/parse-diff.c
> >    svn_boolean_t lines_matched;
> > @@ -630,9 +635,21 @@
> >                                             eol_str, FALSE,
> >                                             target->keywords, FALSE,
> >                                             iterpool));
> > +      lines_read++;
> > +
> > +
> You're adding 2 empty lines here on purpose?

Oups, fixed.
 
> > @@ -833,13 +853,17 @@
> >  /* 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. */
> > + * file underlying TARGET. Do not copy the lines that is within FUZZ offset
> > + * from the beginning or end of hunk unless NR_OF_LINES is set to 0. If
> > + * NR_OF_LINES is non-zero, it represents the number of lines in 
> > HUNK_TEXT. */
> 
> Maybe rename the function to copy_hunk_lines and call 'nr_of_lines'
> just 'n'?

Fixed! We're copying lines so saying that increases readability, I
think and N for number of is well established.
 
> > @@ -895,15 +936,20 @@
> > +apply_one_hunk(patch_target_t *target, hunk_info_t *hi, 
> > +               apr_pool_t *pool)
> >  {
> > +  svn_linenum_t end_line_nr;
> 
> Call it last_line ?

Fixed!

Thanks for your feedback!

A new log msg:

[[[
Fix #3460 - svn patch is not fuzzy when applying unidiffs. Refactor some
parts of the hunk parsing while at it.

* 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_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.

* subversion/libsvn_client/patch.c
  (hunk_info_t): Add field fuzz.
  (match_hunk): Add new parameter fuzz. Record nr of lines read and
    ignore the ones at the beginning and end of the hunk that is fuzzy.
    Use leading_context and trailing_context to ignore the cases when there
    isn't enough context to do fuzzy patching.
  (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 the used fuzz in hi to be used when the hunk should be
    copied to the target.
  (copy_hunk_text): Rename this to ..
  (copy_hunk_lines): .. And add n and fuzz parameter. Record
    read_lines and only copy those who are not within the fuzz limit. They
    have already been or will be copied from the target. If fuzz is zero
    we ignore n and copy all lines.
  (apply_one_hunk): Adjust lines copied to the target to include the
    context lines at the beginning of the hunk who are fuzzy. Adjust the
    current_line of the target to point to the last line of the hunk
    minus fuzz.
  (apply_one_patch): Try to call get_hunk_info() with no fuzz. If we get
    no matching line try with fuzz 1 and if that fails try with fuzz 2.

Patch by: Daniel Näslund <daniel{_AT_}longitudo.com>
Review by: julianfoad
           stsp
]]]

Index: subversion/libsvn_diff/parse-diff.c
===================================================================
--- subversion/libsvn_diff/parse-diff.c	(revision 904086)
+++ subversion/libsvn_diff/parse-diff.c	(arbetskopia)
@@ -224,6 +224,9 @@
   svn_stream_t *original_text;
   svn_stream_t *modified_text;
   svn_linenum_t original_lines;
+  svn_linenum_t leading_context  = 0;
+  svn_linenum_t trailing_context  = 0;
+  svn_boolean_t changed_line_seen  = FALSE;
   apr_pool_t *iterpool;
 
   if (apr_file_eof(patch->patch_file) == APR_EOF)
@@ -278,21 +281,29 @@
             }
 
           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 == '+')
+          else if (c == '+' || c == '-')
             {
               hunk_seen = TRUE;
+              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--;
             }
-          /* Tolerate chopped leading spaces on empty lines. */
-          else if (original_lines > 0 && ! eof && line->len == 0)
-            {
-              hunk_seen = TRUE;
-              original_lines--;
-            }
           else
             {
               in_hunk = FALSE;
@@ -363,6 +374,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. */
Index: subversion/tests/cmdline/patch_tests.py
===================================================================
--- subversion/tests/cmdline/patch_tests.py	(revision 904086)
+++ subversion/tests/cmdline/patch_tests.py	(arbetskopia)
@@ -967,7 +967,137 @@
                                        None, # expected err
                                        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
+
 def patch_keywords(sbox):
   "apply a patch containing keywords"
 
@@ -1042,6 +1172,7 @@
               patch_add_new_dir,
               patch_reject,
               patch_keywords,
+              patch_with_fuzz,
             ]
 
 if __name__ == '__main__':
Index: subversion/include/private/svn_diff_private.h
===================================================================
--- subversion/include/private/svn_diff_private.h	(revision 904086)
+++ subversion/include/private/svn_diff_private.h	(arbetskopia)
@@ -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. */
Index: subversion/libsvn_client/patch.c
===================================================================
--- subversion/libsvn_client/patch.c	(revision 904086)
+++ subversion/libsvn_client/patch.c	(arbetskopia)
@@ -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 {
@@ -588,18 +592,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 = 0;
   svn_linenum_t saved_line;
   svn_boolean_t hunk_eof;
   svn_boolean_t lines_matched;
@@ -630,9 +635,20 @@
                                            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));
 
@@ -658,7 +674,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.
@@ -670,7 +686,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;
 
@@ -684,7 +700,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;
@@ -720,14 +736,15 @@
 
 /* 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.
+ * RESULT_POOL. Always set lines at start and end of HUNK text that is within
+ * FUZZ offset to be matching. Set HI->FUZZ to FUZZ. 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.
  * 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;
@@ -748,7 +765,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. */
@@ -757,7 +774,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. */
@@ -766,7 +783,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));
             }
         }
 
@@ -778,6 +795,7 @@
   (*hi)->hunk = hunk;
   (*hi)->matched_line = matched_line;
   (*hi)->rejected = FALSE;
+  (*hi)->fuzz = fuzz;
 
   return SVN_NO_ERROR;
 }
@@ -833,13 +851,17 @@
 /* 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. */
+ * file underlying TARGET. Do not copy the lines that is within FUZZ offset
+ * from the beginning or end of hunk. N represents the number of lines in
+ * HUNK_TEXT. It is ignored if FUZZ is zero. */
 static svn_error_t *
-copy_hunk_text(svn_stream_t *hunk_text, svn_stream_t *target,
-               const char *abspath, apr_pool_t *scratch_pool)
+copy_hunk_lines(svn_stream_t *hunk_text, svn_stream_t *target,
+                const char *abspath, int fuzz, int n,
+                apr_pool_t *scratch_pool)
 {
   svn_boolean_t eof;
   apr_pool_t *iterpool;
+  int read_lines = 0;
 
   iterpool = svn_pool_create(scratch_pool);
   do
@@ -851,7 +873,9 @@
 
       SVN_ERR(svn_stream_readline_detect_eol(hunk_text, &line, &eol_str,
                                              &eof, iterpool));
-      if (! eof)
+      read_lines++;
+
+      if (! eof && fuzz == 0)
         {
           if (line->len >= 1)
             SVN_ERR(try_stream_write(target, abspath, line->data, line->len,
@@ -860,6 +884,20 @@
             SVN_ERR(try_stream_write(target, abspath, eol_str, strlen(eol_str),
                                      iterpool));
         }
+
+      /* We only want to copy the lines without fuzz. Fuzz means that the
+       * target is changed on those lines. We should use the targets
+       * version then. */
+      else if (! eof && (((read_lines > fuzz) && (read_lines <= n - fuzz))
+                         || n == 0)) 
+        {
+          if (line->len >= 1)
+            SVN_ERR(try_stream_write(target, abspath, line->data, line->len,
+                                     iterpool));
+          if (eol_str)
+            SVN_ERR(try_stream_write(target, abspath, eol_str, strlen(eol_str),
+                                     iterpool));
+        }
     }
   while (! eof);
   svn_pool_destroy(iterpool);
@@ -886,8 +924,10 @@
   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));
+  SVN_ERR(copy_hunk_lines(hi->hunk->diff_text, target->reject,
+                          target->reject_path, 0,
+                          0, /* only needed if fuzz > 0 */
+                          pool));
 
   target->had_rejects = TRUE;
   hi->rejected = TRUE;
@@ -895,15 +935,20 @@
   return SVN_NO_ERROR;
 }
 
-/* Apply a hunk described by hunk info HI to a patch TARGET.
+/* Apply a hunk described by hunk info HI to a patch TARGET. If we have FUZZ
+ * use the lines from the target for those lines instead of the hunk lines.
  * Do all allocations in POOL. */
 static svn_error_t *
-apply_one_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
+apply_one_hunk(patch_target_t *target, hunk_info_t *hi, 
+               apr_pool_t *pool)
 {
+  svn_linenum_t last_line;
+
   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));
+      /* Move forward to the hunk's line, copying data as we go. We want to
+       * use the targets version for the fuzzy lines of the hunk. */
+      SVN_ERR(copy_lines_to_target(target, hi->matched_line + hi->fuzz, pool));
       if (target->eof)
         {
           /* File is shorter than it should be. */
@@ -912,14 +957,17 @@
         }
 
       /* Skip the target's version of the hunk. */
+      last_line = target->current_line + 
+        hi->hunk->original_length - (2 * hi->fuzz);
       SVN_ERR(seek_to_line(target,
-                           target->current_line + hi->hunk->original_length,
+                           last_line,
                            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));
+  SVN_ERR(copy_hunk_lines(hi->hunk->modified_text, target->patched,
+                          target->patched_path, hi->fuzz, 
+                          hi->hunk->modified_length, pool));
 
   return SVN_NO_ERROR;
 }
@@ -1026,6 +1074,7 @@
   apr_finfo_t working_file;
   apr_finfo_t patched_file;
   int i;
+  int MAX_FUZZ = 2;
 
   SVN_ERR(init_patch_target(&target, patch, abs_wc_path, ctx, strip_count,
                             pool, pool));
@@ -1044,13 +1093,21 @@
     {
       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));
+      /* Determine the line the hunk should be applied at. If no match is
+       * found initially, try with fuzz. */
+      do 
+        {
+          SVN_ERR(get_hunk_info(&hi, target, hunk, fuzz, pool, iterpool));
+          fuzz++;
+        }
+      while (hi->matched_line == 0 && fuzz <= MAX_FUZZ);
+
       if (hi->matched_line == 0)
         {
           /* The hunk does not apply, reject it. */

Reply via email to