patch: implement patch.eol=auto mode
authorMartin Geisler <mg@lazybytes.net>
Sun, 20 Dec 2009 17:18:04 +0100
changeset 10102 1720d70cd6d4
parent 10101 155fe35534d3
child 10104 e533fc8a058b
patch: implement patch.eol=auto mode EOLs in patched files are restored to their original value after patching. We use the first EOL found in the file, files with inconsistent EOLs will thus be normalized during this process.
doc/hgrc.5.txt
mercurial/patch.py
tests/test-import-eol
tests/test-import-eol.out
--- a/doc/hgrc.5.txt	Sun Dec 20 17:18:02 2009 +0100
+++ b/doc/hgrc.5.txt	Sun Dec 20 17:18:04 2009 +0100
@@ -648,7 +648,10 @@
     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
-    normalized to either LF (Unix) or CRLF (Windows).
+    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.
     Default: strict.
 
 
--- a/mercurial/patch.py	Sun Dec 20 17:18:02 2009 +0100
+++ b/mercurial/patch.py	Sun Dec 20 17:18:04 2009 +0100
@@ -239,6 +239,7 @@
         self.fp = fp
         self.buf = []
         self.textmode = textmode
+        self.eol = None
 
     def push(self, line):
         if line is not None:
@@ -250,6 +251,11 @@
             del self.buf[0]
             return l
         l = self.fp.readline()
+        if not self.eol:
+            if l.endswith('\r\n'):
+                self.eol = '\r\n'
+            elif l.endswith('\n'):
+                self.eol = '\n'
         if self.textmode and l.endswith('\r\n'):
             l = l[:-2] + '\n'
         return l
@@ -264,13 +270,13 @@
 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
-eolmodes = ['strict', 'crlf', 'lf']
+eolmodes = ['strict', 'crlf', 'lf', 'auto']
 
 class patchfile(object):
     def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
         self.fname = fname
         self.eolmode = eolmode
-        self.eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode]
+        self.eol = None
         self.opener = opener
         self.ui = ui
         self.lines = []
@@ -298,7 +304,10 @@
             return [os.readlink(fname)]
         fp = self.opener(fname, 'r')
         try:
-            return list(linereader(fp, self.eolmode != 'strict'))
+            lr = linereader(fp, self.eolmode != 'strict')
+            lines = list(lr)
+            self.eol = lr.eol
+            return lines
         finally:
             fp.close()
 
@@ -312,10 +321,17 @@
         else:
             fp = self.opener(fname, 'w')
         try:
-            if self.eol and self.eol != '\n':
+            if self.eolmode == 'auto' and self.eol:
+                eol = self.eol
+            elif self.eolmode == 'crlf':
+                eol = '\r\n'
+            else:
+                eol = '\n'
+
+            if self.eolmode != 'strict' and eol != '\n':
                 for l in lines:
                     if l and l[-1] == '\n':
-                        l = l[:-1] + self.eol
+                        l = l[:-1] + eol
                     fp.write(l)
             else:
                 fp.writelines(lines)
--- a/tests/test-import-eol	Sun Dec 20 17:18:02 2009 +0100
+++ b/tests/test-import-eol	Sun Dec 20 17:18:04 2009 +0100
@@ -28,19 +28,35 @@
 python -c 'file("a", "wb").write("a\nbbb\ncc\n\nd\ne")'
 hg ci -Am adda
 python ../makepatch.py
+
 echo % invalid eol
 hg --config patch.eol='LFCR' import eol.diff
 hg revert -a
+
 echo % force LF
 hg --traceback --config patch.eol='LF' import eol.diff
 python -c 'print repr(file("a","rb").read())'
 hg st
+
 echo % force CRLF
 hg up -C 0
 hg --traceback --config patch.eol='CRLF' import eol.diff
 python -c 'print repr(file("a","rb").read())'
 hg st
 
+echo % auto EOL on LF file
+hg up -C 0
+hg --traceback --config patch.eol='auto' import eol.diff
+python -c 'print repr(file("a","rb").read())'
+hg st
+
+echo % auto EOL on CRLF file
+python -c 'file("a", "wb").write("a\r\nbbb\r\ncc\r\n\r\nd\r\ne")'
+hg commit -m 'switch EOLs in a'
+hg --traceback --config patch.eol='auto' import eol.diff
+python -c 'print repr(file("a","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	Sun Dec 20 17:18:02 2009 +0100
+++ b/tests/test-import-eol.out	Sun Dec 20 17:18:04 2009 +0100
@@ -10,6 +10,13 @@
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 applying eol.diff
 'a\r\nyyyy\r\ncc\r\n\r\nd\r\ne'
+% auto EOL on LF file
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying eol.diff
+'a\nyyyy\ncc\n\nd\ne'
+% auto EOL on CRLF file
+applying eol.diff
+'a\r\nyyyy\r\ncc\r\n\r\nd\r\ne'
 adding b
 % binary patch with --eol
 applying bin.diff