##// END OF EJS Templates
patch: drop eol normalization fast-path for 'lf' and 'crlf'...
Patrick Mezard -
r10128:ea7c392f default
parent child Browse files
Show More
@@ -1,1485 +1,1478 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import hex, nullid, short
10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
12 import cStringIO, email.Parser, os, re
12 import cStringIO, email.Parser, os, re
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
16
16
17 class PatchError(Exception):
17 class PatchError(Exception):
18 pass
18 pass
19
19
20 class NoHunks(PatchError):
20 class NoHunks(PatchError):
21 pass
21 pass
22
22
23 # helper functions
23 # helper functions
24
24
25 def copyfile(src, dst, basedir):
25 def copyfile(src, dst, basedir):
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 if os.path.exists(absdst):
27 if os.path.exists(absdst):
28 raise util.Abort(_("cannot create %s: destination already exists") %
28 raise util.Abort(_("cannot create %s: destination already exists") %
29 dst)
29 dst)
30
30
31 dstdir = os.path.dirname(absdst)
31 dstdir = os.path.dirname(absdst)
32 if dstdir and not os.path.isdir(dstdir):
32 if dstdir and not os.path.isdir(dstdir):
33 try:
33 try:
34 os.makedirs(dstdir)
34 os.makedirs(dstdir)
35 except IOError:
35 except IOError:
36 raise util.Abort(
36 raise util.Abort(
37 _("cannot create %s: unable to create destination directory")
37 _("cannot create %s: unable to create destination directory")
38 % dst)
38 % dst)
39
39
40 util.copyfile(abssrc, absdst)
40 util.copyfile(abssrc, absdst)
41
41
42 # public functions
42 # public functions
43
43
44 def extract(ui, fileobj):
44 def extract(ui, fileobj):
45 '''extract patch from data read from fileobj.
45 '''extract patch from data read from fileobj.
46
46
47 patch can be a normal patch or contained in an email message.
47 patch can be a normal patch or contained in an email message.
48
48
49 return tuple (filename, message, user, date, node, p1, p2).
49 return tuple (filename, message, user, date, node, p1, p2).
50 Any item in the returned tuple can be None. If filename is None,
50 Any item in the returned tuple can be None. If filename is None,
51 fileobj did not contain a patch. Caller must unlink filename when done.'''
51 fileobj did not contain a patch. Caller must unlink filename when done.'''
52
52
53 # attempt to detect the start of a patch
53 # attempt to detect the start of a patch
54 # (this heuristic is borrowed from quilt)
54 # (this heuristic is borrowed from quilt)
55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
58
58
59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
60 tmpfp = os.fdopen(fd, 'w')
60 tmpfp = os.fdopen(fd, 'w')
61 try:
61 try:
62 msg = email.Parser.Parser().parse(fileobj)
62 msg = email.Parser.Parser().parse(fileobj)
63
63
64 subject = msg['Subject']
64 subject = msg['Subject']
65 user = msg['From']
65 user = msg['From']
66 if not subject and not user:
66 if not subject and not user:
67 # Not an email, restore parsed headers if any
67 # Not an email, restore parsed headers if any
68 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
68 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
69
69
70 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
70 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
71 # should try to parse msg['Date']
71 # should try to parse msg['Date']
72 date = None
72 date = None
73 nodeid = None
73 nodeid = None
74 branch = None
74 branch = None
75 parents = []
75 parents = []
76
76
77 if subject:
77 if subject:
78 if subject.startswith('[PATCH'):
78 if subject.startswith('[PATCH'):
79 pend = subject.find(']')
79 pend = subject.find(']')
80 if pend >= 0:
80 if pend >= 0:
81 subject = subject[pend+1:].lstrip()
81 subject = subject[pend+1:].lstrip()
82 subject = subject.replace('\n\t', ' ')
82 subject = subject.replace('\n\t', ' ')
83 ui.debug('Subject: %s\n' % subject)
83 ui.debug('Subject: %s\n' % subject)
84 if user:
84 if user:
85 ui.debug('From: %s\n' % user)
85 ui.debug('From: %s\n' % user)
86 diffs_seen = 0
86 diffs_seen = 0
87 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
87 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
88 message = ''
88 message = ''
89 for part in msg.walk():
89 for part in msg.walk():
90 content_type = part.get_content_type()
90 content_type = part.get_content_type()
91 ui.debug('Content-Type: %s\n' % content_type)
91 ui.debug('Content-Type: %s\n' % content_type)
92 if content_type not in ok_types:
92 if content_type not in ok_types:
93 continue
93 continue
94 payload = part.get_payload(decode=True)
94 payload = part.get_payload(decode=True)
95 m = diffre.search(payload)
95 m = diffre.search(payload)
96 if m:
96 if m:
97 hgpatch = False
97 hgpatch = False
98 ignoretext = False
98 ignoretext = False
99
99
100 ui.debug('found patch at byte %d\n' % m.start(0))
100 ui.debug('found patch at byte %d\n' % m.start(0))
101 diffs_seen += 1
101 diffs_seen += 1
102 cfp = cStringIO.StringIO()
102 cfp = cStringIO.StringIO()
103 for line in payload[:m.start(0)].splitlines():
103 for line in payload[:m.start(0)].splitlines():
104 if line.startswith('# HG changeset patch'):
104 if line.startswith('# HG changeset patch'):
105 ui.debug('patch generated by hg export\n')
105 ui.debug('patch generated by hg export\n')
106 hgpatch = True
106 hgpatch = True
107 # drop earlier commit message content
107 # drop earlier commit message content
108 cfp.seek(0)
108 cfp.seek(0)
109 cfp.truncate()
109 cfp.truncate()
110 subject = None
110 subject = None
111 elif hgpatch:
111 elif hgpatch:
112 if line.startswith('# User '):
112 if line.startswith('# User '):
113 user = line[7:]
113 user = line[7:]
114 ui.debug('From: %s\n' % user)
114 ui.debug('From: %s\n' % user)
115 elif line.startswith("# Date "):
115 elif line.startswith("# Date "):
116 date = line[7:]
116 date = line[7:]
117 elif line.startswith("# Branch "):
117 elif line.startswith("# Branch "):
118 branch = line[9:]
118 branch = line[9:]
119 elif line.startswith("# Node ID "):
119 elif line.startswith("# Node ID "):
120 nodeid = line[10:]
120 nodeid = line[10:]
121 elif line.startswith("# Parent "):
121 elif line.startswith("# Parent "):
122 parents.append(line[10:])
122 parents.append(line[10:])
123 elif line == '---' and gitsendmail:
123 elif line == '---' and gitsendmail:
124 ignoretext = True
124 ignoretext = True
125 if not line.startswith('# ') and not ignoretext:
125 if not line.startswith('# ') and not ignoretext:
126 cfp.write(line)
126 cfp.write(line)
127 cfp.write('\n')
127 cfp.write('\n')
128 message = cfp.getvalue()
128 message = cfp.getvalue()
129 if tmpfp:
129 if tmpfp:
130 tmpfp.write(payload)
130 tmpfp.write(payload)
131 if not payload.endswith('\n'):
131 if not payload.endswith('\n'):
132 tmpfp.write('\n')
132 tmpfp.write('\n')
133 elif not diffs_seen and message and content_type == 'text/plain':
133 elif not diffs_seen and message and content_type == 'text/plain':
134 message += '\n' + payload
134 message += '\n' + payload
135 except:
135 except:
136 tmpfp.close()
136 tmpfp.close()
137 os.unlink(tmpname)
137 os.unlink(tmpname)
138 raise
138 raise
139
139
140 if subject and not message.startswith(subject):
140 if subject and not message.startswith(subject):
141 message = '%s\n%s' % (subject, message)
141 message = '%s\n%s' % (subject, message)
142 tmpfp.close()
142 tmpfp.close()
143 if not diffs_seen:
143 if not diffs_seen:
144 os.unlink(tmpname)
144 os.unlink(tmpname)
145 return None, message, user, date, branch, None, None, None
145 return None, message, user, date, branch, None, None, None
146 p1 = parents and parents.pop(0) or None
146 p1 = parents and parents.pop(0) or None
147 p2 = parents and parents.pop(0) or None
147 p2 = parents and parents.pop(0) or None
148 return tmpname, message, user, date, branch, nodeid, p1, p2
148 return tmpname, message, user, date, branch, nodeid, p1, p2
149
149
150 GP_PATCH = 1 << 0 # we have to run patch
150 GP_PATCH = 1 << 0 # we have to run patch
151 GP_FILTER = 1 << 1 # there's some copy/rename operation
151 GP_FILTER = 1 << 1 # there's some copy/rename operation
152 GP_BINARY = 1 << 2 # there's a binary patch
152 GP_BINARY = 1 << 2 # there's a binary patch
153
153
154 class patchmeta(object):
154 class patchmeta(object):
155 """Patched file metadata
155 """Patched file metadata
156
156
157 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
157 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
158 or COPY. 'path' is patched file path. 'oldpath' is set to the
158 or COPY. 'path' is patched file path. 'oldpath' is set to the
159 origin file when 'op' is either COPY or RENAME, None otherwise. If
159 origin file when 'op' is either COPY or RENAME, None otherwise. If
160 file mode is changed, 'mode' is a tuple (islink, isexec) where
160 file mode is changed, 'mode' is a tuple (islink, isexec) where
161 'islink' is True if the file is a symlink and 'isexec' is True if
161 'islink' is True if the file is a symlink and 'isexec' is True if
162 the file is executable. Otherwise, 'mode' is None.
162 the file is executable. Otherwise, 'mode' is None.
163 """
163 """
164 def __init__(self, path):
164 def __init__(self, path):
165 self.path = path
165 self.path = path
166 self.oldpath = None
166 self.oldpath = None
167 self.mode = None
167 self.mode = None
168 self.op = 'MODIFY'
168 self.op = 'MODIFY'
169 self.lineno = 0
169 self.lineno = 0
170 self.binary = False
170 self.binary = False
171
171
172 def setmode(self, mode):
172 def setmode(self, mode):
173 islink = mode & 020000
173 islink = mode & 020000
174 isexec = mode & 0100
174 isexec = mode & 0100
175 self.mode = (islink, isexec)
175 self.mode = (islink, isexec)
176
176
177 def readgitpatch(lr):
177 def readgitpatch(lr):
178 """extract git-style metadata about patches from <patchname>"""
178 """extract git-style metadata about patches from <patchname>"""
179
179
180 # Filter patch for git information
180 # Filter patch for git information
181 gp = None
181 gp = None
182 gitpatches = []
182 gitpatches = []
183 # Can have a git patch with only metadata, causing patch to complain
183 # Can have a git patch with only metadata, causing patch to complain
184 dopatch = 0
184 dopatch = 0
185
185
186 lineno = 0
186 lineno = 0
187 for line in lr:
187 for line in lr:
188 lineno += 1
188 lineno += 1
189 line = line.rstrip(' \r\n')
189 line = line.rstrip(' \r\n')
190 if line.startswith('diff --git'):
190 if line.startswith('diff --git'):
191 m = gitre.match(line)
191 m = gitre.match(line)
192 if m:
192 if m:
193 if gp:
193 if gp:
194 gitpatches.append(gp)
194 gitpatches.append(gp)
195 dst = m.group(2)
195 dst = m.group(2)
196 gp = patchmeta(dst)
196 gp = patchmeta(dst)
197 gp.lineno = lineno
197 gp.lineno = lineno
198 elif gp:
198 elif gp:
199 if line.startswith('--- '):
199 if line.startswith('--- '):
200 if gp.op in ('COPY', 'RENAME'):
200 if gp.op in ('COPY', 'RENAME'):
201 dopatch |= GP_FILTER
201 dopatch |= GP_FILTER
202 gitpatches.append(gp)
202 gitpatches.append(gp)
203 gp = None
203 gp = None
204 dopatch |= GP_PATCH
204 dopatch |= GP_PATCH
205 continue
205 continue
206 if line.startswith('rename from '):
206 if line.startswith('rename from '):
207 gp.op = 'RENAME'
207 gp.op = 'RENAME'
208 gp.oldpath = line[12:]
208 gp.oldpath = line[12:]
209 elif line.startswith('rename to '):
209 elif line.startswith('rename to '):
210 gp.path = line[10:]
210 gp.path = line[10:]
211 elif line.startswith('copy from '):
211 elif line.startswith('copy from '):
212 gp.op = 'COPY'
212 gp.op = 'COPY'
213 gp.oldpath = line[10:]
213 gp.oldpath = line[10:]
214 elif line.startswith('copy to '):
214 elif line.startswith('copy to '):
215 gp.path = line[8:]
215 gp.path = line[8:]
216 elif line.startswith('deleted file'):
216 elif line.startswith('deleted file'):
217 gp.op = 'DELETE'
217 gp.op = 'DELETE'
218 # is the deleted file a symlink?
218 # is the deleted file a symlink?
219 gp.setmode(int(line[-6:], 8))
219 gp.setmode(int(line[-6:], 8))
220 elif line.startswith('new file mode '):
220 elif line.startswith('new file mode '):
221 gp.op = 'ADD'
221 gp.op = 'ADD'
222 gp.setmode(int(line[-6:], 8))
222 gp.setmode(int(line[-6:], 8))
223 elif line.startswith('new mode '):
223 elif line.startswith('new mode '):
224 gp.setmode(int(line[-6:], 8))
224 gp.setmode(int(line[-6:], 8))
225 elif line.startswith('GIT binary patch'):
225 elif line.startswith('GIT binary patch'):
226 dopatch |= GP_BINARY
226 dopatch |= GP_BINARY
227 gp.binary = True
227 gp.binary = True
228 if gp:
228 if gp:
229 gitpatches.append(gp)
229 gitpatches.append(gp)
230
230
231 if not gitpatches:
231 if not gitpatches:
232 dopatch = GP_PATCH
232 dopatch = GP_PATCH
233
233
234 return (dopatch, gitpatches)
234 return (dopatch, gitpatches)
235
235
236 class linereader(object):
236 class linereader(object):
237 # simple class to allow pushing lines back into the input stream
237 # simple class to allow pushing lines back into the input stream
238 def __init__(self, fp, textmode=False):
238 def __init__(self, fp, textmode=False):
239 self.fp = fp
239 self.fp = fp
240 self.buf = []
240 self.buf = []
241 self.textmode = textmode
241 self.textmode = textmode
242 self.eol = None
242 self.eol = None
243
243
244 def push(self, line):
244 def push(self, line):
245 if line is not None:
245 if line is not None:
246 self.buf.append(line)
246 self.buf.append(line)
247
247
248 def readline(self):
248 def readline(self):
249 if self.buf:
249 if self.buf:
250 l = self.buf[0]
250 l = self.buf[0]
251 del self.buf[0]
251 del self.buf[0]
252 return l
252 return l
253 l = self.fp.readline()
253 l = self.fp.readline()
254 if not self.eol:
254 if not self.eol:
255 if l.endswith('\r\n'):
255 if l.endswith('\r\n'):
256 self.eol = '\r\n'
256 self.eol = '\r\n'
257 elif l.endswith('\n'):
257 elif l.endswith('\n'):
258 self.eol = '\n'
258 self.eol = '\n'
259 if self.textmode and l.endswith('\r\n'):
259 if self.textmode and l.endswith('\r\n'):
260 l = l[:-2] + '\n'
260 l = l[:-2] + '\n'
261 return l
261 return l
262
262
263 def __iter__(self):
263 def __iter__(self):
264 while 1:
264 while 1:
265 l = self.readline()
265 l = self.readline()
266 if not l:
266 if not l:
267 break
267 break
268 yield l
268 yield l
269
269
270 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
270 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
271 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
271 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
272 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
272 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
273 eolmodes = ['strict', 'crlf', 'lf', 'auto']
273 eolmodes = ['strict', 'crlf', 'lf', 'auto']
274
274
275 class patchfile(object):
275 class patchfile(object):
276 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
276 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
277 self.fname = fname
277 self.fname = fname
278 self.eolmode = eolmode
278 self.eolmode = eolmode
279 self.eol = None
279 self.eol = None
280 self.opener = opener
280 self.opener = opener
281 self.ui = ui
281 self.ui = ui
282 self.lines = []
282 self.lines = []
283 self.exists = False
283 self.exists = False
284 self.missing = missing
284 self.missing = missing
285 if not missing:
285 if not missing:
286 try:
286 try:
287 self.lines = self.readlines(fname)
287 self.lines = self.readlines(fname)
288 self.exists = True
288 self.exists = True
289 except IOError:
289 except IOError:
290 pass
290 pass
291 else:
291 else:
292 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
292 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
293
293
294 self.hash = {}
294 self.hash = {}
295 self.dirty = 0
295 self.dirty = 0
296 self.offset = 0
296 self.offset = 0
297 self.rej = []
297 self.rej = []
298 self.fileprinted = False
298 self.fileprinted = False
299 self.printfile(False)
299 self.printfile(False)
300 self.hunks = 0
300 self.hunks = 0
301
301
302 def readlines(self, fname):
302 def readlines(self, fname):
303 if os.path.islink(fname):
303 if os.path.islink(fname):
304 return [os.readlink(fname)]
304 return [os.readlink(fname)]
305 fp = self.opener(fname, 'r')
305 fp = self.opener(fname, 'r')
306 try:
306 try:
307 lr = linereader(fp, self.eolmode != 'strict')
307 lr = linereader(fp, self.eolmode != 'strict')
308 lines = list(lr)
308 lines = list(lr)
309 self.eol = lr.eol
309 self.eol = lr.eol
310 return lines
310 return lines
311 finally:
311 finally:
312 fp.close()
312 fp.close()
313
313
314 def writelines(self, fname, lines):
314 def writelines(self, fname, lines):
315 # Ensure supplied data ends in fname, being a regular file or
315 # Ensure supplied data ends in fname, being a regular file or
316 # a symlink. updatedir() will -too magically- take care of
316 # a symlink. updatedir() will -too magically- take care of
317 # setting it to the proper type afterwards.
317 # setting it to the proper type afterwards.
318 islink = os.path.islink(fname)
318 islink = os.path.islink(fname)
319 if islink:
319 if islink:
320 fp = cStringIO.StringIO()
320 fp = cStringIO.StringIO()
321 else:
321 else:
322 fp = self.opener(fname, 'w')
322 fp = self.opener(fname, 'w')
323 try:
323 try:
324 if self.eolmode == 'auto':
324 if self.eolmode == 'auto':
325 eol = self.eol
325 eol = self.eol
326 elif self.eolmode == 'crlf':
326 elif self.eolmode == 'crlf':
327 eol = '\r\n'
327 eol = '\r\n'
328 else:
328 else:
329 eol = '\n'
329 eol = '\n'
330
330
331 if self.eolmode != 'strict' and eol and eol != '\n':
331 if self.eolmode != 'strict' and eol and eol != '\n':
332 for l in lines:
332 for l in lines:
333 if l and l[-1] == '\n':
333 if l and l[-1] == '\n':
334 l = l[:-1] + eol
334 l = l[:-1] + eol
335 fp.write(l)
335 fp.write(l)
336 else:
336 else:
337 fp.writelines(lines)
337 fp.writelines(lines)
338 if islink:
338 if islink:
339 self.opener.symlink(fp.getvalue(), fname)
339 self.opener.symlink(fp.getvalue(), fname)
340 finally:
340 finally:
341 fp.close()
341 fp.close()
342
342
343 def unlink(self, fname):
343 def unlink(self, fname):
344 os.unlink(fname)
344 os.unlink(fname)
345
345
346 def printfile(self, warn):
346 def printfile(self, warn):
347 if self.fileprinted:
347 if self.fileprinted:
348 return
348 return
349 if warn or self.ui.verbose:
349 if warn or self.ui.verbose:
350 self.fileprinted = True
350 self.fileprinted = True
351 s = _("patching file %s\n") % self.fname
351 s = _("patching file %s\n") % self.fname
352 if warn:
352 if warn:
353 self.ui.warn(s)
353 self.ui.warn(s)
354 else:
354 else:
355 self.ui.note(s)
355 self.ui.note(s)
356
356
357
357
358 def findlines(self, l, linenum):
358 def findlines(self, l, linenum):
359 # looks through the hash and finds candidate lines. The
359 # looks through the hash and finds candidate lines. The
360 # result is a list of line numbers sorted based on distance
360 # result is a list of line numbers sorted based on distance
361 # from linenum
361 # from linenum
362
362
363 cand = self.hash.get(l, [])
363 cand = self.hash.get(l, [])
364 if len(cand) > 1:
364 if len(cand) > 1:
365 # resort our list of potentials forward then back.
365 # resort our list of potentials forward then back.
366 cand.sort(key=lambda x: abs(x - linenum))
366 cand.sort(key=lambda x: abs(x - linenum))
367 return cand
367 return cand
368
368
369 def hashlines(self):
369 def hashlines(self):
370 self.hash = {}
370 self.hash = {}
371 for x, s in enumerate(self.lines):
371 for x, s in enumerate(self.lines):
372 self.hash.setdefault(s, []).append(x)
372 self.hash.setdefault(s, []).append(x)
373
373
374 def write_rej(self):
374 def write_rej(self):
375 # our rejects are a little different from patch(1). This always
375 # our rejects are a little different from patch(1). This always
376 # creates rejects in the same form as the original patch. A file
376 # creates rejects in the same form as the original patch. A file
377 # header is inserted so that you can run the reject through patch again
377 # header is inserted so that you can run the reject through patch again
378 # without having to type the filename.
378 # without having to type the filename.
379
379
380 if not self.rej:
380 if not self.rej:
381 return
381 return
382
382
383 fname = self.fname + ".rej"
383 fname = self.fname + ".rej"
384 self.ui.warn(
384 self.ui.warn(
385 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
385 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
386 (len(self.rej), self.hunks, fname))
386 (len(self.rej), self.hunks, fname))
387
387
388 def rejlines():
388 def rejlines():
389 base = os.path.basename(self.fname)
389 base = os.path.basename(self.fname)
390 yield "--- %s\n+++ %s\n" % (base, base)
390 yield "--- %s\n+++ %s\n" % (base, base)
391 for x in self.rej:
391 for x in self.rej:
392 for l in x.hunk:
392 for l in x.hunk:
393 yield l
393 yield l
394 if l[-1] != '\n':
394 if l[-1] != '\n':
395 yield "\n\ No newline at end of file\n"
395 yield "\n\ No newline at end of file\n"
396
396
397 self.writelines(fname, rejlines())
397 self.writelines(fname, rejlines())
398
398
399 def write(self, dest=None):
399 def write(self, dest=None):
400 if not self.dirty:
400 if not self.dirty:
401 return
401 return
402 if not dest:
402 if not dest:
403 dest = self.fname
403 dest = self.fname
404 self.writelines(dest, self.lines)
404 self.writelines(dest, self.lines)
405
405
406 def close(self):
406 def close(self):
407 self.write()
407 self.write()
408 self.write_rej()
408 self.write_rej()
409
409
410 def apply(self, h):
410 def apply(self, h):
411 if not h.complete():
411 if not h.complete():
412 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
412 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
413 (h.number, h.desc, len(h.a), h.lena, len(h.b),
413 (h.number, h.desc, len(h.a), h.lena, len(h.b),
414 h.lenb))
414 h.lenb))
415
415
416 self.hunks += 1
416 self.hunks += 1
417
417
418 if self.missing:
418 if self.missing:
419 self.rej.append(h)
419 self.rej.append(h)
420 return -1
420 return -1
421
421
422 if self.exists and h.createfile():
422 if self.exists and h.createfile():
423 self.ui.warn(_("file %s already exists\n") % self.fname)
423 self.ui.warn(_("file %s already exists\n") % self.fname)
424 self.rej.append(h)
424 self.rej.append(h)
425 return -1
425 return -1
426
426
427 if isinstance(h, binhunk):
427 if isinstance(h, binhunk):
428 if h.rmfile():
428 if h.rmfile():
429 self.unlink(self.fname)
429 self.unlink(self.fname)
430 else:
430 else:
431 self.lines[:] = h.new()
431 self.lines[:] = h.new()
432 self.offset += len(h.new())
432 self.offset += len(h.new())
433 self.dirty = 1
433 self.dirty = 1
434 return 0
434 return 0
435
435
436 horig = h
436 horig = h
437 if self.eolmode == 'auto' and self.eol:
437 if (self.eolmode in ('crlf', 'lf')
438 # If eolmode == 'auto' and target file exists and has line
438 or self.eolmode == 'auto' and self.eol):
439 # endings we have to normalize input data before patching.
439 # If new eols are going to be normalized, then normalize
440 # Otherwise, patchfile operates in 'strict' mode. If
440 # hunk data before patching. Otherwise, preserve input
441 # eolmode is set to 'crlf' or 'lf', input hunk is already
441 # line-endings.
442 # normalized to avoid data copy.
443 h = h.getnormalized()
442 h = h.getnormalized()
444
443
445 # fast case first, no offsets, no fuzz
444 # fast case first, no offsets, no fuzz
446 old = h.old()
445 old = h.old()
447 # patch starts counting at 1 unless we are adding the file
446 # patch starts counting at 1 unless we are adding the file
448 if h.starta == 0:
447 if h.starta == 0:
449 start = 0
448 start = 0
450 else:
449 else:
451 start = h.starta + self.offset - 1
450 start = h.starta + self.offset - 1
452 orig_start = start
451 orig_start = start
453 if diffhelpers.testhunk(old, self.lines, start) == 0:
452 if diffhelpers.testhunk(old, self.lines, start) == 0:
454 if h.rmfile():
453 if h.rmfile():
455 self.unlink(self.fname)
454 self.unlink(self.fname)
456 else:
455 else:
457 self.lines[start : start + h.lena] = h.new()
456 self.lines[start : start + h.lena] = h.new()
458 self.offset += h.lenb - h.lena
457 self.offset += h.lenb - h.lena
459 self.dirty = 1
458 self.dirty = 1
460 return 0
459 return 0
461
460
462 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
461 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
463 self.hashlines()
462 self.hashlines()
464 if h.hunk[-1][0] != ' ':
463 if h.hunk[-1][0] != ' ':
465 # if the hunk tried to put something at the bottom of the file
464 # if the hunk tried to put something at the bottom of the file
466 # override the start line and use eof here
465 # override the start line and use eof here
467 search_start = len(self.lines)
466 search_start = len(self.lines)
468 else:
467 else:
469 search_start = orig_start
468 search_start = orig_start
470
469
471 for fuzzlen in xrange(3):
470 for fuzzlen in xrange(3):
472 for toponly in [ True, False ]:
471 for toponly in [ True, False ]:
473 old = h.old(fuzzlen, toponly)
472 old = h.old(fuzzlen, toponly)
474
473
475 cand = self.findlines(old[0][1:], search_start)
474 cand = self.findlines(old[0][1:], search_start)
476 for l in cand:
475 for l in cand:
477 if diffhelpers.testhunk(old, self.lines, l) == 0:
476 if diffhelpers.testhunk(old, self.lines, l) == 0:
478 newlines = h.new(fuzzlen, toponly)
477 newlines = h.new(fuzzlen, toponly)
479 self.lines[l : l + len(old)] = newlines
478 self.lines[l : l + len(old)] = newlines
480 self.offset += len(newlines) - len(old)
479 self.offset += len(newlines) - len(old)
481 self.dirty = 1
480 self.dirty = 1
482 if fuzzlen:
481 if fuzzlen:
483 fuzzstr = "with fuzz %d " % fuzzlen
482 fuzzstr = "with fuzz %d " % fuzzlen
484 f = self.ui.warn
483 f = self.ui.warn
485 self.printfile(True)
484 self.printfile(True)
486 else:
485 else:
487 fuzzstr = ""
486 fuzzstr = ""
488 f = self.ui.note
487 f = self.ui.note
489 offset = l - orig_start - fuzzlen
488 offset = l - orig_start - fuzzlen
490 if offset == 1:
489 if offset == 1:
491 msg = _("Hunk #%d succeeded at %d %s"
490 msg = _("Hunk #%d succeeded at %d %s"
492 "(offset %d line).\n")
491 "(offset %d line).\n")
493 else:
492 else:
494 msg = _("Hunk #%d succeeded at %d %s"
493 msg = _("Hunk #%d succeeded at %d %s"
495 "(offset %d lines).\n")
494 "(offset %d lines).\n")
496 f(msg % (h.number, l+1, fuzzstr, offset))
495 f(msg % (h.number, l+1, fuzzstr, offset))
497 return fuzzlen
496 return fuzzlen
498 self.printfile(True)
497 self.printfile(True)
499 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
498 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
500 self.rej.append(horig)
499 self.rej.append(horig)
501 return -1
500 return -1
502
501
503 class hunk(object):
502 class hunk(object):
504 def __init__(self, desc, num, lr, context, create=False, remove=False):
503 def __init__(self, desc, num, lr, context, create=False, remove=False):
505 self.number = num
504 self.number = num
506 self.desc = desc
505 self.desc = desc
507 self.hunk = [ desc ]
506 self.hunk = [ desc ]
508 self.a = []
507 self.a = []
509 self.b = []
508 self.b = []
510 self.starta = self.lena = None
509 self.starta = self.lena = None
511 self.startb = self.lenb = None
510 self.startb = self.lenb = None
512 if lr is not None:
511 if lr is not None:
513 if context:
512 if context:
514 self.read_context_hunk(lr)
513 self.read_context_hunk(lr)
515 else:
514 else:
516 self.read_unified_hunk(lr)
515 self.read_unified_hunk(lr)
517 self.create = create
516 self.create = create
518 self.remove = remove and not create
517 self.remove = remove and not create
519
518
520 def getnormalized(self):
519 def getnormalized(self):
521 """Return a copy with line endings normalized to LF."""
520 """Return a copy with line endings normalized to LF."""
522
521
523 def normalize(lines):
522 def normalize(lines):
524 nlines = []
523 nlines = []
525 for line in lines:
524 for line in lines:
526 if line.endswith('\r\n'):
525 if line.endswith('\r\n'):
527 line = line[:-2] + '\n'
526 line = line[:-2] + '\n'
528 nlines.append(line)
527 nlines.append(line)
529 return nlines
528 return nlines
530
529
531 # Dummy object, it is rebuilt manually
530 # Dummy object, it is rebuilt manually
532 nh = hunk(self.desc, self.number, None, None, False, False)
531 nh = hunk(self.desc, self.number, None, None, False, False)
533 nh.number = self.number
532 nh.number = self.number
534 nh.desc = self.desc
533 nh.desc = self.desc
535 nh.a = normalize(self.a)
534 nh.a = normalize(self.a)
536 nh.b = normalize(self.b)
535 nh.b = normalize(self.b)
537 nh.starta = self.starta
536 nh.starta = self.starta
538 nh.startb = self.startb
537 nh.startb = self.startb
539 nh.lena = self.lena
538 nh.lena = self.lena
540 nh.lenb = self.lenb
539 nh.lenb = self.lenb
541 nh.create = self.create
540 nh.create = self.create
542 nh.remove = self.remove
541 nh.remove = self.remove
543 return nh
542 return nh
544
543
545 def read_unified_hunk(self, lr):
544 def read_unified_hunk(self, lr):
546 m = unidesc.match(self.desc)
545 m = unidesc.match(self.desc)
547 if not m:
546 if not m:
548 raise PatchError(_("bad hunk #%d") % self.number)
547 raise PatchError(_("bad hunk #%d") % self.number)
549 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
548 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
550 if self.lena is None:
549 if self.lena is None:
551 self.lena = 1
550 self.lena = 1
552 else:
551 else:
553 self.lena = int(self.lena)
552 self.lena = int(self.lena)
554 if self.lenb is None:
553 if self.lenb is None:
555 self.lenb = 1
554 self.lenb = 1
556 else:
555 else:
557 self.lenb = int(self.lenb)
556 self.lenb = int(self.lenb)
558 self.starta = int(self.starta)
557 self.starta = int(self.starta)
559 self.startb = int(self.startb)
558 self.startb = int(self.startb)
560 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
559 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
561 # if we hit eof before finishing out the hunk, the last line will
560 # if we hit eof before finishing out the hunk, the last line will
562 # be zero length. Lets try to fix it up.
561 # be zero length. Lets try to fix it up.
563 while len(self.hunk[-1]) == 0:
562 while len(self.hunk[-1]) == 0:
564 del self.hunk[-1]
563 del self.hunk[-1]
565 del self.a[-1]
564 del self.a[-1]
566 del self.b[-1]
565 del self.b[-1]
567 self.lena -= 1
566 self.lena -= 1
568 self.lenb -= 1
567 self.lenb -= 1
569
568
570 def read_context_hunk(self, lr):
569 def read_context_hunk(self, lr):
571 self.desc = lr.readline()
570 self.desc = lr.readline()
572 m = contextdesc.match(self.desc)
571 m = contextdesc.match(self.desc)
573 if not m:
572 if not m:
574 raise PatchError(_("bad hunk #%d") % self.number)
573 raise PatchError(_("bad hunk #%d") % self.number)
575 foo, self.starta, foo2, aend, foo3 = m.groups()
574 foo, self.starta, foo2, aend, foo3 = m.groups()
576 self.starta = int(self.starta)
575 self.starta = int(self.starta)
577 if aend is None:
576 if aend is None:
578 aend = self.starta
577 aend = self.starta
579 self.lena = int(aend) - self.starta
578 self.lena = int(aend) - self.starta
580 if self.starta:
579 if self.starta:
581 self.lena += 1
580 self.lena += 1
582 for x in xrange(self.lena):
581 for x in xrange(self.lena):
583 l = lr.readline()
582 l = lr.readline()
584 if l.startswith('---'):
583 if l.startswith('---'):
585 lr.push(l)
584 lr.push(l)
586 break
585 break
587 s = l[2:]
586 s = l[2:]
588 if l.startswith('- ') or l.startswith('! '):
587 if l.startswith('- ') or l.startswith('! '):
589 u = '-' + s
588 u = '-' + s
590 elif l.startswith(' '):
589 elif l.startswith(' '):
591 u = ' ' + s
590 u = ' ' + s
592 else:
591 else:
593 raise PatchError(_("bad hunk #%d old text line %d") %
592 raise PatchError(_("bad hunk #%d old text line %d") %
594 (self.number, x))
593 (self.number, x))
595 self.a.append(u)
594 self.a.append(u)
596 self.hunk.append(u)
595 self.hunk.append(u)
597
596
598 l = lr.readline()
597 l = lr.readline()
599 if l.startswith('\ '):
598 if l.startswith('\ '):
600 s = self.a[-1][:-1]
599 s = self.a[-1][:-1]
601 self.a[-1] = s
600 self.a[-1] = s
602 self.hunk[-1] = s
601 self.hunk[-1] = s
603 l = lr.readline()
602 l = lr.readline()
604 m = contextdesc.match(l)
603 m = contextdesc.match(l)
605 if not m:
604 if not m:
606 raise PatchError(_("bad hunk #%d") % self.number)
605 raise PatchError(_("bad hunk #%d") % self.number)
607 foo, self.startb, foo2, bend, foo3 = m.groups()
606 foo, self.startb, foo2, bend, foo3 = m.groups()
608 self.startb = int(self.startb)
607 self.startb = int(self.startb)
609 if bend is None:
608 if bend is None:
610 bend = self.startb
609 bend = self.startb
611 self.lenb = int(bend) - self.startb
610 self.lenb = int(bend) - self.startb
612 if self.startb:
611 if self.startb:
613 self.lenb += 1
612 self.lenb += 1
614 hunki = 1
613 hunki = 1
615 for x in xrange(self.lenb):
614 for x in xrange(self.lenb):
616 l = lr.readline()
615 l = lr.readline()
617 if l.startswith('\ '):
616 if l.startswith('\ '):
618 s = self.b[-1][:-1]
617 s = self.b[-1][:-1]
619 self.b[-1] = s
618 self.b[-1] = s
620 self.hunk[hunki-1] = s
619 self.hunk[hunki-1] = s
621 continue
620 continue
622 if not l:
621 if not l:
623 lr.push(l)
622 lr.push(l)
624 break
623 break
625 s = l[2:]
624 s = l[2:]
626 if l.startswith('+ ') or l.startswith('! '):
625 if l.startswith('+ ') or l.startswith('! '):
627 u = '+' + s
626 u = '+' + s
628 elif l.startswith(' '):
627 elif l.startswith(' '):
629 u = ' ' + s
628 u = ' ' + s
630 elif len(self.b) == 0:
629 elif len(self.b) == 0:
631 # this can happen when the hunk does not add any lines
630 # this can happen when the hunk does not add any lines
632 lr.push(l)
631 lr.push(l)
633 break
632 break
634 else:
633 else:
635 raise PatchError(_("bad hunk #%d old text line %d") %
634 raise PatchError(_("bad hunk #%d old text line %d") %
636 (self.number, x))
635 (self.number, x))
637 self.b.append(s)
636 self.b.append(s)
638 while True:
637 while True:
639 if hunki >= len(self.hunk):
638 if hunki >= len(self.hunk):
640 h = ""
639 h = ""
641 else:
640 else:
642 h = self.hunk[hunki]
641 h = self.hunk[hunki]
643 hunki += 1
642 hunki += 1
644 if h == u:
643 if h == u:
645 break
644 break
646 elif h.startswith('-'):
645 elif h.startswith('-'):
647 continue
646 continue
648 else:
647 else:
649 self.hunk.insert(hunki-1, u)
648 self.hunk.insert(hunki-1, u)
650 break
649 break
651
650
652 if not self.a:
651 if not self.a:
653 # this happens when lines were only added to the hunk
652 # this happens when lines were only added to the hunk
654 for x in self.hunk:
653 for x in self.hunk:
655 if x.startswith('-') or x.startswith(' '):
654 if x.startswith('-') or x.startswith(' '):
656 self.a.append(x)
655 self.a.append(x)
657 if not self.b:
656 if not self.b:
658 # this happens when lines were only deleted from the hunk
657 # this happens when lines were only deleted from the hunk
659 for x in self.hunk:
658 for x in self.hunk:
660 if x.startswith('+') or x.startswith(' '):
659 if x.startswith('+') or x.startswith(' '):
661 self.b.append(x[1:])
660 self.b.append(x[1:])
662 # @@ -start,len +start,len @@
661 # @@ -start,len +start,len @@
663 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
662 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
664 self.startb, self.lenb)
663 self.startb, self.lenb)
665 self.hunk[0] = self.desc
664 self.hunk[0] = self.desc
666
665
667 def fix_newline(self):
666 def fix_newline(self):
668 diffhelpers.fix_newline(self.hunk, self.a, self.b)
667 diffhelpers.fix_newline(self.hunk, self.a, self.b)
669
668
670 def complete(self):
669 def complete(self):
671 return len(self.a) == self.lena and len(self.b) == self.lenb
670 return len(self.a) == self.lena and len(self.b) == self.lenb
672
671
673 def createfile(self):
672 def createfile(self):
674 return self.starta == 0 and self.lena == 0 and self.create
673 return self.starta == 0 and self.lena == 0 and self.create
675
674
676 def rmfile(self):
675 def rmfile(self):
677 return self.startb == 0 and self.lenb == 0 and self.remove
676 return self.startb == 0 and self.lenb == 0 and self.remove
678
677
679 def fuzzit(self, l, fuzz, toponly):
678 def fuzzit(self, l, fuzz, toponly):
680 # this removes context lines from the top and bottom of list 'l'. It
679 # this removes context lines from the top and bottom of list 'l'. It
681 # checks the hunk to make sure only context lines are removed, and then
680 # checks the hunk to make sure only context lines are removed, and then
682 # returns a new shortened list of lines.
681 # returns a new shortened list of lines.
683 fuzz = min(fuzz, len(l)-1)
682 fuzz = min(fuzz, len(l)-1)
684 if fuzz:
683 if fuzz:
685 top = 0
684 top = 0
686 bot = 0
685 bot = 0
687 hlen = len(self.hunk)
686 hlen = len(self.hunk)
688 for x in xrange(hlen-1):
687 for x in xrange(hlen-1):
689 # the hunk starts with the @@ line, so use x+1
688 # the hunk starts with the @@ line, so use x+1
690 if self.hunk[x+1][0] == ' ':
689 if self.hunk[x+1][0] == ' ':
691 top += 1
690 top += 1
692 else:
691 else:
693 break
692 break
694 if not toponly:
693 if not toponly:
695 for x in xrange(hlen-1):
694 for x in xrange(hlen-1):
696 if self.hunk[hlen-bot-1][0] == ' ':
695 if self.hunk[hlen-bot-1][0] == ' ':
697 bot += 1
696 bot += 1
698 else:
697 else:
699 break
698 break
700
699
701 # top and bot now count context in the hunk
700 # top and bot now count context in the hunk
702 # adjust them if either one is short
701 # adjust them if either one is short
703 context = max(top, bot, 3)
702 context = max(top, bot, 3)
704 if bot < context:
703 if bot < context:
705 bot = max(0, fuzz - (context - bot))
704 bot = max(0, fuzz - (context - bot))
706 else:
705 else:
707 bot = min(fuzz, bot)
706 bot = min(fuzz, bot)
708 if top < context:
707 if top < context:
709 top = max(0, fuzz - (context - top))
708 top = max(0, fuzz - (context - top))
710 else:
709 else:
711 top = min(fuzz, top)
710 top = min(fuzz, top)
712
711
713 return l[top:len(l)-bot]
712 return l[top:len(l)-bot]
714 return l
713 return l
715
714
716 def old(self, fuzz=0, toponly=False):
715 def old(self, fuzz=0, toponly=False):
717 return self.fuzzit(self.a, fuzz, toponly)
716 return self.fuzzit(self.a, fuzz, toponly)
718
717
719 def newctrl(self):
718 def newctrl(self):
720 res = []
719 res = []
721 for x in self.hunk:
720 for x in self.hunk:
722 c = x[0]
721 c = x[0]
723 if c == ' ' or c == '+':
722 if c == ' ' or c == '+':
724 res.append(x)
723 res.append(x)
725 return res
724 return res
726
725
727 def new(self, fuzz=0, toponly=False):
726 def new(self, fuzz=0, toponly=False):
728 return self.fuzzit(self.b, fuzz, toponly)
727 return self.fuzzit(self.b, fuzz, toponly)
729
728
730 class binhunk:
729 class binhunk:
731 'A binary patch file. Only understands literals so far.'
730 'A binary patch file. Only understands literals so far.'
732 def __init__(self, gitpatch):
731 def __init__(self, gitpatch):
733 self.gitpatch = gitpatch
732 self.gitpatch = gitpatch
734 self.text = None
733 self.text = None
735 self.hunk = ['GIT binary patch\n']
734 self.hunk = ['GIT binary patch\n']
736
735
737 def createfile(self):
736 def createfile(self):
738 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
737 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
739
738
740 def rmfile(self):
739 def rmfile(self):
741 return self.gitpatch.op == 'DELETE'
740 return self.gitpatch.op == 'DELETE'
742
741
743 def complete(self):
742 def complete(self):
744 return self.text is not None
743 return self.text is not None
745
744
746 def new(self):
745 def new(self):
747 return [self.text]
746 return [self.text]
748
747
749 def extract(self, lr):
748 def extract(self, lr):
750 line = lr.readline()
749 line = lr.readline()
751 self.hunk.append(line)
750 self.hunk.append(line)
752 while line and not line.startswith('literal '):
751 while line and not line.startswith('literal '):
753 line = lr.readline()
752 line = lr.readline()
754 self.hunk.append(line)
753 self.hunk.append(line)
755 if not line:
754 if not line:
756 raise PatchError(_('could not extract binary patch'))
755 raise PatchError(_('could not extract binary patch'))
757 size = int(line[8:].rstrip())
756 size = int(line[8:].rstrip())
758 dec = []
757 dec = []
759 line = lr.readline()
758 line = lr.readline()
760 self.hunk.append(line)
759 self.hunk.append(line)
761 while len(line) > 1:
760 while len(line) > 1:
762 l = line[0]
761 l = line[0]
763 if l <= 'Z' and l >= 'A':
762 if l <= 'Z' and l >= 'A':
764 l = ord(l) - ord('A') + 1
763 l = ord(l) - ord('A') + 1
765 else:
764 else:
766 l = ord(l) - ord('a') + 27
765 l = ord(l) - ord('a') + 27
767 dec.append(base85.b85decode(line[1:-1])[:l])
766 dec.append(base85.b85decode(line[1:-1])[:l])
768 line = lr.readline()
767 line = lr.readline()
769 self.hunk.append(line)
768 self.hunk.append(line)
770 text = zlib.decompress(''.join(dec))
769 text = zlib.decompress(''.join(dec))
771 if len(text) != size:
770 if len(text) != size:
772 raise PatchError(_('binary patch is %d bytes, not %d') %
771 raise PatchError(_('binary patch is %d bytes, not %d') %
773 len(text), size)
772 len(text), size)
774 self.text = text
773 self.text = text
775
774
776 def parsefilename(str):
775 def parsefilename(str):
777 # --- filename \t|space stuff
776 # --- filename \t|space stuff
778 s = str[4:].rstrip('\r\n')
777 s = str[4:].rstrip('\r\n')
779 i = s.find('\t')
778 i = s.find('\t')
780 if i < 0:
779 if i < 0:
781 i = s.find(' ')
780 i = s.find(' ')
782 if i < 0:
781 if i < 0:
783 return s
782 return s
784 return s[:i]
783 return s[:i]
785
784
786 def selectfile(afile_orig, bfile_orig, hunk, strip):
785 def selectfile(afile_orig, bfile_orig, hunk, strip):
787 def pathstrip(path, count=1):
786 def pathstrip(path, count=1):
788 pathlen = len(path)
787 pathlen = len(path)
789 i = 0
788 i = 0
790 if count == 0:
789 if count == 0:
791 return '', path.rstrip()
790 return '', path.rstrip()
792 while count > 0:
791 while count > 0:
793 i = path.find('/', i)
792 i = path.find('/', i)
794 if i == -1:
793 if i == -1:
795 raise PatchError(_("unable to strip away %d dirs from %s") %
794 raise PatchError(_("unable to strip away %d dirs from %s") %
796 (count, path))
795 (count, path))
797 i += 1
796 i += 1
798 # consume '//' in the path
797 # consume '//' in the path
799 while i < pathlen - 1 and path[i] == '/':
798 while i < pathlen - 1 and path[i] == '/':
800 i += 1
799 i += 1
801 count -= 1
800 count -= 1
802 return path[:i].lstrip(), path[i:].rstrip()
801 return path[:i].lstrip(), path[i:].rstrip()
803
802
804 nulla = afile_orig == "/dev/null"
803 nulla = afile_orig == "/dev/null"
805 nullb = bfile_orig == "/dev/null"
804 nullb = bfile_orig == "/dev/null"
806 abase, afile = pathstrip(afile_orig, strip)
805 abase, afile = pathstrip(afile_orig, strip)
807 gooda = not nulla and util.lexists(afile)
806 gooda = not nulla and util.lexists(afile)
808 bbase, bfile = pathstrip(bfile_orig, strip)
807 bbase, bfile = pathstrip(bfile_orig, strip)
809 if afile == bfile:
808 if afile == bfile:
810 goodb = gooda
809 goodb = gooda
811 else:
810 else:
812 goodb = not nullb and os.path.exists(bfile)
811 goodb = not nullb and os.path.exists(bfile)
813 createfunc = hunk.createfile
812 createfunc = hunk.createfile
814 missing = not goodb and not gooda and not createfunc()
813 missing = not goodb and not gooda and not createfunc()
815
814
816 # some diff programs apparently produce create patches where the
815 # some diff programs apparently produce create patches where the
817 # afile is not /dev/null, but rather the same name as the bfile
816 # afile is not /dev/null, but rather the same name as the bfile
818 if missing and afile == bfile:
817 if missing and afile == bfile:
819 # this isn't very pretty
818 # this isn't very pretty
820 hunk.create = True
819 hunk.create = True
821 if createfunc():
820 if createfunc():
822 missing = False
821 missing = False
823 else:
822 else:
824 hunk.create = False
823 hunk.create = False
825
824
826 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
825 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
827 # diff is between a file and its backup. In this case, the original
826 # diff is between a file and its backup. In this case, the original
828 # file should be patched (see original mpatch code).
827 # file should be patched (see original mpatch code).
829 isbackup = (abase == bbase and bfile.startswith(afile))
828 isbackup = (abase == bbase and bfile.startswith(afile))
830 fname = None
829 fname = None
831 if not missing:
830 if not missing:
832 if gooda and goodb:
831 if gooda and goodb:
833 fname = isbackup and afile or bfile
832 fname = isbackup and afile or bfile
834 elif gooda:
833 elif gooda:
835 fname = afile
834 fname = afile
836
835
837 if not fname:
836 if not fname:
838 if not nullb:
837 if not nullb:
839 fname = isbackup and afile or bfile
838 fname = isbackup and afile or bfile
840 elif not nulla:
839 elif not nulla:
841 fname = afile
840 fname = afile
842 else:
841 else:
843 raise PatchError(_("undefined source and destination files"))
842 raise PatchError(_("undefined source and destination files"))
844
843
845 return fname, missing
844 return fname, missing
846
845
847 def scangitpatch(lr, firstline):
846 def scangitpatch(lr, firstline):
848 """
847 """
849 Git patches can emit:
848 Git patches can emit:
850 - rename a to b
849 - rename a to b
851 - change b
850 - change b
852 - copy a to c
851 - copy a to c
853 - change c
852 - change c
854
853
855 We cannot apply this sequence as-is, the renamed 'a' could not be
854 We cannot apply this sequence as-is, the renamed 'a' could not be
856 found for it would have been renamed already. And we cannot copy
855 found for it would have been renamed already. And we cannot copy
857 from 'b' instead because 'b' would have been changed already. So
856 from 'b' instead because 'b' would have been changed already. So
858 we scan the git patch for copy and rename commands so we can
857 we scan the git patch for copy and rename commands so we can
859 perform the copies ahead of time.
858 perform the copies ahead of time.
860 """
859 """
861 pos = 0
860 pos = 0
862 try:
861 try:
863 pos = lr.fp.tell()
862 pos = lr.fp.tell()
864 fp = lr.fp
863 fp = lr.fp
865 except IOError:
864 except IOError:
866 fp = cStringIO.StringIO(lr.fp.read())
865 fp = cStringIO.StringIO(lr.fp.read())
867 gitlr = linereader(fp, lr.textmode)
866 gitlr = linereader(fp, lr.textmode)
868 gitlr.push(firstline)
867 gitlr.push(firstline)
869 (dopatch, gitpatches) = readgitpatch(gitlr)
868 (dopatch, gitpatches) = readgitpatch(gitlr)
870 fp.seek(pos)
869 fp.seek(pos)
871 return dopatch, gitpatches
870 return dopatch, gitpatches
872
871
873 def iterhunks(ui, fp, sourcefile=None, textmode=False):
872 def iterhunks(ui, fp, sourcefile=None):
874 """Read a patch and yield the following events:
873 """Read a patch and yield the following events:
875 - ("file", afile, bfile, firsthunk): select a new target file.
874 - ("file", afile, bfile, firsthunk): select a new target file.
876 - ("hunk", hunk): a new hunk is ready to be applied, follows a
875 - ("hunk", hunk): a new hunk is ready to be applied, follows a
877 "file" event.
876 "file" event.
878 - ("git", gitchanges): current diff is in git format, gitchanges
877 - ("git", gitchanges): current diff is in git format, gitchanges
879 maps filenames to gitpatch records. Unique event.
878 maps filenames to gitpatch records. Unique event.
880
881 If textmode is True, input line-endings are normalized to LF.
882 """
879 """
883 changed = {}
880 changed = {}
884 current_hunk = None
881 current_hunk = None
885 afile = ""
882 afile = ""
886 bfile = ""
883 bfile = ""
887 state = None
884 state = None
888 hunknum = 0
885 hunknum = 0
889 emitfile = False
886 emitfile = False
890 git = False
887 git = False
891
888
892 # our states
889 # our states
893 BFILE = 1
890 BFILE = 1
894 context = None
891 context = None
895 lr = linereader(fp, textmode)
892 lr = linereader(fp)
896 dopatch = True
893 dopatch = True
897 # gitworkdone is True if a git operation (copy, rename, ...) was
894 # gitworkdone is True if a git operation (copy, rename, ...) was
898 # performed already for the current file. Useful when the file
895 # performed already for the current file. Useful when the file
899 # section may have no hunk.
896 # section may have no hunk.
900 gitworkdone = False
897 gitworkdone = False
901
898
902 while True:
899 while True:
903 newfile = False
900 newfile = False
904 x = lr.readline()
901 x = lr.readline()
905 if not x:
902 if not x:
906 break
903 break
907 if current_hunk:
904 if current_hunk:
908 if x.startswith('\ '):
905 if x.startswith('\ '):
909 current_hunk.fix_newline()
906 current_hunk.fix_newline()
910 yield 'hunk', current_hunk
907 yield 'hunk', current_hunk
911 current_hunk = None
908 current_hunk = None
912 gitworkdone = False
909 gitworkdone = False
913 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
910 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
914 ((context is not False) and x.startswith('***************')))):
911 ((context is not False) and x.startswith('***************')))):
915 try:
912 try:
916 if context is None and x.startswith('***************'):
913 if context is None and x.startswith('***************'):
917 context = True
914 context = True
918 gpatch = changed.get(bfile)
915 gpatch = changed.get(bfile)
919 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
916 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
920 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
917 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
921 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
918 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
922 except PatchError, err:
919 except PatchError, err:
923 ui.debug(err)
920 ui.debug(err)
924 current_hunk = None
921 current_hunk = None
925 continue
922 continue
926 hunknum += 1
923 hunknum += 1
927 if emitfile:
924 if emitfile:
928 emitfile = False
925 emitfile = False
929 yield 'file', (afile, bfile, current_hunk)
926 yield 'file', (afile, bfile, current_hunk)
930 elif state == BFILE and x.startswith('GIT binary patch'):
927 elif state == BFILE and x.startswith('GIT binary patch'):
931 current_hunk = binhunk(changed[bfile])
928 current_hunk = binhunk(changed[bfile])
932 hunknum += 1
929 hunknum += 1
933 if emitfile:
930 if emitfile:
934 emitfile = False
931 emitfile = False
935 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
932 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
936 current_hunk.extract(lr)
933 current_hunk.extract(lr)
937 elif x.startswith('diff --git'):
934 elif x.startswith('diff --git'):
938 # check for git diff, scanning the whole patch file if needed
935 # check for git diff, scanning the whole patch file if needed
939 m = gitre.match(x)
936 m = gitre.match(x)
940 if m:
937 if m:
941 afile, bfile = m.group(1, 2)
938 afile, bfile = m.group(1, 2)
942 if not git:
939 if not git:
943 git = True
940 git = True
944 dopatch, gitpatches = scangitpatch(lr, x)
941 dopatch, gitpatches = scangitpatch(lr, x)
945 yield 'git', gitpatches
942 yield 'git', gitpatches
946 for gp in gitpatches:
943 for gp in gitpatches:
947 changed[gp.path] = gp
944 changed[gp.path] = gp
948 # else error?
945 # else error?
949 # copy/rename + modify should modify target, not source
946 # copy/rename + modify should modify target, not source
950 gp = changed.get(bfile)
947 gp = changed.get(bfile)
951 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
948 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
952 afile = bfile
949 afile = bfile
953 gitworkdone = True
950 gitworkdone = True
954 newfile = True
951 newfile = True
955 elif x.startswith('---'):
952 elif x.startswith('---'):
956 # check for a unified diff
953 # check for a unified diff
957 l2 = lr.readline()
954 l2 = lr.readline()
958 if not l2.startswith('+++'):
955 if not l2.startswith('+++'):
959 lr.push(l2)
956 lr.push(l2)
960 continue
957 continue
961 newfile = True
958 newfile = True
962 context = False
959 context = False
963 afile = parsefilename(x)
960 afile = parsefilename(x)
964 bfile = parsefilename(l2)
961 bfile = parsefilename(l2)
965 elif x.startswith('***'):
962 elif x.startswith('***'):
966 # check for a context diff
963 # check for a context diff
967 l2 = lr.readline()
964 l2 = lr.readline()
968 if not l2.startswith('---'):
965 if not l2.startswith('---'):
969 lr.push(l2)
966 lr.push(l2)
970 continue
967 continue
971 l3 = lr.readline()
968 l3 = lr.readline()
972 lr.push(l3)
969 lr.push(l3)
973 if not l3.startswith("***************"):
970 if not l3.startswith("***************"):
974 lr.push(l2)
971 lr.push(l2)
975 continue
972 continue
976 newfile = True
973 newfile = True
977 context = True
974 context = True
978 afile = parsefilename(x)
975 afile = parsefilename(x)
979 bfile = parsefilename(l2)
976 bfile = parsefilename(l2)
980
977
981 if newfile:
978 if newfile:
982 emitfile = True
979 emitfile = True
983 state = BFILE
980 state = BFILE
984 hunknum = 0
981 hunknum = 0
985 if current_hunk:
982 if current_hunk:
986 if current_hunk.complete():
983 if current_hunk.complete():
987 yield 'hunk', current_hunk
984 yield 'hunk', current_hunk
988 else:
985 else:
989 raise PatchError(_("malformed patch %s %s") % (afile,
986 raise PatchError(_("malformed patch %s %s") % (afile,
990 current_hunk.desc))
987 current_hunk.desc))
991
988
992 if hunknum == 0 and dopatch and not gitworkdone:
989 if hunknum == 0 and dopatch and not gitworkdone:
993 raise NoHunks
990 raise NoHunks
994
991
995 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
992 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
996 """
993 """
997 Reads a patch from fp and tries to apply it.
994 Reads a patch from fp and tries to apply it.
998
995
999 The dict 'changed' is filled in with all of the filenames changed
996 The dict 'changed' is filled in with all of the filenames changed
1000 by the patch. Returns 0 for a clean patch, -1 if any rejects were
997 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1001 found and 1 if there was any fuzz.
998 found and 1 if there was any fuzz.
1002
999
1003 If 'eolmode' is 'strict', the patch content and patched file are
1000 If 'eolmode' is 'strict', the patch content and patched file are
1004 read in binary mode. Otherwise, line endings are ignored when
1001 read in binary mode. Otherwise, line endings are ignored when
1005 patching then normalized according to 'eolmode'.
1002 patching then normalized according to 'eolmode'.
1006 """
1003 """
1007 rejects = 0
1004 rejects = 0
1008 err = 0
1005 err = 0
1009 current_file = None
1006 current_file = None
1010 gitpatches = None
1007 gitpatches = None
1011 opener = util.opener(os.getcwd())
1008 opener = util.opener(os.getcwd())
1012 # In 'auto' mode, we must preserve original eols if target file
1013 # eols are undefined. Otherwise, hunk data will be normalized
1014 # later.
1015 textmode = eolmode not in ('strict', 'auto')
1016
1009
1017 def closefile():
1010 def closefile():
1018 if not current_file:
1011 if not current_file:
1019 return 0
1012 return 0
1020 current_file.close()
1013 current_file.close()
1021 return len(current_file.rej)
1014 return len(current_file.rej)
1022
1015
1023 for state, values in iterhunks(ui, fp, sourcefile, textmode):
1016 for state, values in iterhunks(ui, fp, sourcefile):
1024 if state == 'hunk':
1017 if state == 'hunk':
1025 if not current_file:
1018 if not current_file:
1026 continue
1019 continue
1027 current_hunk = values
1020 current_hunk = values
1028 ret = current_file.apply(current_hunk)
1021 ret = current_file.apply(current_hunk)
1029 if ret >= 0:
1022 if ret >= 0:
1030 changed.setdefault(current_file.fname, None)
1023 changed.setdefault(current_file.fname, None)
1031 if ret > 0:
1024 if ret > 0:
1032 err = 1
1025 err = 1
1033 elif state == 'file':
1026 elif state == 'file':
1034 rejects += closefile()
1027 rejects += closefile()
1035 afile, bfile, first_hunk = values
1028 afile, bfile, first_hunk = values
1036 try:
1029 try:
1037 if sourcefile:
1030 if sourcefile:
1038 current_file = patchfile(ui, sourcefile, opener, eolmode=eolmode)
1031 current_file = patchfile(ui, sourcefile, opener, eolmode=eolmode)
1039 else:
1032 else:
1040 current_file, missing = selectfile(afile, bfile, first_hunk,
1033 current_file, missing = selectfile(afile, bfile, first_hunk,
1041 strip)
1034 strip)
1042 current_file = patchfile(ui, current_file, opener, missing, eolmode)
1035 current_file = patchfile(ui, current_file, opener, missing, eolmode)
1043 except PatchError, err:
1036 except PatchError, err:
1044 ui.warn(str(err) + '\n')
1037 ui.warn(str(err) + '\n')
1045 current_file, current_hunk = None, None
1038 current_file, current_hunk = None, None
1046 rejects += 1
1039 rejects += 1
1047 continue
1040 continue
1048 elif state == 'git':
1041 elif state == 'git':
1049 gitpatches = values
1042 gitpatches = values
1050 cwd = os.getcwd()
1043 cwd = os.getcwd()
1051 for gp in gitpatches:
1044 for gp in gitpatches:
1052 if gp.op in ('COPY', 'RENAME'):
1045 if gp.op in ('COPY', 'RENAME'):
1053 copyfile(gp.oldpath, gp.path, cwd)
1046 copyfile(gp.oldpath, gp.path, cwd)
1054 changed[gp.path] = gp
1047 changed[gp.path] = gp
1055 else:
1048 else:
1056 raise util.Abort(_('unsupported parser state: %s') % state)
1049 raise util.Abort(_('unsupported parser state: %s') % state)
1057
1050
1058 rejects += closefile()
1051 rejects += closefile()
1059
1052
1060 if rejects:
1053 if rejects:
1061 return -1
1054 return -1
1062 return err
1055 return err
1063
1056
1064 def diffopts(ui, opts=None, untrusted=False):
1057 def diffopts(ui, opts=None, untrusted=False):
1065 def get(key, name=None, getter=ui.configbool):
1058 def get(key, name=None, getter=ui.configbool):
1066 return ((opts and opts.get(key)) or
1059 return ((opts and opts.get(key)) or
1067 getter('diff', name or key, None, untrusted=untrusted))
1060 getter('diff', name or key, None, untrusted=untrusted))
1068 return mdiff.diffopts(
1061 return mdiff.diffopts(
1069 text=opts and opts.get('text'),
1062 text=opts and opts.get('text'),
1070 git=get('git'),
1063 git=get('git'),
1071 nodates=get('nodates'),
1064 nodates=get('nodates'),
1072 showfunc=get('show_function', 'showfunc'),
1065 showfunc=get('show_function', 'showfunc'),
1073 ignorews=get('ignore_all_space', 'ignorews'),
1066 ignorews=get('ignore_all_space', 'ignorews'),
1074 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1067 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1075 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1068 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1076 context=get('unified', getter=ui.config))
1069 context=get('unified', getter=ui.config))
1077
1070
1078 def updatedir(ui, repo, patches, similarity=0):
1071 def updatedir(ui, repo, patches, similarity=0):
1079 '''Update dirstate after patch application according to metadata'''
1072 '''Update dirstate after patch application according to metadata'''
1080 if not patches:
1073 if not patches:
1081 return
1074 return
1082 copies = []
1075 copies = []
1083 removes = set()
1076 removes = set()
1084 cfiles = patches.keys()
1077 cfiles = patches.keys()
1085 cwd = repo.getcwd()
1078 cwd = repo.getcwd()
1086 if cwd:
1079 if cwd:
1087 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1080 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1088 for f in patches:
1081 for f in patches:
1089 gp = patches[f]
1082 gp = patches[f]
1090 if not gp:
1083 if not gp:
1091 continue
1084 continue
1092 if gp.op == 'RENAME':
1085 if gp.op == 'RENAME':
1093 copies.append((gp.oldpath, gp.path))
1086 copies.append((gp.oldpath, gp.path))
1094 removes.add(gp.oldpath)
1087 removes.add(gp.oldpath)
1095 elif gp.op == 'COPY':
1088 elif gp.op == 'COPY':
1096 copies.append((gp.oldpath, gp.path))
1089 copies.append((gp.oldpath, gp.path))
1097 elif gp.op == 'DELETE':
1090 elif gp.op == 'DELETE':
1098 removes.add(gp.path)
1091 removes.add(gp.path)
1099 for src, dst in copies:
1092 for src, dst in copies:
1100 repo.copy(src, dst)
1093 repo.copy(src, dst)
1101 if (not similarity) and removes:
1094 if (not similarity) and removes:
1102 repo.remove(sorted(removes), True)
1095 repo.remove(sorted(removes), True)
1103 for f in patches:
1096 for f in patches:
1104 gp = patches[f]
1097 gp = patches[f]
1105 if gp and gp.mode:
1098 if gp and gp.mode:
1106 islink, isexec = gp.mode
1099 islink, isexec = gp.mode
1107 dst = repo.wjoin(gp.path)
1100 dst = repo.wjoin(gp.path)
1108 # patch won't create empty files
1101 # patch won't create empty files
1109 if gp.op == 'ADD' and not os.path.exists(dst):
1102 if gp.op == 'ADD' and not os.path.exists(dst):
1110 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1103 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1111 repo.wwrite(gp.path, '', flags)
1104 repo.wwrite(gp.path, '', flags)
1112 elif gp.op != 'DELETE':
1105 elif gp.op != 'DELETE':
1113 util.set_flags(dst, islink, isexec)
1106 util.set_flags(dst, islink, isexec)
1114 cmdutil.addremove(repo, cfiles, similarity=similarity)
1107 cmdutil.addremove(repo, cfiles, similarity=similarity)
1115 files = patches.keys()
1108 files = patches.keys()
1116 files.extend([r for r in removes if r not in files])
1109 files.extend([r for r in removes if r not in files])
1117 return sorted(files)
1110 return sorted(files)
1118
1111
1119 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1112 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1120 """use <patcher> to apply <patchname> to the working directory.
1113 """use <patcher> to apply <patchname> to the working directory.
1121 returns whether patch was applied with fuzz factor."""
1114 returns whether patch was applied with fuzz factor."""
1122
1115
1123 fuzz = False
1116 fuzz = False
1124 if cwd:
1117 if cwd:
1125 args.append('-d %s' % util.shellquote(cwd))
1118 args.append('-d %s' % util.shellquote(cwd))
1126 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1119 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1127 util.shellquote(patchname)))
1120 util.shellquote(patchname)))
1128
1121
1129 for line in fp:
1122 for line in fp:
1130 line = line.rstrip()
1123 line = line.rstrip()
1131 ui.note(line + '\n')
1124 ui.note(line + '\n')
1132 if line.startswith('patching file '):
1125 if line.startswith('patching file '):
1133 pf = util.parse_patch_output(line)
1126 pf = util.parse_patch_output(line)
1134 printed_file = False
1127 printed_file = False
1135 files.setdefault(pf, None)
1128 files.setdefault(pf, None)
1136 elif line.find('with fuzz') >= 0:
1129 elif line.find('with fuzz') >= 0:
1137 fuzz = True
1130 fuzz = True
1138 if not printed_file:
1131 if not printed_file:
1139 ui.warn(pf + '\n')
1132 ui.warn(pf + '\n')
1140 printed_file = True
1133 printed_file = True
1141 ui.warn(line + '\n')
1134 ui.warn(line + '\n')
1142 elif line.find('saving rejects to file') >= 0:
1135 elif line.find('saving rejects to file') >= 0:
1143 ui.warn(line + '\n')
1136 ui.warn(line + '\n')
1144 elif line.find('FAILED') >= 0:
1137 elif line.find('FAILED') >= 0:
1145 if not printed_file:
1138 if not printed_file:
1146 ui.warn(pf + '\n')
1139 ui.warn(pf + '\n')
1147 printed_file = True
1140 printed_file = True
1148 ui.warn(line + '\n')
1141 ui.warn(line + '\n')
1149 code = fp.close()
1142 code = fp.close()
1150 if code:
1143 if code:
1151 raise PatchError(_("patch command failed: %s") %
1144 raise PatchError(_("patch command failed: %s") %
1152 util.explain_exit(code)[0])
1145 util.explain_exit(code)[0])
1153 return fuzz
1146 return fuzz
1154
1147
1155 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1148 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1156 """use builtin patch to apply <patchobj> to the working directory.
1149 """use builtin patch to apply <patchobj> to the working directory.
1157 returns whether patch was applied with fuzz factor."""
1150 returns whether patch was applied with fuzz factor."""
1158
1151
1159 if files is None:
1152 if files is None:
1160 files = {}
1153 files = {}
1161 if eolmode is None:
1154 if eolmode is None:
1162 eolmode = ui.config('patch', 'eol', 'strict')
1155 eolmode = ui.config('patch', 'eol', 'strict')
1163 if eolmode.lower() not in eolmodes:
1156 if eolmode.lower() not in eolmodes:
1164 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1157 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1165 eolmode = eolmode.lower()
1158 eolmode = eolmode.lower()
1166
1159
1167 try:
1160 try:
1168 fp = open(patchobj, 'rb')
1161 fp = open(patchobj, 'rb')
1169 except TypeError:
1162 except TypeError:
1170 fp = patchobj
1163 fp = patchobj
1171 if cwd:
1164 if cwd:
1172 curdir = os.getcwd()
1165 curdir = os.getcwd()
1173 os.chdir(cwd)
1166 os.chdir(cwd)
1174 try:
1167 try:
1175 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1168 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1176 finally:
1169 finally:
1177 if cwd:
1170 if cwd:
1178 os.chdir(curdir)
1171 os.chdir(curdir)
1179 if ret < 0:
1172 if ret < 0:
1180 raise PatchError
1173 raise PatchError
1181 return ret > 0
1174 return ret > 0
1182
1175
1183 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1176 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1184 """Apply <patchname> to the working directory.
1177 """Apply <patchname> to the working directory.
1185
1178
1186 'eolmode' specifies how end of lines should be handled. It can be:
1179 'eolmode' specifies how end of lines should be handled. It can be:
1187 - 'strict': inputs are read in binary mode, EOLs are preserved
1180 - 'strict': inputs are read in binary mode, EOLs are preserved
1188 - 'crlf': EOLs are ignored when patching and reset to CRLF
1181 - 'crlf': EOLs are ignored when patching and reset to CRLF
1189 - 'lf': EOLs are ignored when patching and reset to LF
1182 - 'lf': EOLs are ignored when patching and reset to LF
1190 - None: get it from user settings, default to 'strict'
1183 - None: get it from user settings, default to 'strict'
1191 'eolmode' is ignored when using an external patcher program.
1184 'eolmode' is ignored when using an external patcher program.
1192
1185
1193 Returns whether patch was applied with fuzz factor.
1186 Returns whether patch was applied with fuzz factor.
1194 """
1187 """
1195 patcher = ui.config('ui', 'patch')
1188 patcher = ui.config('ui', 'patch')
1196 args = []
1189 args = []
1197 if files is None:
1190 if files is None:
1198 files = {}
1191 files = {}
1199 try:
1192 try:
1200 if patcher:
1193 if patcher:
1201 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1194 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1202 files)
1195 files)
1203 else:
1196 else:
1204 try:
1197 try:
1205 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1198 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1206 except NoHunks:
1199 except NoHunks:
1207 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1200 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1208 ui.debug('no valid hunks found; trying with %r instead\n' %
1201 ui.debug('no valid hunks found; trying with %r instead\n' %
1209 patcher)
1202 patcher)
1210 if util.needbinarypatch():
1203 if util.needbinarypatch():
1211 args.append('--binary')
1204 args.append('--binary')
1212 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1205 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1213 files)
1206 files)
1214 except PatchError, err:
1207 except PatchError, err:
1215 s = str(err)
1208 s = str(err)
1216 if s:
1209 if s:
1217 raise util.Abort(s)
1210 raise util.Abort(s)
1218 else:
1211 else:
1219 raise util.Abort(_('patch failed to apply'))
1212 raise util.Abort(_('patch failed to apply'))
1220
1213
1221 def b85diff(to, tn):
1214 def b85diff(to, tn):
1222 '''print base85-encoded binary diff'''
1215 '''print base85-encoded binary diff'''
1223 def gitindex(text):
1216 def gitindex(text):
1224 if not text:
1217 if not text:
1225 return '0' * 40
1218 return '0' * 40
1226 l = len(text)
1219 l = len(text)
1227 s = util.sha1('blob %d\0' % l)
1220 s = util.sha1('blob %d\0' % l)
1228 s.update(text)
1221 s.update(text)
1229 return s.hexdigest()
1222 return s.hexdigest()
1230
1223
1231 def fmtline(line):
1224 def fmtline(line):
1232 l = len(line)
1225 l = len(line)
1233 if l <= 26:
1226 if l <= 26:
1234 l = chr(ord('A') + l - 1)
1227 l = chr(ord('A') + l - 1)
1235 else:
1228 else:
1236 l = chr(l - 26 + ord('a') - 1)
1229 l = chr(l - 26 + ord('a') - 1)
1237 return '%c%s\n' % (l, base85.b85encode(line, True))
1230 return '%c%s\n' % (l, base85.b85encode(line, True))
1238
1231
1239 def chunk(text, csize=52):
1232 def chunk(text, csize=52):
1240 l = len(text)
1233 l = len(text)
1241 i = 0
1234 i = 0
1242 while i < l:
1235 while i < l:
1243 yield text[i:i+csize]
1236 yield text[i:i+csize]
1244 i += csize
1237 i += csize
1245
1238
1246 tohash = gitindex(to)
1239 tohash = gitindex(to)
1247 tnhash = gitindex(tn)
1240 tnhash = gitindex(tn)
1248 if tohash == tnhash:
1241 if tohash == tnhash:
1249 return ""
1242 return ""
1250
1243
1251 # TODO: deltas
1244 # TODO: deltas
1252 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1245 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1253 (tohash, tnhash, len(tn))]
1246 (tohash, tnhash, len(tn))]
1254 for l in chunk(zlib.compress(tn)):
1247 for l in chunk(zlib.compress(tn)):
1255 ret.append(fmtline(l))
1248 ret.append(fmtline(l))
1256 ret.append('\n')
1249 ret.append('\n')
1257 return ''.join(ret)
1250 return ''.join(ret)
1258
1251
1259 def _addmodehdr(header, omode, nmode):
1252 def _addmodehdr(header, omode, nmode):
1260 if omode != nmode:
1253 if omode != nmode:
1261 header.append('old mode %s\n' % omode)
1254 header.append('old mode %s\n' % omode)
1262 header.append('new mode %s\n' % nmode)
1255 header.append('new mode %s\n' % nmode)
1263
1256
1264 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1257 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1265 '''yields diff of changes to files between two nodes, or node and
1258 '''yields diff of changes to files between two nodes, or node and
1266 working directory.
1259 working directory.
1267
1260
1268 if node1 is None, use first dirstate parent instead.
1261 if node1 is None, use first dirstate parent instead.
1269 if node2 is None, compare node1 with working directory.'''
1262 if node2 is None, compare node1 with working directory.'''
1270
1263
1271 if opts is None:
1264 if opts is None:
1272 opts = mdiff.defaultopts
1265 opts = mdiff.defaultopts
1273
1266
1274 if not node1 and not node2:
1267 if not node1 and not node2:
1275 node1 = repo.dirstate.parents()[0]
1268 node1 = repo.dirstate.parents()[0]
1276
1269
1277 def lrugetfilectx():
1270 def lrugetfilectx():
1278 cache = {}
1271 cache = {}
1279 order = []
1272 order = []
1280 def getfilectx(f, ctx):
1273 def getfilectx(f, ctx):
1281 fctx = ctx.filectx(f, filelog=cache.get(f))
1274 fctx = ctx.filectx(f, filelog=cache.get(f))
1282 if f not in cache:
1275 if f not in cache:
1283 if len(cache) > 20:
1276 if len(cache) > 20:
1284 del cache[order.pop(0)]
1277 del cache[order.pop(0)]
1285 cache[f] = fctx.filelog()
1278 cache[f] = fctx.filelog()
1286 else:
1279 else:
1287 order.remove(f)
1280 order.remove(f)
1288 order.append(f)
1281 order.append(f)
1289 return fctx
1282 return fctx
1290 return getfilectx
1283 return getfilectx
1291 getfilectx = lrugetfilectx()
1284 getfilectx = lrugetfilectx()
1292
1285
1293 ctx1 = repo[node1]
1286 ctx1 = repo[node1]
1294 ctx2 = repo[node2]
1287 ctx2 = repo[node2]
1295
1288
1296 if not changes:
1289 if not changes:
1297 changes = repo.status(ctx1, ctx2, match=match)
1290 changes = repo.status(ctx1, ctx2, match=match)
1298 modified, added, removed = changes[:3]
1291 modified, added, removed = changes[:3]
1299
1292
1300 if not modified and not added and not removed:
1293 if not modified and not added and not removed:
1301 return
1294 return
1302
1295
1303 date1 = util.datestr(ctx1.date())
1296 date1 = util.datestr(ctx1.date())
1304 man1 = ctx1.manifest()
1297 man1 = ctx1.manifest()
1305
1298
1306 if repo.ui.quiet:
1299 if repo.ui.quiet:
1307 r = None
1300 r = None
1308 else:
1301 else:
1309 hexfunc = repo.ui.debugflag and hex or short
1302 hexfunc = repo.ui.debugflag and hex or short
1310 r = [hexfunc(node) for node in [node1, node2] if node]
1303 r = [hexfunc(node) for node in [node1, node2] if node]
1311
1304
1312 if opts.git:
1305 if opts.git:
1313 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1306 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1314 copy = copy.copy()
1307 copy = copy.copy()
1315 for k, v in copy.items():
1308 for k, v in copy.items():
1316 copy[v] = k
1309 copy[v] = k
1317
1310
1318 gone = set()
1311 gone = set()
1319 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1312 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1320
1313
1321 for f in sorted(modified + added + removed):
1314 for f in sorted(modified + added + removed):
1322 to = None
1315 to = None
1323 tn = None
1316 tn = None
1324 dodiff = True
1317 dodiff = True
1325 header = []
1318 header = []
1326 if f in man1:
1319 if f in man1:
1327 to = getfilectx(f, ctx1).data()
1320 to = getfilectx(f, ctx1).data()
1328 if f not in removed:
1321 if f not in removed:
1329 tn = getfilectx(f, ctx2).data()
1322 tn = getfilectx(f, ctx2).data()
1330 a, b = f, f
1323 a, b = f, f
1331 if opts.git:
1324 if opts.git:
1332 if f in added:
1325 if f in added:
1333 mode = gitmode[ctx2.flags(f)]
1326 mode = gitmode[ctx2.flags(f)]
1334 if f in copy:
1327 if f in copy:
1335 a = copy[f]
1328 a = copy[f]
1336 omode = gitmode[man1.flags(a)]
1329 omode = gitmode[man1.flags(a)]
1337 _addmodehdr(header, omode, mode)
1330 _addmodehdr(header, omode, mode)
1338 if a in removed and a not in gone:
1331 if a in removed and a not in gone:
1339 op = 'rename'
1332 op = 'rename'
1340 gone.add(a)
1333 gone.add(a)
1341 else:
1334 else:
1342 op = 'copy'
1335 op = 'copy'
1343 header.append('%s from %s\n' % (op, a))
1336 header.append('%s from %s\n' % (op, a))
1344 header.append('%s to %s\n' % (op, f))
1337 header.append('%s to %s\n' % (op, f))
1345 to = getfilectx(a, ctx1).data()
1338 to = getfilectx(a, ctx1).data()
1346 else:
1339 else:
1347 header.append('new file mode %s\n' % mode)
1340 header.append('new file mode %s\n' % mode)
1348 if util.binary(tn):
1341 if util.binary(tn):
1349 dodiff = 'binary'
1342 dodiff = 'binary'
1350 elif f in removed:
1343 elif f in removed:
1351 # have we already reported a copy above?
1344 # have we already reported a copy above?
1352 if f in copy and copy[f] in added and copy[copy[f]] == f:
1345 if f in copy and copy[f] in added and copy[copy[f]] == f:
1353 dodiff = False
1346 dodiff = False
1354 else:
1347 else:
1355 header.append('deleted file mode %s\n' %
1348 header.append('deleted file mode %s\n' %
1356 gitmode[man1.flags(f)])
1349 gitmode[man1.flags(f)])
1357 else:
1350 else:
1358 omode = gitmode[man1.flags(f)]
1351 omode = gitmode[man1.flags(f)]
1359 nmode = gitmode[ctx2.flags(f)]
1352 nmode = gitmode[ctx2.flags(f)]
1360 _addmodehdr(header, omode, nmode)
1353 _addmodehdr(header, omode, nmode)
1361 if util.binary(to) or util.binary(tn):
1354 if util.binary(to) or util.binary(tn):
1362 dodiff = 'binary'
1355 dodiff = 'binary'
1363 r = None
1356 r = None
1364 header.insert(0, mdiff.diffline(r, a, b, opts))
1357 header.insert(0, mdiff.diffline(r, a, b, opts))
1365 if dodiff:
1358 if dodiff:
1366 if dodiff == 'binary':
1359 if dodiff == 'binary':
1367 text = b85diff(to, tn)
1360 text = b85diff(to, tn)
1368 else:
1361 else:
1369 text = mdiff.unidiff(to, date1,
1362 text = mdiff.unidiff(to, date1,
1370 # ctx2 date may be dynamic
1363 # ctx2 date may be dynamic
1371 tn, util.datestr(ctx2.date()),
1364 tn, util.datestr(ctx2.date()),
1372 a, b, r, opts=opts)
1365 a, b, r, opts=opts)
1373 if header and (text or len(header) > 1):
1366 if header and (text or len(header) > 1):
1374 yield ''.join(header)
1367 yield ''.join(header)
1375 if text:
1368 if text:
1376 yield text
1369 yield text
1377
1370
1378 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1371 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1379 opts=None):
1372 opts=None):
1380 '''export changesets as hg patches.'''
1373 '''export changesets as hg patches.'''
1381
1374
1382 total = len(revs)
1375 total = len(revs)
1383 revwidth = max([len(str(rev)) for rev in revs])
1376 revwidth = max([len(str(rev)) for rev in revs])
1384
1377
1385 def single(rev, seqno, fp):
1378 def single(rev, seqno, fp):
1386 ctx = repo[rev]
1379 ctx = repo[rev]
1387 node = ctx.node()
1380 node = ctx.node()
1388 parents = [p.node() for p in ctx.parents() if p]
1381 parents = [p.node() for p in ctx.parents() if p]
1389 branch = ctx.branch()
1382 branch = ctx.branch()
1390 if switch_parent:
1383 if switch_parent:
1391 parents.reverse()
1384 parents.reverse()
1392 prev = (parents and parents[0]) or nullid
1385 prev = (parents and parents[0]) or nullid
1393
1386
1394 if not fp:
1387 if not fp:
1395 fp = cmdutil.make_file(repo, template, node, total=total,
1388 fp = cmdutil.make_file(repo, template, node, total=total,
1396 seqno=seqno, revwidth=revwidth,
1389 seqno=seqno, revwidth=revwidth,
1397 mode='ab')
1390 mode='ab')
1398 if fp != sys.stdout and hasattr(fp, 'name'):
1391 if fp != sys.stdout and hasattr(fp, 'name'):
1399 repo.ui.note("%s\n" % fp.name)
1392 repo.ui.note("%s\n" % fp.name)
1400
1393
1401 fp.write("# HG changeset patch\n")
1394 fp.write("# HG changeset patch\n")
1402 fp.write("# User %s\n" % ctx.user())
1395 fp.write("# User %s\n" % ctx.user())
1403 fp.write("# Date %d %d\n" % ctx.date())
1396 fp.write("# Date %d %d\n" % ctx.date())
1404 if branch and (branch != 'default'):
1397 if branch and (branch != 'default'):
1405 fp.write("# Branch %s\n" % branch)
1398 fp.write("# Branch %s\n" % branch)
1406 fp.write("# Node ID %s\n" % hex(node))
1399 fp.write("# Node ID %s\n" % hex(node))
1407 fp.write("# Parent %s\n" % hex(prev))
1400 fp.write("# Parent %s\n" % hex(prev))
1408 if len(parents) > 1:
1401 if len(parents) > 1:
1409 fp.write("# Parent %s\n" % hex(parents[1]))
1402 fp.write("# Parent %s\n" % hex(parents[1]))
1410 fp.write(ctx.description().rstrip())
1403 fp.write(ctx.description().rstrip())
1411 fp.write("\n\n")
1404 fp.write("\n\n")
1412
1405
1413 for chunk in diff(repo, prev, node, opts=opts):
1406 for chunk in diff(repo, prev, node, opts=opts):
1414 fp.write(chunk)
1407 fp.write(chunk)
1415
1408
1416 for seqno, rev in enumerate(revs):
1409 for seqno, rev in enumerate(revs):
1417 single(rev, seqno+1, fp)
1410 single(rev, seqno+1, fp)
1418
1411
1419 def diffstatdata(lines):
1412 def diffstatdata(lines):
1420 filename, adds, removes = None, 0, 0
1413 filename, adds, removes = None, 0, 0
1421 for line in lines:
1414 for line in lines:
1422 if line.startswith('diff'):
1415 if line.startswith('diff'):
1423 if filename:
1416 if filename:
1424 isbinary = adds == 0 and removes == 0
1417 isbinary = adds == 0 and removes == 0
1425 yield (filename, adds, removes, isbinary)
1418 yield (filename, adds, removes, isbinary)
1426 # set numbers to 0 anyway when starting new file
1419 # set numbers to 0 anyway when starting new file
1427 adds, removes = 0, 0
1420 adds, removes = 0, 0
1428 if line.startswith('diff --git'):
1421 if line.startswith('diff --git'):
1429 filename = gitre.search(line).group(1)
1422 filename = gitre.search(line).group(1)
1430 else:
1423 else:
1431 # format: "diff -r ... -r ... filename"
1424 # format: "diff -r ... -r ... filename"
1432 filename = line.split(None, 5)[-1]
1425 filename = line.split(None, 5)[-1]
1433 elif line.startswith('+') and not line.startswith('+++'):
1426 elif line.startswith('+') and not line.startswith('+++'):
1434 adds += 1
1427 adds += 1
1435 elif line.startswith('-') and not line.startswith('---'):
1428 elif line.startswith('-') and not line.startswith('---'):
1436 removes += 1
1429 removes += 1
1437 if filename:
1430 if filename:
1438 isbinary = adds == 0 and removes == 0
1431 isbinary = adds == 0 and removes == 0
1439 yield (filename, adds, removes, isbinary)
1432 yield (filename, adds, removes, isbinary)
1440
1433
1441 def diffstat(lines, width=80, git=False):
1434 def diffstat(lines, width=80, git=False):
1442 output = []
1435 output = []
1443 stats = list(diffstatdata(lines))
1436 stats = list(diffstatdata(lines))
1444
1437
1445 maxtotal, maxname = 0, 0
1438 maxtotal, maxname = 0, 0
1446 totaladds, totalremoves = 0, 0
1439 totaladds, totalremoves = 0, 0
1447 hasbinary = False
1440 hasbinary = False
1448 for filename, adds, removes, isbinary in stats:
1441 for filename, adds, removes, isbinary in stats:
1449 totaladds += adds
1442 totaladds += adds
1450 totalremoves += removes
1443 totalremoves += removes
1451 maxname = max(maxname, len(filename))
1444 maxname = max(maxname, len(filename))
1452 maxtotal = max(maxtotal, adds+removes)
1445 maxtotal = max(maxtotal, adds+removes)
1453 if isbinary:
1446 if isbinary:
1454 hasbinary = True
1447 hasbinary = True
1455
1448
1456 countwidth = len(str(maxtotal))
1449 countwidth = len(str(maxtotal))
1457 if hasbinary and countwidth < 3:
1450 if hasbinary and countwidth < 3:
1458 countwidth = 3
1451 countwidth = 3
1459 graphwidth = width - countwidth - maxname - 6
1452 graphwidth = width - countwidth - maxname - 6
1460 if graphwidth < 10:
1453 if graphwidth < 10:
1461 graphwidth = 10
1454 graphwidth = 10
1462
1455
1463 def scale(i):
1456 def scale(i):
1464 if maxtotal <= graphwidth:
1457 if maxtotal <= graphwidth:
1465 return i
1458 return i
1466 # If diffstat runs out of room it doesn't print anything,
1459 # If diffstat runs out of room it doesn't print anything,
1467 # which isn't very useful, so always print at least one + or -
1460 # which isn't very useful, so always print at least one + or -
1468 # if there were at least some changes.
1461 # if there were at least some changes.
1469 return max(i * graphwidth // maxtotal, int(bool(i)))
1462 return max(i * graphwidth // maxtotal, int(bool(i)))
1470
1463
1471 for filename, adds, removes, isbinary in stats:
1464 for filename, adds, removes, isbinary in stats:
1472 if git and isbinary:
1465 if git and isbinary:
1473 count = 'Bin'
1466 count = 'Bin'
1474 else:
1467 else:
1475 count = adds + removes
1468 count = adds + removes
1476 pluses = '+' * scale(adds)
1469 pluses = '+' * scale(adds)
1477 minuses = '-' * scale(removes)
1470 minuses = '-' * scale(removes)
1478 output.append(' %-*s | %*s %s%s\n' % (maxname, filename, countwidth,
1471 output.append(' %-*s | %*s %s%s\n' % (maxname, filename, countwidth,
1479 count, pluses, minuses))
1472 count, pluses, minuses))
1480
1473
1481 if stats:
1474 if stats:
1482 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1475 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1483 % (len(stats), totaladds, totalremoves))
1476 % (len(stats), totaladds, totalremoves))
1484
1477
1485 return ''.join(output)
1478 return ''.join(output)
@@ -1,64 +1,64 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # Test interactions between mq and patch.eol
3 # Test interactions between mq and patch.eol
4
4
5 echo "[extensions]" >> $HGRCPATH
5 echo "[extensions]" >> $HGRCPATH
6 echo "mq=" >> $HGRCPATH
6 echo "mq=" >> $HGRCPATH
7
7
8 cat > makepatch.py <<EOF
8 cat > makepatch.py <<EOF
9 f = file('eol.diff', 'wb')
9 f = file('eol.diff', 'wb')
10 w = f.write
10 w = f.write
11 w('test message\n')
11 w('test message\n')
12 w('diff --git a/a b/a\n')
12 w('diff --git a/a b/a\n')
13 w('--- a/a\n')
13 w('--- a/a\n')
14 w('+++ b/a\n')
14 w('+++ b/a\n')
15 w('@@ -1,5 +1,5 @@\n')
15 w('@@ -1,5 +1,5 @@\n')
16 w(' a\n')
16 w(' a\n')
17 w('-b\r\n')
17 w('-b\r\n')
18 w('+y\r\n')
18 w('+y\r\n')
19 w(' c\r\n')
19 w(' c\r\n')
20 w(' d\n')
20 w(' d\n')
21 w('-e\n')
21 w('-e\n')
22 w('\ No newline at end of file\n')
22 w('\ No newline at end of file\n')
23 w('+z\r\n')
23 w('+z\n')
24 w('\ No newline at end of file\r\n')
24 w('\ No newline at end of file\r\n')
25 EOF
25 EOF
26
26
27 cat > cateol.py <<EOF
27 cat > cateol.py <<EOF
28 import sys
28 import sys
29 for line in file(sys.argv[1], 'rb'):
29 for line in file(sys.argv[1], 'rb'):
30 line = line.replace('\r', '<CR>')
30 line = line.replace('\r', '<CR>')
31 line = line.replace('\n', '<LF>')
31 line = line.replace('\n', '<LF>')
32 print line
32 print line
33 EOF
33 EOF
34
34
35 hg init repo
35 hg init repo
36 cd repo
36 cd repo
37 echo '\.diff' > .hgignore
37 echo '\.diff' > .hgignore
38 echo '\.rej' >> .hgignore
38 echo '\.rej' >> .hgignore
39
39
40 # Test different --eol values
40 # Test different --eol values
41 python -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
41 python -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
42 hg ci -Am adda
42 hg ci -Am adda
43 python ../makepatch.py
43 python ../makepatch.py
44 hg qimport eol.diff
44 hg qimport eol.diff
45 echo % should fail in strict mode
45 echo % should fail in strict mode
46 hg qpush
46 hg qpush
47 hg qpop
47 hg qpop
48 echo % invalid eol
48 echo % invalid eol
49 hg --config patch.eol='LFCR' qpush
49 hg --config patch.eol='LFCR' qpush
50 hg qpop
50 hg qpop
51 echo % force LF
51 echo % force LF
52 hg --config patch.eol='CRLF' qpush
52 hg --config patch.eol='CRLF' qpush
53 hg qrefresh
53 hg qrefresh
54 python ../cateol.py .hg/patches/eol.diff
54 python ../cateol.py .hg/patches/eol.diff
55 python ../cateol.py a
55 python ../cateol.py a
56 hg qpop
56 hg qpop
57 echo % push again forcing LF and compare revisions
57 echo % push again forcing LF and compare revisions
58 hg --config patch.eol='CRLF' qpush
58 hg --config patch.eol='CRLF' qpush
59 python ../cateol.py a
59 python ../cateol.py a
60 hg qpop
60 hg qpop
61 echo % push again without LF and compare revisions
61 echo % push again without LF and compare revisions
62 hg qpush
62 hg qpush
63 python ../cateol.py a
63 python ../cateol.py a
64 hg qpop
64 hg qpop
General Comments 0
You need to be logged in to leave comments. Login now