Paste number 68771: --eol patch

Paste number 68771: --eol patch
Pasted by: pmezard
When:8 months, 2 weeks ago
Share:Tweet this! | http://paste.lisp.org/+1H2B
Channel:#mercurial
Paste contents:
Raw Source | XML | Display As
patch: add option to ignore eol in patch, expose in import as --eol

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1544,6 +1544,9 @@
     recorded in the patch. This may happen due to character set
     problems or other deficiencies in the text patch format.
 
+    Set --eol to "LF" or "CRLF" to ignore inputs end-of-lines when
+    patching. Reset patched files end-of-lines to either LF or CRLF.
+
     To read a patch from standard input, use patch name "-".
     See 'hg help dates' for a list of formats valid for -d/--date.
     """
@@ -1616,7 +1619,7 @@
                 files = {}
                 try:
                     fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
-                                       files=files)
+                                       files=files, eol=opts.get('eol'))
                 finally:
                     files = patch.updatedir(ui, repo, files)
                 if not opts.get('no_commit'):
@@ -3148,7 +3151,9 @@
           ('', 'exact', None,
            _('apply patch to the nodes from which it was generated')),
           ('', 'import-branch', None,
-           _('Use any branch information in patch (implied by --exact)'))] +
+           _('use any branch information in patch (implied by --exact)')),
+          ('', 'eol', '',
+           _('ignore inputs end of lines, force them to LF or CRLF'))] +
          commitopts + commitopts2,
          _('hg import [OPTION]... PATCH...')),
     "incoming|in":
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -223,21 +223,50 @@
 
     return (dopatch, gitpatches)
 
+class linereader:
+    # simple class to allow pushing lines back into the input stream
+    def __init__(self, fp, textmode=False):
+        self.fp = fp
+        self.buf = []
+        self.textmode = textmode
+
+    def push(self, line):
+        if line is not None:
+            self.buf.append(line)
+
+    def readline(self):
+        if self.buf:
+            l = self.buf[0]
+            del self.buf[0]
+            return l
+        l = self.fp.readline()
+        if self.textmode and l.endswith('\r\n'):
+            l = l[:-2] + '\n'
+        return l
+
+    def __iter__(self):
+        while 1:
+            l = self.readline()
+            if not l:
+                break
+            yield l
+
 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
 
 class patchfile:
-    def __init__(self, ui, fname, missing=False):
+    def __init__(self, ui, fname, missing=False, eol=None):
         self.fname = fname
         self.ui = ui
         self.lines = []
         self.exists = False
+        self.eol = eol
         self.missing = missing
         if not missing:
             try:
-                fp = file(fname, 'rb')
-                self.lines = fp.readlines()
+                lr = linereader(file(fname, 'rb'), eol is not None)
+                self.lines = list(lr)
                 self.exists = True
             except IOError:
                 pass
@@ -334,7 +363,13 @@
             fp = file(dest, 'wb')
             if st and st.st_nlink > 1:
                 os.chmod(dest, st.st_mode)
-            fp.writelines(self.lines)
+            if self.eol and self.eol != '\n':
+                for l in self.lines:
+                    if l and l[-1] == '\n':
+                        l = l[:-1] + self.eol
+                    fp.write(l)
+            else:
+                fp.writelines(self.lines)
             fp.close()
 
     def close(self):
@@ -366,6 +401,7 @@
             else:
                 self.lines[:] = h.new()
                 self.offset += len(h.new())
+                self.eol = None
                 self.dirty = 1
             return 0
 
@@ -758,30 +794,6 @@
 
     return fname, missing
 
-class linereader:
-    # simple class to allow pushing lines back into the input stream
-    def __init__(self, fp):
-        self.fp = fp
-        self.buf = []
-
-    def push(self, line):
-        if line is not None:
-            self.buf.append(line)
-
-    def readline(self):
-        if self.buf:
-            l = self.buf[0]
-            del self.buf[0]
-            return l
-        return self.fp.readline()
-
-    def __iter__(self):
-        while 1:
-            l = self.readline()
-            if not l:
-                break
-            yield l
-
 def scangitpatch(lr, firstline):
     """        
     Git patches can emit:
@@ -802,19 +814,21 @@
         fp = lr.fp
     except IOError:
         fp = cStringIO.StringIO(lr.fp.read())
-    gitlr = linereader(fp)
+    gitlr = linereader(fp, lr.textmode)
     gitlr.push(firstline)
     (dopatch, gitpatches) = readgitpatch(gitlr)
     fp.seek(pos)
     return dopatch, gitpatches
 
-def iterhunks(ui, fp, sourcefile=None):
+def iterhunks(ui, fp, sourcefile=None, textmode=False):
     """Read a patch and yield the following events:
     - ("file", afile, bfile, firsthunk): select a new target file.
     - ("hunk", hunk): a new hunk is ready to be applied, follows a
     "file" event.
     - ("git", gitchanges): current diff is in git format, gitchanges
     maps filenames to gitpatch records. Unique event.
+
+    If textmode is True, input line-endings are normalized to LF.
     """
     changed = {}
     current_hunk = None
@@ -830,7 +844,7 @@
     # our states
     BFILE = 1
     context = None
-    lr = linereader(fp)
+    lr = linereader(fp, textmode)
     dopatch = True
     # gitworkdone is True if a git operation (copy, rename, ...) was
     # performed already for the current file. Useful when the file
@@ -930,16 +944,31 @@
     if hunknum == 0 and dopatch and not gitworkdone:
         raise NoHunks
 
