patch: fix eolmode=auto with new files
authorPatrick Mezard <pmezard@gmail.com>
Wed, 23 Dec 2009 19:31:47 +0100
changeset 10127 d8214e944b84
parent 10126 78b8acae2088
child 10128 ea7c392f2b08
patch: fix eolmode=auto with new files If target file does not exist or has no eol, current code was normalizing eols to LF. Preserve patch file eols instead.
doc/hgrc.5.txt
mercurial/patch.py
tests/test-import-eol
tests/test-import-eol.out
--- a/doc/hgrc.5.txt	Wed Dec 23 19:18:03 2009 +0100
+++ b/doc/hgrc.5.txt	Wed Dec 23 19:31:47 2009 +0100
@@ -646,12 +646,13 @@
 
 ``eol``
     When set to 'strict' patch content and patched files end of lines
-    are preserved. When set to ``lf`` or ``crlf``, both files end of lines
-    are ignored when patching and the result line endings are
+    are preserved. When set to ``lf`` or ``crlf``, both files end of
+    lines are ignored when patching and the result line endings are
     normalized to either LF (Unix) or CRLF (Windows). When set to
     ``auto``, end of lines are again ignored while patching but line
     endings in patched files are normalized to their original setting
-    on a per-file basis.
+    on a per-file basis. If target file does not exist or has no end
+    of line, patch line endings are preserved.
     Default: strict.
 
 
--- a/mercurial/patch.py	Wed Dec 23 19:18:03 2009 +0100
+++ b/mercurial/patch.py	Wed Dec 23 19:31:47 2009 +0100
@@ -321,14 +321,14 @@
         else:
             fp = self.opener(fname, 'w')
         try:
-            if self.eolmode == 'auto' and self.eol:
+            if self.eolmode == 'auto':
                 eol = self.eol
             elif self.eolmode == 'crlf':
                 eol = '\r\n'
             else:
                 eol = '\n'
 
-            if self.eolmode != 'strict' and eol != '\n':
+            if self.eolmode != 'strict' and eol and eol != '\n':
                 for l in lines:
                     if l and l[-1] == '\n':
                         l = l[:-1] + eol
@@ -433,6 +433,15 @@
                 self.dirty = 1
             return 0
 
+        horig = h
+        if self.eolmode == 'auto' and self.eol:
+            # If eolmode == 'auto' and target file exists and has line
+            # endings we have to normalize input data before patching.
+            # Otherwise, patchfile operates in 'strict' mode. If
+            # eolmode is set to 'crlf' or 'lf', input hunk is already
+            # normalized to avoid data copy.
+            h = h.getnormalized()
+
         # fast case first, no offsets, no fuzz
         old = h.old()
         # patch starts counting at 1 unless we are adding the file
@@ -488,7 +497,7 @@
                         return fuzzlen
         self.printfile(True)
         self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
-        self.rej.append(h)
+        self.rej.append(horig)
         return -1
 
 class hunk(object):
@@ -500,13 +509,39 @@
         self.b = []
         self.starta = self.lena = None
         self.startb = self.lenb = None
-        if context:
-            self.read_context_hunk(lr)
-        else:
-            self.read_unified_hunk(lr)
+        if lr is not None:
+            if context:
+                self.read_context_hunk(lr)
+            else:
+                self.read_unified_hunk(lr)
         self.create = create
         self.remove = remove and not create
 
+    def getnormalized(self):
+        """Return a copy with line endings normalized to LF."""
+
+        def normalize(lines):
+            nlines = []
+            for line in lines:
+                if line.endswith('\r\n'):
+                    line = line[:-2] + '\n'
+                nlines.append(line)
+            return nlines
+
+        # Dummy object, it is rebuilt manually
+        nh = hunk(self.desc, self.number, None, None, False, False)
+        nh.number = self.number
+        nh.desc = self.desc
+        nh.a = normalize(self.a)
+        nh.b = normalize(self.b)
+        nh.starta = self.starta
+        nh.startb = self.startb
+        nh.lena = self.lena
+        nh.lenb = self.lenb
+        nh.create = self.create
+        nh.remove = self.remove
+        return nh
+
     def read_unified_hunk(self, lr):
         m = unidesc.match(self.desc)
         if not m:
@@ -974,7 +1009,10 @@
     current_file = None
     gitpatches = None
     opener = util.opener(os.getcwd())
-    textmode = eolmode != 'strict'
+    # In 'auto' mode, we must preserve original eols if target file
+    # eols are undefined. Otherwise, hunk data will be normalized
+    # later.
+    textmode = eolmode not in ('strict', 'auto')
 
     def closefile():
         if not current_file:
--- a/tests/test-import-eol	Wed Dec 23 19:18:03 2009 +0100
+++ b/tests/test-import-eol	Wed Dec 23 19:31:47 2009 +0100
@@ -57,6 +57,21 @@
 python -c 'print repr(file("a","rb").read())'
 hg st
 
+echo % auto EOL on new file or source without any EOL
+python -c 'file("noeol", "wb").write("noeol")'
+hg add noeol
+hg commit -m 'add noeol'
+python -c 'file("noeol", "wb").write("noeol\r\nnoeol\n")'
+python -c 'file("neweol", "wb").write("neweol\nneweol\r\n")'
+hg add neweol
+hg diff --git > noeol.diff
+hg revert --no-backup noeol neweol
+rm neweol
+hg --traceback --config patch.eol='auto' import -m noeol noeol.diff
+python -c 'print repr(file("noeol","rb").read())'
+python -c 'print repr(file("neweol","rb").read())'
+hg st
+
 # Test --eol and binary patches
 python -c 'file("b", "wb").write("a\x00\nb")'
 hg ci -Am addb
--- a/tests/test-import-eol.out	Wed Dec 23 19:18:03 2009 +0100
+++ b/tests/test-import-eol.out	Wed Dec 23 19:31:47 2009 +0100
@@ -17,6 +17,10 @@
 % auto EOL on CRLF file
 applying eol.diff
 'a\r\nyyyy\r\ncc\r\n\r\nd\r\ne'
+% auto EOL on new file or source without any EOL
+applying noeol.diff
+'noeol\r\nnoeol\n'
+'neweol\nneweol\r\n'
 adding b
 % binary patch with --eol
 applying bin.diff