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