##// END OF EJS Templates
import: abort instead of crashing when copy source does not exist (issue5375)...
Ryan McElroy -
r30078:173bdb50 default
parent child Browse files
Show More
@@ -1,2606 +1,2608 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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import collections
11 import collections
12 import copy
12 import copy
13 import email
13 import email
14 import errno
14 import errno
15 import hashlib
15 import hashlib
16 import os
16 import os
17 import posixpath
17 import posixpath
18 import re
18 import re
19 import shutil
19 import shutil
20 import tempfile
20 import tempfile
21 import zlib
21 import zlib
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import (
24 from .node import (
25 hex,
25 hex,
26 short,
26 short,
27 )
27 )
28 from . import (
28 from . import (
29 base85,
29 base85,
30 copies,
30 copies,
31 diffhelpers,
31 diffhelpers,
32 encoding,
32 encoding,
33 error,
33 error,
34 mail,
34 mail,
35 mdiff,
35 mdiff,
36 pathutil,
36 pathutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40 stringio = util.stringio
40 stringio = util.stringio
41
41
42 gitre = re.compile('diff --git a/(.*) b/(.*)')
42 gitre = re.compile('diff --git a/(.*) b/(.*)')
43 tabsplitter = re.compile(r'(\t+|[^\t]+)')
43 tabsplitter = re.compile(r'(\t+|[^\t]+)')
44
44
45 class PatchError(Exception):
45 class PatchError(Exception):
46 pass
46 pass
47
47
48
48
49 # public functions
49 # public functions
50
50
51 def split(stream):
51 def split(stream):
52 '''return an iterator of individual patches from a stream'''
52 '''return an iterator of individual patches from a stream'''
53 def isheader(line, inheader):
53 def isheader(line, inheader):
54 if inheader and line[0] in (' ', '\t'):
54 if inheader and line[0] in (' ', '\t'):
55 # continuation
55 # continuation
56 return True
56 return True
57 if line[0] in (' ', '-', '+'):
57 if line[0] in (' ', '-', '+'):
58 # diff line - don't check for header pattern in there
58 # diff line - don't check for header pattern in there
59 return False
59 return False
60 l = line.split(': ', 1)
60 l = line.split(': ', 1)
61 return len(l) == 2 and ' ' not in l[0]
61 return len(l) == 2 and ' ' not in l[0]
62
62
63 def chunk(lines):
63 def chunk(lines):
64 return stringio(''.join(lines))
64 return stringio(''.join(lines))
65
65
66 def hgsplit(stream, cur):
66 def hgsplit(stream, cur):
67 inheader = True
67 inheader = True
68
68
69 for line in stream:
69 for line in stream:
70 if not line.strip():
70 if not line.strip():
71 inheader = False
71 inheader = False
72 if not inheader and line.startswith('# HG changeset patch'):
72 if not inheader and line.startswith('# HG changeset patch'):
73 yield chunk(cur)
73 yield chunk(cur)
74 cur = []
74 cur = []
75 inheader = True
75 inheader = True
76
76
77 cur.append(line)
77 cur.append(line)
78
78
79 if cur:
79 if cur:
80 yield chunk(cur)
80 yield chunk(cur)
81
81
82 def mboxsplit(stream, cur):
82 def mboxsplit(stream, cur):
83 for line in stream:
83 for line in stream:
84 if line.startswith('From '):
84 if line.startswith('From '):
85 for c in split(chunk(cur[1:])):
85 for c in split(chunk(cur[1:])):
86 yield c
86 yield c
87 cur = []
87 cur = []
88
88
89 cur.append(line)
89 cur.append(line)
90
90
91 if cur:
91 if cur:
92 for c in split(chunk(cur[1:])):
92 for c in split(chunk(cur[1:])):
93 yield c
93 yield c
94
94
95 def mimesplit(stream, cur):
95 def mimesplit(stream, cur):
96 def msgfp(m):
96 def msgfp(m):
97 fp = stringio()
97 fp = stringio()
98 g = email.Generator.Generator(fp, mangle_from_=False)
98 g = email.Generator.Generator(fp, mangle_from_=False)
99 g.flatten(m)
99 g.flatten(m)
100 fp.seek(0)
100 fp.seek(0)
101 return fp
101 return fp
102
102
103 for line in stream:
103 for line in stream:
104 cur.append(line)
104 cur.append(line)
105 c = chunk(cur)
105 c = chunk(cur)
106
106
107 m = email.Parser.Parser().parse(c)
107 m = email.Parser.Parser().parse(c)
108 if not m.is_multipart():
108 if not m.is_multipart():
109 yield msgfp(m)
109 yield msgfp(m)
110 else:
110 else:
111 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
111 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
112 for part in m.walk():
112 for part in m.walk():
113 ct = part.get_content_type()
113 ct = part.get_content_type()
114 if ct not in ok_types:
114 if ct not in ok_types:
115 continue
115 continue
116 yield msgfp(part)
116 yield msgfp(part)
117
117
118 def headersplit(stream, cur):
118 def headersplit(stream, cur):
119 inheader = False
119 inheader = False
120
120
121 for line in stream:
121 for line in stream:
122 if not inheader and isheader(line, inheader):
122 if not inheader and isheader(line, inheader):
123 yield chunk(cur)
123 yield chunk(cur)
124 cur = []
124 cur = []
125 inheader = True
125 inheader = True
126 if inheader and not isheader(line, inheader):
126 if inheader and not isheader(line, inheader):
127 inheader = False
127 inheader = False
128
128
129 cur.append(line)
129 cur.append(line)
130
130
131 if cur:
131 if cur:
132 yield chunk(cur)
132 yield chunk(cur)
133
133
134 def remainder(cur):
134 def remainder(cur):
135 yield chunk(cur)
135 yield chunk(cur)
136
136
137 class fiter(object):
137 class fiter(object):
138 def __init__(self, fp):
138 def __init__(self, fp):
139 self.fp = fp
139 self.fp = fp
140
140
141 def __iter__(self):
141 def __iter__(self):
142 return self
142 return self
143
143
144 def next(self):
144 def next(self):
145 l = self.fp.readline()
145 l = self.fp.readline()
146 if not l:
146 if not l:
147 raise StopIteration
147 raise StopIteration
148 return l
148 return l
149
149
150 inheader = False
150 inheader = False
151 cur = []
151 cur = []
152
152
153 mimeheaders = ['content-type']
153 mimeheaders = ['content-type']
154
154
155 if not util.safehasattr(stream, 'next'):
155 if not util.safehasattr(stream, 'next'):
156 # http responses, for example, have readline but not next
156 # http responses, for example, have readline but not next
157 stream = fiter(stream)
157 stream = fiter(stream)
158
158
159 for line in stream:
159 for line in stream:
160 cur.append(line)
160 cur.append(line)
161 if line.startswith('# HG changeset patch'):
161 if line.startswith('# HG changeset patch'):
162 return hgsplit(stream, cur)
162 return hgsplit(stream, cur)
163 elif line.startswith('From '):
163 elif line.startswith('From '):
164 return mboxsplit(stream, cur)
164 return mboxsplit(stream, cur)
165 elif isheader(line, inheader):
165 elif isheader(line, inheader):
166 inheader = True
166 inheader = True
167 if line.split(':', 1)[0].lower() in mimeheaders:
167 if line.split(':', 1)[0].lower() in mimeheaders:
168 # let email parser handle this
168 # let email parser handle this
169 return mimesplit(stream, cur)
169 return mimesplit(stream, cur)
170 elif line.startswith('--- ') and inheader:
170 elif line.startswith('--- ') and inheader:
171 # No evil headers seen by diff start, split by hand
171 # No evil headers seen by diff start, split by hand
172 return headersplit(stream, cur)
172 return headersplit(stream, cur)
173 # Not enough info, keep reading
173 # Not enough info, keep reading
174
174
175 # if we are here, we have a very plain patch
175 # if we are here, we have a very plain patch
176 return remainder(cur)
176 return remainder(cur)
177
177
178 ## Some facility for extensible patch parsing:
178 ## Some facility for extensible patch parsing:
179 # list of pairs ("header to match", "data key")
179 # list of pairs ("header to match", "data key")
180 patchheadermap = [('Date', 'date'),
180 patchheadermap = [('Date', 'date'),
181 ('Branch', 'branch'),
181 ('Branch', 'branch'),
182 ('Node ID', 'nodeid'),
182 ('Node ID', 'nodeid'),
183 ]
183 ]
184
184
185 def extract(ui, fileobj):
185 def extract(ui, fileobj):
186 '''extract patch from data read from fileobj.
186 '''extract patch from data read from fileobj.
187
187
188 patch can be a normal patch or contained in an email message.
188 patch can be a normal patch or contained in an email message.
189
189
190 return a dictionary. Standard keys are:
190 return a dictionary. Standard keys are:
191 - filename,
191 - filename,
192 - message,
192 - message,
193 - user,
193 - user,
194 - date,
194 - date,
195 - branch,
195 - branch,
196 - node,
196 - node,
197 - p1,
197 - p1,
198 - p2.
198 - p2.
199 Any item can be missing from the dictionary. If filename is missing,
199 Any item can be missing from the dictionary. If filename is missing,
200 fileobj did not contain a patch. Caller must unlink filename when done.'''
200 fileobj did not contain a patch. Caller must unlink filename when done.'''
201
201
202 # attempt to detect the start of a patch
202 # attempt to detect the start of a patch
203 # (this heuristic is borrowed from quilt)
203 # (this heuristic is borrowed from quilt)
204 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
204 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
205 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
205 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
206 r'---[ \t].*?^\+\+\+[ \t]|'
206 r'---[ \t].*?^\+\+\+[ \t]|'
207 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
207 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
208
208
209 data = {}
209 data = {}
210 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
210 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
211 tmpfp = os.fdopen(fd, 'w')
211 tmpfp = os.fdopen(fd, 'w')
212 try:
212 try:
213 msg = email.Parser.Parser().parse(fileobj)
213 msg = email.Parser.Parser().parse(fileobj)
214
214
215 subject = msg['Subject'] and mail.headdecode(msg['Subject'])
215 subject = msg['Subject'] and mail.headdecode(msg['Subject'])
216 data['user'] = msg['From'] and mail.headdecode(msg['From'])
216 data['user'] = msg['From'] and mail.headdecode(msg['From'])
217 if not subject and not data['user']:
217 if not subject and not data['user']:
218 # Not an email, restore parsed headers if any
218 # Not an email, restore parsed headers if any
219 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
219 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
220
220
221 # should try to parse msg['Date']
221 # should try to parse msg['Date']
222 parents = []
222 parents = []
223
223
224 if subject:
224 if subject:
225 if subject.startswith('[PATCH'):
225 if subject.startswith('[PATCH'):
226 pend = subject.find(']')
226 pend = subject.find(']')
227 if pend >= 0:
227 if pend >= 0:
228 subject = subject[pend + 1:].lstrip()
228 subject = subject[pend + 1:].lstrip()
229 subject = re.sub(r'\n[ \t]+', ' ', subject)
229 subject = re.sub(r'\n[ \t]+', ' ', subject)
230 ui.debug('Subject: %s\n' % subject)
230 ui.debug('Subject: %s\n' % subject)
231 if data['user']:
231 if data['user']:
232 ui.debug('From: %s\n' % data['user'])
232 ui.debug('From: %s\n' % data['user'])
233 diffs_seen = 0
233 diffs_seen = 0
234 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
234 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
235 message = ''
235 message = ''
236 for part in msg.walk():
236 for part in msg.walk():
237 content_type = part.get_content_type()
237 content_type = part.get_content_type()
238 ui.debug('Content-Type: %s\n' % content_type)
238 ui.debug('Content-Type: %s\n' % content_type)
239 if content_type not in ok_types:
239 if content_type not in ok_types:
240 continue
240 continue
241 payload = part.get_payload(decode=True)
241 payload = part.get_payload(decode=True)
242 m = diffre.search(payload)
242 m = diffre.search(payload)
243 if m:
243 if m:
244 hgpatch = False
244 hgpatch = False
245 hgpatchheader = False
245 hgpatchheader = False
246 ignoretext = False
246 ignoretext = False
247
247
248 ui.debug('found patch at byte %d\n' % m.start(0))
248 ui.debug('found patch at byte %d\n' % m.start(0))
249 diffs_seen += 1
249 diffs_seen += 1
250 cfp = stringio()
250 cfp = stringio()
251 for line in payload[:m.start(0)].splitlines():
251 for line in payload[:m.start(0)].splitlines():
252 if line.startswith('# HG changeset patch') and not hgpatch:
252 if line.startswith('# HG changeset patch') and not hgpatch:
253 ui.debug('patch generated by hg export\n')
253 ui.debug('patch generated by hg export\n')
254 hgpatch = True
254 hgpatch = True
255 hgpatchheader = True
255 hgpatchheader = True
256 # drop earlier commit message content
256 # drop earlier commit message content
257 cfp.seek(0)
257 cfp.seek(0)
258 cfp.truncate()
258 cfp.truncate()
259 subject = None
259 subject = None
260 elif hgpatchheader:
260 elif hgpatchheader:
261 if line.startswith('# User '):
261 if line.startswith('# User '):
262 data['user'] = line[7:]
262 data['user'] = line[7:]
263 ui.debug('From: %s\n' % data['user'])
263 ui.debug('From: %s\n' % data['user'])
264 elif line.startswith("# Parent "):
264 elif line.startswith("# Parent "):
265 parents.append(line[9:].lstrip())
265 parents.append(line[9:].lstrip())
266 elif line.startswith("# "):
266 elif line.startswith("# "):
267 for header, key in patchheadermap:
267 for header, key in patchheadermap:
268 prefix = '# %s ' % header
268 prefix = '# %s ' % header
269 if line.startswith(prefix):
269 if line.startswith(prefix):
270 data[key] = line[len(prefix):]
270 data[key] = line[len(prefix):]
271 else:
271 else:
272 hgpatchheader = False
272 hgpatchheader = False
273 elif line == '---':
273 elif line == '---':
274 ignoretext = True
274 ignoretext = True
275 if not hgpatchheader and not ignoretext:
275 if not hgpatchheader and not ignoretext:
276 cfp.write(line)
276 cfp.write(line)
277 cfp.write('\n')
277 cfp.write('\n')
278 message = cfp.getvalue()
278 message = cfp.getvalue()
279 if tmpfp:
279 if tmpfp:
280 tmpfp.write(payload)
280 tmpfp.write(payload)
281 if not payload.endswith('\n'):
281 if not payload.endswith('\n'):
282 tmpfp.write('\n')
282 tmpfp.write('\n')
283 elif not diffs_seen and message and content_type == 'text/plain':
283 elif not diffs_seen and message and content_type == 'text/plain':
284 message += '\n' + payload
284 message += '\n' + payload
285 except: # re-raises
285 except: # re-raises
286 tmpfp.close()
286 tmpfp.close()
287 os.unlink(tmpname)
287 os.unlink(tmpname)
288 raise
288 raise
289
289
290 if subject and not message.startswith(subject):
290 if subject and not message.startswith(subject):
291 message = '%s\n%s' % (subject, message)
291 message = '%s\n%s' % (subject, message)
292 data['message'] = message
292 data['message'] = message
293 tmpfp.close()
293 tmpfp.close()
294 if parents:
294 if parents:
295 data['p1'] = parents.pop(0)
295 data['p1'] = parents.pop(0)
296 if parents:
296 if parents:
297 data['p2'] = parents.pop(0)
297 data['p2'] = parents.pop(0)
298
298
299 if diffs_seen:
299 if diffs_seen:
300 data['filename'] = tmpname
300 data['filename'] = tmpname
301 else:
301 else:
302 os.unlink(tmpname)
302 os.unlink(tmpname)
303 return data
303 return data
304
304
305 class patchmeta(object):
305 class patchmeta(object):
306 """Patched file metadata
306 """Patched file metadata
307
307
308 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
308 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
309 or COPY. 'path' is patched file path. 'oldpath' is set to the
309 or COPY. 'path' is patched file path. 'oldpath' is set to the
310 origin file when 'op' is either COPY or RENAME, None otherwise. If
310 origin file when 'op' is either COPY or RENAME, None otherwise. If
311 file mode is changed, 'mode' is a tuple (islink, isexec) where
311 file mode is changed, 'mode' is a tuple (islink, isexec) where
312 'islink' is True if the file is a symlink and 'isexec' is True if
312 'islink' is True if the file is a symlink and 'isexec' is True if
313 the file is executable. Otherwise, 'mode' is None.
313 the file is executable. Otherwise, 'mode' is None.
314 """
314 """
315 def __init__(self, path):
315 def __init__(self, path):
316 self.path = path
316 self.path = path
317 self.oldpath = None
317 self.oldpath = None
318 self.mode = None
318 self.mode = None
319 self.op = 'MODIFY'
319 self.op = 'MODIFY'
320 self.binary = False
320 self.binary = False
321
321
322 def setmode(self, mode):
322 def setmode(self, mode):
323 islink = mode & 0o20000
323 islink = mode & 0o20000
324 isexec = mode & 0o100
324 isexec = mode & 0o100
325 self.mode = (islink, isexec)
325 self.mode = (islink, isexec)
326
326
327 def copy(self):
327 def copy(self):
328 other = patchmeta(self.path)
328 other = patchmeta(self.path)
329 other.oldpath = self.oldpath
329 other.oldpath = self.oldpath
330 other.mode = self.mode
330 other.mode = self.mode
331 other.op = self.op
331 other.op = self.op
332 other.binary = self.binary
332 other.binary = self.binary
333 return other
333 return other
334
334
335 def _ispatchinga(self, afile):
335 def _ispatchinga(self, afile):
336 if afile == '/dev/null':
336 if afile == '/dev/null':
337 return self.op == 'ADD'
337 return self.op == 'ADD'
338 return afile == 'a/' + (self.oldpath or self.path)
338 return afile == 'a/' + (self.oldpath or self.path)
339
339
340 def _ispatchingb(self, bfile):
340 def _ispatchingb(self, bfile):
341 if bfile == '/dev/null':
341 if bfile == '/dev/null':
342 return self.op == 'DELETE'
342 return self.op == 'DELETE'
343 return bfile == 'b/' + self.path
343 return bfile == 'b/' + self.path
344
344
345 def ispatching(self, afile, bfile):
345 def ispatching(self, afile, bfile):
346 return self._ispatchinga(afile) and self._ispatchingb(bfile)
346 return self._ispatchinga(afile) and self._ispatchingb(bfile)
347
347
348 def __repr__(self):
348 def __repr__(self):
349 return "<patchmeta %s %r>" % (self.op, self.path)
349 return "<patchmeta %s %r>" % (self.op, self.path)
350
350
351 def readgitpatch(lr):
351 def readgitpatch(lr):
352 """extract git-style metadata about patches from <patchname>"""
352 """extract git-style metadata about patches from <patchname>"""
353
353
354 # Filter patch for git information
354 # Filter patch for git information
355 gp = None
355 gp = None
356 gitpatches = []
356 gitpatches = []
357 for line in lr:
357 for line in lr:
358 line = line.rstrip(' \r\n')
358 line = line.rstrip(' \r\n')
359 if line.startswith('diff --git a/'):
359 if line.startswith('diff --git a/'):
360 m = gitre.match(line)
360 m = gitre.match(line)
361 if m:
361 if m:
362 if gp:
362 if gp:
363 gitpatches.append(gp)
363 gitpatches.append(gp)
364 dst = m.group(2)
364 dst = m.group(2)
365 gp = patchmeta(dst)
365 gp = patchmeta(dst)
366 elif gp:
366 elif gp:
367 if line.startswith('--- '):
367 if line.startswith('--- '):
368 gitpatches.append(gp)
368 gitpatches.append(gp)
369 gp = None
369 gp = None
370 continue
370 continue
371 if line.startswith('rename from '):
371 if line.startswith('rename from '):
372 gp.op = 'RENAME'
372 gp.op = 'RENAME'
373 gp.oldpath = line[12:]
373 gp.oldpath = line[12:]
374 elif line.startswith('rename to '):
374 elif line.startswith('rename to '):
375 gp.path = line[10:]
375 gp.path = line[10:]
376 elif line.startswith('copy from '):
376 elif line.startswith('copy from '):
377 gp.op = 'COPY'
377 gp.op = 'COPY'
378 gp.oldpath = line[10:]
378 gp.oldpath = line[10:]
379 elif line.startswith('copy to '):
379 elif line.startswith('copy to '):
380 gp.path = line[8:]
380 gp.path = line[8:]
381 elif line.startswith('deleted file'):
381 elif line.startswith('deleted file'):
382 gp.op = 'DELETE'
382 gp.op = 'DELETE'
383 elif line.startswith('new file mode '):
383 elif line.startswith('new file mode '):
384 gp.op = 'ADD'
384 gp.op = 'ADD'
385 gp.setmode(int(line[-6:], 8))
385 gp.setmode(int(line[-6:], 8))
386 elif line.startswith('new mode '):
386 elif line.startswith('new mode '):
387 gp.setmode(int(line[-6:], 8))
387 gp.setmode(int(line[-6:], 8))
388 elif line.startswith('GIT binary patch'):
388 elif line.startswith('GIT binary patch'):
389 gp.binary = True
389 gp.binary = True
390 if gp:
390 if gp:
391 gitpatches.append(gp)
391 gitpatches.append(gp)
392
392
393 return gitpatches
393 return gitpatches
394
394
395 class linereader(object):
395 class linereader(object):
396 # simple class to allow pushing lines back into the input stream
396 # simple class to allow pushing lines back into the input stream
397 def __init__(self, fp):
397 def __init__(self, fp):
398 self.fp = fp
398 self.fp = fp
399 self.buf = []
399 self.buf = []
400
400
401 def push(self, line):
401 def push(self, line):
402 if line is not None:
402 if line is not None:
403 self.buf.append(line)
403 self.buf.append(line)
404
404
405 def readline(self):
405 def readline(self):
406 if self.buf:
406 if self.buf:
407 l = self.buf[0]
407 l = self.buf[0]
408 del self.buf[0]
408 del self.buf[0]
409 return l
409 return l
410 return self.fp.readline()
410 return self.fp.readline()
411
411
412 def __iter__(self):
412 def __iter__(self):
413 return iter(self.readline, '')
413 return iter(self.readline, '')
414
414
415 class abstractbackend(object):
415 class abstractbackend(object):
416 def __init__(self, ui):
416 def __init__(self, ui):
417 self.ui = ui
417 self.ui = ui
418
418
419 def getfile(self, fname):
419 def getfile(self, fname):
420 """Return target file data and flags as a (data, (islink,
420 """Return target file data and flags as a (data, (islink,
421 isexec)) tuple. Data is None if file is missing/deleted.
421 isexec)) tuple. Data is None if file is missing/deleted.
422 """
422 """
423 raise NotImplementedError
423 raise NotImplementedError
424
424
425 def setfile(self, fname, data, mode, copysource):
425 def setfile(self, fname, data, mode, copysource):
426 """Write data to target file fname and set its mode. mode is a
426 """Write data to target file fname and set its mode. mode is a
427 (islink, isexec) tuple. If data is None, the file content should
427 (islink, isexec) tuple. If data is None, the file content should
428 be left unchanged. If the file is modified after being copied,
428 be left unchanged. If the file is modified after being copied,
429 copysource is set to the original file name.
429 copysource is set to the original file name.
430 """
430 """
431 raise NotImplementedError
431 raise NotImplementedError
432
432
433 def unlink(self, fname):
433 def unlink(self, fname):
434 """Unlink target file."""
434 """Unlink target file."""
435 raise NotImplementedError
435 raise NotImplementedError
436
436
437 def writerej(self, fname, failed, total, lines):
437 def writerej(self, fname, failed, total, lines):
438 """Write rejected lines for fname. total is the number of hunks
438 """Write rejected lines for fname. total is the number of hunks
439 which failed to apply and total the total number of hunks for this
439 which failed to apply and total the total number of hunks for this
440 files.
440 files.
441 """
441 """
442 pass
442 pass
443
443
444 def exists(self, fname):
444 def exists(self, fname):
445 raise NotImplementedError
445 raise NotImplementedError
446
446
447 class fsbackend(abstractbackend):
447 class fsbackend(abstractbackend):
448 def __init__(self, ui, basedir):
448 def __init__(self, ui, basedir):
449 super(fsbackend, self).__init__(ui)
449 super(fsbackend, self).__init__(ui)
450 self.opener = scmutil.opener(basedir)
450 self.opener = scmutil.opener(basedir)
451
451
452 def _join(self, f):
452 def _join(self, f):
453 return os.path.join(self.opener.base, f)
453 return os.path.join(self.opener.base, f)
454
454
455 def getfile(self, fname):
455 def getfile(self, fname):
456 if self.opener.islink(fname):
456 if self.opener.islink(fname):
457 return (self.opener.readlink(fname), (True, False))
457 return (self.opener.readlink(fname), (True, False))
458
458
459 isexec = False
459 isexec = False
460 try:
460 try:
461 isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
461 isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
462 except OSError as e:
462 except OSError as e:
463 if e.errno != errno.ENOENT:
463 if e.errno != errno.ENOENT:
464 raise
464 raise
465 try:
465 try:
466 return (self.opener.read(fname), (False, isexec))
466 return (self.opener.read(fname), (False, isexec))
467 except IOError as e:
467 except IOError as e:
468 if e.errno != errno.ENOENT:
468 if e.errno != errno.ENOENT:
469 raise
469 raise
470 return None, None
470 return None, None
471
471
472 def setfile(self, fname, data, mode, copysource):
472 def setfile(self, fname, data, mode, copysource):
473 islink, isexec = mode
473 islink, isexec = mode
474 if data is None:
474 if data is None:
475 self.opener.setflags(fname, islink, isexec)
475 self.opener.setflags(fname, islink, isexec)
476 return
476 return
477 if islink:
477 if islink:
478 self.opener.symlink(data, fname)
478 self.opener.symlink(data, fname)
479 else:
479 else:
480 self.opener.write(fname, data)
480 self.opener.write(fname, data)
481 if isexec:
481 if isexec:
482 self.opener.setflags(fname, False, True)
482 self.opener.setflags(fname, False, True)
483
483
484 def unlink(self, fname):
484 def unlink(self, fname):
485 self.opener.unlinkpath(fname, ignoremissing=True)
485 self.opener.unlinkpath(fname, ignoremissing=True)
486
486
487 def writerej(self, fname, failed, total, lines):
487 def writerej(self, fname, failed, total, lines):
488 fname = fname + ".rej"
488 fname = fname + ".rej"
489 self.ui.warn(
489 self.ui.warn(
490 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
490 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
491 (failed, total, fname))
491 (failed, total, fname))
492 fp = self.opener(fname, 'w')
492 fp = self.opener(fname, 'w')
493 fp.writelines(lines)
493 fp.writelines(lines)
494 fp.close()
494 fp.close()
495
495
496 def exists(self, fname):
496 def exists(self, fname):
497 return self.opener.lexists(fname)
497 return self.opener.lexists(fname)
498
498
499 class workingbackend(fsbackend):
499 class workingbackend(fsbackend):
500 def __init__(self, ui, repo, similarity):
500 def __init__(self, ui, repo, similarity):
501 super(workingbackend, self).__init__(ui, repo.root)
501 super(workingbackend, self).__init__(ui, repo.root)
502 self.repo = repo
502 self.repo = repo
503 self.similarity = similarity
503 self.similarity = similarity
504 self.removed = set()
504 self.removed = set()
505 self.changed = set()
505 self.changed = set()
506 self.copied = []
506 self.copied = []
507
507
508 def _checkknown(self, fname):
508 def _checkknown(self, fname):
509 if self.repo.dirstate[fname] == '?' and self.exists(fname):
509 if self.repo.dirstate[fname] == '?' and self.exists(fname):
510 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
510 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
511
511
512 def setfile(self, fname, data, mode, copysource):
512 def setfile(self, fname, data, mode, copysource):
513 self._checkknown(fname)
513 self._checkknown(fname)
514 super(workingbackend, self).setfile(fname, data, mode, copysource)
514 super(workingbackend, self).setfile(fname, data, mode, copysource)
515 if copysource is not None:
515 if copysource is not None:
516 self.copied.append((copysource, fname))
516 self.copied.append((copysource, fname))
517 self.changed.add(fname)
517 self.changed.add(fname)
518
518
519 def unlink(self, fname):
519 def unlink(self, fname):
520 self._checkknown(fname)
520 self._checkknown(fname)
521 super(workingbackend, self).unlink(fname)
521 super(workingbackend, self).unlink(fname)
522 self.removed.add(fname)
522 self.removed.add(fname)
523 self.changed.add(fname)
523 self.changed.add(fname)
524
524
525 def close(self):
525 def close(self):
526 wctx = self.repo[None]
526 wctx = self.repo[None]
527 changed = set(self.changed)
527 changed = set(self.changed)
528 for src, dst in self.copied:
528 for src, dst in self.copied:
529 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
529 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
530 if self.removed:
530 if self.removed:
531 wctx.forget(sorted(self.removed))
531 wctx.forget(sorted(self.removed))
532 for f in self.removed:
532 for f in self.removed:
533 if f not in self.repo.dirstate:
533 if f not in self.repo.dirstate:
534 # File was deleted and no longer belongs to the
534 # File was deleted and no longer belongs to the
535 # dirstate, it was probably marked added then
535 # dirstate, it was probably marked added then
536 # deleted, and should not be considered by
536 # deleted, and should not be considered by
537 # marktouched().
537 # marktouched().
538 changed.discard(f)
538 changed.discard(f)
539 if changed:
539 if changed:
540 scmutil.marktouched(self.repo, changed, self.similarity)
540 scmutil.marktouched(self.repo, changed, self.similarity)
541 return sorted(self.changed)
541 return sorted(self.changed)
542
542
543 class filestore(object):
543 class filestore(object):
544 def __init__(self, maxsize=None):
544 def __init__(self, maxsize=None):
545 self.opener = None
545 self.opener = None
546 self.files = {}
546 self.files = {}
547 self.created = 0
547 self.created = 0
548 self.maxsize = maxsize
548 self.maxsize = maxsize
549 if self.maxsize is None:
549 if self.maxsize is None:
550 self.maxsize = 4*(2**20)
550 self.maxsize = 4*(2**20)
551 self.size = 0
551 self.size = 0
552 self.data = {}
552 self.data = {}
553
553
554 def setfile(self, fname, data, mode, copied=None):
554 def setfile(self, fname, data, mode, copied=None):
555 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
555 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
556 self.data[fname] = (data, mode, copied)
556 self.data[fname] = (data, mode, copied)
557 self.size += len(data)
557 self.size += len(data)
558 else:
558 else:
559 if self.opener is None:
559 if self.opener is None:
560 root = tempfile.mkdtemp(prefix='hg-patch-')
560 root = tempfile.mkdtemp(prefix='hg-patch-')
561 self.opener = scmutil.opener(root)
561 self.opener = scmutil.opener(root)
562 # Avoid filename issues with these simple names
562 # Avoid filename issues with these simple names
563 fn = str(self.created)
563 fn = str(self.created)
564 self.opener.write(fn, data)
564 self.opener.write(fn, data)
565 self.created += 1
565 self.created += 1
566 self.files[fname] = (fn, mode, copied)
566 self.files[fname] = (fn, mode, copied)
567
567
568 def getfile(self, fname):
568 def getfile(self, fname):
569 if fname in self.data:
569 if fname in self.data:
570 return self.data[fname]
570 return self.data[fname]
571 if not self.opener or fname not in self.files:
571 if not self.opener or fname not in self.files:
572 return None, None, None
572 return None, None, None
573 fn, mode, copied = self.files[fname]
573 fn, mode, copied = self.files[fname]
574 return self.opener.read(fn), mode, copied
574 return self.opener.read(fn), mode, copied
575
575
576 def close(self):
576 def close(self):
577 if self.opener:
577 if self.opener:
578 shutil.rmtree(self.opener.base)
578 shutil.rmtree(self.opener.base)
579
579
580 class repobackend(abstractbackend):
580 class repobackend(abstractbackend):
581 def __init__(self, ui, repo, ctx, store):
581 def __init__(self, ui, repo, ctx, store):
582 super(repobackend, self).__init__(ui)
582 super(repobackend, self).__init__(ui)
583 self.repo = repo
583 self.repo = repo
584 self.ctx = ctx
584 self.ctx = ctx
585 self.store = store
585 self.store = store
586 self.changed = set()
586 self.changed = set()
587 self.removed = set()
587 self.removed = set()
588 self.copied = {}
588 self.copied = {}
589
589
590 def _checkknown(self, fname):
590 def _checkknown(self, fname):
591 if fname not in self.ctx:
591 if fname not in self.ctx:
592 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
592 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
593
593
594 def getfile(self, fname):
594 def getfile(self, fname):
595 try:
595 try:
596 fctx = self.ctx[fname]
596 fctx = self.ctx[fname]
597 except error.LookupError:
597 except error.LookupError:
598 return None, None
598 return None, None
599 flags = fctx.flags()
599 flags = fctx.flags()
600 return fctx.data(), ('l' in flags, 'x' in flags)
600 return fctx.data(), ('l' in flags, 'x' in flags)
601
601
602 def setfile(self, fname, data, mode, copysource):
602 def setfile(self, fname, data, mode, copysource):
603 if copysource:
603 if copysource:
604 self._checkknown(copysource)
604 self._checkknown(copysource)
605 if data is None:
605 if data is None:
606 data = self.ctx[fname].data()
606 data = self.ctx[fname].data()
607 self.store.setfile(fname, data, mode, copysource)
607 self.store.setfile(fname, data, mode, copysource)
608 self.changed.add(fname)
608 self.changed.add(fname)
609 if copysource:
609 if copysource:
610 self.copied[fname] = copysource
610 self.copied[fname] = copysource
611
611
612 def unlink(self, fname):
612 def unlink(self, fname):
613 self._checkknown(fname)
613 self._checkknown(fname)
614 self.removed.add(fname)
614 self.removed.add(fname)
615
615
616 def exists(self, fname):
616 def exists(self, fname):
617 return fname in self.ctx
617 return fname in self.ctx
618
618
619 def close(self):
619 def close(self):
620 return self.changed | self.removed
620 return self.changed | self.removed
621
621
622 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
622 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
623 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
623 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
624 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
624 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
625 eolmodes = ['strict', 'crlf', 'lf', 'auto']
625 eolmodes = ['strict', 'crlf', 'lf', 'auto']
626
626
627 class patchfile(object):
627 class patchfile(object):
628 def __init__(self, ui, gp, backend, store, eolmode='strict'):
628 def __init__(self, ui, gp, backend, store, eolmode='strict'):
629 self.fname = gp.path
629 self.fname = gp.path
630 self.eolmode = eolmode
630 self.eolmode = eolmode
631 self.eol = None
631 self.eol = None
632 self.backend = backend
632 self.backend = backend
633 self.ui = ui
633 self.ui = ui
634 self.lines = []
634 self.lines = []
635 self.exists = False
635 self.exists = False
636 self.missing = True
636 self.missing = True
637 self.mode = gp.mode
637 self.mode = gp.mode
638 self.copysource = gp.oldpath
638 self.copysource = gp.oldpath
639 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
639 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
640 self.remove = gp.op == 'DELETE'
640 self.remove = gp.op == 'DELETE'
641 if self.copysource is None:
641 if self.copysource is None:
642 data, mode = backend.getfile(self.fname)
642 data, mode = backend.getfile(self.fname)
643 else:
643 else:
644 data, mode = store.getfile(self.copysource)[:2]
644 data, mode = store.getfile(self.copysource)[:2]
645 if data is not None:
645 if data is not None:
646 self.exists = self.copysource is None or backend.exists(self.fname)
646 self.exists = self.copysource is None or backend.exists(self.fname)
647 self.missing = False
647 self.missing = False
648 if data:
648 if data:
649 self.lines = mdiff.splitnewlines(data)
649 self.lines = mdiff.splitnewlines(data)
650 if self.mode is None:
650 if self.mode is None:
651 self.mode = mode
651 self.mode = mode
652 if self.lines:
652 if self.lines:
653 # Normalize line endings
653 # Normalize line endings
654 if self.lines[0].endswith('\r\n'):
654 if self.lines[0].endswith('\r\n'):
655 self.eol = '\r\n'
655 self.eol = '\r\n'
656 elif self.lines[0].endswith('\n'):
656 elif self.lines[0].endswith('\n'):
657 self.eol = '\n'
657 self.eol = '\n'
658 if eolmode != 'strict':
658 if eolmode != 'strict':
659 nlines = []
659 nlines = []
660 for l in self.lines:
660 for l in self.lines:
661 if l.endswith('\r\n'):
661 if l.endswith('\r\n'):
662 l = l[:-2] + '\n'
662 l = l[:-2] + '\n'
663 nlines.append(l)
663 nlines.append(l)
664 self.lines = nlines
664 self.lines = nlines
665 else:
665 else:
666 if self.create:
666 if self.create:
667 self.missing = False
667 self.missing = False
668 if self.mode is None:
668 if self.mode is None:
669 self.mode = (False, False)
669 self.mode = (False, False)
670 if self.missing:
670 if self.missing:
671 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
671 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
672 self.ui.warn(_("(use '--prefix' to apply patch relative to the "
672 self.ui.warn(_("(use '--prefix' to apply patch relative to the "
673 "current directory)\n"))
673 "current directory)\n"))
674
674
675 self.hash = {}
675 self.hash = {}
676 self.dirty = 0
676 self.dirty = 0
677 self.offset = 0
677 self.offset = 0
678 self.skew = 0
678 self.skew = 0
679 self.rej = []
679 self.rej = []
680 self.fileprinted = False
680 self.fileprinted = False
681 self.printfile(False)
681 self.printfile(False)
682 self.hunks = 0
682 self.hunks = 0
683
683
684 def writelines(self, fname, lines, mode):
684 def writelines(self, fname, lines, mode):
685 if self.eolmode == 'auto':
685 if self.eolmode == 'auto':
686 eol = self.eol
686 eol = self.eol
687 elif self.eolmode == 'crlf':
687 elif self.eolmode == 'crlf':
688 eol = '\r\n'
688 eol = '\r\n'
689 else:
689 else:
690 eol = '\n'
690 eol = '\n'
691
691
692 if self.eolmode != 'strict' and eol and eol != '\n':
692 if self.eolmode != 'strict' and eol and eol != '\n':
693 rawlines = []
693 rawlines = []
694 for l in lines:
694 for l in lines:
695 if l and l[-1] == '\n':
695 if l and l[-1] == '\n':
696 l = l[:-1] + eol
696 l = l[:-1] + eol
697 rawlines.append(l)
697 rawlines.append(l)
698 lines = rawlines
698 lines = rawlines
699
699
700 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
700 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
701
701
702 def printfile(self, warn):
702 def printfile(self, warn):
703 if self.fileprinted:
703 if self.fileprinted:
704 return
704 return
705 if warn or self.ui.verbose:
705 if warn or self.ui.verbose:
706 self.fileprinted = True
706 self.fileprinted = True
707 s = _("patching file %s\n") % self.fname
707 s = _("patching file %s\n") % self.fname
708 if warn:
708 if warn:
709 self.ui.warn(s)
709 self.ui.warn(s)
710 else:
710 else:
711 self.ui.note(s)
711 self.ui.note(s)
712
712
713
713
714 def findlines(self, l, linenum):
714 def findlines(self, l, linenum):
715 # looks through the hash and finds candidate lines. The
715 # looks through the hash and finds candidate lines. The
716 # result is a list of line numbers sorted based on distance
716 # result is a list of line numbers sorted based on distance
717 # from linenum
717 # from linenum
718
718
719 cand = self.hash.get(l, [])
719 cand = self.hash.get(l, [])
720 if len(cand) > 1:
720 if len(cand) > 1:
721 # resort our list of potentials forward then back.
721 # resort our list of potentials forward then back.
722 cand.sort(key=lambda x: abs(x - linenum))
722 cand.sort(key=lambda x: abs(x - linenum))
723 return cand
723 return cand
724
724
725 def write_rej(self):
725 def write_rej(self):
726 # our rejects are a little different from patch(1). This always
726 # our rejects are a little different from patch(1). This always
727 # creates rejects in the same form as the original patch. A file
727 # creates rejects in the same form as the original patch. A file
728 # header is inserted so that you can run the reject through patch again
728 # header is inserted so that you can run the reject through patch again
729 # without having to type the filename.
729 # without having to type the filename.
730 if not self.rej:
730 if not self.rej:
731 return
731 return
732 base = os.path.basename(self.fname)
732 base = os.path.basename(self.fname)
733 lines = ["--- %s\n+++ %s\n" % (base, base)]
733 lines = ["--- %s\n+++ %s\n" % (base, base)]
734 for x in self.rej:
734 for x in self.rej:
735 for l in x.hunk:
735 for l in x.hunk:
736 lines.append(l)
736 lines.append(l)
737 if l[-1] != '\n':
737 if l[-1] != '\n':
738 lines.append("\n\ No newline at end of file\n")
738 lines.append("\n\ No newline at end of file\n")
739 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
739 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
740
740
741 def apply(self, h):
741 def apply(self, h):
742 if not h.complete():
742 if not h.complete():
743 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
743 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
744 (h.number, h.desc, len(h.a), h.lena, len(h.b),
744 (h.number, h.desc, len(h.a), h.lena, len(h.b),
745 h.lenb))
745 h.lenb))
746
746
747 self.hunks += 1
747 self.hunks += 1
748
748
749 if self.missing:
749 if self.missing:
750 self.rej.append(h)
750 self.rej.append(h)
751 return -1
751 return -1
752
752
753 if self.exists and self.create:
753 if self.exists and self.create:
754 if self.copysource:
754 if self.copysource:
755 self.ui.warn(_("cannot create %s: destination already "
755 self.ui.warn(_("cannot create %s: destination already "
756 "exists\n") % self.fname)
756 "exists\n") % self.fname)
757 else:
757 else:
758 self.ui.warn(_("file %s already exists\n") % self.fname)
758 self.ui.warn(_("file %s already exists\n") % self.fname)
759 self.rej.append(h)
759 self.rej.append(h)
760 return -1
760 return -1
761
761
762 if isinstance(h, binhunk):
762 if isinstance(h, binhunk):
763 if self.remove:
763 if self.remove:
764 self.backend.unlink(self.fname)
764 self.backend.unlink(self.fname)
765 else:
765 else:
766 l = h.new(self.lines)
766 l = h.new(self.lines)
767 self.lines[:] = l
767 self.lines[:] = l
768 self.offset += len(l)
768 self.offset += len(l)
769 self.dirty = True
769 self.dirty = True
770 return 0
770 return 0
771
771
772 horig = h
772 horig = h
773 if (self.eolmode in ('crlf', 'lf')
773 if (self.eolmode in ('crlf', 'lf')
774 or self.eolmode == 'auto' and self.eol):
774 or self.eolmode == 'auto' and self.eol):
775 # If new eols are going to be normalized, then normalize
775 # If new eols are going to be normalized, then normalize
776 # hunk data before patching. Otherwise, preserve input
776 # hunk data before patching. Otherwise, preserve input
777 # line-endings.
777 # line-endings.
778 h = h.getnormalized()
778 h = h.getnormalized()
779
779
780 # fast case first, no offsets, no fuzz
780 # fast case first, no offsets, no fuzz
781 old, oldstart, new, newstart = h.fuzzit(0, False)
781 old, oldstart, new, newstart = h.fuzzit(0, False)
782 oldstart += self.offset
782 oldstart += self.offset
783 orig_start = oldstart
783 orig_start = oldstart
784 # if there's skew we want to emit the "(offset %d lines)" even
784 # if there's skew we want to emit the "(offset %d lines)" even
785 # when the hunk cleanly applies at start + skew, so skip the
785 # when the hunk cleanly applies at start + skew, so skip the
786 # fast case code
786 # fast case code
787 if (self.skew == 0 and
787 if (self.skew == 0 and
788 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
788 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
789 if self.remove:
789 if self.remove:
790 self.backend.unlink(self.fname)
790 self.backend.unlink(self.fname)
791 else:
791 else:
792 self.lines[oldstart:oldstart + len(old)] = new
792 self.lines[oldstart:oldstart + len(old)] = new
793 self.offset += len(new) - len(old)
793 self.offset += len(new) - len(old)
794 self.dirty = True
794 self.dirty = True
795 return 0
795 return 0
796
796
797 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
797 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
798 self.hash = {}
798 self.hash = {}
799 for x, s in enumerate(self.lines):
799 for x, s in enumerate(self.lines):
800 self.hash.setdefault(s, []).append(x)
800 self.hash.setdefault(s, []).append(x)
801
801
802 for fuzzlen in xrange(self.ui.configint("patch", "fuzz", 2) + 1):
802 for fuzzlen in xrange(self.ui.configint("patch", "fuzz", 2) + 1):
803 for toponly in [True, False]:
803 for toponly in [True, False]:
804 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
804 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
805 oldstart = oldstart + self.offset + self.skew
805 oldstart = oldstart + self.offset + self.skew
806 oldstart = min(oldstart, len(self.lines))
806 oldstart = min(oldstart, len(self.lines))
807 if old:
807 if old:
808 cand = self.findlines(old[0][1:], oldstart)
808 cand = self.findlines(old[0][1:], oldstart)
809 else:
809 else:
810 # Only adding lines with no or fuzzed context, just
810 # Only adding lines with no or fuzzed context, just
811 # take the skew in account
811 # take the skew in account
812 cand = [oldstart]
812 cand = [oldstart]
813
813
814 for l in cand:
814 for l in cand:
815 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
815 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
816 self.lines[l : l + len(old)] = new
816 self.lines[l : l + len(old)] = new
817 self.offset += len(new) - len(old)
817 self.offset += len(new) - len(old)
818 self.skew = l - orig_start
818 self.skew = l - orig_start
819 self.dirty = True
819 self.dirty = True
820 offset = l - orig_start - fuzzlen
820 offset = l - orig_start - fuzzlen
821 if fuzzlen:
821 if fuzzlen:
822 msg = _("Hunk #%d succeeded at %d "
822 msg = _("Hunk #%d succeeded at %d "
823 "with fuzz %d "
823 "with fuzz %d "
824 "(offset %d lines).\n")
824 "(offset %d lines).\n")
825 self.printfile(True)
825 self.printfile(True)
826 self.ui.warn(msg %
826 self.ui.warn(msg %
827 (h.number, l + 1, fuzzlen, offset))
827 (h.number, l + 1, fuzzlen, offset))
828 else:
828 else:
829 msg = _("Hunk #%d succeeded at %d "
829 msg = _("Hunk #%d succeeded at %d "
830 "(offset %d lines).\n")
830 "(offset %d lines).\n")
831 self.ui.note(msg % (h.number, l + 1, offset))
831 self.ui.note(msg % (h.number, l + 1, offset))
832 return fuzzlen
832 return fuzzlen
833 self.printfile(True)
833 self.printfile(True)
834 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
834 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
835 self.rej.append(horig)
835 self.rej.append(horig)
836 return -1
836 return -1
837
837
838 def close(self):
838 def close(self):
839 if self.dirty:
839 if self.dirty:
840 self.writelines(self.fname, self.lines, self.mode)
840 self.writelines(self.fname, self.lines, self.mode)
841 self.write_rej()
841 self.write_rej()
842 return len(self.rej)
842 return len(self.rej)
843
843
844 class header(object):
844 class header(object):
845 """patch header
845 """patch header
846 """
846 """
847 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
847 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
848 diff_re = re.compile('diff -r .* (.*)$')
848 diff_re = re.compile('diff -r .* (.*)$')
849 allhunks_re = re.compile('(?:index|deleted file) ')
849 allhunks_re = re.compile('(?:index|deleted file) ')
850 pretty_re = re.compile('(?:new file|deleted file) ')
850 pretty_re = re.compile('(?:new file|deleted file) ')
851 special_re = re.compile('(?:index|deleted|copy|rename) ')
851 special_re = re.compile('(?:index|deleted|copy|rename) ')
852 newfile_re = re.compile('(?:new file)')
852 newfile_re = re.compile('(?:new file)')
853
853
854 def __init__(self, header):
854 def __init__(self, header):
855 self.header = header
855 self.header = header
856 self.hunks = []
856 self.hunks = []
857
857
858 def binary(self):
858 def binary(self):
859 return any(h.startswith('index ') for h in self.header)
859 return any(h.startswith('index ') for h in self.header)
860
860
861 def pretty(self, fp):
861 def pretty(self, fp):
862 for h in self.header:
862 for h in self.header:
863 if h.startswith('index '):
863 if h.startswith('index '):
864 fp.write(_('this modifies a binary file (all or nothing)\n'))
864 fp.write(_('this modifies a binary file (all or nothing)\n'))
865 break
865 break
866 if self.pretty_re.match(h):
866 if self.pretty_re.match(h):
867 fp.write(h)
867 fp.write(h)
868 if self.binary():
868 if self.binary():
869 fp.write(_('this is a binary file\n'))
869 fp.write(_('this is a binary file\n'))
870 break
870 break
871 if h.startswith('---'):
871 if h.startswith('---'):
872 fp.write(_('%d hunks, %d lines changed\n') %
872 fp.write(_('%d hunks, %d lines changed\n') %
873 (len(self.hunks),
873 (len(self.hunks),
874 sum([max(h.added, h.removed) for h in self.hunks])))
874 sum([max(h.added, h.removed) for h in self.hunks])))
875 break
875 break
876 fp.write(h)
876 fp.write(h)
877
877
878 def write(self, fp):
878 def write(self, fp):
879 fp.write(''.join(self.header))
879 fp.write(''.join(self.header))
880
880
881 def allhunks(self):
881 def allhunks(self):
882 return any(self.allhunks_re.match(h) for h in self.header)
882 return any(self.allhunks_re.match(h) for h in self.header)
883
883
884 def files(self):
884 def files(self):
885 match = self.diffgit_re.match(self.header[0])
885 match = self.diffgit_re.match(self.header[0])
886 if match:
886 if match:
887 fromfile, tofile = match.groups()
887 fromfile, tofile = match.groups()
888 if fromfile == tofile:
888 if fromfile == tofile:
889 return [fromfile]
889 return [fromfile]
890 return [fromfile, tofile]
890 return [fromfile, tofile]
891 else:
891 else:
892 return self.diff_re.match(self.header[0]).groups()
892 return self.diff_re.match(self.header[0]).groups()
893
893
894 def filename(self):
894 def filename(self):
895 return self.files()[-1]
895 return self.files()[-1]
896
896
897 def __repr__(self):
897 def __repr__(self):
898 return '<header %s>' % (' '.join(map(repr, self.files())))
898 return '<header %s>' % (' '.join(map(repr, self.files())))
899
899
900 def isnewfile(self):
900 def isnewfile(self):
901 return any(self.newfile_re.match(h) for h in self.header)
901 return any(self.newfile_re.match(h) for h in self.header)
902
902
903 def special(self):
903 def special(self):
904 # Special files are shown only at the header level and not at the hunk
904 # Special files are shown only at the header level and not at the hunk
905 # level for example a file that has been deleted is a special file.
905 # level for example a file that has been deleted is a special file.
906 # The user cannot change the content of the operation, in the case of
906 # The user cannot change the content of the operation, in the case of
907 # the deleted file he has to take the deletion or not take it, he
907 # the deleted file he has to take the deletion or not take it, he
908 # cannot take some of it.
908 # cannot take some of it.
909 # Newly added files are special if they are empty, they are not special
909 # Newly added files are special if they are empty, they are not special
910 # if they have some content as we want to be able to change it
910 # if they have some content as we want to be able to change it
911 nocontent = len(self.header) == 2
911 nocontent = len(self.header) == 2
912 emptynewfile = self.isnewfile() and nocontent
912 emptynewfile = self.isnewfile() and nocontent
913 return emptynewfile or \
913 return emptynewfile or \
914 any(self.special_re.match(h) for h in self.header)
914 any(self.special_re.match(h) for h in self.header)
915
915
916 class recordhunk(object):
916 class recordhunk(object):
917 """patch hunk
917 """patch hunk
918
918
919 XXX shouldn't we merge this with the other hunk class?
919 XXX shouldn't we merge this with the other hunk class?
920 """
920 """
921 maxcontext = 3
921 maxcontext = 3
922
922
923 def __init__(self, header, fromline, toline, proc, before, hunk, after):
923 def __init__(self, header, fromline, toline, proc, before, hunk, after):
924 def trimcontext(number, lines):
924 def trimcontext(number, lines):
925 delta = len(lines) - self.maxcontext
925 delta = len(lines) - self.maxcontext
926 if False and delta > 0:
926 if False and delta > 0:
927 return number + delta, lines[:self.maxcontext]
927 return number + delta, lines[:self.maxcontext]
928 return number, lines
928 return number, lines
929
929
930 self.header = header
930 self.header = header
931 self.fromline, self.before = trimcontext(fromline, before)
931 self.fromline, self.before = trimcontext(fromline, before)
932 self.toline, self.after = trimcontext(toline, after)
932 self.toline, self.after = trimcontext(toline, after)
933 self.proc = proc
933 self.proc = proc
934 self.hunk = hunk
934 self.hunk = hunk
935 self.added, self.removed = self.countchanges(self.hunk)
935 self.added, self.removed = self.countchanges(self.hunk)
936
936
937 def __eq__(self, v):
937 def __eq__(self, v):
938 if not isinstance(v, recordhunk):
938 if not isinstance(v, recordhunk):
939 return False
939 return False
940
940
941 return ((v.hunk == self.hunk) and
941 return ((v.hunk == self.hunk) and
942 (v.proc == self.proc) and
942 (v.proc == self.proc) and
943 (self.fromline == v.fromline) and
943 (self.fromline == v.fromline) and
944 (self.header.files() == v.header.files()))
944 (self.header.files() == v.header.files()))
945
945
946 def __hash__(self):
946 def __hash__(self):
947 return hash((tuple(self.hunk),
947 return hash((tuple(self.hunk),
948 tuple(self.header.files()),
948 tuple(self.header.files()),
949 self.fromline,
949 self.fromline,
950 self.proc))
950 self.proc))
951
951
952 def countchanges(self, hunk):
952 def countchanges(self, hunk):
953 """hunk -> (n+,n-)"""
953 """hunk -> (n+,n-)"""
954 add = len([h for h in hunk if h[0] == '+'])
954 add = len([h for h in hunk if h[0] == '+'])
955 rem = len([h for h in hunk if h[0] == '-'])
955 rem = len([h for h in hunk if h[0] == '-'])
956 return add, rem
956 return add, rem
957
957
958 def write(self, fp):
958 def write(self, fp):
959 delta = len(self.before) + len(self.after)
959 delta = len(self.before) + len(self.after)
960 if self.after and self.after[-1] == '\\ No newline at end of file\n':
960 if self.after and self.after[-1] == '\\ No newline at end of file\n':
961 delta -= 1
961 delta -= 1
962 fromlen = delta + self.removed
962 fromlen = delta + self.removed
963 tolen = delta + self.added
963 tolen = delta + self.added
964 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
964 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
965 (self.fromline, fromlen, self.toline, tolen,
965 (self.fromline, fromlen, self.toline, tolen,
966 self.proc and (' ' + self.proc)))
966 self.proc and (' ' + self.proc)))
967 fp.write(''.join(self.before + self.hunk + self.after))
967 fp.write(''.join(self.before + self.hunk + self.after))
968
968
969 pretty = write
969 pretty = write
970
970
971 def filename(self):
971 def filename(self):
972 return self.header.filename()
972 return self.header.filename()
973
973
974 def __repr__(self):
974 def __repr__(self):
975 return '<hunk %r@%d>' % (self.filename(), self.fromline)
975 return '<hunk %r@%d>' % (self.filename(), self.fromline)
976
976
977 def filterpatch(ui, headers, operation=None):
977 def filterpatch(ui, headers, operation=None):
978 """Interactively filter patch chunks into applied-only chunks"""
978 """Interactively filter patch chunks into applied-only chunks"""
979 if operation is None:
979 if operation is None:
980 operation = 'record'
980 operation = 'record'
981 messages = {
981 messages = {
982 'multiple': {
982 'multiple': {
983 'discard': _("discard change %d/%d to '%s'?"),
983 'discard': _("discard change %d/%d to '%s'?"),
984 'record': _("record change %d/%d to '%s'?"),
984 'record': _("record change %d/%d to '%s'?"),
985 'revert': _("revert change %d/%d to '%s'?"),
985 'revert': _("revert change %d/%d to '%s'?"),
986 }[operation],
986 }[operation],
987 'single': {
987 'single': {
988 'discard': _("discard this change to '%s'?"),
988 'discard': _("discard this change to '%s'?"),
989 'record': _("record this change to '%s'?"),
989 'record': _("record this change to '%s'?"),
990 'revert': _("revert this change to '%s'?"),
990 'revert': _("revert this change to '%s'?"),
991 }[operation],
991 }[operation],
992 }
992 }
993
993
994 def prompt(skipfile, skipall, query, chunk):
994 def prompt(skipfile, skipall, query, chunk):
995 """prompt query, and process base inputs
995 """prompt query, and process base inputs
996
996
997 - y/n for the rest of file
997 - y/n for the rest of file
998 - y/n for the rest
998 - y/n for the rest
999 - ? (help)
999 - ? (help)
1000 - q (quit)
1000 - q (quit)
1001
1001
1002 Return True/False and possibly updated skipfile and skipall.
1002 Return True/False and possibly updated skipfile and skipall.
1003 """
1003 """
1004 newpatches = None
1004 newpatches = None
1005 if skipall is not None:
1005 if skipall is not None:
1006 return skipall, skipfile, skipall, newpatches
1006 return skipall, skipfile, skipall, newpatches
1007 if skipfile is not None:
1007 if skipfile is not None:
1008 return skipfile, skipfile, skipall, newpatches
1008 return skipfile, skipfile, skipall, newpatches
1009 while True:
1009 while True:
1010 resps = _('[Ynesfdaq?]'
1010 resps = _('[Ynesfdaq?]'
1011 '$$ &Yes, record this change'
1011 '$$ &Yes, record this change'
1012 '$$ &No, skip this change'
1012 '$$ &No, skip this change'
1013 '$$ &Edit this change manually'
1013 '$$ &Edit this change manually'
1014 '$$ &Skip remaining changes to this file'
1014 '$$ &Skip remaining changes to this file'
1015 '$$ Record remaining changes to this &file'
1015 '$$ Record remaining changes to this &file'
1016 '$$ &Done, skip remaining changes and files'
1016 '$$ &Done, skip remaining changes and files'
1017 '$$ Record &all changes to all remaining files'
1017 '$$ Record &all changes to all remaining files'
1018 '$$ &Quit, recording no changes'
1018 '$$ &Quit, recording no changes'
1019 '$$ &? (display help)')
1019 '$$ &? (display help)')
1020 r = ui.promptchoice("%s %s" % (query, resps))
1020 r = ui.promptchoice("%s %s" % (query, resps))
1021 ui.write("\n")
1021 ui.write("\n")
1022 if r == 8: # ?
1022 if r == 8: # ?
1023 for c, t in ui.extractchoices(resps)[1]:
1023 for c, t in ui.extractchoices(resps)[1]:
1024 ui.write('%s - %s\n' % (c, encoding.lower(t)))
1024 ui.write('%s - %s\n' % (c, encoding.lower(t)))
1025 continue
1025 continue
1026 elif r == 0: # yes
1026 elif r == 0: # yes
1027 ret = True
1027 ret = True
1028 elif r == 1: # no
1028 elif r == 1: # no
1029 ret = False
1029 ret = False
1030 elif r == 2: # Edit patch
1030 elif r == 2: # Edit patch
1031 if chunk is None:
1031 if chunk is None:
1032 ui.write(_('cannot edit patch for whole file'))
1032 ui.write(_('cannot edit patch for whole file'))
1033 ui.write("\n")
1033 ui.write("\n")
1034 continue
1034 continue
1035 if chunk.header.binary():
1035 if chunk.header.binary():
1036 ui.write(_('cannot edit patch for binary file'))
1036 ui.write(_('cannot edit patch for binary file'))
1037 ui.write("\n")
1037 ui.write("\n")
1038 continue
1038 continue
1039 # Patch comment based on the Git one (based on comment at end of
1039 # Patch comment based on the Git one (based on comment at end of
1040 # https://mercurial-scm.org/wiki/RecordExtension)
1040 # https://mercurial-scm.org/wiki/RecordExtension)
1041 phelp = '---' + _("""
1041 phelp = '---' + _("""
1042 To remove '-' lines, make them ' ' lines (context).
1042 To remove '-' lines, make them ' ' lines (context).
1043 To remove '+' lines, delete them.
1043 To remove '+' lines, delete them.
1044 Lines starting with # will be removed from the patch.
1044 Lines starting with # will be removed from the patch.
1045
1045
1046 If the patch applies cleanly, the edited hunk will immediately be
1046 If the patch applies cleanly, the edited hunk will immediately be
1047 added to the record list. If it does not apply cleanly, a rejects
1047 added to the record list. If it does not apply cleanly, a rejects
1048 file will be generated: you can use that when you try again. If
1048 file will be generated: you can use that when you try again. If
1049 all lines of the hunk are removed, then the edit is aborted and
1049 all lines of the hunk are removed, then the edit is aborted and
1050 the hunk is left unchanged.
1050 the hunk is left unchanged.
1051 """)
1051 """)
1052 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1052 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1053 suffix=".diff", text=True)
1053 suffix=".diff", text=True)
1054 ncpatchfp = None
1054 ncpatchfp = None
1055 try:
1055 try:
1056 # Write the initial patch
1056 # Write the initial patch
1057 f = os.fdopen(patchfd, "w")
1057 f = os.fdopen(patchfd, "w")
1058 chunk.header.write(f)
1058 chunk.header.write(f)
1059 chunk.write(f)
1059 chunk.write(f)
1060 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1060 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1061 f.close()
1061 f.close()
1062 # Start the editor and wait for it to complete
1062 # Start the editor and wait for it to complete
1063 editor = ui.geteditor()
1063 editor = ui.geteditor()
1064 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1064 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1065 environ={'HGUSER': ui.username()})
1065 environ={'HGUSER': ui.username()})
1066 if ret != 0:
1066 if ret != 0:
1067 ui.warn(_("editor exited with exit code %d\n") % ret)
1067 ui.warn(_("editor exited with exit code %d\n") % ret)
1068 continue
1068 continue
1069 # Remove comment lines
1069 # Remove comment lines
1070 patchfp = open(patchfn)
1070 patchfp = open(patchfn)
1071 ncpatchfp = stringio()
1071 ncpatchfp = stringio()
1072 for line in patchfp:
1072 for line in patchfp:
1073 if not line.startswith('#'):
1073 if not line.startswith('#'):
1074 ncpatchfp.write(line)
1074 ncpatchfp.write(line)
1075 patchfp.close()
1075 patchfp.close()
1076 ncpatchfp.seek(0)
1076 ncpatchfp.seek(0)
1077 newpatches = parsepatch(ncpatchfp)
1077 newpatches = parsepatch(ncpatchfp)
1078 finally:
1078 finally:
1079 os.unlink(patchfn)
1079 os.unlink(patchfn)
1080 del ncpatchfp
1080 del ncpatchfp
1081 # Signal that the chunk shouldn't be applied as-is, but
1081 # Signal that the chunk shouldn't be applied as-is, but
1082 # provide the new patch to be used instead.
1082 # provide the new patch to be used instead.
1083 ret = False
1083 ret = False
1084 elif r == 3: # Skip
1084 elif r == 3: # Skip
1085 ret = skipfile = False
1085 ret = skipfile = False
1086 elif r == 4: # file (Record remaining)
1086 elif r == 4: # file (Record remaining)
1087 ret = skipfile = True
1087 ret = skipfile = True
1088 elif r == 5: # done, skip remaining
1088 elif r == 5: # done, skip remaining
1089 ret = skipall = False
1089 ret = skipall = False
1090 elif r == 6: # all
1090 elif r == 6: # all
1091 ret = skipall = True
1091 ret = skipall = True
1092 elif r == 7: # quit
1092 elif r == 7: # quit
1093 raise error.Abort(_('user quit'))
1093 raise error.Abort(_('user quit'))
1094 return ret, skipfile, skipall, newpatches
1094 return ret, skipfile, skipall, newpatches
1095
1095
1096 seen = set()
1096 seen = set()
1097 applied = {} # 'filename' -> [] of chunks
1097 applied = {} # 'filename' -> [] of chunks
1098 skipfile, skipall = None, None
1098 skipfile, skipall = None, None
1099 pos, total = 1, sum(len(h.hunks) for h in headers)
1099 pos, total = 1, sum(len(h.hunks) for h in headers)
1100 for h in headers:
1100 for h in headers:
1101 pos += len(h.hunks)
1101 pos += len(h.hunks)
1102 skipfile = None
1102 skipfile = None
1103 fixoffset = 0
1103 fixoffset = 0
1104 hdr = ''.join(h.header)
1104 hdr = ''.join(h.header)
1105 if hdr in seen:
1105 if hdr in seen:
1106 continue
1106 continue
1107 seen.add(hdr)
1107 seen.add(hdr)
1108 if skipall is None:
1108 if skipall is None:
1109 h.pretty(ui)
1109 h.pretty(ui)
1110 msg = (_('examine changes to %s?') %
1110 msg = (_('examine changes to %s?') %
1111 _(' and ').join("'%s'" % f for f in h.files()))
1111 _(' and ').join("'%s'" % f for f in h.files()))
1112 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1112 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1113 if not r:
1113 if not r:
1114 continue
1114 continue
1115 applied[h.filename()] = [h]
1115 applied[h.filename()] = [h]
1116 if h.allhunks():
1116 if h.allhunks():
1117 applied[h.filename()] += h.hunks
1117 applied[h.filename()] += h.hunks
1118 continue
1118 continue
1119 for i, chunk in enumerate(h.hunks):
1119 for i, chunk in enumerate(h.hunks):
1120 if skipfile is None and skipall is None:
1120 if skipfile is None and skipall is None:
1121 chunk.pretty(ui)
1121 chunk.pretty(ui)
1122 if total == 1:
1122 if total == 1:
1123 msg = messages['single'] % chunk.filename()
1123 msg = messages['single'] % chunk.filename()
1124 else:
1124 else:
1125 idx = pos - len(h.hunks) + i
1125 idx = pos - len(h.hunks) + i
1126 msg = messages['multiple'] % (idx, total, chunk.filename())
1126 msg = messages['multiple'] % (idx, total, chunk.filename())
1127 r, skipfile, skipall, newpatches = prompt(skipfile,
1127 r, skipfile, skipall, newpatches = prompt(skipfile,
1128 skipall, msg, chunk)
1128 skipall, msg, chunk)
1129 if r:
1129 if r:
1130 if fixoffset:
1130 if fixoffset:
1131 chunk = copy.copy(chunk)
1131 chunk = copy.copy(chunk)
1132 chunk.toline += fixoffset
1132 chunk.toline += fixoffset
1133 applied[chunk.filename()].append(chunk)
1133 applied[chunk.filename()].append(chunk)
1134 elif newpatches is not None:
1134 elif newpatches is not None:
1135 for newpatch in newpatches:
1135 for newpatch in newpatches:
1136 for newhunk in newpatch.hunks:
1136 for newhunk in newpatch.hunks:
1137 if fixoffset:
1137 if fixoffset:
1138 newhunk.toline += fixoffset
1138 newhunk.toline += fixoffset
1139 applied[newhunk.filename()].append(newhunk)
1139 applied[newhunk.filename()].append(newhunk)
1140 else:
1140 else:
1141 fixoffset += chunk.removed - chunk.added
1141 fixoffset += chunk.removed - chunk.added
1142 return (sum([h for h in applied.itervalues()
1142 return (sum([h for h in applied.itervalues()
1143 if h[0].special() or len(h) > 1], []), {})
1143 if h[0].special() or len(h) > 1], []), {})
1144 class hunk(object):
1144 class hunk(object):
1145 def __init__(self, desc, num, lr, context):
1145 def __init__(self, desc, num, lr, context):
1146 self.number = num
1146 self.number = num
1147 self.desc = desc
1147 self.desc = desc
1148 self.hunk = [desc]
1148 self.hunk = [desc]
1149 self.a = []
1149 self.a = []
1150 self.b = []
1150 self.b = []
1151 self.starta = self.lena = None
1151 self.starta = self.lena = None
1152 self.startb = self.lenb = None
1152 self.startb = self.lenb = None
1153 if lr is not None:
1153 if lr is not None:
1154 if context:
1154 if context:
1155 self.read_context_hunk(lr)
1155 self.read_context_hunk(lr)
1156 else:
1156 else:
1157 self.read_unified_hunk(lr)
1157 self.read_unified_hunk(lr)
1158
1158
1159 def getnormalized(self):
1159 def getnormalized(self):
1160 """Return a copy with line endings normalized to LF."""
1160 """Return a copy with line endings normalized to LF."""
1161
1161
1162 def normalize(lines):
1162 def normalize(lines):
1163 nlines = []
1163 nlines = []
1164 for line in lines:
1164 for line in lines:
1165 if line.endswith('\r\n'):
1165 if line.endswith('\r\n'):
1166 line = line[:-2] + '\n'
1166 line = line[:-2] + '\n'
1167 nlines.append(line)
1167 nlines.append(line)
1168 return nlines
1168 return nlines
1169
1169
1170 # Dummy object, it is rebuilt manually
1170 # Dummy object, it is rebuilt manually
1171 nh = hunk(self.desc, self.number, None, None)
1171 nh = hunk(self.desc, self.number, None, None)
1172 nh.number = self.number
1172 nh.number = self.number
1173 nh.desc = self.desc
1173 nh.desc = self.desc
1174 nh.hunk = self.hunk
1174 nh.hunk = self.hunk
1175 nh.a = normalize(self.a)
1175 nh.a = normalize(self.a)
1176 nh.b = normalize(self.b)
1176 nh.b = normalize(self.b)
1177 nh.starta = self.starta
1177 nh.starta = self.starta
1178 nh.startb = self.startb
1178 nh.startb = self.startb
1179 nh.lena = self.lena
1179 nh.lena = self.lena
1180 nh.lenb = self.lenb
1180 nh.lenb = self.lenb
1181 return nh
1181 return nh
1182
1182
1183 def read_unified_hunk(self, lr):
1183 def read_unified_hunk(self, lr):
1184 m = unidesc.match(self.desc)
1184 m = unidesc.match(self.desc)
1185 if not m:
1185 if not m:
1186 raise PatchError(_("bad hunk #%d") % self.number)
1186 raise PatchError(_("bad hunk #%d") % self.number)
1187 self.starta, self.lena, self.startb, self.lenb = m.groups()
1187 self.starta, self.lena, self.startb, self.lenb = m.groups()
1188 if self.lena is None:
1188 if self.lena is None:
1189 self.lena = 1
1189 self.lena = 1
1190 else:
1190 else:
1191 self.lena = int(self.lena)
1191 self.lena = int(self.lena)
1192 if self.lenb is None:
1192 if self.lenb is None:
1193 self.lenb = 1
1193 self.lenb = 1
1194 else:
1194 else:
1195 self.lenb = int(self.lenb)
1195 self.lenb = int(self.lenb)
1196 self.starta = int(self.starta)
1196 self.starta = int(self.starta)
1197 self.startb = int(self.startb)
1197 self.startb = int(self.startb)
1198 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1198 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1199 self.b)
1199 self.b)
1200 # if we hit eof before finishing out the hunk, the last line will
1200 # if we hit eof before finishing out the hunk, the last line will
1201 # be zero length. Lets try to fix it up.
1201 # be zero length. Lets try to fix it up.
1202 while len(self.hunk[-1]) == 0:
1202 while len(self.hunk[-1]) == 0:
1203 del self.hunk[-1]
1203 del self.hunk[-1]
1204 del self.a[-1]
1204 del self.a[-1]
1205 del self.b[-1]
1205 del self.b[-1]
1206 self.lena -= 1
1206 self.lena -= 1
1207 self.lenb -= 1
1207 self.lenb -= 1
1208 self._fixnewline(lr)
1208 self._fixnewline(lr)
1209
1209
1210 def read_context_hunk(self, lr):
1210 def read_context_hunk(self, lr):
1211 self.desc = lr.readline()
1211 self.desc = lr.readline()
1212 m = contextdesc.match(self.desc)
1212 m = contextdesc.match(self.desc)
1213 if not m:
1213 if not m:
1214 raise PatchError(_("bad hunk #%d") % self.number)
1214 raise PatchError(_("bad hunk #%d") % self.number)
1215 self.starta, aend = m.groups()
1215 self.starta, aend = m.groups()
1216 self.starta = int(self.starta)
1216 self.starta = int(self.starta)
1217 if aend is None:
1217 if aend is None:
1218 aend = self.starta
1218 aend = self.starta
1219 self.lena = int(aend) - self.starta
1219 self.lena = int(aend) - self.starta
1220 if self.starta:
1220 if self.starta:
1221 self.lena += 1
1221 self.lena += 1
1222 for x in xrange(self.lena):
1222 for x in xrange(self.lena):
1223 l = lr.readline()
1223 l = lr.readline()
1224 if l.startswith('---'):
1224 if l.startswith('---'):
1225 # lines addition, old block is empty
1225 # lines addition, old block is empty
1226 lr.push(l)
1226 lr.push(l)
1227 break
1227 break
1228 s = l[2:]
1228 s = l[2:]
1229 if l.startswith('- ') or l.startswith('! '):
1229 if l.startswith('- ') or l.startswith('! '):
1230 u = '-' + s
1230 u = '-' + s
1231 elif l.startswith(' '):
1231 elif l.startswith(' '):
1232 u = ' ' + s
1232 u = ' ' + s
1233 else:
1233 else:
1234 raise PatchError(_("bad hunk #%d old text line %d") %
1234 raise PatchError(_("bad hunk #%d old text line %d") %
1235 (self.number, x))
1235 (self.number, x))
1236 self.a.append(u)
1236 self.a.append(u)
1237 self.hunk.append(u)
1237 self.hunk.append(u)
1238
1238
1239 l = lr.readline()
1239 l = lr.readline()
1240 if l.startswith('\ '):
1240 if l.startswith('\ '):
1241 s = self.a[-1][:-1]
1241 s = self.a[-1][:-1]
1242 self.a[-1] = s
1242 self.a[-1] = s
1243 self.hunk[-1] = s
1243 self.hunk[-1] = s
1244 l = lr.readline()
1244 l = lr.readline()
1245 m = contextdesc.match(l)
1245 m = contextdesc.match(l)
1246 if not m:
1246 if not m:
1247 raise PatchError(_("bad hunk #%d") % self.number)
1247 raise PatchError(_("bad hunk #%d") % self.number)
1248 self.startb, bend = m.groups()
1248 self.startb, bend = m.groups()
1249 self.startb = int(self.startb)
1249 self.startb = int(self.startb)
1250 if bend is None:
1250 if bend is None:
1251 bend = self.startb
1251 bend = self.startb
1252 self.lenb = int(bend) - self.startb
1252 self.lenb = int(bend) - self.startb
1253 if self.startb:
1253 if self.startb:
1254 self.lenb += 1
1254 self.lenb += 1
1255 hunki = 1
1255 hunki = 1
1256 for x in xrange(self.lenb):
1256 for x in xrange(self.lenb):
1257 l = lr.readline()
1257 l = lr.readline()
1258 if l.startswith('\ '):
1258 if l.startswith('\ '):
1259 # XXX: the only way to hit this is with an invalid line range.
1259 # XXX: the only way to hit this is with an invalid line range.
1260 # The no-eol marker is not counted in the line range, but I
1260 # The no-eol marker is not counted in the line range, but I
1261 # guess there are diff(1) out there which behave differently.
1261 # guess there are diff(1) out there which behave differently.
1262 s = self.b[-1][:-1]
1262 s = self.b[-1][:-1]
1263 self.b[-1] = s
1263 self.b[-1] = s
1264 self.hunk[hunki - 1] = s
1264 self.hunk[hunki - 1] = s
1265 continue
1265 continue
1266 if not l:
1266 if not l:
1267 # line deletions, new block is empty and we hit EOF
1267 # line deletions, new block is empty and we hit EOF
1268 lr.push(l)
1268 lr.push(l)
1269 break
1269 break
1270 s = l[2:]
1270 s = l[2:]
1271 if l.startswith('+ ') or l.startswith('! '):
1271 if l.startswith('+ ') or l.startswith('! '):
1272 u = '+' + s
1272 u = '+' + s
1273 elif l.startswith(' '):
1273 elif l.startswith(' '):
1274 u = ' ' + s
1274 u = ' ' + s
1275 elif len(self.b) == 0:
1275 elif len(self.b) == 0:
1276 # line deletions, new block is empty
1276 # line deletions, new block is empty
1277 lr.push(l)
1277 lr.push(l)
1278 break
1278 break
1279 else:
1279 else:
1280 raise PatchError(_("bad hunk #%d old text line %d") %
1280 raise PatchError(_("bad hunk #%d old text line %d") %
1281 (self.number, x))
1281 (self.number, x))
1282 self.b.append(s)
1282 self.b.append(s)
1283 while True:
1283 while True:
1284 if hunki >= len(self.hunk):
1284 if hunki >= len(self.hunk):
1285 h = ""
1285 h = ""
1286 else:
1286 else:
1287 h = self.hunk[hunki]
1287 h = self.hunk[hunki]
1288 hunki += 1
1288 hunki += 1
1289 if h == u:
1289 if h == u:
1290 break
1290 break
1291 elif h.startswith('-'):
1291 elif h.startswith('-'):
1292 continue
1292 continue
1293 else:
1293 else:
1294 self.hunk.insert(hunki - 1, u)
1294 self.hunk.insert(hunki - 1, u)
1295 break
1295 break
1296
1296
1297 if not self.a:
1297 if not self.a:
1298 # this happens when lines were only added to the hunk
1298 # this happens when lines were only added to the hunk
1299 for x in self.hunk:
1299 for x in self.hunk:
1300 if x.startswith('-') or x.startswith(' '):
1300 if x.startswith('-') or x.startswith(' '):
1301 self.a.append(x)
1301 self.a.append(x)
1302 if not self.b:
1302 if not self.b:
1303 # this happens when lines were only deleted from the hunk
1303 # this happens when lines were only deleted from the hunk
1304 for x in self.hunk:
1304 for x in self.hunk:
1305 if x.startswith('+') or x.startswith(' '):
1305 if x.startswith('+') or x.startswith(' '):
1306 self.b.append(x[1:])
1306 self.b.append(x[1:])
1307 # @@ -start,len +start,len @@
1307 # @@ -start,len +start,len @@
1308 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1308 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1309 self.startb, self.lenb)
1309 self.startb, self.lenb)
1310 self.hunk[0] = self.desc
1310 self.hunk[0] = self.desc
1311 self._fixnewline(lr)
1311 self._fixnewline(lr)
1312
1312
1313 def _fixnewline(self, lr):
1313 def _fixnewline(self, lr):
1314 l = lr.readline()
1314 l = lr.readline()
1315 if l.startswith('\ '):
1315 if l.startswith('\ '):
1316 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1316 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1317 else:
1317 else:
1318 lr.push(l)
1318 lr.push(l)
1319
1319
1320 def complete(self):
1320 def complete(self):
1321 return len(self.a) == self.lena and len(self.b) == self.lenb
1321 return len(self.a) == self.lena and len(self.b) == self.lenb
1322
1322
1323 def _fuzzit(self, old, new, fuzz, toponly):
1323 def _fuzzit(self, old, new, fuzz, toponly):
1324 # this removes context lines from the top and bottom of list 'l'. It
1324 # this removes context lines from the top and bottom of list 'l'. It
1325 # checks the hunk to make sure only context lines are removed, and then
1325 # checks the hunk to make sure only context lines are removed, and then
1326 # returns a new shortened list of lines.
1326 # returns a new shortened list of lines.
1327 fuzz = min(fuzz, len(old))
1327 fuzz = min(fuzz, len(old))
1328 if fuzz:
1328 if fuzz:
1329 top = 0
1329 top = 0
1330 bot = 0
1330 bot = 0
1331 hlen = len(self.hunk)
1331 hlen = len(self.hunk)
1332 for x in xrange(hlen - 1):
1332 for x in xrange(hlen - 1):
1333 # the hunk starts with the @@ line, so use x+1
1333 # the hunk starts with the @@ line, so use x+1
1334 if self.hunk[x + 1][0] == ' ':
1334 if self.hunk[x + 1][0] == ' ':
1335 top += 1
1335 top += 1
1336 else:
1336 else:
1337 break
1337 break
1338 if not toponly:
1338 if not toponly:
1339 for x in xrange(hlen - 1):
1339 for x in xrange(hlen - 1):
1340 if self.hunk[hlen - bot - 1][0] == ' ':
1340 if self.hunk[hlen - bot - 1][0] == ' ':
1341 bot += 1
1341 bot += 1
1342 else:
1342 else:
1343 break
1343 break
1344
1344
1345 bot = min(fuzz, bot)
1345 bot = min(fuzz, bot)
1346 top = min(fuzz, top)
1346 top = min(fuzz, top)
1347 return old[top:len(old) - bot], new[top:len(new) - bot], top
1347 return old[top:len(old) - bot], new[top:len(new) - bot], top
1348 return old, new, 0
1348 return old, new, 0
1349
1349
1350 def fuzzit(self, fuzz, toponly):
1350 def fuzzit(self, fuzz, toponly):
1351 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1351 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1352 oldstart = self.starta + top
1352 oldstart = self.starta + top
1353 newstart = self.startb + top
1353 newstart = self.startb + top
1354 # zero length hunk ranges already have their start decremented
1354 # zero length hunk ranges already have their start decremented
1355 if self.lena and oldstart > 0:
1355 if self.lena and oldstart > 0:
1356 oldstart -= 1
1356 oldstart -= 1
1357 if self.lenb and newstart > 0:
1357 if self.lenb and newstart > 0:
1358 newstart -= 1
1358 newstart -= 1
1359 return old, oldstart, new, newstart
1359 return old, oldstart, new, newstart
1360
1360
1361 class binhunk(object):
1361 class binhunk(object):
1362 'A binary patch file.'
1362 'A binary patch file.'
1363 def __init__(self, lr, fname):
1363 def __init__(self, lr, fname):
1364 self.text = None
1364 self.text = None
1365 self.delta = False
1365 self.delta = False
1366 self.hunk = ['GIT binary patch\n']
1366 self.hunk = ['GIT binary patch\n']
1367 self._fname = fname
1367 self._fname = fname
1368 self._read(lr)
1368 self._read(lr)
1369
1369
1370 def complete(self):
1370 def complete(self):
1371 return self.text is not None
1371 return self.text is not None
1372
1372
1373 def new(self, lines):
1373 def new(self, lines):
1374 if self.delta:
1374 if self.delta:
1375 return [applybindelta(self.text, ''.join(lines))]
1375 return [applybindelta(self.text, ''.join(lines))]
1376 return [self.text]
1376 return [self.text]
1377
1377
1378 def _read(self, lr):
1378 def _read(self, lr):
1379 def getline(lr, hunk):
1379 def getline(lr, hunk):
1380 l = lr.readline()
1380 l = lr.readline()
1381 hunk.append(l)
1381 hunk.append(l)
1382 return l.rstrip('\r\n')
1382 return l.rstrip('\r\n')
1383
1383
1384 size = 0
1384 size = 0
1385 while True:
1385 while True:
1386 line = getline(lr, self.hunk)
1386 line = getline(lr, self.hunk)
1387 if not line:
1387 if not line:
1388 raise PatchError(_('could not extract "%s" binary data')
1388 raise PatchError(_('could not extract "%s" binary data')
1389 % self._fname)
1389 % self._fname)
1390 if line.startswith('literal '):
1390 if line.startswith('literal '):
1391 size = int(line[8:].rstrip())
1391 size = int(line[8:].rstrip())
1392 break
1392 break
1393 if line.startswith('delta '):
1393 if line.startswith('delta '):
1394 size = int(line[6:].rstrip())
1394 size = int(line[6:].rstrip())
1395 self.delta = True
1395 self.delta = True
1396 break
1396 break
1397 dec = []
1397 dec = []
1398 line = getline(lr, self.hunk)
1398 line = getline(lr, self.hunk)
1399 while len(line) > 1:
1399 while len(line) > 1:
1400 l = line[0]
1400 l = line[0]
1401 if l <= 'Z' and l >= 'A':
1401 if l <= 'Z' and l >= 'A':
1402 l = ord(l) - ord('A') + 1
1402 l = ord(l) - ord('A') + 1
1403 else:
1403 else:
1404 l = ord(l) - ord('a') + 27
1404 l = ord(l) - ord('a') + 27
1405 try:
1405 try:
1406 dec.append(base85.b85decode(line[1:])[:l])
1406 dec.append(base85.b85decode(line[1:])[:l])
1407 except ValueError as e:
1407 except ValueError as e:
1408 raise PatchError(_('could not decode "%s" binary patch: %s')
1408 raise PatchError(_('could not decode "%s" binary patch: %s')
1409 % (self._fname, str(e)))
1409 % (self._fname, str(e)))
1410 line = getline(lr, self.hunk)
1410 line = getline(lr, self.hunk)
1411 text = zlib.decompress(''.join(dec))
1411 text = zlib.decompress(''.join(dec))
1412 if len(text) != size:
1412 if len(text) != size:
1413 raise PatchError(_('"%s" length is %d bytes, should be %d')
1413 raise PatchError(_('"%s" length is %d bytes, should be %d')
1414 % (self._fname, len(text), size))
1414 % (self._fname, len(text), size))
1415 self.text = text
1415 self.text = text
1416
1416
1417 def parsefilename(str):
1417 def parsefilename(str):
1418 # --- filename \t|space stuff
1418 # --- filename \t|space stuff
1419 s = str[4:].rstrip('\r\n')
1419 s = str[4:].rstrip('\r\n')
1420 i = s.find('\t')
1420 i = s.find('\t')
1421 if i < 0:
1421 if i < 0:
1422 i = s.find(' ')
1422 i = s.find(' ')
1423 if i < 0:
1423 if i < 0:
1424 return s
1424 return s
1425 return s[:i]
1425 return s[:i]
1426
1426
1427 def reversehunks(hunks):
1427 def reversehunks(hunks):
1428 '''reverse the signs in the hunks given as argument
1428 '''reverse the signs in the hunks given as argument
1429
1429
1430 This function operates on hunks coming out of patch.filterpatch, that is
1430 This function operates on hunks coming out of patch.filterpatch, that is
1431 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1431 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1432
1432
1433 >>> rawpatch = """diff --git a/folder1/g b/folder1/g
1433 >>> rawpatch = """diff --git a/folder1/g b/folder1/g
1434 ... --- a/folder1/g
1434 ... --- a/folder1/g
1435 ... +++ b/folder1/g
1435 ... +++ b/folder1/g
1436 ... @@ -1,7 +1,7 @@
1436 ... @@ -1,7 +1,7 @@
1437 ... +firstline
1437 ... +firstline
1438 ... c
1438 ... c
1439 ... 1
1439 ... 1
1440 ... 2
1440 ... 2
1441 ... + 3
1441 ... + 3
1442 ... -4
1442 ... -4
1443 ... 5
1443 ... 5
1444 ... d
1444 ... d
1445 ... +lastline"""
1445 ... +lastline"""
1446 >>> hunks = parsepatch(rawpatch)
1446 >>> hunks = parsepatch(rawpatch)
1447 >>> hunkscomingfromfilterpatch = []
1447 >>> hunkscomingfromfilterpatch = []
1448 >>> for h in hunks:
1448 >>> for h in hunks:
1449 ... hunkscomingfromfilterpatch.append(h)
1449 ... hunkscomingfromfilterpatch.append(h)
1450 ... hunkscomingfromfilterpatch.extend(h.hunks)
1450 ... hunkscomingfromfilterpatch.extend(h.hunks)
1451
1451
1452 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1452 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1453 >>> from . import util
1453 >>> from . import util
1454 >>> fp = util.stringio()
1454 >>> fp = util.stringio()
1455 >>> for c in reversedhunks:
1455 >>> for c in reversedhunks:
1456 ... c.write(fp)
1456 ... c.write(fp)
1457 >>> fp.seek(0)
1457 >>> fp.seek(0)
1458 >>> reversedpatch = fp.read()
1458 >>> reversedpatch = fp.read()
1459 >>> print reversedpatch
1459 >>> print reversedpatch
1460 diff --git a/folder1/g b/folder1/g
1460 diff --git a/folder1/g b/folder1/g
1461 --- a/folder1/g
1461 --- a/folder1/g
1462 +++ b/folder1/g
1462 +++ b/folder1/g
1463 @@ -1,4 +1,3 @@
1463 @@ -1,4 +1,3 @@
1464 -firstline
1464 -firstline
1465 c
1465 c
1466 1
1466 1
1467 2
1467 2
1468 @@ -1,6 +2,6 @@
1468 @@ -1,6 +2,6 @@
1469 c
1469 c
1470 1
1470 1
1471 2
1471 2
1472 - 3
1472 - 3
1473 +4
1473 +4
1474 5
1474 5
1475 d
1475 d
1476 @@ -5,3 +6,2 @@
1476 @@ -5,3 +6,2 @@
1477 5
1477 5
1478 d
1478 d
1479 -lastline
1479 -lastline
1480
1480
1481 '''
1481 '''
1482
1482
1483 from . import crecord as crecordmod
1483 from . import crecord as crecordmod
1484 newhunks = []
1484 newhunks = []
1485 for c in hunks:
1485 for c in hunks:
1486 if isinstance(c, crecordmod.uihunk):
1486 if isinstance(c, crecordmod.uihunk):
1487 # curses hunks encapsulate the record hunk in _hunk
1487 # curses hunks encapsulate the record hunk in _hunk
1488 c = c._hunk
1488 c = c._hunk
1489 if isinstance(c, recordhunk):
1489 if isinstance(c, recordhunk):
1490 for j, line in enumerate(c.hunk):
1490 for j, line in enumerate(c.hunk):
1491 if line.startswith("-"):
1491 if line.startswith("-"):
1492 c.hunk[j] = "+" + c.hunk[j][1:]
1492 c.hunk[j] = "+" + c.hunk[j][1:]
1493 elif line.startswith("+"):
1493 elif line.startswith("+"):
1494 c.hunk[j] = "-" + c.hunk[j][1:]
1494 c.hunk[j] = "-" + c.hunk[j][1:]
1495 c.added, c.removed = c.removed, c.added
1495 c.added, c.removed = c.removed, c.added
1496 newhunks.append(c)
1496 newhunks.append(c)
1497 return newhunks
1497 return newhunks
1498
1498
1499 def parsepatch(originalchunks):
1499 def parsepatch(originalchunks):
1500 """patch -> [] of headers -> [] of hunks """
1500 """patch -> [] of headers -> [] of hunks """
1501 class parser(object):
1501 class parser(object):
1502 """patch parsing state machine"""
1502 """patch parsing state machine"""
1503 def __init__(self):
1503 def __init__(self):
1504 self.fromline = 0
1504 self.fromline = 0
1505 self.toline = 0
1505 self.toline = 0
1506 self.proc = ''
1506 self.proc = ''
1507 self.header = None
1507 self.header = None
1508 self.context = []
1508 self.context = []
1509 self.before = []
1509 self.before = []
1510 self.hunk = []
1510 self.hunk = []
1511 self.headers = []
1511 self.headers = []
1512
1512
1513 def addrange(self, limits):
1513 def addrange(self, limits):
1514 fromstart, fromend, tostart, toend, proc = limits
1514 fromstart, fromend, tostart, toend, proc = limits
1515 self.fromline = int(fromstart)
1515 self.fromline = int(fromstart)
1516 self.toline = int(tostart)
1516 self.toline = int(tostart)
1517 self.proc = proc
1517 self.proc = proc
1518
1518
1519 def addcontext(self, context):
1519 def addcontext(self, context):
1520 if self.hunk:
1520 if self.hunk:
1521 h = recordhunk(self.header, self.fromline, self.toline,
1521 h = recordhunk(self.header, self.fromline, self.toline,
1522 self.proc, self.before, self.hunk, context)
1522 self.proc, self.before, self.hunk, context)
1523 self.header.hunks.append(h)
1523 self.header.hunks.append(h)
1524 self.fromline += len(self.before) + h.removed
1524 self.fromline += len(self.before) + h.removed
1525 self.toline += len(self.before) + h.added
1525 self.toline += len(self.before) + h.added
1526 self.before = []
1526 self.before = []
1527 self.hunk = []
1527 self.hunk = []
1528 self.context = context
1528 self.context = context
1529
1529
1530 def addhunk(self, hunk):
1530 def addhunk(self, hunk):
1531 if self.context:
1531 if self.context:
1532 self.before = self.context
1532 self.before = self.context
1533 self.context = []
1533 self.context = []
1534 self.hunk = hunk
1534 self.hunk = hunk
1535
1535
1536 def newfile(self, hdr):
1536 def newfile(self, hdr):
1537 self.addcontext([])
1537 self.addcontext([])
1538 h = header(hdr)
1538 h = header(hdr)
1539 self.headers.append(h)
1539 self.headers.append(h)
1540 self.header = h
1540 self.header = h
1541
1541
1542 def addother(self, line):
1542 def addother(self, line):
1543 pass # 'other' lines are ignored
1543 pass # 'other' lines are ignored
1544
1544
1545 def finished(self):
1545 def finished(self):
1546 self.addcontext([])
1546 self.addcontext([])
1547 return self.headers
1547 return self.headers
1548
1548
1549 transitions = {
1549 transitions = {
1550 'file': {'context': addcontext,
1550 'file': {'context': addcontext,
1551 'file': newfile,
1551 'file': newfile,
1552 'hunk': addhunk,
1552 'hunk': addhunk,
1553 'range': addrange},
1553 'range': addrange},
1554 'context': {'file': newfile,
1554 'context': {'file': newfile,
1555 'hunk': addhunk,
1555 'hunk': addhunk,
1556 'range': addrange,
1556 'range': addrange,
1557 'other': addother},
1557 'other': addother},
1558 'hunk': {'context': addcontext,
1558 'hunk': {'context': addcontext,
1559 'file': newfile,
1559 'file': newfile,
1560 'range': addrange},
1560 'range': addrange},
1561 'range': {'context': addcontext,
1561 'range': {'context': addcontext,
1562 'hunk': addhunk},
1562 'hunk': addhunk},
1563 'other': {'other': addother},
1563 'other': {'other': addother},
1564 }
1564 }
1565
1565
1566 p = parser()
1566 p = parser()
1567 fp = stringio()
1567 fp = stringio()
1568 fp.write(''.join(originalchunks))
1568 fp.write(''.join(originalchunks))
1569 fp.seek(0)
1569 fp.seek(0)
1570
1570
1571 state = 'context'
1571 state = 'context'
1572 for newstate, data in scanpatch(fp):
1572 for newstate, data in scanpatch(fp):
1573 try:
1573 try:
1574 p.transitions[state][newstate](p, data)
1574 p.transitions[state][newstate](p, data)
1575 except KeyError:
1575 except KeyError:
1576 raise PatchError('unhandled transition: %s -> %s' %
1576 raise PatchError('unhandled transition: %s -> %s' %
1577 (state, newstate))
1577 (state, newstate))
1578 state = newstate
1578 state = newstate
1579 del fp
1579 del fp
1580 return p.finished()
1580 return p.finished()
1581
1581
1582 def pathtransform(path, strip, prefix):
1582 def pathtransform(path, strip, prefix):
1583 '''turn a path from a patch into a path suitable for the repository
1583 '''turn a path from a patch into a path suitable for the repository
1584
1584
1585 prefix, if not empty, is expected to be normalized with a / at the end.
1585 prefix, if not empty, is expected to be normalized with a / at the end.
1586
1586
1587 Returns (stripped components, path in repository).
1587 Returns (stripped components, path in repository).
1588
1588
1589 >>> pathtransform('a/b/c', 0, '')
1589 >>> pathtransform('a/b/c', 0, '')
1590 ('', 'a/b/c')
1590 ('', 'a/b/c')
1591 >>> pathtransform(' a/b/c ', 0, '')
1591 >>> pathtransform(' a/b/c ', 0, '')
1592 ('', ' a/b/c')
1592 ('', ' a/b/c')
1593 >>> pathtransform(' a/b/c ', 2, '')
1593 >>> pathtransform(' a/b/c ', 2, '')
1594 ('a/b/', 'c')
1594 ('a/b/', 'c')
1595 >>> pathtransform('a/b/c', 0, 'd/e/')
1595 >>> pathtransform('a/b/c', 0, 'd/e/')
1596 ('', 'd/e/a/b/c')
1596 ('', 'd/e/a/b/c')
1597 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1597 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1598 ('a//b/', 'd/e/c')
1598 ('a//b/', 'd/e/c')
1599 >>> pathtransform('a/b/c', 3, '')
1599 >>> pathtransform('a/b/c', 3, '')
1600 Traceback (most recent call last):
1600 Traceback (most recent call last):
1601 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1601 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1602 '''
1602 '''
1603 pathlen = len(path)
1603 pathlen = len(path)
1604 i = 0
1604 i = 0
1605 if strip == 0:
1605 if strip == 0:
1606 return '', prefix + path.rstrip()
1606 return '', prefix + path.rstrip()
1607 count = strip
1607 count = strip
1608 while count > 0:
1608 while count > 0:
1609 i = path.find('/', i)
1609 i = path.find('/', i)
1610 if i == -1:
1610 if i == -1:
1611 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1611 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1612 (count, strip, path))
1612 (count, strip, path))
1613 i += 1
1613 i += 1
1614 # consume '//' in the path
1614 # consume '//' in the path
1615 while i < pathlen - 1 and path[i] == '/':
1615 while i < pathlen - 1 and path[i] == '/':
1616 i += 1
1616 i += 1
1617 count -= 1
1617 count -= 1
1618 return path[:i].lstrip(), prefix + path[i:].rstrip()
1618 return path[:i].lstrip(), prefix + path[i:].rstrip()
1619
1619
1620 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1620 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1621 nulla = afile_orig == "/dev/null"
1621 nulla = afile_orig == "/dev/null"
1622 nullb = bfile_orig == "/dev/null"
1622 nullb = bfile_orig == "/dev/null"
1623 create = nulla and hunk.starta == 0 and hunk.lena == 0
1623 create = nulla and hunk.starta == 0 and hunk.lena == 0
1624 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1624 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1625 abase, afile = pathtransform(afile_orig, strip, prefix)
1625 abase, afile = pathtransform(afile_orig, strip, prefix)
1626 gooda = not nulla and backend.exists(afile)
1626 gooda = not nulla and backend.exists(afile)
1627 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1627 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1628 if afile == bfile:
1628 if afile == bfile:
1629 goodb = gooda
1629 goodb = gooda
1630 else:
1630 else:
1631 goodb = not nullb and backend.exists(bfile)
1631 goodb = not nullb and backend.exists(bfile)
1632 missing = not goodb and not gooda and not create
1632 missing = not goodb and not gooda and not create
1633
1633
1634 # some diff programs apparently produce patches where the afile is
1634 # some diff programs apparently produce patches where the afile is
1635 # not /dev/null, but afile starts with bfile
1635 # not /dev/null, but afile starts with bfile
1636 abasedir = afile[:afile.rfind('/') + 1]
1636 abasedir = afile[:afile.rfind('/') + 1]
1637 bbasedir = bfile[:bfile.rfind('/') + 1]
1637 bbasedir = bfile[:bfile.rfind('/') + 1]
1638 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1638 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1639 and hunk.starta == 0 and hunk.lena == 0):
1639 and hunk.starta == 0 and hunk.lena == 0):
1640 create = True
1640 create = True
1641 missing = False
1641 missing = False
1642
1642
1643 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1643 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1644 # diff is between a file and its backup. In this case, the original
1644 # diff is between a file and its backup. In this case, the original
1645 # file should be patched (see original mpatch code).
1645 # file should be patched (see original mpatch code).
1646 isbackup = (abase == bbase and bfile.startswith(afile))
1646 isbackup = (abase == bbase and bfile.startswith(afile))
1647 fname = None
1647 fname = None
1648 if not missing:
1648 if not missing:
1649 if gooda and goodb:
1649 if gooda and goodb:
1650 if isbackup:
1650 if isbackup:
1651 fname = afile
1651 fname = afile
1652 else:
1652 else:
1653 fname = bfile
1653 fname = bfile
1654 elif gooda:
1654 elif gooda:
1655 fname = afile
1655 fname = afile
1656
1656
1657 if not fname:
1657 if not fname:
1658 if not nullb:
1658 if not nullb:
1659 if isbackup:
1659 if isbackup:
1660 fname = afile
1660 fname = afile
1661 else:
1661 else:
1662 fname = bfile
1662 fname = bfile
1663 elif not nulla:
1663 elif not nulla:
1664 fname = afile
1664 fname = afile
1665 else:
1665 else:
1666 raise PatchError(_("undefined source and destination files"))
1666 raise PatchError(_("undefined source and destination files"))
1667
1667
1668 gp = patchmeta(fname)
1668 gp = patchmeta(fname)
1669 if create:
1669 if create:
1670 gp.op = 'ADD'
1670 gp.op = 'ADD'
1671 elif remove:
1671 elif remove:
1672 gp.op = 'DELETE'
1672 gp.op = 'DELETE'
1673 return gp
1673 return gp
1674
1674
1675 def scanpatch(fp):
1675 def scanpatch(fp):
1676 """like patch.iterhunks, but yield different events
1676 """like patch.iterhunks, but yield different events
1677
1677
1678 - ('file', [header_lines + fromfile + tofile])
1678 - ('file', [header_lines + fromfile + tofile])
1679 - ('context', [context_lines])
1679 - ('context', [context_lines])
1680 - ('hunk', [hunk_lines])
1680 - ('hunk', [hunk_lines])
1681 - ('range', (-start,len, +start,len, proc))
1681 - ('range', (-start,len, +start,len, proc))
1682 """
1682 """
1683 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1683 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1684 lr = linereader(fp)
1684 lr = linereader(fp)
1685
1685
1686 def scanwhile(first, p):
1686 def scanwhile(first, p):
1687 """scan lr while predicate holds"""
1687 """scan lr while predicate holds"""
1688 lines = [first]
1688 lines = [first]
1689 for line in iter(lr.readline, ''):
1689 for line in iter(lr.readline, ''):
1690 if p(line):
1690 if p(line):
1691 lines.append(line)
1691 lines.append(line)
1692 else:
1692 else:
1693 lr.push(line)
1693 lr.push(line)
1694 break
1694 break
1695 return lines
1695 return lines
1696
1696
1697 for line in iter(lr.readline, ''):
1697 for line in iter(lr.readline, ''):
1698 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1698 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1699 def notheader(line):
1699 def notheader(line):
1700 s = line.split(None, 1)
1700 s = line.split(None, 1)
1701 return not s or s[0] not in ('---', 'diff')
1701 return not s or s[0] not in ('---', 'diff')
1702 header = scanwhile(line, notheader)
1702 header = scanwhile(line, notheader)
1703 fromfile = lr.readline()
1703 fromfile = lr.readline()
1704 if fromfile.startswith('---'):
1704 if fromfile.startswith('---'):
1705 tofile = lr.readline()
1705 tofile = lr.readline()
1706 header += [fromfile, tofile]
1706 header += [fromfile, tofile]
1707 else:
1707 else:
1708 lr.push(fromfile)
1708 lr.push(fromfile)
1709 yield 'file', header
1709 yield 'file', header
1710 elif line[0] == ' ':
1710 elif line[0] == ' ':
1711 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1711 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1712 elif line[0] in '-+':
1712 elif line[0] in '-+':
1713 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1713 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1714 else:
1714 else:
1715 m = lines_re.match(line)
1715 m = lines_re.match(line)
1716 if m:
1716 if m:
1717 yield 'range', m.groups()
1717 yield 'range', m.groups()
1718 else:
1718 else:
1719 yield 'other', line
1719 yield 'other', line
1720
1720
1721 def scangitpatch(lr, firstline):
1721 def scangitpatch(lr, firstline):
1722 """
1722 """
1723 Git patches can emit:
1723 Git patches can emit:
1724 - rename a to b
1724 - rename a to b
1725 - change b
1725 - change b
1726 - copy a to c
1726 - copy a to c
1727 - change c
1727 - change c
1728
1728
1729 We cannot apply this sequence as-is, the renamed 'a' could not be
1729 We cannot apply this sequence as-is, the renamed 'a' could not be
1730 found for it would have been renamed already. And we cannot copy
1730 found for it would have been renamed already. And we cannot copy
1731 from 'b' instead because 'b' would have been changed already. So
1731 from 'b' instead because 'b' would have been changed already. So
1732 we scan the git patch for copy and rename commands so we can
1732 we scan the git patch for copy and rename commands so we can
1733 perform the copies ahead of time.
1733 perform the copies ahead of time.
1734 """
1734 """
1735 pos = 0
1735 pos = 0
1736 try:
1736 try:
1737 pos = lr.fp.tell()
1737 pos = lr.fp.tell()
1738 fp = lr.fp
1738 fp = lr.fp
1739 except IOError:
1739 except IOError:
1740 fp = stringio(lr.fp.read())
1740 fp = stringio(lr.fp.read())
1741 gitlr = linereader(fp)
1741 gitlr = linereader(fp)
1742 gitlr.push(firstline)
1742 gitlr.push(firstline)
1743 gitpatches = readgitpatch(gitlr)
1743 gitpatches = readgitpatch(gitlr)
1744 fp.seek(pos)
1744 fp.seek(pos)
1745 return gitpatches
1745 return gitpatches
1746
1746
1747 def iterhunks(fp):
1747 def iterhunks(fp):
1748 """Read a patch and yield the following events:
1748 """Read a patch and yield the following events:
1749 - ("file", afile, bfile, firsthunk): select a new target file.
1749 - ("file", afile, bfile, firsthunk): select a new target file.
1750 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1750 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1751 "file" event.
1751 "file" event.
1752 - ("git", gitchanges): current diff is in git format, gitchanges
1752 - ("git", gitchanges): current diff is in git format, gitchanges
1753 maps filenames to gitpatch records. Unique event.
1753 maps filenames to gitpatch records. Unique event.
1754 """
1754 """
1755 afile = ""
1755 afile = ""
1756 bfile = ""
1756 bfile = ""
1757 state = None
1757 state = None
1758 hunknum = 0
1758 hunknum = 0
1759 emitfile = newfile = False
1759 emitfile = newfile = False
1760 gitpatches = None
1760 gitpatches = None
1761
1761
1762 # our states
1762 # our states
1763 BFILE = 1
1763 BFILE = 1
1764 context = None
1764 context = None
1765 lr = linereader(fp)
1765 lr = linereader(fp)
1766
1766
1767 for x in iter(lr.readline, ''):
1767 for x in iter(lr.readline, ''):
1768 if state == BFILE and (
1768 if state == BFILE and (
1769 (not context and x[0] == '@')
1769 (not context and x[0] == '@')
1770 or (context is not False and x.startswith('***************'))
1770 or (context is not False and x.startswith('***************'))
1771 or x.startswith('GIT binary patch')):
1771 or x.startswith('GIT binary patch')):
1772 gp = None
1772 gp = None
1773 if (gitpatches and
1773 if (gitpatches and
1774 gitpatches[-1].ispatching(afile, bfile)):
1774 gitpatches[-1].ispatching(afile, bfile)):
1775 gp = gitpatches.pop()
1775 gp = gitpatches.pop()
1776 if x.startswith('GIT binary patch'):
1776 if x.startswith('GIT binary patch'):
1777 h = binhunk(lr, gp.path)
1777 h = binhunk(lr, gp.path)
1778 else:
1778 else:
1779 if context is None and x.startswith('***************'):
1779 if context is None and x.startswith('***************'):
1780 context = True
1780 context = True
1781 h = hunk(x, hunknum + 1, lr, context)
1781 h = hunk(x, hunknum + 1, lr, context)
1782 hunknum += 1
1782 hunknum += 1
1783 if emitfile:
1783 if emitfile:
1784 emitfile = False
1784 emitfile = False
1785 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1785 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1786 yield 'hunk', h
1786 yield 'hunk', h
1787 elif x.startswith('diff --git a/'):
1787 elif x.startswith('diff --git a/'):
1788 m = gitre.match(x.rstrip(' \r\n'))
1788 m = gitre.match(x.rstrip(' \r\n'))
1789 if not m:
1789 if not m:
1790 continue
1790 continue
1791 if gitpatches is None:
1791 if gitpatches is None:
1792 # scan whole input for git metadata
1792 # scan whole input for git metadata
1793 gitpatches = scangitpatch(lr, x)
1793 gitpatches = scangitpatch(lr, x)
1794 yield 'git', [g.copy() for g in gitpatches
1794 yield 'git', [g.copy() for g in gitpatches
1795 if g.op in ('COPY', 'RENAME')]
1795 if g.op in ('COPY', 'RENAME')]
1796 gitpatches.reverse()
1796 gitpatches.reverse()
1797 afile = 'a/' + m.group(1)
1797 afile = 'a/' + m.group(1)
1798 bfile = 'b/' + m.group(2)
1798 bfile = 'b/' + m.group(2)
1799 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1799 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1800 gp = gitpatches.pop()
1800 gp = gitpatches.pop()
1801 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1801 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1802 if not gitpatches:
1802 if not gitpatches:
1803 raise PatchError(_('failed to synchronize metadata for "%s"')
1803 raise PatchError(_('failed to synchronize metadata for "%s"')
1804 % afile[2:])
1804 % afile[2:])
1805 gp = gitpatches[-1]
1805 gp = gitpatches[-1]
1806 newfile = True
1806 newfile = True
1807 elif x.startswith('---'):
1807 elif x.startswith('---'):
1808 # check for a unified diff
1808 # check for a unified diff
1809 l2 = lr.readline()
1809 l2 = lr.readline()
1810 if not l2.startswith('+++'):
1810 if not l2.startswith('+++'):
1811 lr.push(l2)
1811 lr.push(l2)
1812 continue
1812 continue
1813 newfile = True
1813 newfile = True
1814 context = False
1814 context = False
1815 afile = parsefilename(x)
1815 afile = parsefilename(x)
1816 bfile = parsefilename(l2)
1816 bfile = parsefilename(l2)
1817 elif x.startswith('***'):
1817 elif x.startswith('***'):
1818 # check for a context diff
1818 # check for a context diff
1819 l2 = lr.readline()
1819 l2 = lr.readline()
1820 if not l2.startswith('---'):
1820 if not l2.startswith('---'):
1821 lr.push(l2)
1821 lr.push(l2)
1822 continue
1822 continue
1823 l3 = lr.readline()
1823 l3 = lr.readline()
1824 lr.push(l3)
1824 lr.push(l3)
1825 if not l3.startswith("***************"):
1825 if not l3.startswith("***************"):
1826 lr.push(l2)
1826 lr.push(l2)
1827 continue
1827 continue
1828 newfile = True
1828 newfile = True
1829 context = True
1829 context = True
1830 afile = parsefilename(x)
1830 afile = parsefilename(x)
1831 bfile = parsefilename(l2)
1831 bfile = parsefilename(l2)
1832
1832
1833 if newfile:
1833 if newfile:
1834 newfile = False
1834 newfile = False
1835 emitfile = True
1835 emitfile = True
1836 state = BFILE
1836 state = BFILE
1837 hunknum = 0
1837 hunknum = 0
1838
1838
1839 while gitpatches:
1839 while gitpatches:
1840 gp = gitpatches.pop()
1840 gp = gitpatches.pop()
1841 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1841 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1842
1842
1843 def applybindelta(binchunk, data):
1843 def applybindelta(binchunk, data):
1844 """Apply a binary delta hunk
1844 """Apply a binary delta hunk
1845 The algorithm used is the algorithm from git's patch-delta.c
1845 The algorithm used is the algorithm from git's patch-delta.c
1846 """
1846 """
1847 def deltahead(binchunk):
1847 def deltahead(binchunk):
1848 i = 0
1848 i = 0
1849 for c in binchunk:
1849 for c in binchunk:
1850 i += 1
1850 i += 1
1851 if not (ord(c) & 0x80):
1851 if not (ord(c) & 0x80):
1852 return i
1852 return i
1853 return i
1853 return i
1854 out = ""
1854 out = ""
1855 s = deltahead(binchunk)
1855 s = deltahead(binchunk)
1856 binchunk = binchunk[s:]
1856 binchunk = binchunk[s:]
1857 s = deltahead(binchunk)
1857 s = deltahead(binchunk)
1858 binchunk = binchunk[s:]
1858 binchunk = binchunk[s:]
1859 i = 0
1859 i = 0
1860 while i < len(binchunk):
1860 while i < len(binchunk):
1861 cmd = ord(binchunk[i])
1861 cmd = ord(binchunk[i])
1862 i += 1
1862 i += 1
1863 if (cmd & 0x80):
1863 if (cmd & 0x80):
1864 offset = 0
1864 offset = 0
1865 size = 0
1865 size = 0
1866 if (cmd & 0x01):
1866 if (cmd & 0x01):
1867 offset = ord(binchunk[i])
1867 offset = ord(binchunk[i])
1868 i += 1
1868 i += 1
1869 if (cmd & 0x02):
1869 if (cmd & 0x02):
1870 offset |= ord(binchunk[i]) << 8
1870 offset |= ord(binchunk[i]) << 8
1871 i += 1
1871 i += 1
1872 if (cmd & 0x04):
1872 if (cmd & 0x04):
1873 offset |= ord(binchunk[i]) << 16
1873 offset |= ord(binchunk[i]) << 16
1874 i += 1
1874 i += 1
1875 if (cmd & 0x08):
1875 if (cmd & 0x08):
1876 offset |= ord(binchunk[i]) << 24
1876 offset |= ord(binchunk[i]) << 24
1877 i += 1
1877 i += 1
1878 if (cmd & 0x10):
1878 if (cmd & 0x10):
1879 size = ord(binchunk[i])
1879 size = ord(binchunk[i])
1880 i += 1
1880 i += 1
1881 if (cmd & 0x20):
1881 if (cmd & 0x20):
1882 size |= ord(binchunk[i]) << 8
1882 size |= ord(binchunk[i]) << 8
1883 i += 1
1883 i += 1
1884 if (cmd & 0x40):
1884 if (cmd & 0x40):
1885 size |= ord(binchunk[i]) << 16
1885 size |= ord(binchunk[i]) << 16
1886 i += 1
1886 i += 1
1887 if size == 0:
1887 if size == 0:
1888 size = 0x10000
1888 size = 0x10000
1889 offset_end = offset + size
1889 offset_end = offset + size
1890 out += data[offset:offset_end]
1890 out += data[offset:offset_end]
1891 elif cmd != 0:
1891 elif cmd != 0:
1892 offset_end = i + cmd
1892 offset_end = i + cmd
1893 out += binchunk[i:offset_end]
1893 out += binchunk[i:offset_end]
1894 i += cmd
1894 i += cmd
1895 else:
1895 else:
1896 raise PatchError(_('unexpected delta opcode 0'))
1896 raise PatchError(_('unexpected delta opcode 0'))
1897 return out
1897 return out
1898
1898
1899 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1899 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1900 """Reads a patch from fp and tries to apply it.
1900 """Reads a patch from fp and tries to apply it.
1901
1901
1902 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1902 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1903 there was any fuzz.
1903 there was any fuzz.
1904
1904
1905 If 'eolmode' is 'strict', the patch content and patched file are
1905 If 'eolmode' is 'strict', the patch content and patched file are
1906 read in binary mode. Otherwise, line endings are ignored when
1906 read in binary mode. Otherwise, line endings are ignored when
1907 patching then normalized according to 'eolmode'.
1907 patching then normalized according to 'eolmode'.
1908 """
1908 """
1909 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1909 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1910 prefix=prefix, eolmode=eolmode)
1910 prefix=prefix, eolmode=eolmode)
1911
1911
1912 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1912 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1913 eolmode='strict'):
1913 eolmode='strict'):
1914
1914
1915 if prefix:
1915 if prefix:
1916 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1916 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1917 prefix)
1917 prefix)
1918 if prefix != '':
1918 if prefix != '':
1919 prefix += '/'
1919 prefix += '/'
1920 def pstrip(p):
1920 def pstrip(p):
1921 return pathtransform(p, strip - 1, prefix)[1]
1921 return pathtransform(p, strip - 1, prefix)[1]
1922
1922
1923 rejects = 0
1923 rejects = 0
1924 err = 0
1924 err = 0
1925 current_file = None
1925 current_file = None
1926
1926
1927 for state, values in iterhunks(fp):
1927 for state, values in iterhunks(fp):
1928 if state == 'hunk':
1928 if state == 'hunk':
1929 if not current_file:
1929 if not current_file:
1930 continue
1930 continue
1931 ret = current_file.apply(values)
1931 ret = current_file.apply(values)
1932 if ret > 0:
1932 if ret > 0:
1933 err = 1
1933 err = 1
1934 elif state == 'file':
1934 elif state == 'file':
1935 if current_file:
1935 if current_file:
1936 rejects += current_file.close()
1936 rejects += current_file.close()
1937 current_file = None
1937 current_file = None
1938 afile, bfile, first_hunk, gp = values
1938 afile, bfile, first_hunk, gp = values
1939 if gp:
1939 if gp:
1940 gp.path = pstrip(gp.path)
1940 gp.path = pstrip(gp.path)
1941 if gp.oldpath:
1941 if gp.oldpath:
1942 gp.oldpath = pstrip(gp.oldpath)
1942 gp.oldpath = pstrip(gp.oldpath)
1943 else:
1943 else:
1944 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1944 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1945 prefix)
1945 prefix)
1946 if gp.op == 'RENAME':
1946 if gp.op == 'RENAME':
1947 backend.unlink(gp.oldpath)
1947 backend.unlink(gp.oldpath)
1948 if not first_hunk:
1948 if not first_hunk:
1949 if gp.op == 'DELETE':
1949 if gp.op == 'DELETE':
1950 backend.unlink(gp.path)
1950 backend.unlink(gp.path)
1951 continue
1951 continue
1952 data, mode = None, None
1952 data, mode = None, None
1953 if gp.op in ('RENAME', 'COPY'):
1953 if gp.op in ('RENAME', 'COPY'):
1954 data, mode = store.getfile(gp.oldpath)[:2]
1954 data, mode = store.getfile(gp.oldpath)[:2]
1955 # FIXME: failing getfile has never been handled here
1955 if data is None:
1956 assert data is not None
1956 # This means that the old path does not exist
1957 raise PatchError(_("source file '%s' does not exist")
1958 % gp.oldpath)
1957 if gp.mode:
1959 if gp.mode:
1958 mode = gp.mode
1960 mode = gp.mode
1959 if gp.op == 'ADD':
1961 if gp.op == 'ADD':
1960 # Added files without content have no hunk and
1962 # Added files without content have no hunk and
1961 # must be created
1963 # must be created
1962 data = ''
1964 data = ''
1963 if data or mode:
1965 if data or mode:
1964 if (gp.op in ('ADD', 'RENAME', 'COPY')
1966 if (gp.op in ('ADD', 'RENAME', 'COPY')
1965 and backend.exists(gp.path)):
1967 and backend.exists(gp.path)):
1966 raise PatchError(_("cannot create %s: destination "
1968 raise PatchError(_("cannot create %s: destination "
1967 "already exists") % gp.path)
1969 "already exists") % gp.path)
1968 backend.setfile(gp.path, data, mode, gp.oldpath)
1970 backend.setfile(gp.path, data, mode, gp.oldpath)
1969 continue
1971 continue
1970 try:
1972 try:
1971 current_file = patcher(ui, gp, backend, store,
1973 current_file = patcher(ui, gp, backend, store,
1972 eolmode=eolmode)
1974 eolmode=eolmode)
1973 except PatchError as inst:
1975 except PatchError as inst:
1974 ui.warn(str(inst) + '\n')
1976 ui.warn(str(inst) + '\n')
1975 current_file = None
1977 current_file = None
1976 rejects += 1
1978 rejects += 1
1977 continue
1979 continue
1978 elif state == 'git':
1980 elif state == 'git':
1979 for gp in values:
1981 for gp in values:
1980 path = pstrip(gp.oldpath)
1982 path = pstrip(gp.oldpath)
1981 data, mode = backend.getfile(path)
1983 data, mode = backend.getfile(path)
1982 if data is None:
1984 if data is None:
1983 # The error ignored here will trigger a getfile()
1985 # The error ignored here will trigger a getfile()
1984 # error in a place more appropriate for error
1986 # error in a place more appropriate for error
1985 # handling, and will not interrupt the patching
1987 # handling, and will not interrupt the patching
1986 # process.
1988 # process.
1987 pass
1989 pass
1988 else:
1990 else:
1989 store.setfile(path, data, mode)
1991 store.setfile(path, data, mode)
1990 else:
1992 else:
1991 raise error.Abort(_('unsupported parser state: %s') % state)
1993 raise error.Abort(_('unsupported parser state: %s') % state)
1992
1994
1993 if current_file:
1995 if current_file:
1994 rejects += current_file.close()
1996 rejects += current_file.close()
1995
1997
1996 if rejects:
1998 if rejects:
1997 return -1
1999 return -1
1998 return err
2000 return err
1999
2001
2000 def _externalpatch(ui, repo, patcher, patchname, strip, files,
2002 def _externalpatch(ui, repo, patcher, patchname, strip, files,
2001 similarity):
2003 similarity):
2002 """use <patcher> to apply <patchname> to the working directory.
2004 """use <patcher> to apply <patchname> to the working directory.
2003 returns whether patch was applied with fuzz factor."""
2005 returns whether patch was applied with fuzz factor."""
2004
2006
2005 fuzz = False
2007 fuzz = False
2006 args = []
2008 args = []
2007 cwd = repo.root
2009 cwd = repo.root
2008 if cwd:
2010 if cwd:
2009 args.append('-d %s' % util.shellquote(cwd))
2011 args.append('-d %s' % util.shellquote(cwd))
2010 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
2012 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
2011 util.shellquote(patchname)))
2013 util.shellquote(patchname)))
2012 try:
2014 try:
2013 for line in fp:
2015 for line in fp:
2014 line = line.rstrip()
2016 line = line.rstrip()
2015 ui.note(line + '\n')
2017 ui.note(line + '\n')
2016 if line.startswith('patching file '):
2018 if line.startswith('patching file '):
2017 pf = util.parsepatchoutput(line)
2019 pf = util.parsepatchoutput(line)
2018 printed_file = False
2020 printed_file = False
2019 files.add(pf)
2021 files.add(pf)
2020 elif line.find('with fuzz') >= 0:
2022 elif line.find('with fuzz') >= 0:
2021 fuzz = True
2023 fuzz = True
2022 if not printed_file:
2024 if not printed_file:
2023 ui.warn(pf + '\n')
2025 ui.warn(pf + '\n')
2024 printed_file = True
2026 printed_file = True
2025 ui.warn(line + '\n')
2027 ui.warn(line + '\n')
2026 elif line.find('saving rejects to file') >= 0:
2028 elif line.find('saving rejects to file') >= 0:
2027 ui.warn(line + '\n')
2029 ui.warn(line + '\n')
2028 elif line.find('FAILED') >= 0:
2030 elif line.find('FAILED') >= 0:
2029 if not printed_file:
2031 if not printed_file:
2030 ui.warn(pf + '\n')
2032 ui.warn(pf + '\n')
2031 printed_file = True
2033 printed_file = True
2032 ui.warn(line + '\n')
2034 ui.warn(line + '\n')
2033 finally:
2035 finally:
2034 if files:
2036 if files:
2035 scmutil.marktouched(repo, files, similarity)
2037 scmutil.marktouched(repo, files, similarity)
2036 code = fp.close()
2038 code = fp.close()
2037 if code:
2039 if code:
2038 raise PatchError(_("patch command failed: %s") %
2040 raise PatchError(_("patch command failed: %s") %
2039 util.explainexit(code)[0])
2041 util.explainexit(code)[0])
2040 return fuzz
2042 return fuzz
2041
2043
2042 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2044 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2043 eolmode='strict'):
2045 eolmode='strict'):
2044 if files is None:
2046 if files is None:
2045 files = set()
2047 files = set()
2046 if eolmode is None:
2048 if eolmode is None:
2047 eolmode = ui.config('patch', 'eol', 'strict')
2049 eolmode = ui.config('patch', 'eol', 'strict')
2048 if eolmode.lower() not in eolmodes:
2050 if eolmode.lower() not in eolmodes:
2049 raise error.Abort(_('unsupported line endings type: %s') % eolmode)
2051 raise error.Abort(_('unsupported line endings type: %s') % eolmode)
2050 eolmode = eolmode.lower()
2052 eolmode = eolmode.lower()
2051
2053
2052 store = filestore()
2054 store = filestore()
2053 try:
2055 try:
2054 fp = open(patchobj, 'rb')
2056 fp = open(patchobj, 'rb')
2055 except TypeError:
2057 except TypeError:
2056 fp = patchobj
2058 fp = patchobj
2057 try:
2059 try:
2058 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2060 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2059 eolmode=eolmode)
2061 eolmode=eolmode)
2060 finally:
2062 finally:
2061 if fp != patchobj:
2063 if fp != patchobj:
2062 fp.close()
2064 fp.close()
2063 files.update(backend.close())
2065 files.update(backend.close())
2064 store.close()
2066 store.close()
2065 if ret < 0:
2067 if ret < 0:
2066 raise PatchError(_('patch failed to apply'))
2068 raise PatchError(_('patch failed to apply'))
2067 return ret > 0
2069 return ret > 0
2068
2070
2069 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2071 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2070 eolmode='strict', similarity=0):
2072 eolmode='strict', similarity=0):
2071 """use builtin patch to apply <patchobj> to the working directory.
2073 """use builtin patch to apply <patchobj> to the working directory.
2072 returns whether patch was applied with fuzz factor."""
2074 returns whether patch was applied with fuzz factor."""
2073 backend = workingbackend(ui, repo, similarity)
2075 backend = workingbackend(ui, repo, similarity)
2074 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2076 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2075
2077
2076 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2078 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2077 eolmode='strict'):
2079 eolmode='strict'):
2078 backend = repobackend(ui, repo, ctx, store)
2080 backend = repobackend(ui, repo, ctx, store)
2079 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2081 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2080
2082
2081 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2083 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2082 similarity=0):
2084 similarity=0):
2083 """Apply <patchname> to the working directory.
2085 """Apply <patchname> to the working directory.
2084
2086
2085 'eolmode' specifies how end of lines should be handled. It can be:
2087 'eolmode' specifies how end of lines should be handled. It can be:
2086 - 'strict': inputs are read in binary mode, EOLs are preserved
2088 - 'strict': inputs are read in binary mode, EOLs are preserved
2087 - 'crlf': EOLs are ignored when patching and reset to CRLF
2089 - 'crlf': EOLs are ignored when patching and reset to CRLF
2088 - 'lf': EOLs are ignored when patching and reset to LF
2090 - 'lf': EOLs are ignored when patching and reset to LF
2089 - None: get it from user settings, default to 'strict'
2091 - None: get it from user settings, default to 'strict'
2090 'eolmode' is ignored when using an external patcher program.
2092 'eolmode' is ignored when using an external patcher program.
2091
2093
2092 Returns whether patch was applied with fuzz factor.
2094 Returns whether patch was applied with fuzz factor.
2093 """
2095 """
2094 patcher = ui.config('ui', 'patch')
2096 patcher = ui.config('ui', 'patch')
2095 if files is None:
2097 if files is None:
2096 files = set()
2098 files = set()
2097 if patcher:
2099 if patcher:
2098 return _externalpatch(ui, repo, patcher, patchname, strip,
2100 return _externalpatch(ui, repo, patcher, patchname, strip,
2099 files, similarity)
2101 files, similarity)
2100 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2102 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2101 similarity)
2103 similarity)
2102
2104
2103 def changedfiles(ui, repo, patchpath, strip=1):
2105 def changedfiles(ui, repo, patchpath, strip=1):
2104 backend = fsbackend(ui, repo.root)
2106 backend = fsbackend(ui, repo.root)
2105 with open(patchpath, 'rb') as fp:
2107 with open(patchpath, 'rb') as fp:
2106 changed = set()
2108 changed = set()
2107 for state, values in iterhunks(fp):
2109 for state, values in iterhunks(fp):
2108 if state == 'file':
2110 if state == 'file':
2109 afile, bfile, first_hunk, gp = values
2111 afile, bfile, first_hunk, gp = values
2110 if gp:
2112 if gp:
2111 gp.path = pathtransform(gp.path, strip - 1, '')[1]
2113 gp.path = pathtransform(gp.path, strip - 1, '')[1]
2112 if gp.oldpath:
2114 if gp.oldpath:
2113 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
2115 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
2114 else:
2116 else:
2115 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2117 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2116 '')
2118 '')
2117 changed.add(gp.path)
2119 changed.add(gp.path)
2118 if gp.op == 'RENAME':
2120 if gp.op == 'RENAME':
2119 changed.add(gp.oldpath)
2121 changed.add(gp.oldpath)
2120 elif state not in ('hunk', 'git'):
2122 elif state not in ('hunk', 'git'):
2121 raise error.Abort(_('unsupported parser state: %s') % state)
2123 raise error.Abort(_('unsupported parser state: %s') % state)
2122 return changed
2124 return changed
2123
2125
2124 class GitDiffRequired(Exception):
2126 class GitDiffRequired(Exception):
2125 pass
2127 pass
2126
2128
2127 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2129 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2128 '''return diffopts with all features supported and parsed'''
2130 '''return diffopts with all features supported and parsed'''
2129 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2131 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2130 git=True, whitespace=True, formatchanging=True)
2132 git=True, whitespace=True, formatchanging=True)
2131
2133
2132 diffopts = diffallopts
2134 diffopts = diffallopts
2133
2135
2134 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2136 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2135 whitespace=False, formatchanging=False):
2137 whitespace=False, formatchanging=False):
2136 '''return diffopts with only opted-in features parsed
2138 '''return diffopts with only opted-in features parsed
2137
2139
2138 Features:
2140 Features:
2139 - git: git-style diffs
2141 - git: git-style diffs
2140 - whitespace: whitespace options like ignoreblanklines and ignorews
2142 - whitespace: whitespace options like ignoreblanklines and ignorews
2141 - formatchanging: options that will likely break or cause correctness issues
2143 - formatchanging: options that will likely break or cause correctness issues
2142 with most diff parsers
2144 with most diff parsers
2143 '''
2145 '''
2144 def get(key, name=None, getter=ui.configbool, forceplain=None):
2146 def get(key, name=None, getter=ui.configbool, forceplain=None):
2145 if opts:
2147 if opts:
2146 v = opts.get(key)
2148 v = opts.get(key)
2147 # diffopts flags are either None-default (which is passed
2149 # diffopts flags are either None-default (which is passed
2148 # through unchanged, so we can identify unset values), or
2150 # through unchanged, so we can identify unset values), or
2149 # some other falsey default (eg --unified, which defaults
2151 # some other falsey default (eg --unified, which defaults
2150 # to an empty string). We only want to override the config
2152 # to an empty string). We only want to override the config
2151 # entries from hgrc with command line values if they
2153 # entries from hgrc with command line values if they
2152 # appear to have been set, which is any truthy value,
2154 # appear to have been set, which is any truthy value,
2153 # True, or False.
2155 # True, or False.
2154 if v or isinstance(v, bool):
2156 if v or isinstance(v, bool):
2155 return v
2157 return v
2156 if forceplain is not None and ui.plain():
2158 if forceplain is not None and ui.plain():
2157 return forceplain
2159 return forceplain
2158 return getter(section, name or key, None, untrusted=untrusted)
2160 return getter(section, name or key, None, untrusted=untrusted)
2159
2161
2160 # core options, expected to be understood by every diff parser
2162 # core options, expected to be understood by every diff parser
2161 buildopts = {
2163 buildopts = {
2162 'nodates': get('nodates'),
2164 'nodates': get('nodates'),
2163 'showfunc': get('show_function', 'showfunc'),
2165 'showfunc': get('show_function', 'showfunc'),
2164 'context': get('unified', getter=ui.config),
2166 'context': get('unified', getter=ui.config),
2165 }
2167 }
2166
2168
2167 if git:
2169 if git:
2168 buildopts['git'] = get('git')
2170 buildopts['git'] = get('git')
2169 if whitespace:
2171 if whitespace:
2170 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2172 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2171 buildopts['ignorewsamount'] = get('ignore_space_change',
2173 buildopts['ignorewsamount'] = get('ignore_space_change',
2172 'ignorewsamount')
2174 'ignorewsamount')
2173 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2175 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2174 'ignoreblanklines')
2176 'ignoreblanklines')
2175 if formatchanging:
2177 if formatchanging:
2176 buildopts['text'] = opts and opts.get('text')
2178 buildopts['text'] = opts and opts.get('text')
2177 buildopts['nobinary'] = get('nobinary', forceplain=False)
2179 buildopts['nobinary'] = get('nobinary', forceplain=False)
2178 buildopts['noprefix'] = get('noprefix', forceplain=False)
2180 buildopts['noprefix'] = get('noprefix', forceplain=False)
2179
2181
2180 return mdiff.diffopts(**buildopts)
2182 return mdiff.diffopts(**buildopts)
2181
2183
2182 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2184 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2183 losedatafn=None, prefix='', relroot='', copy=None):
2185 losedatafn=None, prefix='', relroot='', copy=None):
2184 '''yields diff of changes to files between two nodes, or node and
2186 '''yields diff of changes to files between two nodes, or node and
2185 working directory.
2187 working directory.
2186
2188
2187 if node1 is None, use first dirstate parent instead.
2189 if node1 is None, use first dirstate parent instead.
2188 if node2 is None, compare node1 with working directory.
2190 if node2 is None, compare node1 with working directory.
2189
2191
2190 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2192 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2191 every time some change cannot be represented with the current
2193 every time some change cannot be represented with the current
2192 patch format. Return False to upgrade to git patch format, True to
2194 patch format. Return False to upgrade to git patch format, True to
2193 accept the loss or raise an exception to abort the diff. It is
2195 accept the loss or raise an exception to abort the diff. It is
2194 called with the name of current file being diffed as 'fn'. If set
2196 called with the name of current file being diffed as 'fn'. If set
2195 to None, patches will always be upgraded to git format when
2197 to None, patches will always be upgraded to git format when
2196 necessary.
2198 necessary.
2197
2199
2198 prefix is a filename prefix that is prepended to all filenames on
2200 prefix is a filename prefix that is prepended to all filenames on
2199 display (used for subrepos).
2201 display (used for subrepos).
2200
2202
2201 relroot, if not empty, must be normalized with a trailing /. Any match
2203 relroot, if not empty, must be normalized with a trailing /. Any match
2202 patterns that fall outside it will be ignored.
2204 patterns that fall outside it will be ignored.
2203
2205
2204 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2206 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2205 information.'''
2207 information.'''
2206
2208
2207 if opts is None:
2209 if opts is None:
2208 opts = mdiff.defaultopts
2210 opts = mdiff.defaultopts
2209
2211
2210 if not node1 and not node2:
2212 if not node1 and not node2:
2211 node1 = repo.dirstate.p1()
2213 node1 = repo.dirstate.p1()
2212
2214
2213 def lrugetfilectx():
2215 def lrugetfilectx():
2214 cache = {}
2216 cache = {}
2215 order = collections.deque()
2217 order = collections.deque()
2216 def getfilectx(f, ctx):
2218 def getfilectx(f, ctx):
2217 fctx = ctx.filectx(f, filelog=cache.get(f))
2219 fctx = ctx.filectx(f, filelog=cache.get(f))
2218 if f not in cache:
2220 if f not in cache:
2219 if len(cache) > 20:
2221 if len(cache) > 20:
2220 del cache[order.popleft()]
2222 del cache[order.popleft()]
2221 cache[f] = fctx.filelog()
2223 cache[f] = fctx.filelog()
2222 else:
2224 else:
2223 order.remove(f)
2225 order.remove(f)
2224 order.append(f)
2226 order.append(f)
2225 return fctx
2227 return fctx
2226 return getfilectx
2228 return getfilectx
2227 getfilectx = lrugetfilectx()
2229 getfilectx = lrugetfilectx()
2228
2230
2229 ctx1 = repo[node1]
2231 ctx1 = repo[node1]
2230 ctx2 = repo[node2]
2232 ctx2 = repo[node2]
2231
2233
2232 relfiltered = False
2234 relfiltered = False
2233 if relroot != '' and match.always():
2235 if relroot != '' and match.always():
2234 # as a special case, create a new matcher with just the relroot
2236 # as a special case, create a new matcher with just the relroot
2235 pats = [relroot]
2237 pats = [relroot]
2236 match = scmutil.match(ctx2, pats, default='path')
2238 match = scmutil.match(ctx2, pats, default='path')
2237 relfiltered = True
2239 relfiltered = True
2238
2240
2239 if not changes:
2241 if not changes:
2240 changes = repo.status(ctx1, ctx2, match=match)
2242 changes = repo.status(ctx1, ctx2, match=match)
2241 modified, added, removed = changes[:3]
2243 modified, added, removed = changes[:3]
2242
2244
2243 if not modified and not added and not removed:
2245 if not modified and not added and not removed:
2244 return []
2246 return []
2245
2247
2246 if repo.ui.debugflag:
2248 if repo.ui.debugflag:
2247 hexfunc = hex
2249 hexfunc = hex
2248 else:
2250 else:
2249 hexfunc = short
2251 hexfunc = short
2250 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2252 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2251
2253
2252 if copy is None:
2254 if copy is None:
2253 copy = {}
2255 copy = {}
2254 if opts.git or opts.upgrade:
2256 if opts.git or opts.upgrade:
2255 copy = copies.pathcopies(ctx1, ctx2, match=match)
2257 copy = copies.pathcopies(ctx1, ctx2, match=match)
2256
2258
2257 if relroot is not None:
2259 if relroot is not None:
2258 if not relfiltered:
2260 if not relfiltered:
2259 # XXX this would ideally be done in the matcher, but that is
2261 # XXX this would ideally be done in the matcher, but that is
2260 # generally meant to 'or' patterns, not 'and' them. In this case we
2262 # generally meant to 'or' patterns, not 'and' them. In this case we
2261 # need to 'and' all the patterns from the matcher with relroot.
2263 # need to 'and' all the patterns from the matcher with relroot.
2262 def filterrel(l):
2264 def filterrel(l):
2263 return [f for f in l if f.startswith(relroot)]
2265 return [f for f in l if f.startswith(relroot)]
2264 modified = filterrel(modified)
2266 modified = filterrel(modified)
2265 added = filterrel(added)
2267 added = filterrel(added)
2266 removed = filterrel(removed)
2268 removed = filterrel(removed)
2267 relfiltered = True
2269 relfiltered = True
2268 # filter out copies where either side isn't inside the relative root
2270 # filter out copies where either side isn't inside the relative root
2269 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2271 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2270 if dst.startswith(relroot)
2272 if dst.startswith(relroot)
2271 and src.startswith(relroot)))
2273 and src.startswith(relroot)))
2272
2274
2273 modifiedset = set(modified)
2275 modifiedset = set(modified)
2274 addedset = set(added)
2276 addedset = set(added)
2275 removedset = set(removed)
2277 removedset = set(removed)
2276 for f in modified:
2278 for f in modified:
2277 if f not in ctx1:
2279 if f not in ctx1:
2278 # Fix up added, since merged-in additions appear as
2280 # Fix up added, since merged-in additions appear as
2279 # modifications during merges
2281 # modifications during merges
2280 modifiedset.remove(f)
2282 modifiedset.remove(f)
2281 addedset.add(f)
2283 addedset.add(f)
2282 for f in removed:
2284 for f in removed:
2283 if f not in ctx1:
2285 if f not in ctx1:
2284 # Merged-in additions that are then removed are reported as removed.
2286 # Merged-in additions that are then removed are reported as removed.
2285 # They are not in ctx1, so We don't want to show them in the diff.
2287 # They are not in ctx1, so We don't want to show them in the diff.
2286 removedset.remove(f)
2288 removedset.remove(f)
2287 modified = sorted(modifiedset)
2289 modified = sorted(modifiedset)
2288 added = sorted(addedset)
2290 added = sorted(addedset)
2289 removed = sorted(removedset)
2291 removed = sorted(removedset)
2290 for dst, src in copy.items():
2292 for dst, src in copy.items():
2291 if src not in ctx1:
2293 if src not in ctx1:
2292 # Files merged in during a merge and then copied/renamed are
2294 # Files merged in during a merge and then copied/renamed are
2293 # reported as copies. We want to show them in the diff as additions.
2295 # reported as copies. We want to show them in the diff as additions.
2294 del copy[dst]
2296 del copy[dst]
2295
2297
2296 def difffn(opts, losedata):
2298 def difffn(opts, losedata):
2297 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2299 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2298 copy, getfilectx, opts, losedata, prefix, relroot)
2300 copy, getfilectx, opts, losedata, prefix, relroot)
2299 if opts.upgrade and not opts.git:
2301 if opts.upgrade and not opts.git:
2300 try:
2302 try:
2301 def losedata(fn):
2303 def losedata(fn):
2302 if not losedatafn or not losedatafn(fn=fn):
2304 if not losedatafn or not losedatafn(fn=fn):
2303 raise GitDiffRequired
2305 raise GitDiffRequired
2304 # Buffer the whole output until we are sure it can be generated
2306 # Buffer the whole output until we are sure it can be generated
2305 return list(difffn(opts.copy(git=False), losedata))
2307 return list(difffn(opts.copy(git=False), losedata))
2306 except GitDiffRequired:
2308 except GitDiffRequired:
2307 return difffn(opts.copy(git=True), None)
2309 return difffn(opts.copy(git=True), None)
2308 else:
2310 else:
2309 return difffn(opts, None)
2311 return difffn(opts, None)
2310
2312
2311 def difflabel(func, *args, **kw):
2313 def difflabel(func, *args, **kw):
2312 '''yields 2-tuples of (output, label) based on the output of func()'''
2314 '''yields 2-tuples of (output, label) based on the output of func()'''
2313 headprefixes = [('diff', 'diff.diffline'),
2315 headprefixes = [('diff', 'diff.diffline'),
2314 ('copy', 'diff.extended'),
2316 ('copy', 'diff.extended'),
2315 ('rename', 'diff.extended'),
2317 ('rename', 'diff.extended'),
2316 ('old', 'diff.extended'),
2318 ('old', 'diff.extended'),
2317 ('new', 'diff.extended'),
2319 ('new', 'diff.extended'),
2318 ('deleted', 'diff.extended'),
2320 ('deleted', 'diff.extended'),
2319 ('---', 'diff.file_a'),
2321 ('---', 'diff.file_a'),
2320 ('+++', 'diff.file_b')]
2322 ('+++', 'diff.file_b')]
2321 textprefixes = [('@', 'diff.hunk'),
2323 textprefixes = [('@', 'diff.hunk'),
2322 ('-', 'diff.deleted'),
2324 ('-', 'diff.deleted'),
2323 ('+', 'diff.inserted')]
2325 ('+', 'diff.inserted')]
2324 head = False
2326 head = False
2325 for chunk in func(*args, **kw):
2327 for chunk in func(*args, **kw):
2326 lines = chunk.split('\n')
2328 lines = chunk.split('\n')
2327 for i, line in enumerate(lines):
2329 for i, line in enumerate(lines):
2328 if i != 0:
2330 if i != 0:
2329 yield ('\n', '')
2331 yield ('\n', '')
2330 if head:
2332 if head:
2331 if line.startswith('@'):
2333 if line.startswith('@'):
2332 head = False
2334 head = False
2333 else:
2335 else:
2334 if line and line[0] not in ' +-@\\':
2336 if line and line[0] not in ' +-@\\':
2335 head = True
2337 head = True
2336 stripline = line
2338 stripline = line
2337 diffline = False
2339 diffline = False
2338 if not head and line and line[0] in '+-':
2340 if not head and line and line[0] in '+-':
2339 # highlight tabs and trailing whitespace, but only in
2341 # highlight tabs and trailing whitespace, but only in
2340 # changed lines
2342 # changed lines
2341 stripline = line.rstrip()
2343 stripline = line.rstrip()
2342 diffline = True
2344 diffline = True
2343
2345
2344 prefixes = textprefixes
2346 prefixes = textprefixes
2345 if head:
2347 if head:
2346 prefixes = headprefixes
2348 prefixes = headprefixes
2347 for prefix, label in prefixes:
2349 for prefix, label in prefixes:
2348 if stripline.startswith(prefix):
2350 if stripline.startswith(prefix):
2349 if diffline:
2351 if diffline:
2350 for token in tabsplitter.findall(stripline):
2352 for token in tabsplitter.findall(stripline):
2351 if '\t' == token[0]:
2353 if '\t' == token[0]:
2352 yield (token, 'diff.tab')
2354 yield (token, 'diff.tab')
2353 else:
2355 else:
2354 yield (token, label)
2356 yield (token, label)
2355 else:
2357 else:
2356 yield (stripline, label)
2358 yield (stripline, label)
2357 break
2359 break
2358 else:
2360 else:
2359 yield (line, '')
2361 yield (line, '')
2360 if line != stripline:
2362 if line != stripline:
2361 yield (line[len(stripline):], 'diff.trailingwhitespace')
2363 yield (line[len(stripline):], 'diff.trailingwhitespace')
2362
2364
2363 def diffui(*args, **kw):
2365 def diffui(*args, **kw):
2364 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2366 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2365 return difflabel(diff, *args, **kw)
2367 return difflabel(diff, *args, **kw)
2366
2368
2367 def _filepairs(modified, added, removed, copy, opts):
2369 def _filepairs(modified, added, removed, copy, opts):
2368 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2370 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2369 before and f2 is the the name after. For added files, f1 will be None,
2371 before and f2 is the the name after. For added files, f1 will be None,
2370 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2372 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2371 or 'rename' (the latter two only if opts.git is set).'''
2373 or 'rename' (the latter two only if opts.git is set).'''
2372 gone = set()
2374 gone = set()
2373
2375
2374 copyto = dict([(v, k) for k, v in copy.items()])
2376 copyto = dict([(v, k) for k, v in copy.items()])
2375
2377
2376 addedset, removedset = set(added), set(removed)
2378 addedset, removedset = set(added), set(removed)
2377
2379
2378 for f in sorted(modified + added + removed):
2380 for f in sorted(modified + added + removed):
2379 copyop = None
2381 copyop = None
2380 f1, f2 = f, f
2382 f1, f2 = f, f
2381 if f in addedset:
2383 if f in addedset:
2382 f1 = None
2384 f1 = None
2383 if f in copy:
2385 if f in copy:
2384 if opts.git:
2386 if opts.git:
2385 f1 = copy[f]
2387 f1 = copy[f]
2386 if f1 in removedset and f1 not in gone:
2388 if f1 in removedset and f1 not in gone:
2387 copyop = 'rename'
2389 copyop = 'rename'
2388 gone.add(f1)
2390 gone.add(f1)
2389 else:
2391 else:
2390 copyop = 'copy'
2392 copyop = 'copy'
2391 elif f in removedset:
2393 elif f in removedset:
2392 f2 = None
2394 f2 = None
2393 if opts.git:
2395 if opts.git:
2394 # have we already reported a copy above?
2396 # have we already reported a copy above?
2395 if (f in copyto and copyto[f] in addedset
2397 if (f in copyto and copyto[f] in addedset
2396 and copy[copyto[f]] == f):
2398 and copy[copyto[f]] == f):
2397 continue
2399 continue
2398 yield f1, f2, copyop
2400 yield f1, f2, copyop
2399
2401
2400 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2402 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2401 copy, getfilectx, opts, losedatafn, prefix, relroot):
2403 copy, getfilectx, opts, losedatafn, prefix, relroot):
2402 '''given input data, generate a diff and yield it in blocks
2404 '''given input data, generate a diff and yield it in blocks
2403
2405
2404 If generating a diff would lose data like flags or binary data and
2406 If generating a diff would lose data like flags or binary data and
2405 losedatafn is not None, it will be called.
2407 losedatafn is not None, it will be called.
2406
2408
2407 relroot is removed and prefix is added to every path in the diff output.
2409 relroot is removed and prefix is added to every path in the diff output.
2408
2410
2409 If relroot is not empty, this function expects every path in modified,
2411 If relroot is not empty, this function expects every path in modified,
2410 added, removed and copy to start with it.'''
2412 added, removed and copy to start with it.'''
2411
2413
2412 def gitindex(text):
2414 def gitindex(text):
2413 if not text:
2415 if not text:
2414 text = ""
2416 text = ""
2415 l = len(text)
2417 l = len(text)
2416 s = hashlib.sha1('blob %d\0' % l)
2418 s = hashlib.sha1('blob %d\0' % l)
2417 s.update(text)
2419 s.update(text)
2418 return s.hexdigest()
2420 return s.hexdigest()
2419
2421
2420 if opts.noprefix:
2422 if opts.noprefix:
2421 aprefix = bprefix = ''
2423 aprefix = bprefix = ''
2422 else:
2424 else:
2423 aprefix = 'a/'
2425 aprefix = 'a/'
2424 bprefix = 'b/'
2426 bprefix = 'b/'
2425
2427
2426 def diffline(f, revs):
2428 def diffline(f, revs):
2427 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2429 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2428 return 'diff %s %s' % (revinfo, f)
2430 return 'diff %s %s' % (revinfo, f)
2429
2431
2430 date1 = util.datestr(ctx1.date())
2432 date1 = util.datestr(ctx1.date())
2431 date2 = util.datestr(ctx2.date())
2433 date2 = util.datestr(ctx2.date())
2432
2434
2433 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2435 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2434
2436
2435 if relroot != '' and (repo.ui.configbool('devel', 'all')
2437 if relroot != '' and (repo.ui.configbool('devel', 'all')
2436 or repo.ui.configbool('devel', 'check-relroot')):
2438 or repo.ui.configbool('devel', 'check-relroot')):
2437 for f in modified + added + removed + copy.keys() + copy.values():
2439 for f in modified + added + removed + copy.keys() + copy.values():
2438 if f is not None and not f.startswith(relroot):
2440 if f is not None and not f.startswith(relroot):
2439 raise AssertionError(
2441 raise AssertionError(
2440 "file %s doesn't start with relroot %s" % (f, relroot))
2442 "file %s doesn't start with relroot %s" % (f, relroot))
2441
2443
2442 for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts):
2444 for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts):
2443 content1 = None
2445 content1 = None
2444 content2 = None
2446 content2 = None
2445 flag1 = None
2447 flag1 = None
2446 flag2 = None
2448 flag2 = None
2447 if f1:
2449 if f1:
2448 content1 = getfilectx(f1, ctx1).data()
2450 content1 = getfilectx(f1, ctx1).data()
2449 if opts.git or losedatafn:
2451 if opts.git or losedatafn:
2450 flag1 = ctx1.flags(f1)
2452 flag1 = ctx1.flags(f1)
2451 if f2:
2453 if f2:
2452 content2 = getfilectx(f2, ctx2).data()
2454 content2 = getfilectx(f2, ctx2).data()
2453 if opts.git or losedatafn:
2455 if opts.git or losedatafn:
2454 flag2 = ctx2.flags(f2)
2456 flag2 = ctx2.flags(f2)
2455 binary = False
2457 binary = False
2456 if opts.git or losedatafn:
2458 if opts.git or losedatafn:
2457 binary = util.binary(content1) or util.binary(content2)
2459 binary = util.binary(content1) or util.binary(content2)
2458
2460
2459 if losedatafn and not opts.git:
2461 if losedatafn and not opts.git:
2460 if (binary or
2462 if (binary or
2461 # copy/rename
2463 # copy/rename
2462 f2 in copy or
2464 f2 in copy or
2463 # empty file creation
2465 # empty file creation
2464 (not f1 and not content2) or
2466 (not f1 and not content2) or
2465 # empty file deletion
2467 # empty file deletion
2466 (not content1 and not f2) or
2468 (not content1 and not f2) or
2467 # create with flags
2469 # create with flags
2468 (not f1 and flag2) or
2470 (not f1 and flag2) or
2469 # change flags
2471 # change flags
2470 (f1 and f2 and flag1 != flag2)):
2472 (f1 and f2 and flag1 != flag2)):
2471 losedatafn(f2 or f1)
2473 losedatafn(f2 or f1)
2472
2474
2473 path1 = f1 or f2
2475 path1 = f1 or f2
2474 path2 = f2 or f1
2476 path2 = f2 or f1
2475 path1 = posixpath.join(prefix, path1[len(relroot):])
2477 path1 = posixpath.join(prefix, path1[len(relroot):])
2476 path2 = posixpath.join(prefix, path2[len(relroot):])
2478 path2 = posixpath.join(prefix, path2[len(relroot):])
2477 header = []
2479 header = []
2478 if opts.git:
2480 if opts.git:
2479 header.append('diff --git %s%s %s%s' %
2481 header.append('diff --git %s%s %s%s' %
2480 (aprefix, path1, bprefix, path2))
2482 (aprefix, path1, bprefix, path2))
2481 if not f1: # added
2483 if not f1: # added
2482 header.append('new file mode %s' % gitmode[flag2])
2484 header.append('new file mode %s' % gitmode[flag2])
2483 elif not f2: # removed
2485 elif not f2: # removed
2484 header.append('deleted file mode %s' % gitmode[flag1])
2486 header.append('deleted file mode %s' % gitmode[flag1])
2485 else: # modified/copied/renamed
2487 else: # modified/copied/renamed
2486 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2488 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2487 if mode1 != mode2:
2489 if mode1 != mode2:
2488 header.append('old mode %s' % mode1)
2490 header.append('old mode %s' % mode1)
2489 header.append('new mode %s' % mode2)
2491 header.append('new mode %s' % mode2)
2490 if copyop is not None:
2492 if copyop is not None:
2491 header.append('%s from %s' % (copyop, path1))
2493 header.append('%s from %s' % (copyop, path1))
2492 header.append('%s to %s' % (copyop, path2))
2494 header.append('%s to %s' % (copyop, path2))
2493 elif revs and not repo.ui.quiet:
2495 elif revs and not repo.ui.quiet:
2494 header.append(diffline(path1, revs))
2496 header.append(diffline(path1, revs))
2495
2497
2496 if binary and opts.git and not opts.nobinary:
2498 if binary and opts.git and not opts.nobinary:
2497 text = mdiff.b85diff(content1, content2)
2499 text = mdiff.b85diff(content1, content2)
2498 if text:
2500 if text:
2499 header.append('index %s..%s' %
2501 header.append('index %s..%s' %
2500 (gitindex(content1), gitindex(content2)))
2502 (gitindex(content1), gitindex(content2)))
2501 else:
2503 else:
2502 text = mdiff.unidiff(content1, date1,
2504 text = mdiff.unidiff(content1, date1,
2503 content2, date2,
2505 content2, date2,
2504 path1, path2, opts=opts)
2506 path1, path2, opts=opts)
2505 if header and (text or len(header) > 1):
2507 if header and (text or len(header) > 1):
2506 yield '\n'.join(header) + '\n'
2508 yield '\n'.join(header) + '\n'
2507 if text:
2509 if text:
2508 yield text
2510 yield text
2509
2511
2510 def diffstatsum(stats):
2512 def diffstatsum(stats):
2511 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2513 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2512 for f, a, r, b in stats:
2514 for f, a, r, b in stats:
2513 maxfile = max(maxfile, encoding.colwidth(f))
2515 maxfile = max(maxfile, encoding.colwidth(f))
2514 maxtotal = max(maxtotal, a + r)
2516 maxtotal = max(maxtotal, a + r)
2515 addtotal += a
2517 addtotal += a
2516 removetotal += r
2518 removetotal += r
2517 binary = binary or b
2519 binary = binary or b
2518
2520
2519 return maxfile, maxtotal, addtotal, removetotal, binary
2521 return maxfile, maxtotal, addtotal, removetotal, binary
2520
2522
2521 def diffstatdata(lines):
2523 def diffstatdata(lines):
2522 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2524 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2523
2525
2524 results = []
2526 results = []
2525 filename, adds, removes, isbinary = None, 0, 0, False
2527 filename, adds, removes, isbinary = None, 0, 0, False
2526
2528
2527 def addresult():
2529 def addresult():
2528 if filename:
2530 if filename:
2529 results.append((filename, adds, removes, isbinary))
2531 results.append((filename, adds, removes, isbinary))
2530
2532
2531 for line in lines:
2533 for line in lines:
2532 if line.startswith('diff'):
2534 if line.startswith('diff'):
2533 addresult()
2535 addresult()
2534 # set numbers to 0 anyway when starting new file
2536 # set numbers to 0 anyway when starting new file
2535 adds, removes, isbinary = 0, 0, False
2537 adds, removes, isbinary = 0, 0, False
2536 if line.startswith('diff --git a/'):
2538 if line.startswith('diff --git a/'):
2537 filename = gitre.search(line).group(2)
2539 filename = gitre.search(line).group(2)
2538 elif line.startswith('diff -r'):
2540 elif line.startswith('diff -r'):
2539 # format: "diff -r ... -r ... filename"
2541 # format: "diff -r ... -r ... filename"
2540 filename = diffre.search(line).group(1)
2542 filename = diffre.search(line).group(1)
2541 elif line.startswith('+') and not line.startswith('+++ '):
2543 elif line.startswith('+') and not line.startswith('+++ '):
2542 adds += 1
2544 adds += 1
2543 elif line.startswith('-') and not line.startswith('--- '):
2545 elif line.startswith('-') and not line.startswith('--- '):
2544 removes += 1
2546 removes += 1
2545 elif (line.startswith('GIT binary patch') or
2547 elif (line.startswith('GIT binary patch') or
2546 line.startswith('Binary file')):
2548 line.startswith('Binary file')):
2547 isbinary = True
2549 isbinary = True
2548 addresult()
2550 addresult()
2549 return results
2551 return results
2550
2552
2551 def diffstat(lines, width=80, git=False):
2553 def diffstat(lines, width=80, git=False):
2552 output = []
2554 output = []
2553 stats = diffstatdata(lines)
2555 stats = diffstatdata(lines)
2554 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2556 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2555
2557
2556 countwidth = len(str(maxtotal))
2558 countwidth = len(str(maxtotal))
2557 if hasbinary and countwidth < 3:
2559 if hasbinary and countwidth < 3:
2558 countwidth = 3
2560 countwidth = 3
2559 graphwidth = width - countwidth - maxname - 6
2561 graphwidth = width - countwidth - maxname - 6
2560 if graphwidth < 10:
2562 if graphwidth < 10:
2561 graphwidth = 10
2563 graphwidth = 10
2562
2564
2563 def scale(i):
2565 def scale(i):
2564 if maxtotal <= graphwidth:
2566 if maxtotal <= graphwidth:
2565 return i
2567 return i
2566 # If diffstat runs out of room it doesn't print anything,
2568 # If diffstat runs out of room it doesn't print anything,
2567 # which isn't very useful, so always print at least one + or -
2569 # which isn't very useful, so always print at least one + or -
2568 # if there were at least some changes.
2570 # if there were at least some changes.
2569 return max(i * graphwidth // maxtotal, int(bool(i)))
2571 return max(i * graphwidth // maxtotal, int(bool(i)))
2570
2572
2571 for filename, adds, removes, isbinary in stats:
2573 for filename, adds, removes, isbinary in stats:
2572 if isbinary:
2574 if isbinary:
2573 count = 'Bin'
2575 count = 'Bin'
2574 else:
2576 else:
2575 count = adds + removes
2577 count = adds + removes
2576 pluses = '+' * scale(adds)
2578 pluses = '+' * scale(adds)
2577 minuses = '-' * scale(removes)
2579 minuses = '-' * scale(removes)
2578 output.append(' %s%s | %*s %s%s\n' %
2580 output.append(' %s%s | %*s %s%s\n' %
2579 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2581 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2580 countwidth, count, pluses, minuses))
2582 countwidth, count, pluses, minuses))
2581
2583
2582 if stats:
2584 if stats:
2583 output.append(_(' %d files changed, %d insertions(+), '
2585 output.append(_(' %d files changed, %d insertions(+), '
2584 '%d deletions(-)\n')
2586 '%d deletions(-)\n')
2585 % (len(stats), totaladds, totalremoves))
2587 % (len(stats), totaladds, totalremoves))
2586
2588
2587 return ''.join(output)
2589 return ''.join(output)
2588
2590
2589 def diffstatui(*args, **kw):
2591 def diffstatui(*args, **kw):
2590 '''like diffstat(), but yields 2-tuples of (output, label) for
2592 '''like diffstat(), but yields 2-tuples of (output, label) for
2591 ui.write()
2593 ui.write()
2592 '''
2594 '''
2593
2595
2594 for line in diffstat(*args, **kw).splitlines():
2596 for line in diffstat(*args, **kw).splitlines():
2595 if line and line[-1] in '+-':
2597 if line and line[-1] in '+-':
2596 name, graph = line.rsplit(' ', 1)
2598 name, graph = line.rsplit(' ', 1)
2597 yield (name + ' ', '')
2599 yield (name + ' ', '')
2598 m = re.search(r'\++', graph)
2600 m = re.search(r'\++', graph)
2599 if m:
2601 if m:
2600 yield (m.group(0), 'diffstat.inserted')
2602 yield (m.group(0), 'diffstat.inserted')
2601 m = re.search(r'-+', graph)
2603 m = re.search(r'-+', graph)
2602 if m:
2604 if m:
2603 yield (m.group(0), 'diffstat.deleted')
2605 yield (m.group(0), 'diffstat.deleted')
2604 else:
2606 else:
2605 yield (line, '')
2607 yield (line, '')
2606 yield ('\n', '')
2608 yield ('\n', '')
@@ -1,1795 +1,1805 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 import with no args:
13 import with no args:
14
14
15 $ hg --cwd a import
15 $ hg --cwd a import
16 abort: need at least one patch to import
16 abort: need at least one patch to import
17 [255]
17 [255]
18
18
19 generate patches for the test
19 generate patches for the test
20
20
21 $ hg --cwd a export tip > exported-tip.patch
21 $ hg --cwd a export tip > exported-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23
23
24
24
25 import exported patch
25 import exported patch
26 (this also tests that editor is not invoked, if the patch contains the
26 (this also tests that editor is not invoked, if the patch contains the
27 commit message and '--edit' is not specified)
27 commit message and '--edit' is not specified)
28
28
29 $ hg clone -r0 a b
29 $ hg clone -r0 a b
30 adding changesets
30 adding changesets
31 adding manifests
31 adding manifests
32 adding file changes
32 adding file changes
33 added 1 changesets with 2 changes to 2 files
33 added 1 changesets with 2 changes to 2 files
34 updating to branch default
34 updating to branch default
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 applying ../exported-tip.patch
37 applying ../exported-tip.patch
38
38
39 message and committer and date should be same
39 message and committer and date should be same
40
40
41 $ hg --cwd b tip
41 $ hg --cwd b tip
42 changeset: 1:1d4bd90af0e4
42 changeset: 1:1d4bd90af0e4
43 tag: tip
43 tag: tip
44 user: someone
44 user: someone
45 date: Thu Jan 01 00:00:01 1970 +0000
45 date: Thu Jan 01 00:00:01 1970 +0000
46 summary: second change
46 summary: second change
47
47
48 $ rm -r b
48 $ rm -r b
49
49
50
50
51 import exported patch with external patcher
51 import exported patch with external patcher
52 (this also tests that editor is invoked, if the '--edit' is specified,
52 (this also tests that editor is invoked, if the '--edit' is specified,
53 regardless of the commit message in the patch)
53 regardless of the commit message in the patch)
54
54
55 $ cat > dummypatch.py <<EOF
55 $ cat > dummypatch.py <<EOF
56 > from __future__ import print_function
56 > from __future__ import print_function
57 > print('patching file a')
57 > print('patching file a')
58 > file('a', 'wb').write('line2\n')
58 > file('a', 'wb').write('line2\n')
59 > EOF
59 > EOF
60 $ hg clone -r0 a b
60 $ hg clone -r0 a b
61 adding changesets
61 adding changesets
62 adding manifests
62 adding manifests
63 adding file changes
63 adding file changes
64 added 1 changesets with 2 changes to 2 files
64 added 1 changesets with 2 changes to 2 files
65 updating to branch default
65 updating to branch default
66 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
67 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
68 applying ../exported-tip.patch
68 applying ../exported-tip.patch
69 second change
69 second change
70
70
71
71
72 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 HG: Enter commit message. Lines beginning with 'HG:' are removed.
73 HG: Leave message empty to abort commit.
73 HG: Leave message empty to abort commit.
74 HG: --
74 HG: --
75 HG: user: someone
75 HG: user: someone
76 HG: branch 'default'
76 HG: branch 'default'
77 HG: changed a
77 HG: changed a
78 $ cat b/a
78 $ cat b/a
79 line2
79 line2
80 $ rm -r b
80 $ rm -r b
81
81
82
82
83 import of plain diff should fail without message
83 import of plain diff should fail without message
84 (this also tests that editor is invoked, if the patch doesn't contain
84 (this also tests that editor is invoked, if the patch doesn't contain
85 the commit message, regardless of '--edit')
85 the commit message, regardless of '--edit')
86
86
87 $ hg clone -r0 a b
87 $ hg clone -r0 a b
88 adding changesets
88 adding changesets
89 adding manifests
89 adding manifests
90 adding file changes
90 adding file changes
91 added 1 changesets with 2 changes to 2 files
91 added 1 changesets with 2 changes to 2 files
92 updating to branch default
92 updating to branch default
93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 $ cat > $TESTTMP/editor.sh <<EOF
94 $ cat > $TESTTMP/editor.sh <<EOF
95 > env | grep HGEDITFORM
95 > env | grep HGEDITFORM
96 > cat \$1
96 > cat \$1
97 > EOF
97 > EOF
98 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
98 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
99 applying ../diffed-tip.patch
99 applying ../diffed-tip.patch
100 HGEDITFORM=import.normal.normal
100 HGEDITFORM=import.normal.normal
101
101
102
102
103 HG: Enter commit message. Lines beginning with 'HG:' are removed.
103 HG: Enter commit message. Lines beginning with 'HG:' are removed.
104 HG: Leave message empty to abort commit.
104 HG: Leave message empty to abort commit.
105 HG: --
105 HG: --
106 HG: user: test
106 HG: user: test
107 HG: branch 'default'
107 HG: branch 'default'
108 HG: changed a
108 HG: changed a
109 abort: empty commit message
109 abort: empty commit message
110 [255]
110 [255]
111
111
112 Test avoiding editor invocation at applying the patch with --exact,
112 Test avoiding editor invocation at applying the patch with --exact,
113 even if commit message is empty
113 even if commit message is empty
114
114
115 $ echo a >> b/a
115 $ echo a >> b/a
116 $ hg --cwd b commit -m ' '
116 $ hg --cwd b commit -m ' '
117 $ hg --cwd b tip -T "{node}\n"
117 $ hg --cwd b tip -T "{node}\n"
118 d8804f3f5396d800812f579c8452796a5993bdb2
118 d8804f3f5396d800812f579c8452796a5993bdb2
119 $ hg --cwd b export -o ../empty-log.diff .
119 $ hg --cwd b export -o ../empty-log.diff .
120 $ hg --cwd b update -q -C ".^1"
120 $ hg --cwd b update -q -C ".^1"
121 $ hg --cwd b --config extensions.strip= strip -q tip
121 $ hg --cwd b --config extensions.strip= strip -q tip
122 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
122 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
123 applying ../empty-log.diff
123 applying ../empty-log.diff
124 $ hg --cwd b tip -T "{node}\n"
124 $ hg --cwd b tip -T "{node}\n"
125 d8804f3f5396d800812f579c8452796a5993bdb2
125 d8804f3f5396d800812f579c8452796a5993bdb2
126
126
127 $ rm -r b
127 $ rm -r b
128
128
129
129
130 import of plain diff should be ok with message
130 import of plain diff should be ok with message
131
131
132 $ hg clone -r0 a b
132 $ hg clone -r0 a b
133 adding changesets
133 adding changesets
134 adding manifests
134 adding manifests
135 adding file changes
135 adding file changes
136 added 1 changesets with 2 changes to 2 files
136 added 1 changesets with 2 changes to 2 files
137 updating to branch default
137 updating to branch default
138 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 $ hg --cwd b import -mpatch ../diffed-tip.patch
139 $ hg --cwd b import -mpatch ../diffed-tip.patch
140 applying ../diffed-tip.patch
140 applying ../diffed-tip.patch
141 $ rm -r b
141 $ rm -r b
142
142
143
143
144 import of plain diff with specific date and user
144 import of plain diff with specific date and user
145 (this also tests that editor is not invoked, if
145 (this also tests that editor is not invoked, if
146 '--message'/'--logfile' is specified and '--edit' is not)
146 '--message'/'--logfile' is specified and '--edit' is not)
147
147
148 $ hg clone -r0 a b
148 $ hg clone -r0 a b
149 adding changesets
149 adding changesets
150 adding manifests
150 adding manifests
151 adding file changes
151 adding file changes
152 added 1 changesets with 2 changes to 2 files
152 added 1 changesets with 2 changes to 2 files
153 updating to branch default
153 updating to branch default
154 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
155 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
156 applying ../diffed-tip.patch
156 applying ../diffed-tip.patch
157 $ hg -R b tip -pv
157 $ hg -R b tip -pv
158 changeset: 1:ca68f19f3a40
158 changeset: 1:ca68f19f3a40
159 tag: tip
159 tag: tip
160 user: user@nowhere.net
160 user: user@nowhere.net
161 date: Thu Jan 01 00:00:01 1970 +0000
161 date: Thu Jan 01 00:00:01 1970 +0000
162 files: a
162 files: a
163 description:
163 description:
164 patch
164 patch
165
165
166
166
167 diff -r 80971e65b431 -r ca68f19f3a40 a
167 diff -r 80971e65b431 -r ca68f19f3a40 a
168 --- a/a Thu Jan 01 00:00:00 1970 +0000
168 --- a/a Thu Jan 01 00:00:00 1970 +0000
169 +++ b/a Thu Jan 01 00:00:01 1970 +0000
169 +++ b/a Thu Jan 01 00:00:01 1970 +0000
170 @@ -1,1 +1,2 @@
170 @@ -1,1 +1,2 @@
171 line 1
171 line 1
172 +line 2
172 +line 2
173
173
174 $ rm -r b
174 $ rm -r b
175
175
176
176
177 import of plain diff should be ok with --no-commit
177 import of plain diff should be ok with --no-commit
178 (this also tests that editor is not invoked, if '--no-commit' is
178 (this also tests that editor is not invoked, if '--no-commit' is
179 specified, regardless of '--edit')
179 specified, regardless of '--edit')
180
180
181 $ hg clone -r0 a b
181 $ hg clone -r0 a b
182 adding changesets
182 adding changesets
183 adding manifests
183 adding manifests
184 adding file changes
184 adding file changes
185 added 1 changesets with 2 changes to 2 files
185 added 1 changesets with 2 changes to 2 files
186 updating to branch default
186 updating to branch default
187 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
188 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
189 applying ../diffed-tip.patch
189 applying ../diffed-tip.patch
190 $ hg --cwd b diff --nodates
190 $ hg --cwd b diff --nodates
191 diff -r 80971e65b431 a
191 diff -r 80971e65b431 a
192 --- a/a
192 --- a/a
193 +++ b/a
193 +++ b/a
194 @@ -1,1 +1,2 @@
194 @@ -1,1 +1,2 @@
195 line 1
195 line 1
196 +line 2
196 +line 2
197 $ rm -r b
197 $ rm -r b
198
198
199
199
200 import of malformed plain diff should fail
200 import of malformed plain diff should fail
201
201
202 $ hg clone -r0 a b
202 $ hg clone -r0 a b
203 adding changesets
203 adding changesets
204 adding manifests
204 adding manifests
205 adding file changes
205 adding file changes
206 added 1 changesets with 2 changes to 2 files
206 added 1 changesets with 2 changes to 2 files
207 updating to branch default
207 updating to branch default
208 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
209 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
209 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
210 $ hg --cwd b import -mpatch ../broken.patch
210 $ hg --cwd b import -mpatch ../broken.patch
211 applying ../broken.patch
211 applying ../broken.patch
212 abort: bad hunk #1
212 abort: bad hunk #1
213 [255]
213 [255]
214 $ rm -r b
214 $ rm -r b
215
215
216
216
217 hg -R repo import
217 hg -R repo import
218 put the clone in a subdir - having a directory named "a"
218 put the clone in a subdir - having a directory named "a"
219 used to hide a bug.
219 used to hide a bug.
220
220
221 $ mkdir dir
221 $ mkdir dir
222 $ hg clone -r0 a dir/b
222 $ hg clone -r0 a dir/b
223 adding changesets
223 adding changesets
224 adding manifests
224 adding manifests
225 adding file changes
225 adding file changes
226 added 1 changesets with 2 changes to 2 files
226 added 1 changesets with 2 changes to 2 files
227 updating to branch default
227 updating to branch default
228 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
229 $ cd dir
229 $ cd dir
230 $ hg -R b import ../exported-tip.patch
230 $ hg -R b import ../exported-tip.patch
231 applying ../exported-tip.patch
231 applying ../exported-tip.patch
232 $ cd ..
232 $ cd ..
233 $ rm -r dir
233 $ rm -r dir
234
234
235
235
236 import from stdin
236 import from stdin
237
237
238 $ hg clone -r0 a b
238 $ hg clone -r0 a b
239 adding changesets
239 adding changesets
240 adding manifests
240 adding manifests
241 adding file changes
241 adding file changes
242 added 1 changesets with 2 changes to 2 files
242 added 1 changesets with 2 changes to 2 files
243 updating to branch default
243 updating to branch default
244 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 $ hg --cwd b import - < exported-tip.patch
245 $ hg --cwd b import - < exported-tip.patch
246 applying patch from stdin
246 applying patch from stdin
247 $ rm -r b
247 $ rm -r b
248
248
249
249
250 import two patches in one stream
250 import two patches in one stream
251
251
252 $ hg init b
252 $ hg init b
253 $ hg --cwd a export 0:tip | hg --cwd b import -
253 $ hg --cwd a export 0:tip | hg --cwd b import -
254 applying patch from stdin
254 applying patch from stdin
255 $ hg --cwd a id
255 $ hg --cwd a id
256 1d4bd90af0e4 tip
256 1d4bd90af0e4 tip
257 $ hg --cwd b id
257 $ hg --cwd b id
258 1d4bd90af0e4 tip
258 1d4bd90af0e4 tip
259 $ rm -r b
259 $ rm -r b
260
260
261
261
262 override commit message
262 override commit message
263
263
264 $ hg clone -r0 a b
264 $ hg clone -r0 a b
265 adding changesets
265 adding changesets
266 adding manifests
266 adding manifests
267 adding file changes
267 adding file changes
268 added 1 changesets with 2 changes to 2 files
268 added 1 changesets with 2 changes to 2 files
269 updating to branch default
269 updating to branch default
270 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
271 $ hg --cwd b import -m 'override' - < exported-tip.patch
271 $ hg --cwd b import -m 'override' - < exported-tip.patch
272 applying patch from stdin
272 applying patch from stdin
273 $ hg --cwd b tip | grep override
273 $ hg --cwd b tip | grep override
274 summary: override
274 summary: override
275 $ rm -r b
275 $ rm -r b
276
276
277 $ cat > mkmsg.py <<EOF
277 $ cat > mkmsg.py <<EOF
278 > import email.Message, sys
278 > import email.Message, sys
279 > msg = email.Message.Message()
279 > msg = email.Message.Message()
280 > patch = open(sys.argv[1], 'rb').read()
280 > patch = open(sys.argv[1], 'rb').read()
281 > msg.set_payload('email commit message\n' + patch)
281 > msg.set_payload('email commit message\n' + patch)
282 > msg['Subject'] = 'email patch'
282 > msg['Subject'] = 'email patch'
283 > msg['From'] = 'email patcher'
283 > msg['From'] = 'email patcher'
284 > file(sys.argv[2], 'wb').write(msg.as_string())
284 > file(sys.argv[2], 'wb').write(msg.as_string())
285 > EOF
285 > EOF
286
286
287
287
288 plain diff in email, subject, message body
288 plain diff in email, subject, message body
289
289
290 $ hg clone -r0 a b
290 $ hg clone -r0 a b
291 adding changesets
291 adding changesets
292 adding manifests
292 adding manifests
293 adding file changes
293 adding file changes
294 added 1 changesets with 2 changes to 2 files
294 added 1 changesets with 2 changes to 2 files
295 updating to branch default
295 updating to branch default
296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 $ python mkmsg.py diffed-tip.patch msg.patch
297 $ python mkmsg.py diffed-tip.patch msg.patch
298 $ hg --cwd b import ../msg.patch
298 $ hg --cwd b import ../msg.patch
299 applying ../msg.patch
299 applying ../msg.patch
300 $ hg --cwd b tip | grep email
300 $ hg --cwd b tip | grep email
301 user: email patcher
301 user: email patcher
302 summary: email patch
302 summary: email patch
303 $ rm -r b
303 $ rm -r b
304
304
305
305
306 plain diff in email, no subject, message body
306 plain diff in email, no subject, message body
307
307
308 $ hg clone -r0 a b
308 $ hg clone -r0 a b
309 adding changesets
309 adding changesets
310 adding manifests
310 adding manifests
311 adding file changes
311 adding file changes
312 added 1 changesets with 2 changes to 2 files
312 added 1 changesets with 2 changes to 2 files
313 updating to branch default
313 updating to branch default
314 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
315 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
315 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
316 applying patch from stdin
316 applying patch from stdin
317 $ rm -r b
317 $ rm -r b
318
318
319
319
320 plain diff in email, subject, no message body
320 plain diff in email, subject, no message body
321
321
322 $ hg clone -r0 a b
322 $ hg clone -r0 a b
323 adding changesets
323 adding changesets
324 adding manifests
324 adding manifests
325 adding file changes
325 adding file changes
326 added 1 changesets with 2 changes to 2 files
326 added 1 changesets with 2 changes to 2 files
327 updating to branch default
327 updating to branch default
328 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
329 $ grep -v '^email ' msg.patch | hg --cwd b import -
329 $ grep -v '^email ' msg.patch | hg --cwd b import -
330 applying patch from stdin
330 applying patch from stdin
331 $ rm -r b
331 $ rm -r b
332
332
333
333
334 plain diff in email, no subject, no message body, should fail
334 plain diff in email, no subject, no message body, should fail
335
335
336 $ hg clone -r0 a b
336 $ hg clone -r0 a b
337 adding changesets
337 adding changesets
338 adding manifests
338 adding manifests
339 adding file changes
339 adding file changes
340 added 1 changesets with 2 changes to 2 files
340 added 1 changesets with 2 changes to 2 files
341 updating to branch default
341 updating to branch default
342 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
343 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
343 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
344 applying patch from stdin
344 applying patch from stdin
345 abort: empty commit message
345 abort: empty commit message
346 [255]
346 [255]
347 $ rm -r b
347 $ rm -r b
348
348
349
349
350 hg export in email, should use patch header
350 hg export in email, should use patch header
351
351
352 $ hg clone -r0 a b
352 $ hg clone -r0 a b
353 adding changesets
353 adding changesets
354 adding manifests
354 adding manifests
355 adding file changes
355 adding file changes
356 added 1 changesets with 2 changes to 2 files
356 added 1 changesets with 2 changes to 2 files
357 updating to branch default
357 updating to branch default
358 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 $ python mkmsg.py exported-tip.patch msg.patch
359 $ python mkmsg.py exported-tip.patch msg.patch
360 $ cat msg.patch | hg --cwd b import -
360 $ cat msg.patch | hg --cwd b import -
361 applying patch from stdin
361 applying patch from stdin
362 $ hg --cwd b tip | grep second
362 $ hg --cwd b tip | grep second
363 summary: second change
363 summary: second change
364 $ rm -r b
364 $ rm -r b
365
365
366
366
367 subject: duplicate detection, removal of [PATCH]
367 subject: duplicate detection, removal of [PATCH]
368 The '---' tests the gitsendmail handling without proper mail headers
368 The '---' tests the gitsendmail handling without proper mail headers
369
369
370 $ cat > mkmsg2.py <<EOF
370 $ cat > mkmsg2.py <<EOF
371 > import email.Message, sys
371 > import email.Message, sys
372 > msg = email.Message.Message()
372 > msg = email.Message.Message()
373 > patch = open(sys.argv[1], 'rb').read()
373 > patch = open(sys.argv[1], 'rb').read()
374 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
374 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
375 > msg['Subject'] = '[PATCH] email patch'
375 > msg['Subject'] = '[PATCH] email patch'
376 > msg['From'] = 'email patcher'
376 > msg['From'] = 'email patcher'
377 > file(sys.argv[2], 'wb').write(msg.as_string())
377 > file(sys.argv[2], 'wb').write(msg.as_string())
378 > EOF
378 > EOF
379
379
380
380
381 plain diff in email, [PATCH] subject, message body with subject
381 plain diff in email, [PATCH] subject, message body with subject
382
382
383 $ hg clone -r0 a b
383 $ hg clone -r0 a b
384 adding changesets
384 adding changesets
385 adding manifests
385 adding manifests
386 adding file changes
386 adding file changes
387 added 1 changesets with 2 changes to 2 files
387 added 1 changesets with 2 changes to 2 files
388 updating to branch default
388 updating to branch default
389 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
390 $ python mkmsg2.py diffed-tip.patch msg.patch
390 $ python mkmsg2.py diffed-tip.patch msg.patch
391 $ cat msg.patch | hg --cwd b import -
391 $ cat msg.patch | hg --cwd b import -
392 applying patch from stdin
392 applying patch from stdin
393 $ hg --cwd b tip --template '{desc}\n'
393 $ hg --cwd b tip --template '{desc}\n'
394 email patch
394 email patch
395
395
396 next line
396 next line
397 $ rm -r b
397 $ rm -r b
398
398
399
399
400 Issue963: Parent of working dir incorrect after import of multiple
400 Issue963: Parent of working dir incorrect after import of multiple
401 patches and rollback
401 patches and rollback
402
402
403 We weren't backing up the correct dirstate file when importing many
403 We weren't backing up the correct dirstate file when importing many
404 patches: import patch1 patch2; rollback
404 patches: import patch1 patch2; rollback
405
405
406 $ echo line 3 >> a/a
406 $ echo line 3 >> a/a
407 $ hg --cwd a ci -m'third change'
407 $ hg --cwd a ci -m'third change'
408 $ hg --cwd a export -o '../patch%R' 1 2
408 $ hg --cwd a export -o '../patch%R' 1 2
409 $ hg clone -qr0 a b
409 $ hg clone -qr0 a b
410 $ hg --cwd b parents --template 'parent: {rev}\n'
410 $ hg --cwd b parents --template 'parent: {rev}\n'
411 parent: 0
411 parent: 0
412 $ hg --cwd b import -v ../patch1 ../patch2
412 $ hg --cwd b import -v ../patch1 ../patch2
413 applying ../patch1
413 applying ../patch1
414 patching file a
414 patching file a
415 committing files:
415 committing files:
416 a
416 a
417 committing manifest
417 committing manifest
418 committing changelog
418 committing changelog
419 created 1d4bd90af0e4
419 created 1d4bd90af0e4
420 applying ../patch2
420 applying ../patch2
421 patching file a
421 patching file a
422 committing files:
422 committing files:
423 a
423 a
424 committing manifest
424 committing manifest
425 committing changelog
425 committing changelog
426 created 6d019af21222
426 created 6d019af21222
427 $ hg --cwd b rollback
427 $ hg --cwd b rollback
428 repository tip rolled back to revision 0 (undo import)
428 repository tip rolled back to revision 0 (undo import)
429 working directory now based on revision 0
429 working directory now based on revision 0
430 $ hg --cwd b parents --template 'parent: {rev}\n'
430 $ hg --cwd b parents --template 'parent: {rev}\n'
431 parent: 0
431 parent: 0
432
432
433 Test that "hg rollback" doesn't restore dirstate to one at the
433 Test that "hg rollback" doesn't restore dirstate to one at the
434 beginning of the rollbacked transaction in not-"parent-gone" case.
434 beginning of the rollbacked transaction in not-"parent-gone" case.
435
435
436 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
436 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
437 to be restored at rollbacking, after DirstateTransactionPlan (see wiki
437 to be restored at rollbacking, after DirstateTransactionPlan (see wiki
438 page for detail).
438 page for detail).
439
439
440 $ hg --cwd b branch -q foobar
440 $ hg --cwd b branch -q foobar
441 $ hg --cwd b commit -m foobar
441 $ hg --cwd b commit -m foobar
442 $ hg --cwd b update 0 -q
442 $ hg --cwd b update 0 -q
443 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
443 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
444 applying ../patch1
444 applying ../patch1
445 applying ../patch2
445 applying ../patch2
446 $ hg --cwd b update -q 1
446 $ hg --cwd b update -q 1
447 $ hg --cwd b rollback -q
447 $ hg --cwd b rollback -q
448 $ hg --cwd b parents --template 'parent: {rev}\n'
448 $ hg --cwd b parents --template 'parent: {rev}\n'
449 parent: 1
449 parent: 1
450
450
451 $ hg --cwd b update -q -C 0
451 $ hg --cwd b update -q -C 0
452 $ hg --cwd b --config extensions.strip= strip -q 1
452 $ hg --cwd b --config extensions.strip= strip -q 1
453
453
454 Test visibility of in-memory distate changes inside transaction to
454 Test visibility of in-memory distate changes inside transaction to
455 external process
455 external process
456
456
457 $ echo foo > a/foo
457 $ echo foo > a/foo
458 $ hg --cwd a commit -A -m 'adding foo' foo
458 $ hg --cwd a commit -A -m 'adding foo' foo
459 $ hg --cwd a export -o '../patch%R' 3
459 $ hg --cwd a export -o '../patch%R' 3
460
460
461 $ cat > $TESTTMP/checkvisibility.sh <<EOF
461 $ cat > $TESTTMP/checkvisibility.sh <<EOF
462 > echo "===="
462 > echo "===="
463 > hg parents --template "VISIBLE {rev}:{node|short}\n"
463 > hg parents --template "VISIBLE {rev}:{node|short}\n"
464 > hg status -amr
464 > hg status -amr
465 > # test that pending changes are hidden
465 > # test that pending changes are hidden
466 > unset HG_PENDING
466 > unset HG_PENDING
467 > hg parents --template "ACTUAL {rev}:{node|short}\n"
467 > hg parents --template "ACTUAL {rev}:{node|short}\n"
468 > hg status -amr
468 > hg status -amr
469 > echo "===="
469 > echo "===="
470 > EOF
470 > EOF
471
471
472 == test visibility to external editor
472 == test visibility to external editor
473
473
474 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
474 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
475 ====
475 ====
476 VISIBLE 0:80971e65b431
476 VISIBLE 0:80971e65b431
477 ACTUAL 0:80971e65b431
477 ACTUAL 0:80971e65b431
478 ====
478 ====
479
479
480 $ HGEDITOR="sh $TESTTMP/checkvisibility.sh" hg --cwd b import -v --edit ../patch1 ../patch2 ../patch3
480 $ HGEDITOR="sh $TESTTMP/checkvisibility.sh" hg --cwd b import -v --edit ../patch1 ../patch2 ../patch3
481 applying ../patch1
481 applying ../patch1
482 patching file a
482 patching file a
483 ====
483 ====
484 VISIBLE 0:80971e65b431
484 VISIBLE 0:80971e65b431
485 M a
485 M a
486 ACTUAL 0:80971e65b431
486 ACTUAL 0:80971e65b431
487 M a
487 M a
488 ====
488 ====
489 committing files:
489 committing files:
490 a
490 a
491 committing manifest
491 committing manifest
492 committing changelog
492 committing changelog
493 created 1d4bd90af0e4
493 created 1d4bd90af0e4
494 applying ../patch2
494 applying ../patch2
495 patching file a
495 patching file a
496 ====
496 ====
497 VISIBLE 1:1d4bd90af0e4
497 VISIBLE 1:1d4bd90af0e4
498 M a
498 M a
499 ACTUAL 0:80971e65b431
499 ACTUAL 0:80971e65b431
500 M a
500 M a
501 ====
501 ====
502 committing files:
502 committing files:
503 a
503 a
504 committing manifest
504 committing manifest
505 committing changelog
505 committing changelog
506 created 6d019af21222
506 created 6d019af21222
507 applying ../patch3
507 applying ../patch3
508 patching file foo
508 patching file foo
509 adding foo
509 adding foo
510 ====
510 ====
511 VISIBLE 2:6d019af21222
511 VISIBLE 2:6d019af21222
512 A foo
512 A foo
513 ACTUAL 0:80971e65b431
513 ACTUAL 0:80971e65b431
514 M a
514 M a
515 ====
515 ====
516 committing files:
516 committing files:
517 foo
517 foo
518 committing manifest
518 committing manifest
519 committing changelog
519 committing changelog
520 created 55e3f75b2378
520 created 55e3f75b2378
521
521
522 $ hg --cwd b rollback -q
522 $ hg --cwd b rollback -q
523
523
524 (content of file "a" is already changed and it should be recognized as
524 (content of file "a" is already changed and it should be recognized as
525 "M", even though dirstate is restored to one before "hg import")
525 "M", even though dirstate is restored to one before "hg import")
526
526
527 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
527 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
528 ====
528 ====
529 VISIBLE 0:80971e65b431
529 VISIBLE 0:80971e65b431
530 M a
530 M a
531 ACTUAL 0:80971e65b431
531 ACTUAL 0:80971e65b431
532 M a
532 M a
533 ====
533 ====
534 $ hg --cwd b revert --no-backup a
534 $ hg --cwd b revert --no-backup a
535 $ rm -f b/foo
535 $ rm -f b/foo
536
536
537 == test visibility to precommit external hook
537 == test visibility to precommit external hook
538
538
539 $ cat >> b/.hg/hgrc <<EOF
539 $ cat >> b/.hg/hgrc <<EOF
540 > [hooks]
540 > [hooks]
541 > precommit.visibility = sh $TESTTMP/checkvisibility.sh
541 > precommit.visibility = sh $TESTTMP/checkvisibility.sh
542 > EOF
542 > EOF
543
543
544 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
544 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
545 ====
545 ====
546 VISIBLE 0:80971e65b431
546 VISIBLE 0:80971e65b431
547 ACTUAL 0:80971e65b431
547 ACTUAL 0:80971e65b431
548 ====
548 ====
549
549
550 $ hg --cwd b import ../patch1 ../patch2 ../patch3
550 $ hg --cwd b import ../patch1 ../patch2 ../patch3
551 applying ../patch1
551 applying ../patch1
552 ====
552 ====
553 VISIBLE 0:80971e65b431
553 VISIBLE 0:80971e65b431
554 M a
554 M a
555 ACTUAL 0:80971e65b431
555 ACTUAL 0:80971e65b431
556 M a
556 M a
557 ====
557 ====
558 applying ../patch2
558 applying ../patch2
559 ====
559 ====
560 VISIBLE 1:1d4bd90af0e4
560 VISIBLE 1:1d4bd90af0e4
561 M a
561 M a
562 ACTUAL 0:80971e65b431
562 ACTUAL 0:80971e65b431
563 M a
563 M a
564 ====
564 ====
565 applying ../patch3
565 applying ../patch3
566 ====
566 ====
567 VISIBLE 2:6d019af21222
567 VISIBLE 2:6d019af21222
568 A foo
568 A foo
569 ACTUAL 0:80971e65b431
569 ACTUAL 0:80971e65b431
570 M a
570 M a
571 ====
571 ====
572
572
573 $ hg --cwd b rollback -q
573 $ hg --cwd b rollback -q
574 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
574 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
575 ====
575 ====
576 VISIBLE 0:80971e65b431
576 VISIBLE 0:80971e65b431
577 M a
577 M a
578 ACTUAL 0:80971e65b431
578 ACTUAL 0:80971e65b431
579 M a
579 M a
580 ====
580 ====
581 $ hg --cwd b revert --no-backup a
581 $ hg --cwd b revert --no-backup a
582 $ rm -f b/foo
582 $ rm -f b/foo
583
583
584 $ cat >> b/.hg/hgrc <<EOF
584 $ cat >> b/.hg/hgrc <<EOF
585 > [hooks]
585 > [hooks]
586 > precommit.visibility =
586 > precommit.visibility =
587 > EOF
587 > EOF
588
588
589 == test visibility to pretxncommit external hook
589 == test visibility to pretxncommit external hook
590
590
591 $ cat >> b/.hg/hgrc <<EOF
591 $ cat >> b/.hg/hgrc <<EOF
592 > [hooks]
592 > [hooks]
593 > pretxncommit.visibility = sh $TESTTMP/checkvisibility.sh
593 > pretxncommit.visibility = sh $TESTTMP/checkvisibility.sh
594 > EOF
594 > EOF
595
595
596 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
596 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
597 ====
597 ====
598 VISIBLE 0:80971e65b431
598 VISIBLE 0:80971e65b431
599 ACTUAL 0:80971e65b431
599 ACTUAL 0:80971e65b431
600 ====
600 ====
601
601
602 $ hg --cwd b import ../patch1 ../patch2 ../patch3
602 $ hg --cwd b import ../patch1 ../patch2 ../patch3
603 applying ../patch1
603 applying ../patch1
604 ====
604 ====
605 VISIBLE 0:80971e65b431
605 VISIBLE 0:80971e65b431
606 M a
606 M a
607 ACTUAL 0:80971e65b431
607 ACTUAL 0:80971e65b431
608 M a
608 M a
609 ====
609 ====
610 applying ../patch2
610 applying ../patch2
611 ====
611 ====
612 VISIBLE 1:1d4bd90af0e4
612 VISIBLE 1:1d4bd90af0e4
613 M a
613 M a
614 ACTUAL 0:80971e65b431
614 ACTUAL 0:80971e65b431
615 M a
615 M a
616 ====
616 ====
617 applying ../patch3
617 applying ../patch3
618 ====
618 ====
619 VISIBLE 2:6d019af21222
619 VISIBLE 2:6d019af21222
620 A foo
620 A foo
621 ACTUAL 0:80971e65b431
621 ACTUAL 0:80971e65b431
622 M a
622 M a
623 ====
623 ====
624
624
625 $ hg --cwd b rollback -q
625 $ hg --cwd b rollback -q
626 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
626 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
627 ====
627 ====
628 VISIBLE 0:80971e65b431
628 VISIBLE 0:80971e65b431
629 M a
629 M a
630 ACTUAL 0:80971e65b431
630 ACTUAL 0:80971e65b431
631 M a
631 M a
632 ====
632 ====
633 $ hg --cwd b revert --no-backup a
633 $ hg --cwd b revert --no-backup a
634 $ rm -f b/foo
634 $ rm -f b/foo
635
635
636 $ cat >> b/.hg/hgrc <<EOF
636 $ cat >> b/.hg/hgrc <<EOF
637 > [hooks]
637 > [hooks]
638 > pretxncommit.visibility =
638 > pretxncommit.visibility =
639 > EOF
639 > EOF
640
640
641 $ rm -r b
641 $ rm -r b
642
642
643
643
644 importing a patch in a subdirectory failed at the commit stage
644 importing a patch in a subdirectory failed at the commit stage
645
645
646 $ echo line 2 >> a/d1/d2/a
646 $ echo line 2 >> a/d1/d2/a
647 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
647 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
648
648
649 hg import in a subdirectory
649 hg import in a subdirectory
650
650
651 $ hg clone -r0 a b
651 $ hg clone -r0 a b
652 adding changesets
652 adding changesets
653 adding manifests
653 adding manifests
654 adding file changes
654 adding file changes
655 added 1 changesets with 2 changes to 2 files
655 added 1 changesets with 2 changes to 2 files
656 updating to branch default
656 updating to branch default
657 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
657 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
658 $ hg --cwd a export tip > tmp
658 $ hg --cwd a export tip > tmp
659 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
659 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
660 $ dir=`pwd`
660 $ dir=`pwd`
661 $ cd b/d1/d2 2>&1 > /dev/null
661 $ cd b/d1/d2 2>&1 > /dev/null
662 $ hg import ../../../subdir-tip.patch
662 $ hg import ../../../subdir-tip.patch
663 applying ../../../subdir-tip.patch
663 applying ../../../subdir-tip.patch
664 $ cd "$dir"
664 $ cd "$dir"
665
665
666 message should be 'subdir change'
666 message should be 'subdir change'
667 committer should be 'someoneelse'
667 committer should be 'someoneelse'
668
668
669 $ hg --cwd b tip
669 $ hg --cwd b tip
670 changeset: 1:3577f5aea227
670 changeset: 1:3577f5aea227
671 tag: tip
671 tag: tip
672 user: someoneelse
672 user: someoneelse
673 date: Thu Jan 01 00:00:01 1970 +0000
673 date: Thu Jan 01 00:00:01 1970 +0000
674 summary: subdir change
674 summary: subdir change
675
675
676
676
677 should be empty
677 should be empty
678
678
679 $ hg --cwd b status
679 $ hg --cwd b status
680
680
681
681
682 Test fuzziness (ambiguous patch location, fuzz=2)
682 Test fuzziness (ambiguous patch location, fuzz=2)
683
683
684 $ hg init fuzzy
684 $ hg init fuzzy
685 $ cd fuzzy
685 $ cd fuzzy
686 $ echo line1 > a
686 $ echo line1 > a
687 $ echo line0 >> a
687 $ echo line0 >> a
688 $ echo line3 >> a
688 $ echo line3 >> a
689 $ hg ci -Am adda
689 $ hg ci -Am adda
690 adding a
690 adding a
691 $ echo line1 > a
691 $ echo line1 > a
692 $ echo line2 >> a
692 $ echo line2 >> a
693 $ echo line0 >> a
693 $ echo line0 >> a
694 $ echo line3 >> a
694 $ echo line3 >> a
695 $ hg ci -m change a
695 $ hg ci -m change a
696 $ hg export tip > fuzzy-tip.patch
696 $ hg export tip > fuzzy-tip.patch
697 $ hg up -C 0
697 $ hg up -C 0
698 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
698 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
699 $ echo line1 > a
699 $ echo line1 > a
700 $ echo line0 >> a
700 $ echo line0 >> a
701 $ echo line1 >> a
701 $ echo line1 >> a
702 $ echo line0 >> a
702 $ echo line0 >> a
703 $ hg ci -m brancha
703 $ hg ci -m brancha
704 created new head
704 created new head
705 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
705 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
706 applying fuzzy-tip.patch
706 applying fuzzy-tip.patch
707 patching file a
707 patching file a
708 Hunk #1 FAILED at 0
708 Hunk #1 FAILED at 0
709 1 out of 1 hunks FAILED -- saving rejects to file a.rej
709 1 out of 1 hunks FAILED -- saving rejects to file a.rej
710 abort: patch failed to apply
710 abort: patch failed to apply
711 [255]
711 [255]
712 $ hg import --no-commit -v fuzzy-tip.patch
712 $ hg import --no-commit -v fuzzy-tip.patch
713 applying fuzzy-tip.patch
713 applying fuzzy-tip.patch
714 patching file a
714 patching file a
715 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
715 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
716 applied to working directory
716 applied to working directory
717 $ hg revert -a
717 $ hg revert -a
718 reverting a
718 reverting a
719
719
720
720
721 import with --no-commit should have written .hg/last-message.txt
721 import with --no-commit should have written .hg/last-message.txt
722
722
723 $ cat .hg/last-message.txt
723 $ cat .hg/last-message.txt
724 change (no-eol)
724 change (no-eol)
725
725
726
726
727 test fuzziness with eol=auto
727 test fuzziness with eol=auto
728
728
729 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
729 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
730 applying fuzzy-tip.patch
730 applying fuzzy-tip.patch
731 patching file a
731 patching file a
732 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
732 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
733 applied to working directory
733 applied to working directory
734 $ cd ..
734 $ cd ..
735
735
736
736
737 Test hunk touching empty files (issue906)
737 Test hunk touching empty files (issue906)
738
738
739 $ hg init empty
739 $ hg init empty
740 $ cd empty
740 $ cd empty
741 $ touch a
741 $ touch a
742 $ touch b1
742 $ touch b1
743 $ touch c1
743 $ touch c1
744 $ echo d > d
744 $ echo d > d
745 $ hg ci -Am init
745 $ hg ci -Am init
746 adding a
746 adding a
747 adding b1
747 adding b1
748 adding c1
748 adding c1
749 adding d
749 adding d
750 $ echo a > a
750 $ echo a > a
751 $ echo b > b1
751 $ echo b > b1
752 $ hg mv b1 b2
752 $ hg mv b1 b2
753 $ echo c > c1
753 $ echo c > c1
754 $ hg copy c1 c2
754 $ hg copy c1 c2
755 $ rm d
755 $ rm d
756 $ touch d
756 $ touch d
757 $ hg diff --git
757 $ hg diff --git
758 diff --git a/a b/a
758 diff --git a/a b/a
759 --- a/a
759 --- a/a
760 +++ b/a
760 +++ b/a
761 @@ -0,0 +1,1 @@
761 @@ -0,0 +1,1 @@
762 +a
762 +a
763 diff --git a/b1 b/b2
763 diff --git a/b1 b/b2
764 rename from b1
764 rename from b1
765 rename to b2
765 rename to b2
766 --- a/b1
766 --- a/b1
767 +++ b/b2
767 +++ b/b2
768 @@ -0,0 +1,1 @@
768 @@ -0,0 +1,1 @@
769 +b
769 +b
770 diff --git a/c1 b/c1
770 diff --git a/c1 b/c1
771 --- a/c1
771 --- a/c1
772 +++ b/c1
772 +++ b/c1
773 @@ -0,0 +1,1 @@
773 @@ -0,0 +1,1 @@
774 +c
774 +c
775 diff --git a/c1 b/c2
775 diff --git a/c1 b/c2
776 copy from c1
776 copy from c1
777 copy to c2
777 copy to c2
778 --- a/c1
778 --- a/c1
779 +++ b/c2
779 +++ b/c2
780 @@ -0,0 +1,1 @@
780 @@ -0,0 +1,1 @@
781 +c
781 +c
782 diff --git a/d b/d
782 diff --git a/d b/d
783 --- a/d
783 --- a/d
784 +++ b/d
784 +++ b/d
785 @@ -1,1 +0,0 @@
785 @@ -1,1 +0,0 @@
786 -d
786 -d
787 $ hg ci -m empty
787 $ hg ci -m empty
788 $ hg export --git tip > empty.diff
788 $ hg export --git tip > empty.diff
789 $ hg up -C 0
789 $ hg up -C 0
790 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
790 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
791 $ hg import empty.diff
791 $ hg import empty.diff
792 applying empty.diff
792 applying empty.diff
793 $ for name in a b1 b2 c1 c2 d; do
793 $ for name in a b1 b2 c1 c2 d; do
794 > echo % $name file
794 > echo % $name file
795 > test -f $name && cat $name
795 > test -f $name && cat $name
796 > done
796 > done
797 % a file
797 % a file
798 a
798 a
799 % b1 file
799 % b1 file
800 % b2 file
800 % b2 file
801 b
801 b
802 % c1 file
802 % c1 file
803 c
803 c
804 % c2 file
804 % c2 file
805 c
805 c
806 % d file
806 % d file
807 $ cd ..
807 $ cd ..
808
808
809
809
810 Test importing a patch ending with a binary file removal
810 Test importing a patch ending with a binary file removal
811
811
812 $ hg init binaryremoval
812 $ hg init binaryremoval
813 $ cd binaryremoval
813 $ cd binaryremoval
814 $ echo a > a
814 $ echo a > a
815 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
815 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
816 $ hg ci -Am addall
816 $ hg ci -Am addall
817 adding a
817 adding a
818 adding b
818 adding b
819 $ hg rm a
819 $ hg rm a
820 $ hg rm b
820 $ hg rm b
821 $ hg st
821 $ hg st
822 R a
822 R a
823 R b
823 R b
824 $ hg ci -m remove
824 $ hg ci -m remove
825 $ hg export --git . > remove.diff
825 $ hg export --git . > remove.diff
826 $ cat remove.diff | grep git
826 $ cat remove.diff | grep git
827 diff --git a/a b/a
827 diff --git a/a b/a
828 diff --git a/b b/b
828 diff --git a/b b/b
829 $ hg up -C 0
829 $ hg up -C 0
830 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
830 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
831 $ hg import remove.diff
831 $ hg import remove.diff
832 applying remove.diff
832 applying remove.diff
833 $ hg manifest
833 $ hg manifest
834 $ cd ..
834 $ cd ..
835
835
836
836
837 Issue927: test update+rename with common name
837 Issue927: test update+rename with common name
838
838
839 $ hg init t
839 $ hg init t
840 $ cd t
840 $ cd t
841 $ touch a
841 $ touch a
842 $ hg ci -Am t
842 $ hg ci -Am t
843 adding a
843 adding a
844 $ echo a > a
844 $ echo a > a
845
845
846 Here, bfile.startswith(afile)
846 Here, bfile.startswith(afile)
847
847
848 $ hg copy a a2
848 $ hg copy a a2
849 $ hg ci -m copya
849 $ hg ci -m copya
850 $ hg export --git tip > copy.diff
850 $ hg export --git tip > copy.diff
851 $ hg up -C 0
851 $ hg up -C 0
852 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
852 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
853 $ hg import copy.diff
853 $ hg import copy.diff
854 applying copy.diff
854 applying copy.diff
855
855
856 a should contain an 'a'
856 a should contain an 'a'
857
857
858 $ cat a
858 $ cat a
859 a
859 a
860
860
861 and a2 should have duplicated it
861 and a2 should have duplicated it
862
862
863 $ cat a2
863 $ cat a2
864 a
864 a
865 $ cd ..
865 $ cd ..
866
866
867
867
868 test -p0
868 test -p0
869
869
870 $ hg init p0
870 $ hg init p0
871 $ cd p0
871 $ cd p0
872 $ echo a > a
872 $ echo a > a
873 $ hg ci -Am t
873 $ hg ci -Am t
874 adding a
874 adding a
875 $ hg import -p foo
875 $ hg import -p foo
876 abort: invalid value 'foo' for option -p, expected int
876 abort: invalid value 'foo' for option -p, expected int
877 [255]
877 [255]
878 $ hg import -p0 - << EOF
878 $ hg import -p0 - << EOF
879 > foobar
879 > foobar
880 > --- a Sat Apr 12 22:43:58 2008 -0400
880 > --- a Sat Apr 12 22:43:58 2008 -0400
881 > +++ a Sat Apr 12 22:44:05 2008 -0400
881 > +++ a Sat Apr 12 22:44:05 2008 -0400
882 > @@ -1,1 +1,1 @@
882 > @@ -1,1 +1,1 @@
883 > -a
883 > -a
884 > +bb
884 > +bb
885 > EOF
885 > EOF
886 applying patch from stdin
886 applying patch from stdin
887 $ hg status
887 $ hg status
888 $ cat a
888 $ cat a
889 bb
889 bb
890
890
891 test --prefix
891 test --prefix
892
892
893 $ mkdir -p dir/dir2
893 $ mkdir -p dir/dir2
894 $ echo b > dir/dir2/b
894 $ echo b > dir/dir2/b
895 $ hg ci -Am b
895 $ hg ci -Am b
896 adding dir/dir2/b
896 adding dir/dir2/b
897 $ hg import -p2 --prefix dir - << EOF
897 $ hg import -p2 --prefix dir - << EOF
898 > foobar
898 > foobar
899 > --- drop1/drop2/dir2/b
899 > --- drop1/drop2/dir2/b
900 > +++ drop1/drop2/dir2/b
900 > +++ drop1/drop2/dir2/b
901 > @@ -1,1 +1,1 @@
901 > @@ -1,1 +1,1 @@
902 > -b
902 > -b
903 > +cc
903 > +cc
904 > EOF
904 > EOF
905 applying patch from stdin
905 applying patch from stdin
906 $ hg status
906 $ hg status
907 $ cat dir/dir2/b
907 $ cat dir/dir2/b
908 cc
908 cc
909 $ cd ..
909 $ cd ..
910
910
911
911
912 test paths outside repo root
912 test paths outside repo root
913
913
914 $ mkdir outside
914 $ mkdir outside
915 $ touch outside/foo
915 $ touch outside/foo
916 $ hg init inside
916 $ hg init inside
917 $ cd inside
917 $ cd inside
918 $ hg import - <<EOF
918 $ hg import - <<EOF
919 > diff --git a/a b/b
919 > diff --git a/a b/b
920 > rename from ../outside/foo
920 > rename from ../outside/foo
921 > rename to bar
921 > rename to bar
922 > EOF
922 > EOF
923 applying patch from stdin
923 applying patch from stdin
924 abort: path contains illegal component: ../outside/foo (glob)
924 abort: path contains illegal component: ../outside/foo (glob)
925 [255]
925 [255]
926 $ cd ..
926 $ cd ..
927
927
928
928
929 test import with similarity and git and strip (issue295 et al.)
929 test import with similarity and git and strip (issue295 et al.)
930
930
931 $ hg init sim
931 $ hg init sim
932 $ cd sim
932 $ cd sim
933 $ echo 'this is a test' > a
933 $ echo 'this is a test' > a
934 $ hg ci -Ama
934 $ hg ci -Ama
935 adding a
935 adding a
936 $ cat > ../rename.diff <<EOF
936 $ cat > ../rename.diff <<EOF
937 > diff --git a/foo/a b/foo/a
937 > diff --git a/foo/a b/foo/a
938 > deleted file mode 100644
938 > deleted file mode 100644
939 > --- a/foo/a
939 > --- a/foo/a
940 > +++ /dev/null
940 > +++ /dev/null
941 > @@ -1,1 +0,0 @@
941 > @@ -1,1 +0,0 @@
942 > -this is a test
942 > -this is a test
943 > diff --git a/foo/b b/foo/b
943 > diff --git a/foo/b b/foo/b
944 > new file mode 100644
944 > new file mode 100644
945 > --- /dev/null
945 > --- /dev/null
946 > +++ b/foo/b
946 > +++ b/foo/b
947 > @@ -0,0 +1,2 @@
947 > @@ -0,0 +1,2 @@
948 > +this is a test
948 > +this is a test
949 > +foo
949 > +foo
950 > EOF
950 > EOF
951 $ hg import --no-commit -v -s 1 ../rename.diff -p2
951 $ hg import --no-commit -v -s 1 ../rename.diff -p2
952 applying ../rename.diff
952 applying ../rename.diff
953 patching file a
953 patching file a
954 patching file b
954 patching file b
955 adding b
955 adding b
956 recording removal of a as rename to b (88% similar)
956 recording removal of a as rename to b (88% similar)
957 applied to working directory
957 applied to working directory
958 $ hg st -C
958 $ hg st -C
959 A b
959 A b
960 a
960 a
961 R a
961 R a
962 $ hg revert -a
962 $ hg revert -a
963 undeleting a
963 undeleting a
964 forgetting b
964 forgetting b
965 $ rm b
965 $ rm b
966 $ hg import --no-commit -v -s 100 ../rename.diff -p2
966 $ hg import --no-commit -v -s 100 ../rename.diff -p2
967 applying ../rename.diff
967 applying ../rename.diff
968 patching file a
968 patching file a
969 patching file b
969 patching file b
970 adding b
970 adding b
971 applied to working directory
971 applied to working directory
972 $ hg st -C
972 $ hg st -C
973 A b
973 A b
974 R a
974 R a
975 $ cd ..
975 $ cd ..
976
976
977
977
978 Issue1495: add empty file from the end of patch
978 Issue1495: add empty file from the end of patch
979
979
980 $ hg init addemptyend
980 $ hg init addemptyend
981 $ cd addemptyend
981 $ cd addemptyend
982 $ touch a
982 $ touch a
983 $ hg addremove
983 $ hg addremove
984 adding a
984 adding a
985 $ hg ci -m "commit"
985 $ hg ci -m "commit"
986 $ cat > a.patch <<EOF
986 $ cat > a.patch <<EOF
987 > add a, b
987 > add a, b
988 > diff --git a/a b/a
988 > diff --git a/a b/a
989 > --- a/a
989 > --- a/a
990 > +++ b/a
990 > +++ b/a
991 > @@ -0,0 +1,1 @@
991 > @@ -0,0 +1,1 @@
992 > +a
992 > +a
993 > diff --git a/b b/b
993 > diff --git a/b b/b
994 > new file mode 100644
994 > new file mode 100644
995 > EOF
995 > EOF
996 $ hg import --no-commit a.patch
996 $ hg import --no-commit a.patch
997 applying a.patch
997 applying a.patch
998
998
999 apply a good patch followed by an empty patch (mainly to ensure
999 apply a good patch followed by an empty patch (mainly to ensure
1000 that dirstate is *not* updated when import crashes)
1000 that dirstate is *not* updated when import crashes)
1001 $ hg update -q -C .
1001 $ hg update -q -C .
1002 $ rm b
1002 $ rm b
1003 $ touch empty.patch
1003 $ touch empty.patch
1004 $ hg import a.patch empty.patch
1004 $ hg import a.patch empty.patch
1005 applying a.patch
1005 applying a.patch
1006 applying empty.patch
1006 applying empty.patch
1007 transaction abort!
1007 transaction abort!
1008 rollback completed
1008 rollback completed
1009 abort: empty.patch: no diffs found
1009 abort: empty.patch: no diffs found
1010 [255]
1010 [255]
1011 $ hg tip --template '{rev} {desc|firstline}\n'
1011 $ hg tip --template '{rev} {desc|firstline}\n'
1012 0 commit
1012 0 commit
1013 $ hg -q status
1013 $ hg -q status
1014 M a
1014 M a
1015 $ cd ..
1015 $ cd ..
1016
1016
1017 create file when source is not /dev/null
1017 create file when source is not /dev/null
1018
1018
1019 $ cat > create.patch <<EOF
1019 $ cat > create.patch <<EOF
1020 > diff -Naur proj-orig/foo proj-new/foo
1020 > diff -Naur proj-orig/foo proj-new/foo
1021 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
1021 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
1022 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1022 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1023 > @@ -0,0 +1,1 @@
1023 > @@ -0,0 +1,1 @@
1024 > +a
1024 > +a
1025 > EOF
1025 > EOF
1026
1026
1027 some people have patches like the following too
1027 some people have patches like the following too
1028
1028
1029 $ cat > create2.patch <<EOF
1029 $ cat > create2.patch <<EOF
1030 > diff -Naur proj-orig/foo proj-new/foo
1030 > diff -Naur proj-orig/foo proj-new/foo
1031 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
1031 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
1032 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1032 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1033 > @@ -0,0 +1,1 @@
1033 > @@ -0,0 +1,1 @@
1034 > +a
1034 > +a
1035 > EOF
1035 > EOF
1036 $ hg init oddcreate
1036 $ hg init oddcreate
1037 $ cd oddcreate
1037 $ cd oddcreate
1038 $ hg import --no-commit ../create.patch
1038 $ hg import --no-commit ../create.patch
1039 applying ../create.patch
1039 applying ../create.patch
1040 $ cat foo
1040 $ cat foo
1041 a
1041 a
1042 $ rm foo
1042 $ rm foo
1043 $ hg revert foo
1043 $ hg revert foo
1044 $ hg import --no-commit ../create2.patch
1044 $ hg import --no-commit ../create2.patch
1045 applying ../create2.patch
1045 applying ../create2.patch
1046 $ cat foo
1046 $ cat foo
1047 a
1047 a
1048
1048
1049 $ cd ..
1049 $ cd ..
1050
1050
1051 Issue1859: first line mistaken for email headers
1051 Issue1859: first line mistaken for email headers
1052
1052
1053 $ hg init emailconfusion
1053 $ hg init emailconfusion
1054 $ cd emailconfusion
1054 $ cd emailconfusion
1055 $ cat > a.patch <<EOF
1055 $ cat > a.patch <<EOF
1056 > module: summary
1056 > module: summary
1057 >
1057 >
1058 > description
1058 > description
1059 >
1059 >
1060 >
1060 >
1061 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
1061 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
1062 > --- /dev/null
1062 > --- /dev/null
1063 > +++ b/a
1063 > +++ b/a
1064 > @@ -0,0 +1,1 @@
1064 > @@ -0,0 +1,1 @@
1065 > +a
1065 > +a
1066 > EOF
1066 > EOF
1067 $ hg import -d '0 0' a.patch
1067 $ hg import -d '0 0' a.patch
1068 applying a.patch
1068 applying a.patch
1069 $ hg parents -v
1069 $ hg parents -v
1070 changeset: 0:5a681217c0ad
1070 changeset: 0:5a681217c0ad
1071 tag: tip
1071 tag: tip
1072 user: test
1072 user: test
1073 date: Thu Jan 01 00:00:00 1970 +0000
1073 date: Thu Jan 01 00:00:00 1970 +0000
1074 files: a
1074 files: a
1075 description:
1075 description:
1076 module: summary
1076 module: summary
1077
1077
1078 description
1078 description
1079
1079
1080
1080
1081 $ cd ..
1081 $ cd ..
1082
1082
1083
1083
1084 in commit message
1084 in commit message
1085
1085
1086 $ hg init commitconfusion
1086 $ hg init commitconfusion
1087 $ cd commitconfusion
1087 $ cd commitconfusion
1088 $ cat > a.patch <<EOF
1088 $ cat > a.patch <<EOF
1089 > module: summary
1089 > module: summary
1090 >
1090 >
1091 > --- description
1091 > --- description
1092 >
1092 >
1093 > diff --git a/a b/a
1093 > diff --git a/a b/a
1094 > new file mode 100644
1094 > new file mode 100644
1095 > --- /dev/null
1095 > --- /dev/null
1096 > +++ b/a
1096 > +++ b/a
1097 > @@ -0,0 +1,1 @@
1097 > @@ -0,0 +1,1 @@
1098 > +a
1098 > +a
1099 > EOF
1099 > EOF
1100 > hg import -d '0 0' a.patch
1100 > hg import -d '0 0' a.patch
1101 > hg parents -v
1101 > hg parents -v
1102 > cd ..
1102 > cd ..
1103 >
1103 >
1104 > echo '% tricky header splitting'
1104 > echo '% tricky header splitting'
1105 > cat > trickyheaders.patch <<EOF
1105 > cat > trickyheaders.patch <<EOF
1106 > From: User A <user@a>
1106 > From: User A <user@a>
1107 > Subject: [PATCH] from: tricky!
1107 > Subject: [PATCH] from: tricky!
1108 >
1108 >
1109 > # HG changeset patch
1109 > # HG changeset patch
1110 > # User User B
1110 > # User User B
1111 > # Date 1266264441 18000
1111 > # Date 1266264441 18000
1112 > # Branch stable
1112 > # Branch stable
1113 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
1113 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
1114 > # Parent 0000000000000000000000000000000000000000
1114 > # Parent 0000000000000000000000000000000000000000
1115 > from: tricky!
1115 > from: tricky!
1116 >
1116 >
1117 > That is not a header.
1117 > That is not a header.
1118 >
1118 >
1119 > diff -r 000000000000 -r f2be6a1170ac foo
1119 > diff -r 000000000000 -r f2be6a1170ac foo
1120 > --- /dev/null
1120 > --- /dev/null
1121 > +++ b/foo
1121 > +++ b/foo
1122 > @@ -0,0 +1,1 @@
1122 > @@ -0,0 +1,1 @@
1123 > +foo
1123 > +foo
1124 > EOF
1124 > EOF
1125 applying a.patch
1125 applying a.patch
1126 changeset: 0:f34d9187897d
1126 changeset: 0:f34d9187897d
1127 tag: tip
1127 tag: tip
1128 user: test
1128 user: test
1129 date: Thu Jan 01 00:00:00 1970 +0000
1129 date: Thu Jan 01 00:00:00 1970 +0000
1130 files: a
1130 files: a
1131 description:
1131 description:
1132 module: summary
1132 module: summary
1133
1133
1134
1134
1135 % tricky header splitting
1135 % tricky header splitting
1136
1136
1137 $ hg init trickyheaders
1137 $ hg init trickyheaders
1138 $ cd trickyheaders
1138 $ cd trickyheaders
1139 $ hg import -d '0 0' ../trickyheaders.patch
1139 $ hg import -d '0 0' ../trickyheaders.patch
1140 applying ../trickyheaders.patch
1140 applying ../trickyheaders.patch
1141 $ hg export --git tip
1141 $ hg export --git tip
1142 # HG changeset patch
1142 # HG changeset patch
1143 # User User B
1143 # User User B
1144 # Date 0 0
1144 # Date 0 0
1145 # Thu Jan 01 00:00:00 1970 +0000
1145 # Thu Jan 01 00:00:00 1970 +0000
1146 # Node ID eb56ab91903632294ac504838508cb370c0901d2
1146 # Node ID eb56ab91903632294ac504838508cb370c0901d2
1147 # Parent 0000000000000000000000000000000000000000
1147 # Parent 0000000000000000000000000000000000000000
1148 from: tricky!
1148 from: tricky!
1149
1149
1150 That is not a header.
1150 That is not a header.
1151
1151
1152 diff --git a/foo b/foo
1152 diff --git a/foo b/foo
1153 new file mode 100644
1153 new file mode 100644
1154 --- /dev/null
1154 --- /dev/null
1155 +++ b/foo
1155 +++ b/foo
1156 @@ -0,0 +1,1 @@
1156 @@ -0,0 +1,1 @@
1157 +foo
1157 +foo
1158 $ cd ..
1158 $ cd ..
1159
1159
1160
1160
1161 Issue2102: hg export and hg import speak different languages
1161 Issue2102: hg export and hg import speak different languages
1162
1162
1163 $ hg init issue2102
1163 $ hg init issue2102
1164 $ cd issue2102
1164 $ cd issue2102
1165 $ mkdir -p src/cmd/gc
1165 $ mkdir -p src/cmd/gc
1166 $ touch src/cmd/gc/mksys.bash
1166 $ touch src/cmd/gc/mksys.bash
1167 $ hg ci -Am init
1167 $ hg ci -Am init
1168 adding src/cmd/gc/mksys.bash
1168 adding src/cmd/gc/mksys.bash
1169 $ hg import - <<EOF
1169 $ hg import - <<EOF
1170 > # HG changeset patch
1170 > # HG changeset patch
1171 > # User Rob Pike
1171 > # User Rob Pike
1172 > # Date 1216685449 25200
1172 > # Date 1216685449 25200
1173 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
1173 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
1174 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
1174 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
1175 > help management of empty pkg and lib directories in perforce
1175 > help management of empty pkg and lib directories in perforce
1176 >
1176 >
1177 > R=gri
1177 > R=gri
1178 > DELTA=4 (4 added, 0 deleted, 0 changed)
1178 > DELTA=4 (4 added, 0 deleted, 0 changed)
1179 > OCL=13328
1179 > OCL=13328
1180 > CL=13328
1180 > CL=13328
1181 >
1181 >
1182 > diff --git a/lib/place-holder b/lib/place-holder
1182 > diff --git a/lib/place-holder b/lib/place-holder
1183 > new file mode 100644
1183 > new file mode 100644
1184 > --- /dev/null
1184 > --- /dev/null
1185 > +++ b/lib/place-holder
1185 > +++ b/lib/place-holder
1186 > @@ -0,0 +1,2 @@
1186 > @@ -0,0 +1,2 @@
1187 > +perforce does not maintain empty directories.
1187 > +perforce does not maintain empty directories.
1188 > +this file helps.
1188 > +this file helps.
1189 > diff --git a/pkg/place-holder b/pkg/place-holder
1189 > diff --git a/pkg/place-holder b/pkg/place-holder
1190 > new file mode 100644
1190 > new file mode 100644
1191 > --- /dev/null
1191 > --- /dev/null
1192 > +++ b/pkg/place-holder
1192 > +++ b/pkg/place-holder
1193 > @@ -0,0 +1,2 @@
1193 > @@ -0,0 +1,2 @@
1194 > +perforce does not maintain empty directories.
1194 > +perforce does not maintain empty directories.
1195 > +this file helps.
1195 > +this file helps.
1196 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1196 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1197 > old mode 100644
1197 > old mode 100644
1198 > new mode 100755
1198 > new mode 100755
1199 > EOF
1199 > EOF
1200 applying patch from stdin
1200 applying patch from stdin
1201
1201
1202 #if execbit
1202 #if execbit
1203
1203
1204 $ hg sum
1204 $ hg sum
1205 parent: 1:d59915696727 tip
1205 parent: 1:d59915696727 tip
1206 help management of empty pkg and lib directories in perforce
1206 help management of empty pkg and lib directories in perforce
1207 branch: default
1207 branch: default
1208 commit: (clean)
1208 commit: (clean)
1209 update: (current)
1209 update: (current)
1210 phases: 2 draft
1210 phases: 2 draft
1211
1211
1212 $ hg diff --git -c tip
1212 $ hg diff --git -c tip
1213 diff --git a/lib/place-holder b/lib/place-holder
1213 diff --git a/lib/place-holder b/lib/place-holder
1214 new file mode 100644
1214 new file mode 100644
1215 --- /dev/null
1215 --- /dev/null
1216 +++ b/lib/place-holder
1216 +++ b/lib/place-holder
1217 @@ -0,0 +1,2 @@
1217 @@ -0,0 +1,2 @@
1218 +perforce does not maintain empty directories.
1218 +perforce does not maintain empty directories.
1219 +this file helps.
1219 +this file helps.
1220 diff --git a/pkg/place-holder b/pkg/place-holder
1220 diff --git a/pkg/place-holder b/pkg/place-holder
1221 new file mode 100644
1221 new file mode 100644
1222 --- /dev/null
1222 --- /dev/null
1223 +++ b/pkg/place-holder
1223 +++ b/pkg/place-holder
1224 @@ -0,0 +1,2 @@
1224 @@ -0,0 +1,2 @@
1225 +perforce does not maintain empty directories.
1225 +perforce does not maintain empty directories.
1226 +this file helps.
1226 +this file helps.
1227 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1227 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1228 old mode 100644
1228 old mode 100644
1229 new mode 100755
1229 new mode 100755
1230
1230
1231 #else
1231 #else
1232
1232
1233 $ hg sum
1233 $ hg sum
1234 parent: 1:28f089cc9ccc tip
1234 parent: 1:28f089cc9ccc tip
1235 help management of empty pkg and lib directories in perforce
1235 help management of empty pkg and lib directories in perforce
1236 branch: default
1236 branch: default
1237 commit: (clean)
1237 commit: (clean)
1238 update: (current)
1238 update: (current)
1239 phases: 2 draft
1239 phases: 2 draft
1240
1240
1241 $ hg diff --git -c tip
1241 $ hg diff --git -c tip
1242 diff --git a/lib/place-holder b/lib/place-holder
1242 diff --git a/lib/place-holder b/lib/place-holder
1243 new file mode 100644
1243 new file mode 100644
1244 --- /dev/null
1244 --- /dev/null
1245 +++ b/lib/place-holder
1245 +++ b/lib/place-holder
1246 @@ -0,0 +1,2 @@
1246 @@ -0,0 +1,2 @@
1247 +perforce does not maintain empty directories.
1247 +perforce does not maintain empty directories.
1248 +this file helps.
1248 +this file helps.
1249 diff --git a/pkg/place-holder b/pkg/place-holder
1249 diff --git a/pkg/place-holder b/pkg/place-holder
1250 new file mode 100644
1250 new file mode 100644
1251 --- /dev/null
1251 --- /dev/null
1252 +++ b/pkg/place-holder
1252 +++ b/pkg/place-holder
1253 @@ -0,0 +1,2 @@
1253 @@ -0,0 +1,2 @@
1254 +perforce does not maintain empty directories.
1254 +perforce does not maintain empty directories.
1255 +this file helps.
1255 +this file helps.
1256
1256
1257 /* The mode change for mksys.bash is missing here, because on platforms */
1257 /* The mode change for mksys.bash is missing here, because on platforms */
1258 /* that don't support execbits, mode changes in patches are ignored when */
1258 /* that don't support execbits, mode changes in patches are ignored when */
1259 /* they are imported. This is obviously also the reason for why the hash */
1259 /* they are imported. This is obviously also the reason for why the hash */
1260 /* in the created changeset is different to the one you see above the */
1260 /* in the created changeset is different to the one you see above the */
1261 /* #else clause */
1261 /* #else clause */
1262
1262
1263 #endif
1263 #endif
1264 $ cd ..
1264 $ cd ..
1265
1265
1266
1266
1267 diff lines looking like headers
1267 diff lines looking like headers
1268
1268
1269 $ hg init difflineslikeheaders
1269 $ hg init difflineslikeheaders
1270 $ cd difflineslikeheaders
1270 $ cd difflineslikeheaders
1271 $ echo a >a
1271 $ echo a >a
1272 $ echo b >b
1272 $ echo b >b
1273 $ echo c >c
1273 $ echo c >c
1274 $ hg ci -Am1
1274 $ hg ci -Am1
1275 adding a
1275 adding a
1276 adding b
1276 adding b
1277 adding c
1277 adding c
1278
1278
1279 $ echo "key: value" >>a
1279 $ echo "key: value" >>a
1280 $ echo "key: value" >>b
1280 $ echo "key: value" >>b
1281 $ echo "foo" >>c
1281 $ echo "foo" >>c
1282 $ hg ci -m2
1282 $ hg ci -m2
1283
1283
1284 $ hg up -C 0
1284 $ hg up -C 0
1285 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1285 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1286 $ hg diff --git -c1 >want
1286 $ hg diff --git -c1 >want
1287 $ hg diff -c1 | hg import --no-commit -
1287 $ hg diff -c1 | hg import --no-commit -
1288 applying patch from stdin
1288 applying patch from stdin
1289 $ hg diff --git >have
1289 $ hg diff --git >have
1290 $ diff want have
1290 $ diff want have
1291 $ cd ..
1291 $ cd ..
1292
1292
1293 import a unified diff with no lines of context (diff -U0)
1293 import a unified diff with no lines of context (diff -U0)
1294
1294
1295 $ hg init diffzero
1295 $ hg init diffzero
1296 $ cd diffzero
1296 $ cd diffzero
1297 $ cat > f << EOF
1297 $ cat > f << EOF
1298 > c2
1298 > c2
1299 > c4
1299 > c4
1300 > c5
1300 > c5
1301 > EOF
1301 > EOF
1302 $ hg commit -Am0
1302 $ hg commit -Am0
1303 adding f
1303 adding f
1304
1304
1305 $ hg import --no-commit - << EOF
1305 $ hg import --no-commit - << EOF
1306 > # HG changeset patch
1306 > # HG changeset patch
1307 > # User test
1307 > # User test
1308 > # Date 0 0
1308 > # Date 0 0
1309 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1309 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1310 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1310 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1311 > 1
1311 > 1
1312 > diff -r 8679a12a975b -r f4974ab632f3 f
1312 > diff -r 8679a12a975b -r f4974ab632f3 f
1313 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1313 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1314 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1314 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1315 > @@ -0,0 +1,1 @@
1315 > @@ -0,0 +1,1 @@
1316 > +c1
1316 > +c1
1317 > @@ -1,0 +3,1 @@
1317 > @@ -1,0 +3,1 @@
1318 > +c3
1318 > +c3
1319 > @@ -3,1 +4,0 @@
1319 > @@ -3,1 +4,0 @@
1320 > -c5
1320 > -c5
1321 > EOF
1321 > EOF
1322 applying patch from stdin
1322 applying patch from stdin
1323
1323
1324 $ cat f
1324 $ cat f
1325 c1
1325 c1
1326 c2
1326 c2
1327 c3
1327 c3
1328 c4
1328 c4
1329
1329
1330 $ cd ..
1330 $ cd ..
1331
1331
1332 no segfault while importing a unified diff which start line is zero but chunk
1332 no segfault while importing a unified diff which start line is zero but chunk
1333 size is non-zero
1333 size is non-zero
1334
1334
1335 $ hg init startlinezero
1335 $ hg init startlinezero
1336 $ cd startlinezero
1336 $ cd startlinezero
1337 $ echo foo > foo
1337 $ echo foo > foo
1338 $ hg commit -Amfoo
1338 $ hg commit -Amfoo
1339 adding foo
1339 adding foo
1340
1340
1341 $ hg import --no-commit - << EOF
1341 $ hg import --no-commit - << EOF
1342 > diff a/foo b/foo
1342 > diff a/foo b/foo
1343 > --- a/foo
1343 > --- a/foo
1344 > +++ b/foo
1344 > +++ b/foo
1345 > @@ -0,1 +0,1 @@
1345 > @@ -0,1 +0,1 @@
1346 > foo
1346 > foo
1347 > EOF
1347 > EOF
1348 applying patch from stdin
1348 applying patch from stdin
1349
1349
1350 $ cd ..
1350 $ cd ..
1351
1351
1352 Test corner case involving fuzz and skew
1352 Test corner case involving fuzz and skew
1353
1353
1354 $ hg init morecornercases
1354 $ hg init morecornercases
1355 $ cd morecornercases
1355 $ cd morecornercases
1356
1356
1357 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1357 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1358 > diff --git a/a b/a
1358 > diff --git a/a b/a
1359 > --- a/a
1359 > --- a/a
1360 > +++ b/a
1360 > +++ b/a
1361 > @@ -1,0 +1,1 @@
1361 > @@ -1,0 +1,1 @@
1362 > +line
1362 > +line
1363 > EOF
1363 > EOF
1364
1364
1365 $ cat > 02-no-context-middle-of-file.diff <<EOF
1365 $ cat > 02-no-context-middle-of-file.diff <<EOF
1366 > diff --git a/a b/a
1366 > diff --git a/a b/a
1367 > --- a/a
1367 > --- a/a
1368 > +++ b/a
1368 > +++ b/a
1369 > @@ -1,1 +1,1 @@
1369 > @@ -1,1 +1,1 @@
1370 > -2
1370 > -2
1371 > +add some skew
1371 > +add some skew
1372 > @@ -2,0 +2,1 @@
1372 > @@ -2,0 +2,1 @@
1373 > +line
1373 > +line
1374 > EOF
1374 > EOF
1375
1375
1376 $ cat > 03-no-context-end-of-file.diff <<EOF
1376 $ cat > 03-no-context-end-of-file.diff <<EOF
1377 > diff --git a/a b/a
1377 > diff --git a/a b/a
1378 > --- a/a
1378 > --- a/a
1379 > +++ b/a
1379 > +++ b/a
1380 > @@ -10,0 +10,1 @@
1380 > @@ -10,0 +10,1 @@
1381 > +line
1381 > +line
1382 > EOF
1382 > EOF
1383
1383
1384 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1384 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1385 > diff --git a/a b/a
1385 > diff --git a/a b/a
1386 > --- a/a
1386 > --- a/a
1387 > +++ b/a
1387 > +++ b/a
1388 > @@ -1,1 +1,1 @@
1388 > @@ -1,1 +1,1 @@
1389 > -2
1389 > -2
1390 > +add some skew
1390 > +add some skew
1391 > @@ -2,2 +2,3 @@
1391 > @@ -2,2 +2,3 @@
1392 > not matching, should fuzz
1392 > not matching, should fuzz
1393 > ... a bit
1393 > ... a bit
1394 > +line
1394 > +line
1395 > EOF
1395 > EOF
1396
1396
1397 $ cat > a <<EOF
1397 $ cat > a <<EOF
1398 > 1
1398 > 1
1399 > 2
1399 > 2
1400 > 3
1400 > 3
1401 > 4
1401 > 4
1402 > EOF
1402 > EOF
1403 $ hg ci -Am adda a
1403 $ hg ci -Am adda a
1404 $ for p in *.diff; do
1404 $ for p in *.diff; do
1405 > hg import -v --no-commit $p
1405 > hg import -v --no-commit $p
1406 > cat a
1406 > cat a
1407 > hg revert -aqC a
1407 > hg revert -aqC a
1408 > # patch -p1 < $p
1408 > # patch -p1 < $p
1409 > # cat a
1409 > # cat a
1410 > # hg revert -aC a
1410 > # hg revert -aC a
1411 > done
1411 > done
1412 applying 01-no-context-beginning-of-file.diff
1412 applying 01-no-context-beginning-of-file.diff
1413 patching file a
1413 patching file a
1414 applied to working directory
1414 applied to working directory
1415 1
1415 1
1416 line
1416 line
1417 2
1417 2
1418 3
1418 3
1419 4
1419 4
1420 applying 02-no-context-middle-of-file.diff
1420 applying 02-no-context-middle-of-file.diff
1421 patching file a
1421 patching file a
1422 Hunk #1 succeeded at 2 (offset 1 lines).
1422 Hunk #1 succeeded at 2 (offset 1 lines).
1423 Hunk #2 succeeded at 4 (offset 1 lines).
1423 Hunk #2 succeeded at 4 (offset 1 lines).
1424 applied to working directory
1424 applied to working directory
1425 1
1425 1
1426 add some skew
1426 add some skew
1427 3
1427 3
1428 line
1428 line
1429 4
1429 4
1430 applying 03-no-context-end-of-file.diff
1430 applying 03-no-context-end-of-file.diff
1431 patching file a
1431 patching file a
1432 Hunk #1 succeeded at 5 (offset -6 lines).
1432 Hunk #1 succeeded at 5 (offset -6 lines).
1433 applied to working directory
1433 applied to working directory
1434 1
1434 1
1435 2
1435 2
1436 3
1436 3
1437 4
1437 4
1438 line
1438 line
1439 applying 04-middle-of-file-completely-fuzzed.diff
1439 applying 04-middle-of-file-completely-fuzzed.diff
1440 patching file a
1440 patching file a
1441 Hunk #1 succeeded at 2 (offset 1 lines).
1441 Hunk #1 succeeded at 2 (offset 1 lines).
1442 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1442 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1443 applied to working directory
1443 applied to working directory
1444 1
1444 1
1445 add some skew
1445 add some skew
1446 3
1446 3
1447 4
1447 4
1448 line
1448 line
1449 $ cd ..
1449 $ cd ..
1450
1450
1451 Test partial application
1451 Test partial application
1452 ------------------------
1452 ------------------------
1453
1453
1454 prepare a stack of patches depending on each other
1454 prepare a stack of patches depending on each other
1455
1455
1456 $ hg init partial
1456 $ hg init partial
1457 $ cd partial
1457 $ cd partial
1458 $ cat << EOF > a
1458 $ cat << EOF > a
1459 > one
1459 > one
1460 > two
1460 > two
1461 > three
1461 > three
1462 > four
1462 > four
1463 > five
1463 > five
1464 > six
1464 > six
1465 > seven
1465 > seven
1466 > EOF
1466 > EOF
1467 $ hg add a
1467 $ hg add a
1468 $ echo 'b' > b
1468 $ echo 'b' > b
1469 $ hg add b
1469 $ hg add b
1470 $ hg commit -m 'initial' -u Babar
1470 $ hg commit -m 'initial' -u Babar
1471 $ cat << EOF > a
1471 $ cat << EOF > a
1472 > one
1472 > one
1473 > two
1473 > two
1474 > 3
1474 > 3
1475 > four
1475 > four
1476 > five
1476 > five
1477 > six
1477 > six
1478 > seven
1478 > seven
1479 > EOF
1479 > EOF
1480 $ hg commit -m 'three' -u Celeste
1480 $ hg commit -m 'three' -u Celeste
1481 $ cat << EOF > a
1481 $ cat << EOF > a
1482 > one
1482 > one
1483 > two
1483 > two
1484 > 3
1484 > 3
1485 > 4
1485 > 4
1486 > five
1486 > five
1487 > six
1487 > six
1488 > seven
1488 > seven
1489 > EOF
1489 > EOF
1490 $ hg commit -m 'four' -u Rataxes
1490 $ hg commit -m 'four' -u Rataxes
1491 $ cat << EOF > a
1491 $ cat << EOF > a
1492 > one
1492 > one
1493 > two
1493 > two
1494 > 3
1494 > 3
1495 > 4
1495 > 4
1496 > 5
1496 > 5
1497 > six
1497 > six
1498 > seven
1498 > seven
1499 > EOF
1499 > EOF
1500 $ echo bb >> b
1500 $ echo bb >> b
1501 $ hg commit -m 'five' -u Arthur
1501 $ hg commit -m 'five' -u Arthur
1502 $ echo 'Babar' > jungle
1502 $ echo 'Babar' > jungle
1503 $ hg add jungle
1503 $ hg add jungle
1504 $ hg ci -m 'jungle' -u Zephir
1504 $ hg ci -m 'jungle' -u Zephir
1505 $ echo 'Celeste' >> jungle
1505 $ echo 'Celeste' >> jungle
1506 $ hg ci -m 'extended jungle' -u Cornelius
1506 $ hg ci -m 'extended jungle' -u Cornelius
1507 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1507 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1508 @ extended jungle [Cornelius] 1: +1/-0
1508 @ extended jungle [Cornelius] 1: +1/-0
1509 |
1509 |
1510 o jungle [Zephir] 1: +1/-0
1510 o jungle [Zephir] 1: +1/-0
1511 |
1511 |
1512 o five [Arthur] 2: +2/-1
1512 o five [Arthur] 2: +2/-1
1513 |
1513 |
1514 o four [Rataxes] 1: +1/-1
1514 o four [Rataxes] 1: +1/-1
1515 |
1515 |
1516 o three [Celeste] 1: +1/-1
1516 o three [Celeste] 1: +1/-1
1517 |
1517 |
1518 o initial [Babar] 2: +8/-0
1518 o initial [Babar] 2: +8/-0
1519
1519
1520
1520
1521 Importing with some success and some errors:
1521 Importing with some success and some errors:
1522
1522
1523 $ hg update --rev 'desc(initial)'
1523 $ hg update --rev 'desc(initial)'
1524 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1524 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1525 $ hg export --rev 'desc(five)' | hg import --partial -
1525 $ hg export --rev 'desc(five)' | hg import --partial -
1526 applying patch from stdin
1526 applying patch from stdin
1527 patching file a
1527 patching file a
1528 Hunk #1 FAILED at 1
1528 Hunk #1 FAILED at 1
1529 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1529 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1530 patch applied partially
1530 patch applied partially
1531 (fix the .rej files and run `hg commit --amend`)
1531 (fix the .rej files and run `hg commit --amend`)
1532 [1]
1532 [1]
1533
1533
1534 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1534 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1535 @ five [Arthur] 1: +1/-0
1535 @ five [Arthur] 1: +1/-0
1536 |
1536 |
1537 | o extended jungle [Cornelius] 1: +1/-0
1537 | o extended jungle [Cornelius] 1: +1/-0
1538 | |
1538 | |
1539 | o jungle [Zephir] 1: +1/-0
1539 | o jungle [Zephir] 1: +1/-0
1540 | |
1540 | |
1541 | o five [Arthur] 2: +2/-1
1541 | o five [Arthur] 2: +2/-1
1542 | |
1542 | |
1543 | o four [Rataxes] 1: +1/-1
1543 | o four [Rataxes] 1: +1/-1
1544 | |
1544 | |
1545 | o three [Celeste] 1: +1/-1
1545 | o three [Celeste] 1: +1/-1
1546 |/
1546 |/
1547 o initial [Babar] 2: +8/-0
1547 o initial [Babar] 2: +8/-0
1548
1548
1549 $ hg export
1549 $ hg export
1550 # HG changeset patch
1550 # HG changeset patch
1551 # User Arthur
1551 # User Arthur
1552 # Date 0 0
1552 # Date 0 0
1553 # Thu Jan 01 00:00:00 1970 +0000
1553 # Thu Jan 01 00:00:00 1970 +0000
1554 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1554 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1555 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1555 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1556 five
1556 five
1557
1557
1558 diff -r 8e4f0351909e -r 26e6446bb252 b
1558 diff -r 8e4f0351909e -r 26e6446bb252 b
1559 --- a/b Thu Jan 01 00:00:00 1970 +0000
1559 --- a/b Thu Jan 01 00:00:00 1970 +0000
1560 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1560 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1561 @@ -1,1 +1,2 @@
1561 @@ -1,1 +1,2 @@
1562 b
1562 b
1563 +bb
1563 +bb
1564 $ hg status -c .
1564 $ hg status -c .
1565 C a
1565 C a
1566 C b
1566 C b
1567 $ ls
1567 $ ls
1568 a
1568 a
1569 a.rej
1569 a.rej
1570 b
1570 b
1571
1571
1572 Importing with zero success:
1572 Importing with zero success:
1573
1573
1574 $ hg update --rev 'desc(initial)'
1574 $ hg update --rev 'desc(initial)'
1575 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1575 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1576 $ hg export --rev 'desc(four)' | hg import --partial -
1576 $ hg export --rev 'desc(four)' | hg import --partial -
1577 applying patch from stdin
1577 applying patch from stdin
1578 patching file a
1578 patching file a
1579 Hunk #1 FAILED at 0
1579 Hunk #1 FAILED at 0
1580 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1580 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1581 patch applied partially
1581 patch applied partially
1582 (fix the .rej files and run `hg commit --amend`)
1582 (fix the .rej files and run `hg commit --amend`)
1583 [1]
1583 [1]
1584
1584
1585 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1585 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1586 @ four [Rataxes] 0: +0/-0
1586 @ four [Rataxes] 0: +0/-0
1587 |
1587 |
1588 | o five [Arthur] 1: +1/-0
1588 | o five [Arthur] 1: +1/-0
1589 |/
1589 |/
1590 | o extended jungle [Cornelius] 1: +1/-0
1590 | o extended jungle [Cornelius] 1: +1/-0
1591 | |
1591 | |
1592 | o jungle [Zephir] 1: +1/-0
1592 | o jungle [Zephir] 1: +1/-0
1593 | |
1593 | |
1594 | o five [Arthur] 2: +2/-1
1594 | o five [Arthur] 2: +2/-1
1595 | |
1595 | |
1596 | o four [Rataxes] 1: +1/-1
1596 | o four [Rataxes] 1: +1/-1
1597 | |
1597 | |
1598 | o three [Celeste] 1: +1/-1
1598 | o three [Celeste] 1: +1/-1
1599 |/
1599 |/
1600 o initial [Babar] 2: +8/-0
1600 o initial [Babar] 2: +8/-0
1601
1601
1602 $ hg export
1602 $ hg export
1603 # HG changeset patch
1603 # HG changeset patch
1604 # User Rataxes
1604 # User Rataxes
1605 # Date 0 0
1605 # Date 0 0
1606 # Thu Jan 01 00:00:00 1970 +0000
1606 # Thu Jan 01 00:00:00 1970 +0000
1607 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1607 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1608 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1608 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1609 four
1609 four
1610
1610
1611 $ hg status -c .
1611 $ hg status -c .
1612 C a
1612 C a
1613 C b
1613 C b
1614 $ ls
1614 $ ls
1615 a
1615 a
1616 a.rej
1616 a.rej
1617 b
1617 b
1618
1618
1619 Importing with unknown file:
1619 Importing with unknown file:
1620
1620
1621 $ hg update --rev 'desc(initial)'
1621 $ hg update --rev 'desc(initial)'
1622 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1622 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1623 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1623 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1624 applying patch from stdin
1624 applying patch from stdin
1625 unable to find 'jungle' for patching
1625 unable to find 'jungle' for patching
1626 (use '--prefix' to apply patch relative to the current directory)
1626 (use '--prefix' to apply patch relative to the current directory)
1627 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1627 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1628 patch applied partially
1628 patch applied partially
1629 (fix the .rej files and run `hg commit --amend`)
1629 (fix the .rej files and run `hg commit --amend`)
1630 [1]
1630 [1]
1631
1631
1632 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1632 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1633 @ extended jungle [Cornelius] 0: +0/-0
1633 @ extended jungle [Cornelius] 0: +0/-0
1634 |
1634 |
1635 | o four [Rataxes] 0: +0/-0
1635 | o four [Rataxes] 0: +0/-0
1636 |/
1636 |/
1637 | o five [Arthur] 1: +1/-0
1637 | o five [Arthur] 1: +1/-0
1638 |/
1638 |/
1639 | o extended jungle [Cornelius] 1: +1/-0
1639 | o extended jungle [Cornelius] 1: +1/-0
1640 | |
1640 | |
1641 | o jungle [Zephir] 1: +1/-0
1641 | o jungle [Zephir] 1: +1/-0
1642 | |
1642 | |
1643 | o five [Arthur] 2: +2/-1
1643 | o five [Arthur] 2: +2/-1
1644 | |
1644 | |
1645 | o four [Rataxes] 1: +1/-1
1645 | o four [Rataxes] 1: +1/-1
1646 | |
1646 | |
1647 | o three [Celeste] 1: +1/-1
1647 | o three [Celeste] 1: +1/-1
1648 |/
1648 |/
1649 o initial [Babar] 2: +8/-0
1649 o initial [Babar] 2: +8/-0
1650
1650
1651 $ hg export
1651 $ hg export
1652 # HG changeset patch
1652 # HG changeset patch
1653 # User Cornelius
1653 # User Cornelius
1654 # Date 0 0
1654 # Date 0 0
1655 # Thu Jan 01 00:00:00 1970 +0000
1655 # Thu Jan 01 00:00:00 1970 +0000
1656 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1656 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1657 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1657 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1658 extended jungle
1658 extended jungle
1659
1659
1660 $ hg status -c .
1660 $ hg status -c .
1661 C a
1661 C a
1662 C b
1662 C b
1663 $ ls
1663 $ ls
1664 a
1664 a
1665 a.rej
1665 a.rej
1666 b
1666 b
1667 jungle.rej
1667 jungle.rej
1668
1668
1669 Importing multiple failing patches:
1669 Importing multiple failing patches:
1670
1670
1671 $ hg update --rev 'desc(initial)'
1671 $ hg update --rev 'desc(initial)'
1672 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1672 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1673 $ echo 'B' > b # just to make another commit
1673 $ echo 'B' > b # just to make another commit
1674 $ hg commit -m "a new base"
1674 $ hg commit -m "a new base"
1675 created new head
1675 created new head
1676 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1676 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1677 applying patch from stdin
1677 applying patch from stdin
1678 patching file a
1678 patching file a
1679 Hunk #1 FAILED at 0
1679 Hunk #1 FAILED at 0
1680 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1680 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1681 patch applied partially
1681 patch applied partially
1682 (fix the .rej files and run `hg commit --amend`)
1682 (fix the .rej files and run `hg commit --amend`)
1683 [1]
1683 [1]
1684 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1684 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1685 @ four [Rataxes] 0: +0/-0
1685 @ four [Rataxes] 0: +0/-0
1686 |
1686 |
1687 o a new base [test] 1: +1/-1
1687 o a new base [test] 1: +1/-1
1688 |
1688 |
1689 | o extended jungle [Cornelius] 0: +0/-0
1689 | o extended jungle [Cornelius] 0: +0/-0
1690 |/
1690 |/
1691 | o four [Rataxes] 0: +0/-0
1691 | o four [Rataxes] 0: +0/-0
1692 |/
1692 |/
1693 | o five [Arthur] 1: +1/-0
1693 | o five [Arthur] 1: +1/-0
1694 |/
1694 |/
1695 | o extended jungle [Cornelius] 1: +1/-0
1695 | o extended jungle [Cornelius] 1: +1/-0
1696 | |
1696 | |
1697 | o jungle [Zephir] 1: +1/-0
1697 | o jungle [Zephir] 1: +1/-0
1698 | |
1698 | |
1699 | o five [Arthur] 2: +2/-1
1699 | o five [Arthur] 2: +2/-1
1700 | |
1700 | |
1701 | o four [Rataxes] 1: +1/-1
1701 | o four [Rataxes] 1: +1/-1
1702 | |
1702 | |
1703 | o three [Celeste] 1: +1/-1
1703 | o three [Celeste] 1: +1/-1
1704 |/
1704 |/
1705 o initial [Babar] 2: +8/-0
1705 o initial [Babar] 2: +8/-0
1706
1706
1707 $ hg export
1707 $ hg export
1708 # HG changeset patch
1708 # HG changeset patch
1709 # User Rataxes
1709 # User Rataxes
1710 # Date 0 0
1710 # Date 0 0
1711 # Thu Jan 01 00:00:00 1970 +0000
1711 # Thu Jan 01 00:00:00 1970 +0000
1712 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1712 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1713 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1713 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1714 four
1714 four
1715
1715
1716 $ hg status -c .
1716 $ hg status -c .
1717 C a
1717 C a
1718 C b
1718 C b
1719
1719
1720 Importing some extra header
1720 Importing some extra header
1721 ===========================
1721 ===========================
1722
1722
1723 $ cat > $TESTTMP/parseextra.py <<EOF
1723 $ cat > $TESTTMP/parseextra.py <<EOF
1724 > import mercurial.patch
1724 > import mercurial.patch
1725 > import mercurial.cmdutil
1725 > import mercurial.cmdutil
1726 >
1726 >
1727 > def processfoo(repo, data, extra, opts):
1727 > def processfoo(repo, data, extra, opts):
1728 > if 'foo' in data:
1728 > if 'foo' in data:
1729 > extra['foo'] = data['foo']
1729 > extra['foo'] = data['foo']
1730 > def postimport(ctx):
1730 > def postimport(ctx):
1731 > if 'foo' in ctx.extra():
1731 > if 'foo' in ctx.extra():
1732 > ctx.repo().ui.write('imported-foo: %s\n' % ctx.extra()['foo'])
1732 > ctx.repo().ui.write('imported-foo: %s\n' % ctx.extra()['foo'])
1733 >
1733 >
1734 > mercurial.patch.patchheadermap.append(('Foo', 'foo'))
1734 > mercurial.patch.patchheadermap.append(('Foo', 'foo'))
1735 > mercurial.cmdutil.extrapreimport.append('foo')
1735 > mercurial.cmdutil.extrapreimport.append('foo')
1736 > mercurial.cmdutil.extrapreimportmap['foo'] = processfoo
1736 > mercurial.cmdutil.extrapreimportmap['foo'] = processfoo
1737 > mercurial.cmdutil.extrapostimport.append('foo')
1737 > mercurial.cmdutil.extrapostimport.append('foo')
1738 > mercurial.cmdutil.extrapostimportmap['foo'] = postimport
1738 > mercurial.cmdutil.extrapostimportmap['foo'] = postimport
1739 > EOF
1739 > EOF
1740 $ cat >> $HGRCPATH <<EOF
1740 $ cat >> $HGRCPATH <<EOF
1741 > [extensions]
1741 > [extensions]
1742 > parseextra=$TESTTMP/parseextra.py
1742 > parseextra=$TESTTMP/parseextra.py
1743 > EOF
1743 > EOF
1744 $ hg up -C tip
1744 $ hg up -C tip
1745 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1745 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1746 $ cat > $TESTTMP/foo.patch <<EOF
1746 $ cat > $TESTTMP/foo.patch <<EOF
1747 > # HG changeset patch
1747 > # HG changeset patch
1748 > # User Rataxes
1748 > # User Rataxes
1749 > # Date 0 0
1749 > # Date 0 0
1750 > # Thu Jan 01 00:00:00 1970 +0000
1750 > # Thu Jan 01 00:00:00 1970 +0000
1751 > # Foo bar
1751 > # Foo bar
1752 > height
1752 > height
1753 >
1753 >
1754 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1754 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1755 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1755 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1756 > @@ -5,3 +5,4 @@
1756 > @@ -5,3 +5,4 @@
1757 > five
1757 > five
1758 > six
1758 > six
1759 > seven
1759 > seven
1760 > +heigt
1760 > +heigt
1761 > EOF
1761 > EOF
1762 $ hg import $TESTTMP/foo.patch
1762 $ hg import $TESTTMP/foo.patch
1763 applying $TESTTMP/foo.patch
1763 applying $TESTTMP/foo.patch
1764 imported-foo: bar
1764 imported-foo: bar
1765 $ hg log --debug -r . | grep extra
1765 $ hg log --debug -r . | grep extra
1766 extra: branch=default
1766 extra: branch=default
1767 extra: foo=bar
1767 extra: foo=bar
1768
1768
1769 Warn the user that paths are relative to the root of
1769 Warn the user that paths are relative to the root of
1770 repository when file not found for patching
1770 repository when file not found for patching
1771
1771
1772 $ mkdir filedir
1772 $ mkdir filedir
1773 $ echo "file1" >> filedir/file1
1773 $ echo "file1" >> filedir/file1
1774 $ hg add filedir/file1
1774 $ hg add filedir/file1
1775 $ hg commit -m "file1"
1775 $ hg commit -m "file1"
1776 $ cd filedir
1776 $ cd filedir
1777 $ hg import -p 2 - <<EOF
1777 $ hg import -p 2 - <<EOF
1778 > # HG changeset patch
1778 > # HG changeset patch
1779 > # User test
1779 > # User test
1780 > # Date 0 0
1780 > # Date 0 0
1781 > file2
1781 > file2
1782 >
1782 >
1783 > diff --git a/filedir/file1 b/filedir/file1
1783 > diff --git a/filedir/file1 b/filedir/file1
1784 > --- a/filedir/file1
1784 > --- a/filedir/file1
1785 > +++ b/filedir/file1
1785 > +++ b/filedir/file1
1786 > @@ -1,1 +1,2 @@
1786 > @@ -1,1 +1,2 @@
1787 > file1
1787 > file1
1788 > +file2
1788 > +file2
1789 > EOF
1789 > EOF
1790 applying patch from stdin
1790 applying patch from stdin
1791 unable to find 'file1' for patching
1791 unable to find 'file1' for patching
1792 (use '--prefix' to apply patch relative to the current directory)
1792 (use '--prefix' to apply patch relative to the current directory)
1793 1 out of 1 hunks FAILED -- saving rejects to file file1.rej
1793 1 out of 1 hunks FAILED -- saving rejects to file file1.rej
1794 abort: patch failed to apply
1794 abort: patch failed to apply
1795 [255]
1795 [255]
1796
1797 test import crash (issue5375)
1798 $ cd ..
1799 $ hg init repo
1800 $ cd repo
1801 $ printf "diff --git a/a b/b\nrename from a\nrename to b" | hg import -
1802 applying patch from stdin
1803 a not tracked!
1804 abort: source file 'a' does not exist
1805 [255]
General Comments 0
You need to be logged in to leave comments. Login now