##// 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 607 Optional. It's the hostname that the sender can use to identify
608 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 621 [[paths]]
611 622 paths::
612 623 Assigns symbolic names to repositories. The left side is the
@@ -485,10 +485,10 b' def reposetup(ui, repo):'
485 485 release(lock, wlock)
486 486
487 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 489 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
490 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 492 # shrink keywords read from working dir
493 493 self.lines = kwt.shrinklines(self.fname, self.lines)
494 494
@@ -1764,7 +1764,7 b' def import_(ui, repo, patch1, *patches, '
1764 1764 files = {}
1765 1765 try:
1766 1766 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1767 files=files)
1767 files=files, eolmode=None)
1768 1768 finally:
1769 1769 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1770 1770 if not opts.get('no_commit'):
@@ -228,13 +228,42 b' def readgitpatch(lr):'
228 228
229 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 259 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
232 260 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
233 261 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
234 262
235 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 265 self.fname = fname
266 self.eol = eol
238 267 self.opener = opener
239 268 self.ui = ui
240 269 self.lines = []
@@ -260,14 +289,20 b' class patchfile(object):'
260 289 def readlines(self, fname):
261 290 fp = self.opener(fname, 'r')
262 291 try:
263 return fp.readlines()
292 return list(linereader(fp, self.eol is not None))
264 293 finally:
265 294 fp.close()
266 295
267 296 def writelines(self, fname, lines):
268 297 fp = self.opener(fname, 'w')
269 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 306 finally:
272 307 fp.close()
273 308
@@ -782,28 +817,6 b' def selectfile(afile_orig, bfile_orig, h'
782 817
783 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 820 def scangitpatch(lr, firstline):
808 821 """
809 822 Git patches can emit:
@@ -824,19 +837,21 b' def scangitpatch(lr, firstline):'
824 837 fp = lr.fp
825 838 except IOError:
826 839 fp = cStringIO.StringIO(lr.fp.read())
827 gitlr = linereader(fp)
840 gitlr = linereader(fp, lr.textmode)
828 841 gitlr.push(firstline)
829 842 (dopatch, gitpatches) = readgitpatch(gitlr)
830 843 fp.seek(pos)
831 844 return dopatch, gitpatches
832 845
833 def iterhunks(ui, fp, sourcefile=None):
846 def iterhunks(ui, fp, sourcefile=None, textmode=False):
834 847 """Read a patch and yield the following events:
835 848 - ("file", afile, bfile, firsthunk): select a new target file.
836 849 - ("hunk", hunk): a new hunk is ready to be applied, follows a
837 850 "file" event.
838 851 - ("git", gitchanges): current diff is in git format, gitchanges
839 852 maps filenames to gitpatch records. Unique event.
853
854 If textmode is True, input line-endings are normalized to LF.
840 855 """
841 856 changed = {}
842 857 current_hunk = None
@@ -850,7 +865,7 b' def iterhunks(ui, fp, sourcefile=None):'
850 865 # our states
851 866 BFILE = 1
852 867 context = None
853 lr = linereader(fp)
868 lr = linereader(fp, textmode)
854 869 dopatch = True
855 870 # gitworkdone is True if a git operation (copy, rename, ...) was
856 871 # performed already for the current file. Useful when the file
@@ -954,17 +969,25 b' def iterhunks(ui, fp, sourcefile=None):'
954 969 if hunknum == 0 and dopatch and not gitworkdone:
955 970 raise NoHunks
956 971
957 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
959 filled in with all of the filenames changed by the patch. Returns 0
960 for a clean patch, -1 if any rejects were found and 1 if there was
961 any fuzz."""
972 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
973 eol=None):
974 """
975 Reads a patch from fp and tries to apply it.
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 985 rejects = 0
964 986 err = 0
965 987 current_file = None
966 988 gitpatches = None
967 989 opener = util.opener(os.getcwd())
990 textmode = eol is not None
968 991
969 992 def closefile():
970 993 if not current_file:
@@ -972,7 +995,7 b' def applydiff(ui, fp, changed, strip=1, '
972 995 current_file.close()
973 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 999 if state == 'hunk':
977 1000 if not current_file:
978 1001 continue
@@ -987,11 +1010,11 b' def applydiff(ui, fp, changed, strip=1, '
987 1010 afile, bfile, first_hunk = values
988 1011 try:
989 1012 if sourcefile:
990 current_file = patchfile(ui, sourcefile, opener)
1013 current_file = patchfile(ui, sourcefile, opener, eol=eol)
991 1014 else:
992 1015 current_file, missing = selectfile(afile, bfile, first_hunk,
993 1016 strip, reverse)
994 current_file = patchfile(ui, current_file, opener, missing)
1017 current_file = patchfile(ui, current_file, opener, missing, eol)
995 1018 except PatchError, err:
996 1019 ui.warn(str(err) + '\n')
997 1020 current_file, current_hunk = None, None
@@ -1104,9 +1127,17 b' def externalpatch(patcher, args, patchna'
1104 1127 util.explain_exit(code)[0])
1105 1128 return fuzz
1106 1129
1107 def internalpatch(patchobj, ui, strip, cwd, files={}):
1130 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
1108 1131 """use builtin patch to apply <patchobj> to the working directory.
1109 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 1141 try:
1111 1142 fp = file(patchobj, 'rb')
1112 1143 except TypeError:
@@ -1115,7 +1146,7 b' def internalpatch(patchobj, ui, strip, c'
1115 1146 curdir = os.getcwd()
1116 1147 os.chdir(cwd)
1117 1148 try:
1118 ret = applydiff(ui, fp, files, strip=strip)
1149 ret = applydiff(ui, fp, files, strip=strip, eol=eol)
1119 1150 finally:
1120 1151 if cwd:
1121 1152 os.chdir(curdir)
@@ -1123,9 +1154,18 b' def internalpatch(patchobj, ui, strip, c'
1123 1154 raise PatchError
1124 1155 return ret > 0
1125 1156
1126 def patch(patchname, ui, strip=1, cwd=None, files={}):
1127 """apply <patchname> to the working directory.
1128 returns whether patch was applied with fuzz factor."""
1157 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
1158 """Apply <patchname> to the working directory.
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 1169 patcher = ui.config('ui', 'patch')
1130 1170 args = []
1131 1171 try:
@@ -1134,7 +1174,7 b' def patch(patchname, ui, strip=1, cwd=No'
1134 1174 files)
1135 1175 else:
1136 1176 try:
1137 return internalpatch(patchname, ui, strip, cwd, files)
1177 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1138 1178 except NoHunks:
1139 1179 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1140 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