| 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: |
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.