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