##// END OF EJS Templates
Add patch.eol to ignore EOLs when patching (issue1019)...
Patrick Mezard -
r8810:ac92775b default
parent child Browse files
Show More
@@ -0,0 +1,53 b''
1 #!/bin/sh
2
3 cat > makepatch.py <<EOF
4 f = file('eol.diff', 'wb')
5 w = f.write
6 w('test message\n')
7 w('diff --git a/a b/a\n')
8 w('--- a/a\n')
9 w('+++ b/a\n')
10 w('@@ -1,5 +1,5 @@\n')
11 w(' a\n')
12 w('-b\r\n')
13 w('+y\r\n')
14 w(' c\r\n')
15 w(' d\n')
16 w('-e\n')
17 w('\ No newline at end of file\n')
18 w('+z\r\n')
19 w('\ No newline at end of file\r\n')
20 EOF
21
22 hg init repo
23 cd repo
24 echo '\.diff' > .hgignore
25
26 # Test different --eol values
27 python -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
28 hg ci -Am adda
29 python ../makepatch.py
30 echo % invalid eol
31 hg --config patch.eol='LFCR' import eol.diff
32 hg revert -a
33 echo % force LF
34 hg --traceback --config patch.eol='LF' import eol.diff
35 python -c 'print repr(file("a","rb").read())'
36 hg st
37 echo % force CRLF
38 hg up -C 0
39 hg --traceback --config patch.eol='CRLF' import eol.diff
40 python -c 'print repr(file("a","rb").read())'
41 hg st
42
43 # Test --eol and binary patches
44 python -c 'file("b", "wb").write("a\x00\nb")'
45 hg ci -Am addb
46 python -c 'file("b", "wb").write("a\x00\nc")'
47 hg diff --git > bin.diff
48 hg revert --no-backup b
49 echo % binary patch with --eol
50 hg import --config patch.eol='CRLF' -m changeb bin.diff
51 python -c 'print repr(file("b","rb").read())'
52 hg st
53 cd ..
@@ -0,0 +1,16 b''
1 adding .hgignore
2 adding a
3 % invalid eol
4 applying eol.diff
5 abort: Unsupported line endings type: LFCR
6 % force LF
7 applying eol.diff
8 'a\ny\nc\nd\nz'
9 % force CRLF
10 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 applying eol.diff
12 'a\r\ny\r\nc\r\nd\r\nz'
13 adding b
14 % binary patch with --eol
15 applying bin.diff
16 'a\x00\nc'
@@ -607,6 +607,17 b' smtp::'
607 Optional. It's the hostname that the sender can use to identify
607 Optional. It's the hostname that the sender can use to identify
608 itself to the MTA.
608 itself to the MTA.
609
609
610 [[patch]]
611 patch::
612 Settings used when applying patches, for instance through the 'import'
613 command or with Mercurial Queues extension.
614 eol;;
615 When set to 'strict' patch content and patched files end of lines
616 are preserved. When set to 'lf' or 'crlf', both files end of lines
617 are ignored when patching and the result line endings are
618 normalized to either LF (Unix) or CRLF (Windows).
619 Default: strict.
620
610 [[paths]]
621 [[paths]]
611 paths::
622 paths::
612 Assigns symbolic names to repositories. The left side is the
623 Assigns symbolic names to repositories. The left side is the
@@ -485,10 +485,10 b' def reposetup(ui, repo):'
485 release(lock, wlock)
485 release(lock, wlock)
486
486
487 # monkeypatches
487 # monkeypatches
488 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False):
488 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False, eol=None):
489 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
489 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
490 rejects or conflicts due to expanded keywords in working dir.'''
490 rejects or conflicts due to expanded keywords in working dir.'''
491 orig(self, ui, fname, opener, missing)
491 orig(self, ui, fname, opener, missing, eol)
492 # shrink keywords read from working dir
492 # shrink keywords read from working dir
493 self.lines = kwt.shrinklines(self.fname, self.lines)
493 self.lines = kwt.shrinklines(self.fname, self.lines)
494
494
@@ -1764,7 +1764,7 b' def import_(ui, repo, patch1, *patches, '
1764 files = {}
1764 files = {}
1765 try:
1765 try:
1766 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1766 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1767 files=files)
1767 files=files, eolmode=None)
1768 finally:
1768 finally:
1769 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1769 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1770 if not opts.get('no_commit'):
1770 if not opts.get('no_commit'):
@@ -228,13 +228,42 b' def readgitpatch(lr):'
228
228
229 return (dopatch, gitpatches)
229 return (dopatch, gitpatches)
230
230
231 class linereader:
232 # simple class to allow pushing lines back into the input stream
233 def __init__(self, fp, textmode=False):
234 self.fp = fp
235 self.buf = []
236 self.textmode = textmode
237
238 def push(self, line):
239 if line is not None:
240 self.buf.append(line)
241
242 def readline(self):
243 if self.buf:
244 l = self.buf[0]
245 del self.buf[0]
246 return l
247 l = self.fp.readline()
248 if self.textmode and l.endswith('\r\n'):
249 l = l[:-2] + '\n'
250 return l
251
252 def __iter__(self):
253 while 1:
254 l = self.readline()
255 if not l:
256 break
257 yield l
258
231 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
259 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
232 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
260 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
233 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
261 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
234
262
235 class patchfile(object):
263 class patchfile(object):
236 def __init__(self, ui, fname, opener, missing=False):
264 def __init__(self, ui, fname, opener, missing=False, eol=None):
237 self.fname = fname
265 self.fname = fname
266 self.eol = eol
238 self.opener = opener
267 self.opener = opener
239 self.ui = ui
268 self.ui = ui
240 self.lines = []
269 self.lines = []
@@ -260,14 +289,20 b' class patchfile(object):'
260 def readlines(self, fname):
289 def readlines(self, fname):
261 fp = self.opener(fname, 'r')
290 fp = self.opener(fname, 'r')
262 try:
291 try:
263 return fp.readlines()
292 return list(linereader(fp, self.eol is not None))
264 finally:
293 finally:
265 fp.close()
294 fp.close()
266
295
267 def writelines(self, fname, lines):
296 def writelines(self, fname, lines):
268 fp = self.opener(fname, 'w')
297 fp = self.opener(fname, 'w')
269 try:
298 try:
270 fp.writelines(lines)
299 if self.eol and self.eol != '\n':
300 for l in lines:
301 if l and l[-1] == '\n':
302 l = l[:1] + self.eol
303 fp.write(l)
304 else:
305 fp.writelines(lines)
271 finally:
306 finally:
272 fp.close()
307 fp.close()
273
308
@@ -782,28 +817,6 b' def selectfile(afile_orig, bfile_orig, h'
782
817
783 return fname, missing
818 return fname, missing
784
819
785 class linereader(object):
786 # simple class to allow pushing lines back into the input stream
787 def __init__(self, fp):
788 self.fp = fp
789 self.buf = []
790
791 def push(self, line):
792 if line is not None:
793 self.buf.append(line)
794
795 def readline(self):
796 if self.buf:
797 return self.buf.pop(0)
798 return self.fp.readline()
799
800 def __iter__(self):
801 while 1:
802 l = self.readline()
803 if not l:
804 break
805 yield l
806
807 def scangitpatch(lr, firstline):
820 def scangitpatch(lr, firstline):
808 """
821 """
809 Git patches can emit:
822 Git patches can emit:
@@ -824,19 +837,21 b' def scangitpatch(lr, firstline):'
824 fp = lr.fp
837 fp = lr.fp
825 except IOError:
838 except IOError:
826 fp = cStringIO.StringIO(lr.fp.read())
839 fp = cStringIO.StringIO(lr.fp.read())
827 gitlr = linereader(fp)
840 gitlr = linereader(fp, lr.textmode)
828 gitlr.push(firstline)
841 gitlr.push(firstline)
829 (dopatch, gitpatches) = readgitpatch(gitlr)
842 (dopatch, gitpatches) = readgitpatch(gitlr)
830 fp.seek(pos)
843 fp.seek(pos)
831 return dopatch, gitpatches
844 return dopatch, gitpatches
832
845
833 def iterhunks(ui, fp, sourcefile=None):
846 def iterhunks(ui, fp, sourcefile=None, textmode=False):
834 """Read a patch and yield the following events:
847 """Read a patch and yield the following events:
835 - ("file", afile, bfile, firsthunk): select a new target file.
848 - ("file", afile, bfile, firsthunk): select a new target file.
836 - ("hunk", hunk): a new hunk is ready to be applied, follows a
849 - ("hunk", hunk): a new hunk is ready to be applied, follows a
837 "file" event.
850 "file" event.
838 - ("git", gitchanges): current diff is in git format, gitchanges
851 - ("git", gitchanges): current diff is in git format, gitchanges
839 maps filenames to gitpatch records. Unique event.
852 maps filenames to gitpatch records. Unique event.
853
854 If textmode is True, input line-endings are normalized to LF.
840 """
855 """
841 changed = {}
856 changed = {}
842 current_hunk = None
857 current_hunk = None
@@ -850,7 +865,7 b' def iterhunks(ui, fp, sourcefile=None):'
850 # our states
865 # our states
851 BFILE = 1
866 BFILE = 1
852 context = None
867 context = None
853 lr = linereader(fp)
868 lr = linereader(fp, textmode)
854 dopatch = True
869 dopatch = True
855 # gitworkdone is True if a git operation (copy, rename, ...) was
870 # gitworkdone is True if a git operation (copy, rename, ...) was
856 # performed already for the current file. Useful when the file
871 # performed already for the current file. Useful when the file
@@ -954,17 +969,25 b' def iterhunks(ui, fp, sourcefile=None):'
954 if hunknum == 0 and dopatch and not gitworkdone:
969 if hunknum == 0 and dopatch and not gitworkdone:
955 raise NoHunks
970 raise NoHunks
956
971
957 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
972 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
958 """reads a patch from fp and tries to apply it. The dict 'changed' is
973 eol=None):
959 filled in with all of the filenames changed by the patch. Returns 0
974 """
960 for a clean patch, -1 if any rejects were found and 1 if there was
975 Reads a patch from fp and tries to apply it.
961 any fuzz."""
962
976
977 The dict 'changed' is filled in with all of the filenames changed
978 by the patch. Returns 0 for a clean patch, -1 if any rejects were
979 found and 1 if there was any fuzz.
980
981 If 'eol' is None, the patch content and patched file are read in
982 binary mode. Otherwise, line endings are ignored when patching then
983 normalized to 'eol' (usually '\n' or \r\n').
984 """
963 rejects = 0
985 rejects = 0
964 err = 0
986 err = 0
965 current_file = None
987 current_file = None
966 gitpatches = None
988 gitpatches = None
967 opener = util.opener(os.getcwd())
989 opener = util.opener(os.getcwd())
990 textmode = eol is not None
968
991
969 def closefile():
992 def closefile():
970 if not current_file:
993 if not current_file:
@@ -972,7 +995,7 b' def applydiff(ui, fp, changed, strip=1, '
972 current_file.close()
995 current_file.close()
973 return len(current_file.rej)
996 return len(current_file.rej)
974
997
975 for state, values in iterhunks(ui, fp, sourcefile):
998 for state, values in iterhunks(ui, fp, sourcefile, textmode):
976 if state == 'hunk':
999 if state == 'hunk':
977 if not current_file:
1000 if not current_file:
978 continue
1001 continue
@@ -987,11 +1010,11 b' def applydiff(ui, fp, changed, strip=1, '
987 afile, bfile, first_hunk = values
1010 afile, bfile, first_hunk = values
988 try:
1011 try:
989 if sourcefile:
1012 if sourcefile:
990 current_file = patchfile(ui, sourcefile, opener)
1013 current_file = patchfile(ui, sourcefile, opener, eol=eol)
991 else:
1014 else:
992 current_file, missing = selectfile(afile, bfile, first_hunk,
1015 current_file, missing = selectfile(afile, bfile, first_hunk,
993 strip, reverse)
1016 strip, reverse)
994 current_file = patchfile(ui, current_file, opener, missing)
1017 current_file = patchfile(ui, current_file, opener, missing, eol)
995 except PatchError, err:
1018 except PatchError, err:
996 ui.warn(str(err) + '\n')
1019 ui.warn(str(err) + '\n')
997 current_file, current_hunk = None, None
1020 current_file, current_hunk = None, None
@@ -1104,9 +1127,17 b' def externalpatch(patcher, args, patchna'
1104 util.explain_exit(code)[0])
1127 util.explain_exit(code)[0])
1105 return fuzz
1128 return fuzz
1106
1129
1107 def internalpatch(patchobj, ui, strip, cwd, files={}):
1130 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
1108 """use builtin patch to apply <patchobj> to the working directory.
1131 """use builtin patch to apply <patchobj> to the working directory.
1109 returns whether patch was applied with fuzz factor."""
1132 returns whether patch was applied with fuzz factor."""
1133
1134 if eolmode is None:
1135 eolmode = ui.config('patch', 'eol', 'strict')
1136 try:
1137 eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
1138 except KeyError:
1139 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1140
1110 try:
1141 try:
1111 fp = file(patchobj, 'rb')
1142 fp = file(patchobj, 'rb')
1112 except TypeError:
1143 except TypeError:
@@ -1115,7 +1146,7 b' def internalpatch(patchobj, ui, strip, c'
1115 curdir = os.getcwd()
1146 curdir = os.getcwd()
1116 os.chdir(cwd)
1147 os.chdir(cwd)
1117 try:
1148 try:
1118 ret = applydiff(ui, fp, files, strip=strip)
1149 ret = applydiff(ui, fp, files, strip=strip, eol=eol)
1119 finally:
1150 finally:
1120 if cwd:
1151 if cwd:
1121 os.chdir(curdir)
1152 os.chdir(curdir)
@@ -1123,9 +1154,18 b' def internalpatch(patchobj, ui, strip, c'
1123 raise PatchError
1154 raise PatchError
1124 return ret > 0
1155 return ret > 0
1125
1156
1126 def patch(patchname, ui, strip=1, cwd=None, files={}):
1157 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
1127 """apply <patchname> to the working directory.
1158 """Apply <patchname> to the working directory.
1128 returns whether patch was applied with fuzz factor."""
1159
1160 'eolmode' specifies how end of lines should be handled. It can be:
1161 - 'strict': inputs are read in binary mode, EOLs are preserved
1162 - 'crlf': EOLs are ignored when patching and reset to CRLF
1163 - 'lf': EOLs are ignored when patching and reset to LF
1164 - None: get it from user settings, default to 'strict'
1165 'eolmode' is ignored when using an external patcher program.
1166
1167 Returns whether patch was applied with fuzz factor.
1168 """
1129 patcher = ui.config('ui', 'patch')
1169 patcher = ui.config('ui', 'patch')
1130 args = []
1170 args = []
1131 try:
1171 try:
@@ -1134,7 +1174,7 b' def patch(patchname, ui, strip=1, cwd=No'
1134 files)
1174 files)
1135 else:
1175 else:
1136 try:
1176 try:
1137 return internalpatch(patchname, ui, strip, cwd, files)
1177 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1138 except NoHunks:
1178 except NoHunks:
1139 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1179 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1140 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1180 ui.debug(_('no valid hunks found; trying with %r instead\n') %
General Comments 0
You need to be logged in to leave comments. Login now