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