[issue1941] test case, possible fix for built-in patch

Patrick Mézard pmezard at gmail.com
Wed Dec 23 17:04:09 CST 2009


Le 10/12/09 02:14, Greg Onufer a écrit :
> This is regarding http://mercurial.selenic.com/bts/issue1941
> 
> Summary: The built-in patch implementation applies the hunks to the
> wrong lines of the file if the file in the repo has been modified to
> skew the patch line numbers and the file contains repetitive sequences
> of lines.
> 
> In my case I added a 41-line copyright/license header to the file and
> then imported a changeset from a related repo.  The result was not
> what was expected.  Old GNU patch (2.5.4, iirc) did the same thing
> (but with a scary warning), current versions (I used 2.5.8 and 2.5.9)
> of GNU patch handled it without any difficulty.
> 
> Attached are three changesets.
> 
> The first changeset modifies the patching code to warn about
> mis-ordered hunks (like GNU patch did when I used an old GNU patch)
> since that warns the user that something went horribly wrong.  For the
> case where I originally encountered this problem, the new output is:
> 
>     ...
>     Hunk #10 succeeded at 1545 (offset 41 lines).
>     Hunk #11 succeeded at 1762 (offset 41 lines).
>     misordered hunks! output would be garbled
>     Hunk #12 succeeded at 1703 (offset -26 lines).
>     Hunk #13 succeeded at 1711 (offset -26 lines).
>     ...
> 
> The second changeset modifies the built-in patch implementation to
> keep track of the offset where hunks are applied on a running basis so
> as to make a better guess at where the hunk should be applied.  This
> results in:
> 
>     ...
>     Hunk #10 succeeded at 1545 (offset 41 lines).
>     Hunk #11 succeeded at 1762 (offset 41 lines).
>     Hunk #12 succeeded at 1770 (offset 41 lines).
>     Hunk #13 succeeded at 1778 (offset 41 lines).
>     ...
> 
> Which is exactly the same as current versions of GNU patch-- both the
> patch command status output as well as resulting patched file.
> 
> The last changeset adds a test case which is a simpler version of my
> actual problematic files.   The test case works with Hg 1.4.1 if a
> recent version of GNU patch is used as an external patch program.

First, thanks for the excellent report, analysis and patches.

I missed the patch because it was prefixed like an issue notification and patches usually come with a [PATCH] prefix. If you can, try to use the patchbomb extension to contribute, it handles these things automatically, and inline patches by default which is easier for reviews.

I have folded patch 3 into 1 and pushed it in stable:

    http://hg.intevation.org/mercurial/crew/rev/9a4034b630c4

Here is an annotated version of patch 2:

> diff --git a/mercurial/patch.py b/mercurial/patch.py
> --- a/mercurial/patch.py
> +++ b/mercurial/patch.py
> @@ -286,6 +286,7 @@
>          self.hash = {}
>          self.dirty = 0
>          self.offset = 0
> +        self.lasthunk = 0
>          self.rej = []
>          self.fileprinted = False
>          self.printfile(False)
> @@ -427,9 +428,12 @@
>              if h.rmfile():
>                  self.unlink(self.fname)
>              else:
> +                if start < self.lasthunk:
> +                    self.ui.warn(_("misordered hunks! output would be garbled\n"))
>                  self.lines[start : start + h.lena] = h.new()
>                  self.offset += h.lenb - h.lena
>                  self.dirty = 1
> +                self.lasthunk = start + h.lena
>              return 0
>  
>          # ok, we couldn't match the hunk.  Lets look for offsets and fuzz it
> @@ -448,7 +452,10 @@
>                  cand = self.findlines(old[0][1:], search_start)
>                  for l in cand:
>                      if diffhelpers.testhunk(old, self.lines, l) == 0:
> +                        if l < self.lasthunk:
> +                            self.ui.warn(_("misordered hunks! output would be garbled\n"))
>                          newlines = h.new(fuzzlen, toponly)
> +                        self.lasthunk = l

Did you mean:

+                        self.lasthunk = l + len(newlines) - len(old)

here ?

>                          self.lines[l : l + len(old)] = newlines
>                          self.offset += len(newlines) - len(old)
>                          self.dirty = 1


It would be great to have a reliable test for this, appended to test-patch-offset. It could be done by switching 2 hunks of an existing diff manually. Can you resubmit patch 2 with such a test?

--
Patrick Mézard


More information about the Mercurial-devel mailing list