-def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
-    """reads a patch from fp and tries to apply it.  The dict 'changed' is
-       filled in with all of the filenames changed by the patch.  Returns 0
-       for a clean patch, -1 if any rejects were found and 1 if there was
-       any fuzz."""
+def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False, 
+              eol=None):
+    """
+    Reads a patch from fp and tries to apply it. 
 
+    The dict 'changed' is filled in with all of the filenames changed
+    by the patch. Returns 0 for a clean patch, -1 if any rejects were
+    found and 1 if there was any fuzz.
+
+    If 'eol' is set to 'crlf' or 'lf', line endings are ignored when
+    patching and forced to either '\r\n' or '\n' in the patched
+    file. 'eol' should be left to None otherwise.
+    """
     rejects = 0
     err = 0
     current_file = None
     gitpatches = None
+    if eol is not None:
+        if eol.lower() == 'crlf':
+            eol = '\r\n'
+        elif eol.lower() == 'lf':
+            eol = '\n'
+        else:
+            raise util.Abort(_('Unsupported line endings type: %s') % eol)
+    textmode = eol is not None
 
     def closefile():
         if not current_file:
@@ -947,7 +976,7 @@
         current_file.close()
         return len(current_file.rej)
 
-    for state, values in iterhunks(ui, fp, sourcefile):
+    for state, values in iterhunks(ui, fp, sourcefile, textmode):
         if state == 'hunk':
             if not current_file:
                 continue
@@ -962,11 +991,11 @@
             afile, bfile, first_hunk = values
             try:
                 if sourcefile:
-                    current_file = patchfile(ui, sourcefile)
+                    current_file = patchfile(ui, sourcefile, eol=eol)
                 else:
                     current_file, missing = selectfile(afile, bfile, first_hunk,
                                             strip, reverse)
-                    current_file = patchfile(ui, current_file, missing)
+                    current_file = patchfile(ui, current_file, missing, eol)
             except PatchError, err:
                 ui.warn(str(err) + '\n')
                 current_file, current_hunk = None, None
@@ -1082,7 +1111,7 @@
                          util.explain_exit(code)[0])
     return fuzz
 
-def internalpatch(patchobj, ui, strip, cwd, files={}):
+def internalpatch(patchobj, ui, strip, cwd, files={}, eol=None):
     """use builtin patch to apply <patchobj> to the working directory.
     returns whether patch was applied with fuzz factor."""
     try:
@@ -1093,7 +1122,7 @@
         curdir = os.getcwd()
         os.chdir(cwd)
     try:
-        ret = applydiff(ui, fp, files, strip=strip)
+        ret = applydiff(ui, fp, files, strip=strip, eol=eol)
     finally:
         if cwd:
             os.chdir(curdir)
@@ -1101,7 +1130,7 @@
         raise PatchError
     return ret > 0
 
-def patch(patchname, ui, strip=1, cwd=None, files={}):
+def patch(patchname, ui, strip=1, cwd=None, files={}, eol=None):
     """apply <patchname> to the working directory.
     returns whether patch was applied with fuzz factor."""
     patcher = ui.config('ui', 'patch')
@@ -1112,7 +1141,7 @@
                                  files)
         else:
             try:
-                return internalpatch(patchname, ui, strip, cwd, files)
+                return internalpatch(patchname, ui, strip, cwd, files, eol)
             except NoHunks:
                 patcher = util.find_exe('gpatch') or util.find_exe('patch')
                 ui.debug(_('no valid hunks found; trying with %r instead\n') %
diff --git a/tests/test-import-eol b/tests/test-import-eol
new file mode 100755
--- /dev/null
+++ b/tests/test-import-eol
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+cat > makepatch.py <<EOF
+f = file('eol.diff', 'wb')
+w = f.write
+w('test message\n')
+w('diff --git a/a b/a\n')
+w('--- a/a\n')
+w('+++ b/a\n')
+w('@@ -1,5 +1,5 @@\n')
+w(' a\n')
+w('-b\r\n')
+w('+y\r\n')
+w(' c\r\n')
+w(' d\n')
+w('-e\n')
+w('\ No newline at end of file\n')
+w('+z\r\n')
+w('\ No newline at end of file\r\n')
+EOF
+
+hg init repo
+cd repo
+echo '\.diff' > .hgignore
+
+# Test different --eol values
+python -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
+hg ci -Am adda
+python ../makepatch.py
+echo % invalid eol
+hg import --eol 'LFCR' eol.diff
+hg revert -a
+echo % force LF
+hg --traceback import --eol LF eol.diff
+python -c 'print repr(file("a","rb").read())'
+hg st
+echo % force CRLF
+hg up -C 0
+hg --traceback import --eol CRLF 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
+python -c 'file("b", "wb").write("a\x00\nc")'
+hg diff --git > bin.diff
+hg revert --no-backup b
+echo % binary patch with --eol
+hg import --eol CRLF -m changeb bin.diff
+python -c 'print repr(file("b","rb").read())'
+hg st
+cd ..
diff --git a/tests/test-import-eol.out b/tests/test-import-eol.out
new file mode 100644
--- /dev/null
+++ b/tests/test-import-eol.out
@@ -0,0 +1,15 @@
+adding .hgignore
+adding a
+% invalid eol
+applying eol.diff
+abort: Unsupported line endings type: lfcr
+% force LF
+applying eol.diff
+'a\ny\nc\nd\nz'
+% force CRLF
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying eol.diff
+'a\r\ny\r\nc\r\nd\r\nz'
+adding b
+applying bin.diff
+'a\x00\nc'

This paste has no annotations.

Colorize as:
Show Line Numbers

Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.