##// END OF EJS Templates
patch: add support for git delta hunks...
Nicolas Vigier -
r20137:9f1d4323 default
parent child Browse files
Show More
@@ -1,1866 +1,1931 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 import cStringIO, email, os, errno, re, posixpath
10 10 import tempfile, zlib, shutil
11 11 # On python2.4 you have to import these by name or they fail to
12 12 # load. This was not a problem on Python 2.7.
13 13 import email.Generator
14 14 import email.Parser
15 15
16 16 from i18n import _
17 17 from node import hex, short
18 18 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 19
20 20 gitre = re.compile('diff --git a/(.*) b/(.*)')
21 21
22 22 class PatchError(Exception):
23 23 pass
24 24
25 25
26 26 # public functions
27 27
28 28 def split(stream):
29 29 '''return an iterator of individual patches from a stream'''
30 30 def isheader(line, inheader):
31 31 if inheader and line[0] in (' ', '\t'):
32 32 # continuation
33 33 return True
34 34 if line[0] in (' ', '-', '+'):
35 35 # diff line - don't check for header pattern in there
36 36 return False
37 37 l = line.split(': ', 1)
38 38 return len(l) == 2 and ' ' not in l[0]
39 39
40 40 def chunk(lines):
41 41 return cStringIO.StringIO(''.join(lines))
42 42
43 43 def hgsplit(stream, cur):
44 44 inheader = True
45 45
46 46 for line in stream:
47 47 if not line.strip():
48 48 inheader = False
49 49 if not inheader and line.startswith('# HG changeset patch'):
50 50 yield chunk(cur)
51 51 cur = []
52 52 inheader = True
53 53
54 54 cur.append(line)
55 55
56 56 if cur:
57 57 yield chunk(cur)
58 58
59 59 def mboxsplit(stream, cur):
60 60 for line in stream:
61 61 if line.startswith('From '):
62 62 for c in split(chunk(cur[1:])):
63 63 yield c
64 64 cur = []
65 65
66 66 cur.append(line)
67 67
68 68 if cur:
69 69 for c in split(chunk(cur[1:])):
70 70 yield c
71 71
72 72 def mimesplit(stream, cur):
73 73 def msgfp(m):
74 74 fp = cStringIO.StringIO()
75 75 g = email.Generator.Generator(fp, mangle_from_=False)
76 76 g.flatten(m)
77 77 fp.seek(0)
78 78 return fp
79 79
80 80 for line in stream:
81 81 cur.append(line)
82 82 c = chunk(cur)
83 83
84 84 m = email.Parser.Parser().parse(c)
85 85 if not m.is_multipart():
86 86 yield msgfp(m)
87 87 else:
88 88 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
89 89 for part in m.walk():
90 90 ct = part.get_content_type()
91 91 if ct not in ok_types:
92 92 continue
93 93 yield msgfp(part)
94 94
95 95 def headersplit(stream, cur):
96 96 inheader = False
97 97
98 98 for line in stream:
99 99 if not inheader and isheader(line, inheader):
100 100 yield chunk(cur)
101 101 cur = []
102 102 inheader = True
103 103 if inheader and not isheader(line, inheader):
104 104 inheader = False
105 105
106 106 cur.append(line)
107 107
108 108 if cur:
109 109 yield chunk(cur)
110 110
111 111 def remainder(cur):
112 112 yield chunk(cur)
113 113
114 114 class fiter(object):
115 115 def __init__(self, fp):
116 116 self.fp = fp
117 117
118 118 def __iter__(self):
119 119 return self
120 120
121 121 def next(self):
122 122 l = self.fp.readline()
123 123 if not l:
124 124 raise StopIteration
125 125 return l
126 126
127 127 inheader = False
128 128 cur = []
129 129
130 130 mimeheaders = ['content-type']
131 131
132 132 if not util.safehasattr(stream, 'next'):
133 133 # http responses, for example, have readline but not next
134 134 stream = fiter(stream)
135 135
136 136 for line in stream:
137 137 cur.append(line)
138 138 if line.startswith('# HG changeset patch'):
139 139 return hgsplit(stream, cur)
140 140 elif line.startswith('From '):
141 141 return mboxsplit(stream, cur)
142 142 elif isheader(line, inheader):
143 143 inheader = True
144 144 if line.split(':', 1)[0].lower() in mimeheaders:
145 145 # let email parser handle this
146 146 return mimesplit(stream, cur)
147 147 elif line.startswith('--- ') and inheader:
148 148 # No evil headers seen by diff start, split by hand
149 149 return headersplit(stream, cur)
150 150 # Not enough info, keep reading
151 151
152 152 # if we are here, we have a very plain patch
153 153 return remainder(cur)
154 154
155 155 def extract(ui, fileobj):
156 156 '''extract patch from data read from fileobj.
157 157
158 158 patch can be a normal patch or contained in an email message.
159 159
160 160 return tuple (filename, message, user, date, branch, node, p1, p2).
161 161 Any item in the returned tuple can be None. If filename is None,
162 162 fileobj did not contain a patch. Caller must unlink filename when done.'''
163 163
164 164 # attempt to detect the start of a patch
165 165 # (this heuristic is borrowed from quilt)
166 166 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
167 167 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
168 168 r'---[ \t].*?^\+\+\+[ \t]|'
169 169 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
170 170
171 171 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
172 172 tmpfp = os.fdopen(fd, 'w')
173 173 try:
174 174 msg = email.Parser.Parser().parse(fileobj)
175 175
176 176 subject = msg['Subject']
177 177 user = msg['From']
178 178 if not subject and not user:
179 179 # Not an email, restore parsed headers if any
180 180 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
181 181
182 182 # should try to parse msg['Date']
183 183 date = None
184 184 nodeid = None
185 185 branch = None
186 186 parents = []
187 187
188 188 if subject:
189 189 if subject.startswith('[PATCH'):
190 190 pend = subject.find(']')
191 191 if pend >= 0:
192 192 subject = subject[pend + 1:].lstrip()
193 193 subject = re.sub(r'\n[ \t]+', ' ', subject)
194 194 ui.debug('Subject: %s\n' % subject)
195 195 if user:
196 196 ui.debug('From: %s\n' % user)
197 197 diffs_seen = 0
198 198 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
199 199 message = ''
200 200 for part in msg.walk():
201 201 content_type = part.get_content_type()
202 202 ui.debug('Content-Type: %s\n' % content_type)
203 203 if content_type not in ok_types:
204 204 continue
205 205 payload = part.get_payload(decode=True)
206 206 m = diffre.search(payload)
207 207 if m:
208 208 hgpatch = False
209 209 hgpatchheader = False
210 210 ignoretext = False
211 211
212 212 ui.debug('found patch at byte %d\n' % m.start(0))
213 213 diffs_seen += 1
214 214 cfp = cStringIO.StringIO()
215 215 for line in payload[:m.start(0)].splitlines():
216 216 if line.startswith('# HG changeset patch') and not hgpatch:
217 217 ui.debug('patch generated by hg export\n')
218 218 hgpatch = True
219 219 hgpatchheader = True
220 220 # drop earlier commit message content
221 221 cfp.seek(0)
222 222 cfp.truncate()
223 223 subject = None
224 224 elif hgpatchheader:
225 225 if line.startswith('# User '):
226 226 user = line[7:]
227 227 ui.debug('From: %s\n' % user)
228 228 elif line.startswith("# Date "):
229 229 date = line[7:]
230 230 elif line.startswith("# Branch "):
231 231 branch = line[9:]
232 232 elif line.startswith("# Node ID "):
233 233 nodeid = line[10:]
234 234 elif line.startswith("# Parent "):
235 235 parents.append(line[9:].lstrip())
236 236 elif not line.startswith("# "):
237 237 hgpatchheader = False
238 238 elif line == '---':
239 239 ignoretext = True
240 240 if not hgpatchheader and not ignoretext:
241 241 cfp.write(line)
242 242 cfp.write('\n')
243 243 message = cfp.getvalue()
244 244 if tmpfp:
245 245 tmpfp.write(payload)
246 246 if not payload.endswith('\n'):
247 247 tmpfp.write('\n')
248 248 elif not diffs_seen and message and content_type == 'text/plain':
249 249 message += '\n' + payload
250 250 except: # re-raises
251 251 tmpfp.close()
252 252 os.unlink(tmpname)
253 253 raise
254 254
255 255 if subject and not message.startswith(subject):
256 256 message = '%s\n%s' % (subject, message)
257 257 tmpfp.close()
258 258 if not diffs_seen:
259 259 os.unlink(tmpname)
260 260 return None, message, user, date, branch, None, None, None
261 261 p1 = parents and parents.pop(0) or None
262 262 p2 = parents and parents.pop(0) or None
263 263 return tmpname, message, user, date, branch, nodeid, p1, p2
264 264
265 265 class patchmeta(object):
266 266 """Patched file metadata
267 267
268 268 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
269 269 or COPY. 'path' is patched file path. 'oldpath' is set to the
270 270 origin file when 'op' is either COPY or RENAME, None otherwise. If
271 271 file mode is changed, 'mode' is a tuple (islink, isexec) where
272 272 'islink' is True if the file is a symlink and 'isexec' is True if
273 273 the file is executable. Otherwise, 'mode' is None.
274 274 """
275 275 def __init__(self, path):
276 276 self.path = path
277 277 self.oldpath = None
278 278 self.mode = None
279 279 self.op = 'MODIFY'
280 280 self.binary = False
281 281
282 282 def setmode(self, mode):
283 283 islink = mode & 020000
284 284 isexec = mode & 0100
285 285 self.mode = (islink, isexec)
286 286
287 287 def copy(self):
288 288 other = patchmeta(self.path)
289 289 other.oldpath = self.oldpath
290 290 other.mode = self.mode
291 291 other.op = self.op
292 292 other.binary = self.binary
293 293 return other
294 294
295 295 def _ispatchinga(self, afile):
296 296 if afile == '/dev/null':
297 297 return self.op == 'ADD'
298 298 return afile == 'a/' + (self.oldpath or self.path)
299 299
300 300 def _ispatchingb(self, bfile):
301 301 if bfile == '/dev/null':
302 302 return self.op == 'DELETE'
303 303 return bfile == 'b/' + self.path
304 304
305 305 def ispatching(self, afile, bfile):
306 306 return self._ispatchinga(afile) and self._ispatchingb(bfile)
307 307
308 308 def __repr__(self):
309 309 return "<patchmeta %s %r>" % (self.op, self.path)
310 310
311 311 def readgitpatch(lr):
312 312 """extract git-style metadata about patches from <patchname>"""
313 313
314 314 # Filter patch for git information
315 315 gp = None
316 316 gitpatches = []
317 317 for line in lr:
318 318 line = line.rstrip(' \r\n')
319 319 if line.startswith('diff --git a/'):
320 320 m = gitre.match(line)
321 321 if m:
322 322 if gp:
323 323 gitpatches.append(gp)
324 324 dst = m.group(2)
325 325 gp = patchmeta(dst)
326 326 elif gp:
327 327 if line.startswith('--- '):
328 328 gitpatches.append(gp)
329 329 gp = None
330 330 continue
331 331 if line.startswith('rename from '):
332 332 gp.op = 'RENAME'
333 333 gp.oldpath = line[12:]
334 334 elif line.startswith('rename to '):
335 335 gp.path = line[10:]
336 336 elif line.startswith('copy from '):
337 337 gp.op = 'COPY'
338 338 gp.oldpath = line[10:]
339 339 elif line.startswith('copy to '):
340 340 gp.path = line[8:]
341 341 elif line.startswith('deleted file'):
342 342 gp.op = 'DELETE'
343 343 elif line.startswith('new file mode '):
344 344 gp.op = 'ADD'
345 345 gp.setmode(int(line[-6:], 8))
346 346 elif line.startswith('new mode '):
347 347 gp.setmode(int(line[-6:], 8))
348 348 elif line.startswith('GIT binary patch'):
349 349 gp.binary = True
350 350 if gp:
351 351 gitpatches.append(gp)
352 352
353 353 return gitpatches
354 354
355 355 class linereader(object):
356 356 # simple class to allow pushing lines back into the input stream
357 357 def __init__(self, fp):
358 358 self.fp = fp
359 359 self.buf = []
360 360
361 361 def push(self, line):
362 362 if line is not None:
363 363 self.buf.append(line)
364 364
365 365 def readline(self):
366 366 if self.buf:
367 367 l = self.buf[0]
368 368 del self.buf[0]
369 369 return l
370 370 return self.fp.readline()
371 371
372 372 def __iter__(self):
373 373 while True:
374 374 l = self.readline()
375 375 if not l:
376 376 break
377 377 yield l
378 378
379 379 class abstractbackend(object):
380 380 def __init__(self, ui):
381 381 self.ui = ui
382 382
383 383 def getfile(self, fname):
384 384 """Return target file data and flags as a (data, (islink,
385 385 isexec)) tuple.
386 386 """
387 387 raise NotImplementedError
388 388
389 389 def setfile(self, fname, data, mode, copysource):
390 390 """Write data to target file fname and set its mode. mode is a
391 391 (islink, isexec) tuple. If data is None, the file content should
392 392 be left unchanged. If the file is modified after being copied,
393 393 copysource is set to the original file name.
394 394 """
395 395 raise NotImplementedError
396 396
397 397 def unlink(self, fname):
398 398 """Unlink target file."""
399 399 raise NotImplementedError
400 400
401 401 def writerej(self, fname, failed, total, lines):
402 402 """Write rejected lines for fname. total is the number of hunks
403 403 which failed to apply and total the total number of hunks for this
404 404 files.
405 405 """
406 406 pass
407 407
408 408 def exists(self, fname):
409 409 raise NotImplementedError
410 410
411 411 class fsbackend(abstractbackend):
412 412 def __init__(self, ui, basedir):
413 413 super(fsbackend, self).__init__(ui)
414 414 self.opener = scmutil.opener(basedir)
415 415
416 416 def _join(self, f):
417 417 return os.path.join(self.opener.base, f)
418 418
419 419 def getfile(self, fname):
420 420 path = self._join(fname)
421 421 if os.path.islink(path):
422 422 return (os.readlink(path), (True, False))
423 423 isexec = False
424 424 try:
425 425 isexec = os.lstat(path).st_mode & 0100 != 0
426 426 except OSError, e:
427 427 if e.errno != errno.ENOENT:
428 428 raise
429 429 return (self.opener.read(fname), (False, isexec))
430 430
431 431 def setfile(self, fname, data, mode, copysource):
432 432 islink, isexec = mode
433 433 if data is None:
434 434 util.setflags(self._join(fname), islink, isexec)
435 435 return
436 436 if islink:
437 437 self.opener.symlink(data, fname)
438 438 else:
439 439 self.opener.write(fname, data)
440 440 if isexec:
441 441 util.setflags(self._join(fname), False, True)
442 442
443 443 def unlink(self, fname):
444 444 util.unlinkpath(self._join(fname), ignoremissing=True)
445 445
446 446 def writerej(self, fname, failed, total, lines):
447 447 fname = fname + ".rej"
448 448 self.ui.warn(
449 449 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
450 450 (failed, total, fname))
451 451 fp = self.opener(fname, 'w')
452 452 fp.writelines(lines)
453 453 fp.close()
454 454
455 455 def exists(self, fname):
456 456 return os.path.lexists(self._join(fname))
457 457
458 458 class workingbackend(fsbackend):
459 459 def __init__(self, ui, repo, similarity):
460 460 super(workingbackend, self).__init__(ui, repo.root)
461 461 self.repo = repo
462 462 self.similarity = similarity
463 463 self.removed = set()
464 464 self.changed = set()
465 465 self.copied = []
466 466
467 467 def _checkknown(self, fname):
468 468 if self.repo.dirstate[fname] == '?' and self.exists(fname):
469 469 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
470 470
471 471 def setfile(self, fname, data, mode, copysource):
472 472 self._checkknown(fname)
473 473 super(workingbackend, self).setfile(fname, data, mode, copysource)
474 474 if copysource is not None:
475 475 self.copied.append((copysource, fname))
476 476 self.changed.add(fname)
477 477
478 478 def unlink(self, fname):
479 479 self._checkknown(fname)
480 480 super(workingbackend, self).unlink(fname)
481 481 self.removed.add(fname)
482 482 self.changed.add(fname)
483 483
484 484 def close(self):
485 485 wctx = self.repo[None]
486 486 changed = set(self.changed)
487 487 for src, dst in self.copied:
488 488 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
489 489 if self.removed:
490 490 wctx.forget(sorted(self.removed))
491 491 for f in self.removed:
492 492 if f not in self.repo.dirstate:
493 493 # File was deleted and no longer belongs to the
494 494 # dirstate, it was probably marked added then
495 495 # deleted, and should not be considered by
496 496 # marktouched().
497 497 changed.discard(f)
498 498 if changed:
499 499 scmutil.marktouched(self.repo, changed, self.similarity)
500 500 return sorted(self.changed)
501 501
502 502 class filestore(object):
503 503 def __init__(self, maxsize=None):
504 504 self.opener = None
505 505 self.files = {}
506 506 self.created = 0
507 507 self.maxsize = maxsize
508 508 if self.maxsize is None:
509 509 self.maxsize = 4*(2**20)
510 510 self.size = 0
511 511 self.data = {}
512 512
513 513 def setfile(self, fname, data, mode, copied=None):
514 514 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
515 515 self.data[fname] = (data, mode, copied)
516 516 self.size += len(data)
517 517 else:
518 518 if self.opener is None:
519 519 root = tempfile.mkdtemp(prefix='hg-patch-')
520 520 self.opener = scmutil.opener(root)
521 521 # Avoid filename issues with these simple names
522 522 fn = str(self.created)
523 523 self.opener.write(fn, data)
524 524 self.created += 1
525 525 self.files[fname] = (fn, mode, copied)
526 526
527 527 def getfile(self, fname):
528 528 if fname in self.data:
529 529 return self.data[fname]
530 530 if not self.opener or fname not in self.files:
531 531 raise IOError
532 532 fn, mode, copied = self.files[fname]
533 533 return self.opener.read(fn), mode, copied
534 534
535 535 def close(self):
536 536 if self.opener:
537 537 shutil.rmtree(self.opener.base)
538 538
539 539 class repobackend(abstractbackend):
540 540 def __init__(self, ui, repo, ctx, store):
541 541 super(repobackend, self).__init__(ui)
542 542 self.repo = repo
543 543 self.ctx = ctx
544 544 self.store = store
545 545 self.changed = set()
546 546 self.removed = set()
547 547 self.copied = {}
548 548
549 549 def _checkknown(self, fname):
550 550 if fname not in self.ctx:
551 551 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
552 552
553 553 def getfile(self, fname):
554 554 try:
555 555 fctx = self.ctx[fname]
556 556 except error.LookupError:
557 557 raise IOError
558 558 flags = fctx.flags()
559 559 return fctx.data(), ('l' in flags, 'x' in flags)
560 560
561 561 def setfile(self, fname, data, mode, copysource):
562 562 if copysource:
563 563 self._checkknown(copysource)
564 564 if data is None:
565 565 data = self.ctx[fname].data()
566 566 self.store.setfile(fname, data, mode, copysource)
567 567 self.changed.add(fname)
568 568 if copysource:
569 569 self.copied[fname] = copysource
570 570
571 571 def unlink(self, fname):
572 572 self._checkknown(fname)
573 573 self.removed.add(fname)
574 574
575 575 def exists(self, fname):
576 576 return fname in self.ctx
577 577
578 578 def close(self):
579 579 return self.changed | self.removed
580 580
581 581 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
582 582 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
583 583 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
584 584 eolmodes = ['strict', 'crlf', 'lf', 'auto']
585 585
586 586 class patchfile(object):
587 587 def __init__(self, ui, gp, backend, store, eolmode='strict'):
588 588 self.fname = gp.path
589 589 self.eolmode = eolmode
590 590 self.eol = None
591 591 self.backend = backend
592 592 self.ui = ui
593 593 self.lines = []
594 594 self.exists = False
595 595 self.missing = True
596 596 self.mode = gp.mode
597 597 self.copysource = gp.oldpath
598 598 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
599 599 self.remove = gp.op == 'DELETE'
600 600 try:
601 601 if self.copysource is None:
602 602 data, mode = backend.getfile(self.fname)
603 603 self.exists = True
604 604 else:
605 605 data, mode = store.getfile(self.copysource)[:2]
606 606 self.exists = backend.exists(self.fname)
607 607 self.missing = False
608 608 if data:
609 609 self.lines = mdiff.splitnewlines(data)
610 610 if self.mode is None:
611 611 self.mode = mode
612 612 if self.lines:
613 613 # Normalize line endings
614 614 if self.lines[0].endswith('\r\n'):
615 615 self.eol = '\r\n'
616 616 elif self.lines[0].endswith('\n'):
617 617 self.eol = '\n'
618 618 if eolmode != 'strict':
619 619 nlines = []
620 620 for l in self.lines:
621 621 if l.endswith('\r\n'):
622 622 l = l[:-2] + '\n'
623 623 nlines.append(l)
624 624 self.lines = nlines
625 625 except IOError:
626 626 if self.create:
627 627 self.missing = False
628 628 if self.mode is None:
629 629 self.mode = (False, False)
630 630 if self.missing:
631 631 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
632 632
633 633 self.hash = {}
634 634 self.dirty = 0
635 635 self.offset = 0
636 636 self.skew = 0
637 637 self.rej = []
638 638 self.fileprinted = False
639 639 self.printfile(False)
640 640 self.hunks = 0
641 641
642 642 def writelines(self, fname, lines, mode):
643 643 if self.eolmode == 'auto':
644 644 eol = self.eol
645 645 elif self.eolmode == 'crlf':
646 646 eol = '\r\n'
647 647 else:
648 648 eol = '\n'
649 649
650 650 if self.eolmode != 'strict' and eol and eol != '\n':
651 651 rawlines = []
652 652 for l in lines:
653 653 if l and l[-1] == '\n':
654 654 l = l[:-1] + eol
655 655 rawlines.append(l)
656 656 lines = rawlines
657 657
658 658 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
659 659
660 660 def printfile(self, warn):
661 661 if self.fileprinted:
662 662 return
663 663 if warn or self.ui.verbose:
664 664 self.fileprinted = True
665 665 s = _("patching file %s\n") % self.fname
666 666 if warn:
667 667 self.ui.warn(s)
668 668 else:
669 669 self.ui.note(s)
670 670
671 671
672 672 def findlines(self, l, linenum):
673 673 # looks through the hash and finds candidate lines. The
674 674 # result is a list of line numbers sorted based on distance
675 675 # from linenum
676 676
677 677 cand = self.hash.get(l, [])
678 678 if len(cand) > 1:
679 679 # resort our list of potentials forward then back.
680 680 cand.sort(key=lambda x: abs(x - linenum))
681 681 return cand
682 682
683 683 def write_rej(self):
684 684 # our rejects are a little different from patch(1). This always
685 685 # creates rejects in the same form as the original patch. A file
686 686 # header is inserted so that you can run the reject through patch again
687 687 # without having to type the filename.
688 688 if not self.rej:
689 689 return
690 690 base = os.path.basename(self.fname)
691 691 lines = ["--- %s\n+++ %s\n" % (base, base)]
692 692 for x in self.rej:
693 693 for l in x.hunk:
694 694 lines.append(l)
695 695 if l[-1] != '\n':
696 696 lines.append("\n\ No newline at end of file\n")
697 697 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
698 698
699 699 def apply(self, h):
700 700 if not h.complete():
701 701 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
702 702 (h.number, h.desc, len(h.a), h.lena, len(h.b),
703 703 h.lenb))
704 704
705 705 self.hunks += 1
706 706
707 707 if self.missing:
708 708 self.rej.append(h)
709 709 return -1
710 710
711 711 if self.exists and self.create:
712 712 if self.copysource:
713 713 self.ui.warn(_("cannot create %s: destination already "
714 714 "exists\n" % self.fname))
715 715 else:
716 716 self.ui.warn(_("file %s already exists\n") % self.fname)
717 717 self.rej.append(h)
718 718 return -1
719 719
720 720 if isinstance(h, binhunk):
721 721 if self.remove:
722 722 self.backend.unlink(self.fname)
723 723 else:
724 self.lines[:] = h.new()
725 self.offset += len(h.new())
724 l = h.new(self.lines)
725 self.lines[:] = l
726 self.offset += len(l)
726 727 self.dirty = True
727 728 return 0
728 729
729 730 horig = h
730 731 if (self.eolmode in ('crlf', 'lf')
731 732 or self.eolmode == 'auto' and self.eol):
732 733 # If new eols are going to be normalized, then normalize
733 734 # hunk data before patching. Otherwise, preserve input
734 735 # line-endings.
735 736 h = h.getnormalized()
736 737
737 738 # fast case first, no offsets, no fuzz
738 739 old, oldstart, new, newstart = h.fuzzit(0, False)
739 740 oldstart += self.offset
740 741 orig_start = oldstart
741 742 # if there's skew we want to emit the "(offset %d lines)" even
742 743 # when the hunk cleanly applies at start + skew, so skip the
743 744 # fast case code
744 745 if (self.skew == 0 and
745 746 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
746 747 if self.remove:
747 748 self.backend.unlink(self.fname)
748 749 else:
749 750 self.lines[oldstart:oldstart + len(old)] = new
750 751 self.offset += len(new) - len(old)
751 752 self.dirty = True
752 753 return 0
753 754
754 755 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
755 756 self.hash = {}
756 757 for x, s in enumerate(self.lines):
757 758 self.hash.setdefault(s, []).append(x)
758 759
759 760 for fuzzlen in xrange(3):
760 761 for toponly in [True, False]:
761 762 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
762 763 oldstart = oldstart + self.offset + self.skew
763 764 oldstart = min(oldstart, len(self.lines))
764 765 if old:
765 766 cand = self.findlines(old[0][1:], oldstart)
766 767 else:
767 768 # Only adding lines with no or fuzzed context, just
768 769 # take the skew in account
769 770 cand = [oldstart]
770 771
771 772 for l in cand:
772 773 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
773 774 self.lines[l : l + len(old)] = new
774 775 self.offset += len(new) - len(old)
775 776 self.skew = l - orig_start
776 777 self.dirty = True
777 778 offset = l - orig_start - fuzzlen
778 779 if fuzzlen:
779 780 msg = _("Hunk #%d succeeded at %d "
780 781 "with fuzz %d "
781 782 "(offset %d lines).\n")
782 783 self.printfile(True)
783 784 self.ui.warn(msg %
784 785 (h.number, l + 1, fuzzlen, offset))
785 786 else:
786 787 msg = _("Hunk #%d succeeded at %d "
787 788 "(offset %d lines).\n")
788 789 self.ui.note(msg % (h.number, l + 1, offset))
789 790 return fuzzlen
790 791 self.printfile(True)
791 792 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
792 793 self.rej.append(horig)
793 794 return -1
794 795
795 796 def close(self):
796 797 if self.dirty:
797 798 self.writelines(self.fname, self.lines, self.mode)
798 799 self.write_rej()
799 800 return len(self.rej)
800 801
801 802 class hunk(object):
802 803 def __init__(self, desc, num, lr, context):
803 804 self.number = num
804 805 self.desc = desc
805 806 self.hunk = [desc]
806 807 self.a = []
807 808 self.b = []
808 809 self.starta = self.lena = None
809 810 self.startb = self.lenb = None
810 811 if lr is not None:
811 812 if context:
812 813 self.read_context_hunk(lr)
813 814 else:
814 815 self.read_unified_hunk(lr)
815 816
816 817 def getnormalized(self):
817 818 """Return a copy with line endings normalized to LF."""
818 819
819 820 def normalize(lines):
820 821 nlines = []
821 822 for line in lines:
822 823 if line.endswith('\r\n'):
823 824 line = line[:-2] + '\n'
824 825 nlines.append(line)
825 826 return nlines
826 827
827 828 # Dummy object, it is rebuilt manually
828 829 nh = hunk(self.desc, self.number, None, None)
829 830 nh.number = self.number
830 831 nh.desc = self.desc
831 832 nh.hunk = self.hunk
832 833 nh.a = normalize(self.a)
833 834 nh.b = normalize(self.b)
834 835 nh.starta = self.starta
835 836 nh.startb = self.startb
836 837 nh.lena = self.lena
837 838 nh.lenb = self.lenb
838 839 return nh
839 840
840 841 def read_unified_hunk(self, lr):
841 842 m = unidesc.match(self.desc)
842 843 if not m:
843 844 raise PatchError(_("bad hunk #%d") % self.number)
844 845 self.starta, self.lena, self.startb, self.lenb = m.groups()
845 846 if self.lena is None:
846 847 self.lena = 1
847 848 else:
848 849 self.lena = int(self.lena)
849 850 if self.lenb is None:
850 851 self.lenb = 1
851 852 else:
852 853 self.lenb = int(self.lenb)
853 854 self.starta = int(self.starta)
854 855 self.startb = int(self.startb)
855 856 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
856 857 self.b)
857 858 # if we hit eof before finishing out the hunk, the last line will
858 859 # be zero length. Lets try to fix it up.
859 860 while len(self.hunk[-1]) == 0:
860 861 del self.hunk[-1]
861 862 del self.a[-1]
862 863 del self.b[-1]
863 864 self.lena -= 1
864 865 self.lenb -= 1
865 866 self._fixnewline(lr)
866 867
867 868 def read_context_hunk(self, lr):
868 869 self.desc = lr.readline()
869 870 m = contextdesc.match(self.desc)
870 871 if not m:
871 872 raise PatchError(_("bad hunk #%d") % self.number)
872 873 self.starta, aend = m.groups()
873 874 self.starta = int(self.starta)
874 875 if aend is None:
875 876 aend = self.starta
876 877 self.lena = int(aend) - self.starta
877 878 if self.starta:
878 879 self.lena += 1
879 880 for x in xrange(self.lena):
880 881 l = lr.readline()
881 882 if l.startswith('---'):
882 883 # lines addition, old block is empty
883 884 lr.push(l)
884 885 break
885 886 s = l[2:]
886 887 if l.startswith('- ') or l.startswith('! '):
887 888 u = '-' + s
888 889 elif l.startswith(' '):
889 890 u = ' ' + s
890 891 else:
891 892 raise PatchError(_("bad hunk #%d old text line %d") %
892 893 (self.number, x))
893 894 self.a.append(u)
894 895 self.hunk.append(u)
895 896
896 897 l = lr.readline()
897 898 if l.startswith('\ '):
898 899 s = self.a[-1][:-1]
899 900 self.a[-1] = s
900 901 self.hunk[-1] = s
901 902 l = lr.readline()
902 903 m = contextdesc.match(l)
903 904 if not m:
904 905 raise PatchError(_("bad hunk #%d") % self.number)
905 906 self.startb, bend = m.groups()
906 907 self.startb = int(self.startb)
907 908 if bend is None:
908 909 bend = self.startb
909 910 self.lenb = int(bend) - self.startb
910 911 if self.startb:
911 912 self.lenb += 1
912 913 hunki = 1
913 914 for x in xrange(self.lenb):
914 915 l = lr.readline()
915 916 if l.startswith('\ '):
916 917 # XXX: the only way to hit this is with an invalid line range.
917 918 # The no-eol marker is not counted in the line range, but I
918 919 # guess there are diff(1) out there which behave differently.
919 920 s = self.b[-1][:-1]
920 921 self.b[-1] = s
921 922 self.hunk[hunki - 1] = s
922 923 continue
923 924 if not l:
924 925 # line deletions, new block is empty and we hit EOF
925 926 lr.push(l)
926 927 break
927 928 s = l[2:]
928 929 if l.startswith('+ ') or l.startswith('! '):
929 930 u = '+' + s
930 931 elif l.startswith(' '):
931 932 u = ' ' + s
932 933 elif len(self.b) == 0:
933 934 # line deletions, new block is empty
934 935 lr.push(l)
935 936 break
936 937 else:
937 938 raise PatchError(_("bad hunk #%d old text line %d") %
938 939 (self.number, x))
939 940 self.b.append(s)
940 941 while True:
941 942 if hunki >= len(self.hunk):
942 943 h = ""
943 944 else:
944 945 h = self.hunk[hunki]
945 946 hunki += 1
946 947 if h == u:
947 948 break
948 949 elif h.startswith('-'):
949 950 continue
950 951 else:
951 952 self.hunk.insert(hunki - 1, u)
952 953 break
953 954
954 955 if not self.a:
955 956 # this happens when lines were only added to the hunk
956 957 for x in self.hunk:
957 958 if x.startswith('-') or x.startswith(' '):
958 959 self.a.append(x)
959 960 if not self.b:
960 961 # this happens when lines were only deleted from the hunk
961 962 for x in self.hunk:
962 963 if x.startswith('+') or x.startswith(' '):
963 964 self.b.append(x[1:])
964 965 # @@ -start,len +start,len @@
965 966 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
966 967 self.startb, self.lenb)
967 968 self.hunk[0] = self.desc
968 969 self._fixnewline(lr)
969 970
970 971 def _fixnewline(self, lr):
971 972 l = lr.readline()
972 973 if l.startswith('\ '):
973 974 diffhelpers.fix_newline(self.hunk, self.a, self.b)
974 975 else:
975 976 lr.push(l)
976 977
977 978 def complete(self):
978 979 return len(self.a) == self.lena and len(self.b) == self.lenb
979 980
980 981 def _fuzzit(self, old, new, fuzz, toponly):
981 982 # this removes context lines from the top and bottom of list 'l'. It
982 983 # checks the hunk to make sure only context lines are removed, and then
983 984 # returns a new shortened list of lines.
984 985 fuzz = min(fuzz, len(old))
985 986 if fuzz:
986 987 top = 0
987 988 bot = 0
988 989 hlen = len(self.hunk)
989 990 for x in xrange(hlen - 1):
990 991 # the hunk starts with the @@ line, so use x+1
991 992 if self.hunk[x + 1][0] == ' ':
992 993 top += 1
993 994 else:
994 995 break
995 996 if not toponly:
996 997 for x in xrange(hlen - 1):
997 998 if self.hunk[hlen - bot - 1][0] == ' ':
998 999 bot += 1
999 1000 else:
1000 1001 break
1001 1002
1002 1003 bot = min(fuzz, bot)
1003 1004 top = min(fuzz, top)
1004 1005 return old[top:len(old) - bot], new[top:len(new) - bot], top
1005 1006 return old, new, 0
1006 1007
1007 1008 def fuzzit(self, fuzz, toponly):
1008 1009 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1009 1010 oldstart = self.starta + top
1010 1011 newstart = self.startb + top
1011 1012 # zero length hunk ranges already have their start decremented
1012 1013 if self.lena and oldstart > 0:
1013 1014 oldstart -= 1
1014 1015 if self.lenb and newstart > 0:
1015 1016 newstart -= 1
1016 1017 return old, oldstart, new, newstart
1017 1018
1018 1019 class binhunk(object):
1019 'A binary patch file. Only understands literals so far.'
1020 'A binary patch file.'
1020 1021 def __init__(self, lr, fname):
1021 1022 self.text = None
1023 self.delta = False
1022 1024 self.hunk = ['GIT binary patch\n']
1023 1025 self._fname = fname
1024 1026 self._read(lr)
1025 1027
1026 1028 def complete(self):
1027 1029 return self.text is not None
1028 1030
1029 def new(self):
1031 def new(self, lines):
1032 if self.delta:
1033 return [applybindelta(self.text, ''.join(lines))]
1030 1034 return [self.text]
1031 1035
1032 1036 def _read(self, lr):
1033 1037 def getline(lr, hunk):
1034 1038 l = lr.readline()
1035 1039 hunk.append(l)
1036 1040 return l.rstrip('\r\n')
1037 1041
1042 size = 0
1038 1043 while True:
1039 1044 line = getline(lr, self.hunk)
1040 1045 if not line:
1041 1046 raise PatchError(_('could not extract "%s" binary data')
1042 1047 % self._fname)
1043 1048 if line.startswith('literal '):
1049 size = int(line[8:].rstrip())
1044 1050 break
1045 size = int(line[8:].rstrip())
1051 if line.startswith('delta '):
1052 size = int(line[6:].rstrip())
1053 self.delta = True
1054 break
1046 1055 dec = []
1047 1056 line = getline(lr, self.hunk)
1048 1057 while len(line) > 1:
1049 1058 l = line[0]
1050 1059 if l <= 'Z' and l >= 'A':
1051 1060 l = ord(l) - ord('A') + 1
1052 1061 else:
1053 1062 l = ord(l) - ord('a') + 27
1054 1063 try:
1055 1064 dec.append(base85.b85decode(line[1:])[:l])
1056 1065 except ValueError, e:
1057 1066 raise PatchError(_('could not decode "%s" binary patch: %s')
1058 1067 % (self._fname, str(e)))
1059 1068 line = getline(lr, self.hunk)
1060 1069 text = zlib.decompress(''.join(dec))
1061 1070 if len(text) != size:
1062 1071 raise PatchError(_('"%s" length is %d bytes, should be %d')
1063 1072 % (self._fname, len(text), size))
1064 1073 self.text = text
1065 1074
1066 1075 def parsefilename(str):
1067 1076 # --- filename \t|space stuff
1068 1077 s = str[4:].rstrip('\r\n')
1069 1078 i = s.find('\t')
1070 1079 if i < 0:
1071 1080 i = s.find(' ')
1072 1081 if i < 0:
1073 1082 return s
1074 1083 return s[:i]
1075 1084
1076 1085 def pathstrip(path, strip):
1077 1086 pathlen = len(path)
1078 1087 i = 0
1079 1088 if strip == 0:
1080 1089 return '', path.rstrip()
1081 1090 count = strip
1082 1091 while count > 0:
1083 1092 i = path.find('/', i)
1084 1093 if i == -1:
1085 1094 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1086 1095 (count, strip, path))
1087 1096 i += 1
1088 1097 # consume '//' in the path
1089 1098 while i < pathlen - 1 and path[i] == '/':
1090 1099 i += 1
1091 1100 count -= 1
1092 1101 return path[:i].lstrip(), path[i:].rstrip()
1093 1102
1094 1103 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1095 1104 nulla = afile_orig == "/dev/null"
1096 1105 nullb = bfile_orig == "/dev/null"
1097 1106 create = nulla and hunk.starta == 0 and hunk.lena == 0
1098 1107 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1099 1108 abase, afile = pathstrip(afile_orig, strip)
1100 1109 gooda = not nulla and backend.exists(afile)
1101 1110 bbase, bfile = pathstrip(bfile_orig, strip)
1102 1111 if afile == bfile:
1103 1112 goodb = gooda
1104 1113 else:
1105 1114 goodb = not nullb and backend.exists(bfile)
1106 1115 missing = not goodb and not gooda and not create
1107 1116
1108 1117 # some diff programs apparently produce patches where the afile is
1109 1118 # not /dev/null, but afile starts with bfile
1110 1119 abasedir = afile[:afile.rfind('/') + 1]
1111 1120 bbasedir = bfile[:bfile.rfind('/') + 1]
1112 1121 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1113 1122 and hunk.starta == 0 and hunk.lena == 0):
1114 1123 create = True
1115 1124 missing = False
1116 1125
1117 1126 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1118 1127 # diff is between a file and its backup. In this case, the original
1119 1128 # file should be patched (see original mpatch code).
1120 1129 isbackup = (abase == bbase and bfile.startswith(afile))
1121 1130 fname = None
1122 1131 if not missing:
1123 1132 if gooda and goodb:
1124 1133 fname = isbackup and afile or bfile
1125 1134 elif gooda:
1126 1135 fname = afile
1127 1136
1128 1137 if not fname:
1129 1138 if not nullb:
1130 1139 fname = isbackup and afile or bfile
1131 1140 elif not nulla:
1132 1141 fname = afile
1133 1142 else:
1134 1143 raise PatchError(_("undefined source and destination files"))
1135 1144
1136 1145 gp = patchmeta(fname)
1137 1146 if create:
1138 1147 gp.op = 'ADD'
1139 1148 elif remove:
1140 1149 gp.op = 'DELETE'
1141 1150 return gp
1142 1151
1143 1152 def scangitpatch(lr, firstline):
1144 1153 """
1145 1154 Git patches can emit:
1146 1155 - rename a to b
1147 1156 - change b
1148 1157 - copy a to c
1149 1158 - change c
1150 1159
1151 1160 We cannot apply this sequence as-is, the renamed 'a' could not be
1152 1161 found for it would have been renamed already. And we cannot copy
1153 1162 from 'b' instead because 'b' would have been changed already. So
1154 1163 we scan the git patch for copy and rename commands so we can
1155 1164 perform the copies ahead of time.
1156 1165 """
1157 1166 pos = 0
1158 1167 try:
1159 1168 pos = lr.fp.tell()
1160 1169 fp = lr.fp
1161 1170 except IOError:
1162 1171 fp = cStringIO.StringIO(lr.fp.read())
1163 1172 gitlr = linereader(fp)
1164 1173 gitlr.push(firstline)
1165 1174 gitpatches = readgitpatch(gitlr)
1166 1175 fp.seek(pos)
1167 1176 return gitpatches
1168 1177
1169 1178 def iterhunks(fp):
1170 1179 """Read a patch and yield the following events:
1171 1180 - ("file", afile, bfile, firsthunk): select a new target file.
1172 1181 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1173 1182 "file" event.
1174 1183 - ("git", gitchanges): current diff is in git format, gitchanges
1175 1184 maps filenames to gitpatch records. Unique event.
1176 1185 """
1177 1186 afile = ""
1178 1187 bfile = ""
1179 1188 state = None
1180 1189 hunknum = 0
1181 1190 emitfile = newfile = False
1182 1191 gitpatches = None
1183 1192
1184 1193 # our states
1185 1194 BFILE = 1
1186 1195 context = None
1187 1196 lr = linereader(fp)
1188 1197
1189 1198 while True:
1190 1199 x = lr.readline()
1191 1200 if not x:
1192 1201 break
1193 1202 if state == BFILE and (
1194 1203 (not context and x[0] == '@')
1195 1204 or (context is not False and x.startswith('***************'))
1196 1205 or x.startswith('GIT binary patch')):
1197 1206 gp = None
1198 1207 if (gitpatches and
1199 1208 gitpatches[-1].ispatching(afile, bfile)):
1200 1209 gp = gitpatches.pop()
1201 1210 if x.startswith('GIT binary patch'):
1202 1211 h = binhunk(lr, gp.path)
1203 1212 else:
1204 1213 if context is None and x.startswith('***************'):
1205 1214 context = True
1206 1215 h = hunk(x, hunknum + 1, lr, context)
1207 1216 hunknum += 1
1208 1217 if emitfile:
1209 1218 emitfile = False
1210 1219 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1211 1220 yield 'hunk', h
1212 1221 elif x.startswith('diff --git a/'):
1213 1222 m = gitre.match(x.rstrip(' \r\n'))
1214 1223 if not m:
1215 1224 continue
1216 1225 if gitpatches is None:
1217 1226 # scan whole input for git metadata
1218 1227 gitpatches = scangitpatch(lr, x)
1219 1228 yield 'git', [g.copy() for g in gitpatches
1220 1229 if g.op in ('COPY', 'RENAME')]
1221 1230 gitpatches.reverse()
1222 1231 afile = 'a/' + m.group(1)
1223 1232 bfile = 'b/' + m.group(2)
1224 1233 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1225 1234 gp = gitpatches.pop()
1226 1235 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1227 1236 if not gitpatches:
1228 1237 raise PatchError(_('failed to synchronize metadata for "%s"')
1229 1238 % afile[2:])
1230 1239 gp = gitpatches[-1]
1231 1240 newfile = True
1232 1241 elif x.startswith('---'):
1233 1242 # check for a unified diff
1234 1243 l2 = lr.readline()
1235 1244 if not l2.startswith('+++'):
1236 1245 lr.push(l2)
1237 1246 continue
1238 1247 newfile = True
1239 1248 context = False
1240 1249 afile = parsefilename(x)
1241 1250 bfile = parsefilename(l2)
1242 1251 elif x.startswith('***'):
1243 1252 # check for a context diff
1244 1253 l2 = lr.readline()
1245 1254 if not l2.startswith('---'):
1246 1255 lr.push(l2)
1247 1256 continue
1248 1257 l3 = lr.readline()
1249 1258 lr.push(l3)
1250 1259 if not l3.startswith("***************"):
1251 1260 lr.push(l2)
1252 1261 continue
1253 1262 newfile = True
1254 1263 context = True
1255 1264 afile = parsefilename(x)
1256 1265 bfile = parsefilename(l2)
1257 1266
1258 1267 if newfile:
1259 1268 newfile = False
1260 1269 emitfile = True
1261 1270 state = BFILE
1262 1271 hunknum = 0
1263 1272
1264 1273 while gitpatches:
1265 1274 gp = gitpatches.pop()
1266 1275 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1267 1276
1277 def applybindelta(binchunk, data):
1278 """Apply a binary delta hunk
1279 The algorithm used is the algorithm from git's patch-delta.c
1280 """
1281 def deltahead(binchunk):
1282 i = 0
1283 for c in binchunk:
1284 i += 1
1285 if not (ord(c) & 0x80):
1286 return i
1287 return i
1288 out = ""
1289 s = deltahead(binchunk)
1290 binchunk = binchunk[s:]
1291 s = deltahead(binchunk)
1292 binchunk = binchunk[s:]
1293 i = 0
1294 while i < len(binchunk):
1295 cmd = ord(binchunk[i])
1296 i += 1
1297 if (cmd & 0x80):
1298 offset = 0
1299 size = 0
1300 if (cmd & 0x01):
1301 offset = ord(binchunk[i])
1302 i += 1
1303 if (cmd & 0x02):
1304 offset |= ord(binchunk[i]) << 8
1305 i += 1
1306 if (cmd & 0x04):
1307 offset |= ord(binchunk[i]) << 16
1308 i += 1
1309 if (cmd & 0x08):
1310 offset |= ord(binchunk[i]) << 24
1311 i += 1
1312 if (cmd & 0x10):
1313 size = ord(binchunk[i])
1314 i += 1
1315 if (cmd & 0x20):
1316 size |= ord(binchunk[i]) << 8
1317 i += 1
1318 if (cmd & 0x40):
1319 size |= ord(binchunk[i]) << 16
1320 i += 1
1321 if size == 0:
1322 size = 0x10000
1323 offset_end = offset + size
1324 out += data[offset:offset_end]
1325 elif cmd != 0:
1326 offset_end = i + cmd
1327 out += binchunk[i:offset_end]
1328 i += cmd
1329 else:
1330 raise PatchError(_('unexpected delta opcode 0'))
1331 return out
1332
1268 1333 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1269 1334 """Reads a patch from fp and tries to apply it.
1270 1335
1271 1336 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1272 1337 there was any fuzz.
1273 1338
1274 1339 If 'eolmode' is 'strict', the patch content and patched file are
1275 1340 read in binary mode. Otherwise, line endings are ignored when
1276 1341 patching then normalized according to 'eolmode'.
1277 1342 """
1278 1343 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1279 1344 eolmode=eolmode)
1280 1345
1281 1346 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1282 1347 eolmode='strict'):
1283 1348
1284 1349 def pstrip(p):
1285 1350 return pathstrip(p, strip - 1)[1]
1286 1351
1287 1352 rejects = 0
1288 1353 err = 0
1289 1354 current_file = None
1290 1355
1291 1356 for state, values in iterhunks(fp):
1292 1357 if state == 'hunk':
1293 1358 if not current_file:
1294 1359 continue
1295 1360 ret = current_file.apply(values)
1296 1361 if ret > 0:
1297 1362 err = 1
1298 1363 elif state == 'file':
1299 1364 if current_file:
1300 1365 rejects += current_file.close()
1301 1366 current_file = None
1302 1367 afile, bfile, first_hunk, gp = values
1303 1368 if gp:
1304 1369 gp.path = pstrip(gp.path)
1305 1370 if gp.oldpath:
1306 1371 gp.oldpath = pstrip(gp.oldpath)
1307 1372 else:
1308 1373 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1309 1374 if gp.op == 'RENAME':
1310 1375 backend.unlink(gp.oldpath)
1311 1376 if not first_hunk:
1312 1377 if gp.op == 'DELETE':
1313 1378 backend.unlink(gp.path)
1314 1379 continue
1315 1380 data, mode = None, None
1316 1381 if gp.op in ('RENAME', 'COPY'):
1317 1382 data, mode = store.getfile(gp.oldpath)[:2]
1318 1383 if gp.mode:
1319 1384 mode = gp.mode
1320 1385 if gp.op == 'ADD':
1321 1386 # Added files without content have no hunk and
1322 1387 # must be created
1323 1388 data = ''
1324 1389 if data or mode:
1325 1390 if (gp.op in ('ADD', 'RENAME', 'COPY')
1326 1391 and backend.exists(gp.path)):
1327 1392 raise PatchError(_("cannot create %s: destination "
1328 1393 "already exists") % gp.path)
1329 1394 backend.setfile(gp.path, data, mode, gp.oldpath)
1330 1395 continue
1331 1396 try:
1332 1397 current_file = patcher(ui, gp, backend, store,
1333 1398 eolmode=eolmode)
1334 1399 except PatchError, inst:
1335 1400 ui.warn(str(inst) + '\n')
1336 1401 current_file = None
1337 1402 rejects += 1
1338 1403 continue
1339 1404 elif state == 'git':
1340 1405 for gp in values:
1341 1406 path = pstrip(gp.oldpath)
1342 1407 try:
1343 1408 data, mode = backend.getfile(path)
1344 1409 except IOError, e:
1345 1410 if e.errno != errno.ENOENT:
1346 1411 raise
1347 1412 # The error ignored here will trigger a getfile()
1348 1413 # error in a place more appropriate for error
1349 1414 # handling, and will not interrupt the patching
1350 1415 # process.
1351 1416 else:
1352 1417 store.setfile(path, data, mode)
1353 1418 else:
1354 1419 raise util.Abort(_('unsupported parser state: %s') % state)
1355 1420
1356 1421 if current_file:
1357 1422 rejects += current_file.close()
1358 1423
1359 1424 if rejects:
1360 1425 return -1
1361 1426 return err
1362 1427
1363 1428 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1364 1429 similarity):
1365 1430 """use <patcher> to apply <patchname> to the working directory.
1366 1431 returns whether patch was applied with fuzz factor."""
1367 1432
1368 1433 fuzz = False
1369 1434 args = []
1370 1435 cwd = repo.root
1371 1436 if cwd:
1372 1437 args.append('-d %s' % util.shellquote(cwd))
1373 1438 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1374 1439 util.shellquote(patchname)))
1375 1440 try:
1376 1441 for line in fp:
1377 1442 line = line.rstrip()
1378 1443 ui.note(line + '\n')
1379 1444 if line.startswith('patching file '):
1380 1445 pf = util.parsepatchoutput(line)
1381 1446 printed_file = False
1382 1447 files.add(pf)
1383 1448 elif line.find('with fuzz') >= 0:
1384 1449 fuzz = True
1385 1450 if not printed_file:
1386 1451 ui.warn(pf + '\n')
1387 1452 printed_file = True
1388 1453 ui.warn(line + '\n')
1389 1454 elif line.find('saving rejects to file') >= 0:
1390 1455 ui.warn(line + '\n')
1391 1456 elif line.find('FAILED') >= 0:
1392 1457 if not printed_file:
1393 1458 ui.warn(pf + '\n')
1394 1459 printed_file = True
1395 1460 ui.warn(line + '\n')
1396 1461 finally:
1397 1462 if files:
1398 1463 scmutil.marktouched(repo, files, similarity)
1399 1464 code = fp.close()
1400 1465 if code:
1401 1466 raise PatchError(_("patch command failed: %s") %
1402 1467 util.explainexit(code)[0])
1403 1468 return fuzz
1404 1469
1405 1470 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1406 1471 if files is None:
1407 1472 files = set()
1408 1473 if eolmode is None:
1409 1474 eolmode = ui.config('patch', 'eol', 'strict')
1410 1475 if eolmode.lower() not in eolmodes:
1411 1476 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1412 1477 eolmode = eolmode.lower()
1413 1478
1414 1479 store = filestore()
1415 1480 try:
1416 1481 fp = open(patchobj, 'rb')
1417 1482 except TypeError:
1418 1483 fp = patchobj
1419 1484 try:
1420 1485 ret = applydiff(ui, fp, backend, store, strip=strip,
1421 1486 eolmode=eolmode)
1422 1487 finally:
1423 1488 if fp != patchobj:
1424 1489 fp.close()
1425 1490 files.update(backend.close())
1426 1491 store.close()
1427 1492 if ret < 0:
1428 1493 raise PatchError(_('patch failed to apply'))
1429 1494 return ret > 0
1430 1495
1431 1496 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1432 1497 similarity=0):
1433 1498 """use builtin patch to apply <patchobj> to the working directory.
1434 1499 returns whether patch was applied with fuzz factor."""
1435 1500 backend = workingbackend(ui, repo, similarity)
1436 1501 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1437 1502
1438 1503 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1439 1504 eolmode='strict'):
1440 1505 backend = repobackend(ui, repo, ctx, store)
1441 1506 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1442 1507
1443 1508 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1444 1509 similarity=0):
1445 1510 """Apply <patchname> to the working directory.
1446 1511
1447 1512 'eolmode' specifies how end of lines should be handled. It can be:
1448 1513 - 'strict': inputs are read in binary mode, EOLs are preserved
1449 1514 - 'crlf': EOLs are ignored when patching and reset to CRLF
1450 1515 - 'lf': EOLs are ignored when patching and reset to LF
1451 1516 - None: get it from user settings, default to 'strict'
1452 1517 'eolmode' is ignored when using an external patcher program.
1453 1518
1454 1519 Returns whether patch was applied with fuzz factor.
1455 1520 """
1456 1521 patcher = ui.config('ui', 'patch')
1457 1522 if files is None:
1458 1523 files = set()
1459 1524 try:
1460 1525 if patcher:
1461 1526 return _externalpatch(ui, repo, patcher, patchname, strip,
1462 1527 files, similarity)
1463 1528 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1464 1529 similarity)
1465 1530 except PatchError, err:
1466 1531 raise util.Abort(str(err))
1467 1532
1468 1533 def changedfiles(ui, repo, patchpath, strip=1):
1469 1534 backend = fsbackend(ui, repo.root)
1470 1535 fp = open(patchpath, 'rb')
1471 1536 try:
1472 1537 changed = set()
1473 1538 for state, values in iterhunks(fp):
1474 1539 if state == 'file':
1475 1540 afile, bfile, first_hunk, gp = values
1476 1541 if gp:
1477 1542 gp.path = pathstrip(gp.path, strip - 1)[1]
1478 1543 if gp.oldpath:
1479 1544 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1480 1545 else:
1481 1546 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1482 1547 changed.add(gp.path)
1483 1548 if gp.op == 'RENAME':
1484 1549 changed.add(gp.oldpath)
1485 1550 elif state not in ('hunk', 'git'):
1486 1551 raise util.Abort(_('unsupported parser state: %s') % state)
1487 1552 return changed
1488 1553 finally:
1489 1554 fp.close()
1490 1555
1491 1556 class GitDiffRequired(Exception):
1492 1557 pass
1493 1558
1494 1559 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1495 1560 def get(key, name=None, getter=ui.configbool):
1496 1561 return ((opts and opts.get(key)) or
1497 1562 getter(section, name or key, None, untrusted=untrusted))
1498 1563 return mdiff.diffopts(
1499 1564 text=opts and opts.get('text'),
1500 1565 git=get('git'),
1501 1566 nodates=get('nodates'),
1502 1567 showfunc=get('show_function', 'showfunc'),
1503 1568 ignorews=get('ignore_all_space', 'ignorews'),
1504 1569 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1505 1570 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1506 1571 context=get('unified', getter=ui.config))
1507 1572
1508 1573 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1509 1574 losedatafn=None, prefix=''):
1510 1575 '''yields diff of changes to files between two nodes, or node and
1511 1576 working directory.
1512 1577
1513 1578 if node1 is None, use first dirstate parent instead.
1514 1579 if node2 is None, compare node1 with working directory.
1515 1580
1516 1581 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1517 1582 every time some change cannot be represented with the current
1518 1583 patch format. Return False to upgrade to git patch format, True to
1519 1584 accept the loss or raise an exception to abort the diff. It is
1520 1585 called with the name of current file being diffed as 'fn'. If set
1521 1586 to None, patches will always be upgraded to git format when
1522 1587 necessary.
1523 1588
1524 1589 prefix is a filename prefix that is prepended to all filenames on
1525 1590 display (used for subrepos).
1526 1591 '''
1527 1592
1528 1593 if opts is None:
1529 1594 opts = mdiff.defaultopts
1530 1595
1531 1596 if not node1 and not node2:
1532 1597 node1 = repo.dirstate.p1()
1533 1598
1534 1599 def lrugetfilectx():
1535 1600 cache = {}
1536 1601 order = util.deque()
1537 1602 def getfilectx(f, ctx):
1538 1603 fctx = ctx.filectx(f, filelog=cache.get(f))
1539 1604 if f not in cache:
1540 1605 if len(cache) > 20:
1541 1606 del cache[order.popleft()]
1542 1607 cache[f] = fctx.filelog()
1543 1608 else:
1544 1609 order.remove(f)
1545 1610 order.append(f)
1546 1611 return fctx
1547 1612 return getfilectx
1548 1613 getfilectx = lrugetfilectx()
1549 1614
1550 1615 ctx1 = repo[node1]
1551 1616 ctx2 = repo[node2]
1552 1617
1553 1618 if not changes:
1554 1619 changes = repo.status(ctx1, ctx2, match=match)
1555 1620 modified, added, removed = changes[:3]
1556 1621
1557 1622 if not modified and not added and not removed:
1558 1623 return []
1559 1624
1560 1625 revs = None
1561 1626 hexfunc = repo.ui.debugflag and hex or short
1562 1627 revs = [hexfunc(node) for node in [node1, node2] if node]
1563 1628
1564 1629 copy = {}
1565 1630 if opts.git or opts.upgrade:
1566 1631 copy = copies.pathcopies(ctx1, ctx2)
1567 1632
1568 1633 def difffn(opts, losedata):
1569 1634 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1570 1635 copy, getfilectx, opts, losedata, prefix)
1571 1636 if opts.upgrade and not opts.git:
1572 1637 try:
1573 1638 def losedata(fn):
1574 1639 if not losedatafn or not losedatafn(fn=fn):
1575 1640 raise GitDiffRequired
1576 1641 # Buffer the whole output until we are sure it can be generated
1577 1642 return list(difffn(opts.copy(git=False), losedata))
1578 1643 except GitDiffRequired:
1579 1644 return difffn(opts.copy(git=True), None)
1580 1645 else:
1581 1646 return difffn(opts, None)
1582 1647
1583 1648 def difflabel(func, *args, **kw):
1584 1649 '''yields 2-tuples of (output, label) based on the output of func()'''
1585 1650 headprefixes = [('diff', 'diff.diffline'),
1586 1651 ('copy', 'diff.extended'),
1587 1652 ('rename', 'diff.extended'),
1588 1653 ('old', 'diff.extended'),
1589 1654 ('new', 'diff.extended'),
1590 1655 ('deleted', 'diff.extended'),
1591 1656 ('---', 'diff.file_a'),
1592 1657 ('+++', 'diff.file_b')]
1593 1658 textprefixes = [('@', 'diff.hunk'),
1594 1659 ('-', 'diff.deleted'),
1595 1660 ('+', 'diff.inserted')]
1596 1661 head = False
1597 1662 for chunk in func(*args, **kw):
1598 1663 lines = chunk.split('\n')
1599 1664 for i, line in enumerate(lines):
1600 1665 if i != 0:
1601 1666 yield ('\n', '')
1602 1667 if head:
1603 1668 if line.startswith('@'):
1604 1669 head = False
1605 1670 else:
1606 1671 if line and line[0] not in ' +-@\\':
1607 1672 head = True
1608 1673 stripline = line
1609 1674 if not head and line and line[0] in '+-':
1610 1675 # highlight trailing whitespace, but only in changed lines
1611 1676 stripline = line.rstrip()
1612 1677 prefixes = textprefixes
1613 1678 if head:
1614 1679 prefixes = headprefixes
1615 1680 for prefix, label in prefixes:
1616 1681 if stripline.startswith(prefix):
1617 1682 yield (stripline, label)
1618 1683 break
1619 1684 else:
1620 1685 yield (line, '')
1621 1686 if line != stripline:
1622 1687 yield (line[len(stripline):], 'diff.trailingwhitespace')
1623 1688
1624 1689 def diffui(*args, **kw):
1625 1690 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1626 1691 return difflabel(diff, *args, **kw)
1627 1692
1628 1693 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1629 1694 copy, getfilectx, opts, losedatafn, prefix):
1630 1695
1631 1696 def join(f):
1632 1697 return posixpath.join(prefix, f)
1633 1698
1634 1699 def addmodehdr(header, omode, nmode):
1635 1700 if omode != nmode:
1636 1701 header.append('old mode %s\n' % omode)
1637 1702 header.append('new mode %s\n' % nmode)
1638 1703
1639 1704 def addindexmeta(meta, revs):
1640 1705 if opts.git:
1641 1706 i = len(revs)
1642 1707 if i==2:
1643 1708 meta.append('index %s..%s\n' % tuple(revs))
1644 1709 elif i==3:
1645 1710 meta.append('index %s,%s..%s\n' % tuple(revs))
1646 1711
1647 1712 def gitindex(text):
1648 1713 if not text:
1649 1714 text = ""
1650 1715 l = len(text)
1651 1716 s = util.sha1('blob %d\0' % l)
1652 1717 s.update(text)
1653 1718 return s.hexdigest()
1654 1719
1655 1720 def diffline(a, b, revs):
1656 1721 if opts.git:
1657 1722 line = 'diff --git a/%s b/%s\n' % (a, b)
1658 1723 elif not repo.ui.quiet:
1659 1724 if revs:
1660 1725 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1661 1726 line = 'diff %s %s\n' % (revinfo, a)
1662 1727 else:
1663 1728 line = 'diff %s\n' % a
1664 1729 else:
1665 1730 line = ''
1666 1731 return line
1667 1732
1668 1733 date1 = util.datestr(ctx1.date())
1669 1734 man1 = ctx1.manifest()
1670 1735
1671 1736 gone = set()
1672 1737 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1673 1738
1674 1739 copyto = dict([(v, k) for k, v in copy.items()])
1675 1740
1676 1741 if opts.git:
1677 1742 revs = None
1678 1743
1679 1744 for f in sorted(modified + added + removed):
1680 1745 to = None
1681 1746 tn = None
1682 1747 dodiff = True
1683 1748 header = []
1684 1749 if f in man1:
1685 1750 to = getfilectx(f, ctx1).data()
1686 1751 if f not in removed:
1687 1752 tn = getfilectx(f, ctx2).data()
1688 1753 a, b = f, f
1689 1754 if opts.git or losedatafn:
1690 1755 if f in added or (f in modified and to is None):
1691 1756 mode = gitmode[ctx2.flags(f)]
1692 1757 if f in copy or f in copyto:
1693 1758 if opts.git:
1694 1759 if f in copy:
1695 1760 a = copy[f]
1696 1761 else:
1697 1762 a = copyto[f]
1698 1763 omode = gitmode[man1.flags(a)]
1699 1764 addmodehdr(header, omode, mode)
1700 1765 if a in removed and a not in gone:
1701 1766 op = 'rename'
1702 1767 gone.add(a)
1703 1768 else:
1704 1769 op = 'copy'
1705 1770 header.append('%s from %s\n' % (op, join(a)))
1706 1771 header.append('%s to %s\n' % (op, join(f)))
1707 1772 to = getfilectx(a, ctx1).data()
1708 1773 else:
1709 1774 losedatafn(f)
1710 1775 else:
1711 1776 if opts.git:
1712 1777 header.append('new file mode %s\n' % mode)
1713 1778 elif ctx2.flags(f):
1714 1779 losedatafn(f)
1715 1780 # In theory, if tn was copied or renamed we should check
1716 1781 # if the source is binary too but the copy record already
1717 1782 # forces git mode.
1718 1783 if util.binary(tn):
1719 1784 if opts.git:
1720 1785 dodiff = 'binary'
1721 1786 else:
1722 1787 losedatafn(f)
1723 1788 if not opts.git and not tn:
1724 1789 # regular diffs cannot represent new empty file
1725 1790 losedatafn(f)
1726 1791 elif f in removed or (f in modified and tn is None):
1727 1792 if opts.git:
1728 1793 # have we already reported a copy above?
1729 1794 if ((f in copy and copy[f] in added
1730 1795 and copyto[copy[f]] == f) or
1731 1796 (f in copyto and copyto[f] in added
1732 1797 and copy[copyto[f]] == f)):
1733 1798 dodiff = False
1734 1799 else:
1735 1800 header.append('deleted file mode %s\n' %
1736 1801 gitmode[man1.flags(f)])
1737 1802 if util.binary(to):
1738 1803 dodiff = 'binary'
1739 1804 elif not to or util.binary(to):
1740 1805 # regular diffs cannot represent empty file deletion
1741 1806 losedatafn(f)
1742 1807 else:
1743 1808 oflag = man1.flags(f)
1744 1809 nflag = ctx2.flags(f)
1745 1810 binary = util.binary(to) or util.binary(tn)
1746 1811 if opts.git:
1747 1812 addmodehdr(header, gitmode[oflag], gitmode[nflag])
1748 1813 if binary:
1749 1814 dodiff = 'binary'
1750 1815 elif binary or nflag != oflag:
1751 1816 losedatafn(f)
1752 1817
1753 1818 if dodiff:
1754 1819 if opts.git or revs:
1755 1820 header.insert(0, diffline(join(a), join(b), revs))
1756 1821 if dodiff == 'binary':
1757 1822 text = mdiff.b85diff(to, tn)
1758 1823 if text:
1759 1824 addindexmeta(header, [gitindex(to), gitindex(tn)])
1760 1825 else:
1761 1826 text = mdiff.unidiff(to, date1,
1762 1827 # ctx2 date may be dynamic
1763 1828 tn, util.datestr(ctx2.date()),
1764 1829 join(a), join(b), opts=opts)
1765 1830 if header and (text or len(header) > 1):
1766 1831 yield ''.join(header)
1767 1832 if text:
1768 1833 yield text
1769 1834
1770 1835 def diffstatsum(stats):
1771 1836 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1772 1837 for f, a, r, b in stats:
1773 1838 maxfile = max(maxfile, encoding.colwidth(f))
1774 1839 maxtotal = max(maxtotal, a + r)
1775 1840 addtotal += a
1776 1841 removetotal += r
1777 1842 binary = binary or b
1778 1843
1779 1844 return maxfile, maxtotal, addtotal, removetotal, binary
1780 1845
1781 1846 def diffstatdata(lines):
1782 1847 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1783 1848
1784 1849 results = []
1785 1850 filename, adds, removes, isbinary = None, 0, 0, False
1786 1851
1787 1852 def addresult():
1788 1853 if filename:
1789 1854 results.append((filename, adds, removes, isbinary))
1790 1855
1791 1856 for line in lines:
1792 1857 if line.startswith('diff'):
1793 1858 addresult()
1794 1859 # set numbers to 0 anyway when starting new file
1795 1860 adds, removes, isbinary = 0, 0, False
1796 1861 if line.startswith('diff --git a/'):
1797 1862 filename = gitre.search(line).group(1)
1798 1863 elif line.startswith('diff -r'):
1799 1864 # format: "diff -r ... -r ... filename"
1800 1865 filename = diffre.search(line).group(1)
1801 1866 elif line.startswith('+') and not line.startswith('+++ '):
1802 1867 adds += 1
1803 1868 elif line.startswith('-') and not line.startswith('--- '):
1804 1869 removes += 1
1805 1870 elif (line.startswith('GIT binary patch') or
1806 1871 line.startswith('Binary file')):
1807 1872 isbinary = True
1808 1873 addresult()
1809 1874 return results
1810 1875
1811 1876 def diffstat(lines, width=80, git=False):
1812 1877 output = []
1813 1878 stats = diffstatdata(lines)
1814 1879 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1815 1880
1816 1881 countwidth = len(str(maxtotal))
1817 1882 if hasbinary and countwidth < 3:
1818 1883 countwidth = 3
1819 1884 graphwidth = width - countwidth - maxname - 6
1820 1885 if graphwidth < 10:
1821 1886 graphwidth = 10
1822 1887
1823 1888 def scale(i):
1824 1889 if maxtotal <= graphwidth:
1825 1890 return i
1826 1891 # If diffstat runs out of room it doesn't print anything,
1827 1892 # which isn't very useful, so always print at least one + or -
1828 1893 # if there were at least some changes.
1829 1894 return max(i * graphwidth // maxtotal, int(bool(i)))
1830 1895
1831 1896 for filename, adds, removes, isbinary in stats:
1832 1897 if isbinary:
1833 1898 count = 'Bin'
1834 1899 else:
1835 1900 count = adds + removes
1836 1901 pluses = '+' * scale(adds)
1837 1902 minuses = '-' * scale(removes)
1838 1903 output.append(' %s%s | %*s %s%s\n' %
1839 1904 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1840 1905 countwidth, count, pluses, minuses))
1841 1906
1842 1907 if stats:
1843 1908 output.append(_(' %d files changed, %d insertions(+), '
1844 1909 '%d deletions(-)\n')
1845 1910 % (len(stats), totaladds, totalremoves))
1846 1911
1847 1912 return ''.join(output)
1848 1913
1849 1914 def diffstatui(*args, **kw):
1850 1915 '''like diffstat(), but yields 2-tuples of (output, label) for
1851 1916 ui.write()
1852 1917 '''
1853 1918
1854 1919 for line in diffstat(*args, **kw).splitlines():
1855 1920 if line and line[-1] in '+-':
1856 1921 name, graph = line.rsplit(' ', 1)
1857 1922 yield (name + ' ', '')
1858 1923 m = re.search(r'\++', graph)
1859 1924 if m:
1860 1925 yield (m.group(0), 'diffstat.inserted')
1861 1926 m = re.search(r'-+', graph)
1862 1927 if m:
1863 1928 yield (m.group(0), 'diffstat.deleted')
1864 1929 else:
1865 1930 yield (line, '')
1866 1931 yield ('\n', '')
@@ -1,611 +1,720 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3
4 4 New file:
5 5
6 6 $ hg import -d "1000000 0" -mnew - <<EOF
7 7 > diff --git a/new b/new
8 8 > new file mode 100644
9 9 > index 0000000..7898192
10 10 > --- /dev/null
11 11 > +++ b/new
12 12 > @@ -0,0 +1 @@
13 13 > +a
14 14 > EOF
15 15 applying patch from stdin
16 16
17 17 $ hg tip -q
18 18 0:ae3ee40d2079
19 19
20 20 New empty file:
21 21
22 22 $ hg import -d "1000000 0" -mempty - <<EOF
23 23 > diff --git a/empty b/empty
24 24 > new file mode 100644
25 25 > EOF
26 26 applying patch from stdin
27 27
28 28 $ hg tip -q
29 29 1:ab199dc869b5
30 30
31 31 $ hg locate empty
32 32 empty
33 33
34 34 chmod +x:
35 35
36 36 $ hg import -d "1000000 0" -msetx - <<EOF
37 37 > diff --git a/new b/new
38 38 > old mode 100644
39 39 > new mode 100755
40 40 > EOF
41 41 applying patch from stdin
42 42
43 43 #if execbit
44 44 $ hg tip -q
45 45 2:3a34410f282e
46 46 $ test -x new
47 47 $ hg rollback -q
48 48 #else
49 49 $ hg tip -q
50 50 1:ab199dc869b5
51 51 #endif
52 52
53 53 Copy and removing x bit:
54 54
55 55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
56 56 > diff --git a/new b/copy
57 57 > old mode 100755
58 58 > new mode 100644
59 59 > similarity index 100%
60 60 > copy from new
61 61 > copy to copy
62 62 > diff --git a/new b/copyx
63 63 > similarity index 100%
64 64 > copy from new
65 65 > copy to copyx
66 66 > EOF
67 67 applying patch from stdin
68 68
69 69 $ test -f copy
70 70 #if execbit
71 71 $ test ! -x copy
72 72 $ test -x copyx
73 73 $ hg tip -q
74 74 2:21dfaae65c71
75 75 #else
76 76 $ hg tip -q
77 77 2:0efdaa8e3bf3
78 78 #endif
79 79
80 80 $ hg up -qCr1
81 81 $ hg rollback -q
82 82
83 83 Copy (like above but independent of execbit):
84 84
85 85 $ hg import -d "1000000 0" -mcopy - <<EOF
86 86 > diff --git a/new b/copy
87 87 > similarity index 100%
88 88 > copy from new
89 89 > copy to copy
90 90 > diff --git a/new b/copyx
91 91 > similarity index 100%
92 92 > copy from new
93 93 > copy to copyx
94 94 > EOF
95 95 applying patch from stdin
96 96
97 97 $ hg tip -q
98 98 2:0efdaa8e3bf3
99 99 $ test -f copy
100 100
101 101 $ cat copy
102 102 a
103 103
104 104 $ hg cat copy
105 105 a
106 106
107 107 Rename:
108 108
109 109 $ hg import -d "1000000 0" -mrename - <<EOF
110 110 > diff --git a/copy b/rename
111 111 > similarity index 100%
112 112 > rename from copy
113 113 > rename to rename
114 114 > EOF
115 115 applying patch from stdin
116 116
117 117 $ hg tip -q
118 118 3:b1f57753fad2
119 119
120 120 $ hg locate
121 121 copyx
122 122 empty
123 123 new
124 124 rename
125 125
126 126 Delete:
127 127
128 128 $ hg import -d "1000000 0" -mdelete - <<EOF
129 129 > diff --git a/copyx b/copyx
130 130 > deleted file mode 100755
131 131 > index 7898192..0000000
132 132 > --- a/copyx
133 133 > +++ /dev/null
134 134 > @@ -1 +0,0 @@
135 135 > -a
136 136 > EOF
137 137 applying patch from stdin
138 138
139 139 $ hg tip -q
140 140 4:1bd1da94b9b2
141 141
142 142 $ hg locate
143 143 empty
144 144 new
145 145 rename
146 146
147 147 $ test -f copyx
148 148 [1]
149 149
150 150 Regular diff:
151 151
152 152 $ hg import -d "1000000 0" -mregular - <<EOF
153 153 > diff --git a/rename b/rename
154 154 > index 7898192..72e1fe3 100644
155 155 > --- a/rename
156 156 > +++ b/rename
157 157 > @@ -1 +1,5 @@
158 158 > a
159 159 > +a
160 160 > +a
161 161 > +a
162 162 > +a
163 163 > EOF
164 164 applying patch from stdin
165 165
166 166 $ hg tip -q
167 167 5:46fe99cb3035
168 168
169 169 Copy and modify:
170 170
171 171 $ hg import -d "1000000 0" -mcopymod - <<EOF
172 172 > diff --git a/rename b/copy2
173 173 > similarity index 80%
174 174 > copy from rename
175 175 > copy to copy2
176 176 > index 72e1fe3..b53c148 100644
177 177 > --- a/rename
178 178 > +++ b/copy2
179 179 > @@ -1,5 +1,5 @@
180 180 > a
181 181 > a
182 182 > -a
183 183 > +b
184 184 > a
185 185 > a
186 186 > EOF
187 187 applying patch from stdin
188 188
189 189 $ hg tip -q
190 190 6:ffeb3197c12d
191 191
192 192 $ hg cat copy2
193 193 a
194 194 a
195 195 b
196 196 a
197 197 a
198 198
199 199 Rename and modify:
200 200
201 201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
202 202 > diff --git a/copy2 b/rename2
203 203 > similarity index 80%
204 204 > rename from copy2
205 205 > rename to rename2
206 206 > index b53c148..8f81e29 100644
207 207 > --- a/copy2
208 208 > +++ b/rename2
209 209 > @@ -1,5 +1,5 @@
210 210 > a
211 211 > a
212 212 > b
213 213 > -a
214 214 > +c
215 215 > a
216 216 > EOF
217 217 applying patch from stdin
218 218
219 219 $ hg tip -q
220 220 7:401aede9e6bb
221 221
222 222 $ hg locate copy2
223 223 [1]
224 224 $ hg cat rename2
225 225 a
226 226 a
227 227 b
228 228 c
229 229 a
230 230
231 231 One file renamed multiple times:
232 232
233 233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
234 234 > diff --git a/rename2 b/rename3
235 235 > rename from rename2
236 236 > rename to rename3
237 237 > diff --git a/rename2 b/rename3-2
238 238 > rename from rename2
239 239 > rename to rename3-2
240 240 > EOF
241 241 applying patch from stdin
242 242
243 243 $ hg tip -q
244 244 8:2ef727e684e8
245 245
246 246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
247 247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
248 248
249 249 $ hg locate rename2 rename3 rename3-2
250 250 rename3
251 251 rename3-2
252 252
253 253 $ hg cat rename3
254 254 a
255 255 a
256 256 b
257 257 c
258 258 a
259 259
260 260 $ hg cat rename3-2
261 261 a
262 262 a
263 263 b
264 264 c
265 265 a
266 266
267 267 $ echo foo > foo
268 268 $ hg add foo
269 269 $ hg ci -m 'add foo'
270 270
271 271 Binary files and regular patch hunks:
272 272
273 273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
274 274 > diff --git a/binary b/binary
275 275 > new file mode 100644
276 276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
277 277 > GIT binary patch
278 278 > literal 4
279 279 > Lc\${NkU|;|M00aO5
280 280 >
281 281 > diff --git a/foo b/foo2
282 282 > rename from foo
283 283 > rename to foo2
284 284 > EOF
285 285 applying patch from stdin
286 286
287 287 $ hg tip -q
288 288 10:27377172366e
289 289
290 290 $ cat foo2
291 291 foo
292 292
293 293 $ hg manifest --debug | grep binary
294 294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
295 295
296 296 Multiple binary files:
297 297
298 298 $ hg import -d "1000000 0" -m multibinary - <<EOF
299 299 > diff --git a/mbinary1 b/mbinary1
300 300 > new file mode 100644
301 301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
302 302 > GIT binary patch
303 303 > literal 4
304 304 > Lc\${NkU|;|M00aO5
305 305 >
306 306 > diff --git a/mbinary2 b/mbinary2
307 307 > new file mode 100644
308 308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
309 309 > GIT binary patch
310 310 > literal 5
311 311 > Mc\${NkU|\`?^000jF3jhEB
312 312 >
313 313 > EOF
314 314 applying patch from stdin
315 315
316 316 $ hg tip -q
317 317 11:18b73a84b4ab
318 318
319 319 $ hg manifest --debug | grep mbinary
320 320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
321 321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
322 322
323 Binary file and delta hunk:
324
325 $ hg import -d "1000000 0" -m delta - <<'EOF'
326 > diff --git a/delta b/delta
327 > new file mode 100644
328 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
329 > GIT binary patch
330 > literal 3749
331 > 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
403 > P00000NkvXXu0mjfZ5|Er
404 >
405 > literal 0
406 > HcmV?d00001
407 >
408 > EOF
409 applying patch from stdin
410
411 $ hg manifest --debug | grep delta
412 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
413
414 $ hg import -d "1000000 0" -m delta - <<'EOF'
415 > diff --git a/delta b/delta
416 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
417 > GIT binary patch
418 > delta 49
419 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
420 > FSO9ZT4bA`n
421 >
422 > delta 49
423 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
424 > HOA1;9yU-AD
425 >
426 > EOF
427 applying patch from stdin
428
429 $ hg manifest --debug | grep delta
430 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
431
323 432 Filenames with spaces:
324 433
325 434 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
326 435 > diff --git a/foo bar b/foo bar
327 436 > new file mode 100644
328 437 > index 0000000..257cc56
329 438 > --- /dev/null
330 439 > +++ b/foo bar EOL
331 440 > @@ -0,0 +1 @@
332 441 > +foo
333 442 > EOF
334 443 applying patch from stdin
335 444
336 445 $ hg tip -q
337 12:47500ce1614e
446 14:4b79479c9a6d
338 447
339 448 $ cat "foo bar"
340 449 foo
341 450
342 451 Copy then modify the original file:
343 452
344 453 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
345 454 > diff --git a/foo2 b/foo2
346 455 > index 257cc56..fe08ec6 100644
347 456 > --- a/foo2
348 457 > +++ b/foo2
349 458 > @@ -1 +1,2 @@
350 459 > foo
351 460 > +new line
352 461 > diff --git a/foo2 b/foo3
353 462 > similarity index 100%
354 463 > copy from foo2
355 464 > copy to foo3
356 465 > EOF
357 466 applying patch from stdin
358 467
359 468 $ hg tip -q
360 13:6757efb07ea9
469 15:9cbe44af4ae9
361 470
362 471 $ cat foo3
363 472 foo
364 473
365 474 Move text file and patch as binary
366 475
367 476 $ echo a > text2
368 477 $ hg ci -Am0
369 478 adding text2
370 479 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
371 480 > diff --git a/text2 b/binary2
372 481 > rename from text2
373 482 > rename to binary2
374 483 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
375 484 > GIT binary patch
376 485 > literal 5
377 486 > Mc$`b*O5$Pw00T?_*Z=?k
378 487 >
379 488 > EOF
380 489 applying patch from stdin
381 490
382 491 $ cat binary2
383 492 a
384 493 b
385 494 \x00 (no-eol) (esc)
386 495
387 496 $ hg st --copies --change .
388 497 A binary2
389 498 text2
390 499 R text2
391 500
392 501 Invalid base85 content
393 502
394 503 $ hg rollback
395 repository tip rolled back to revision 14 (undo import)
396 working directory now based on revision 14
504 repository tip rolled back to revision 16 (undo import)
505 working directory now based on revision 16
397 506 $ hg revert -aq
398 507 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
399 508 > diff --git a/text2 b/binary2
400 509 > rename from text2
401 510 > rename to binary2
402 511 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
403 512 > GIT binary patch
404 513 > literal 5
405 514 > Mc$`b*O.$Pw00T?_*Z=?k
406 515 >
407 516 > EOF
408 517 applying patch from stdin
409 518 abort: could not decode "binary2" binary patch: bad base85 character at position 6
410 519 [255]
411 520
412 521 $ hg revert -aq
413 522 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
414 523 > diff --git a/text2 b/binary2
415 524 > rename from text2
416 525 > rename to binary2
417 526 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
418 527 > GIT binary patch
419 528 > literal 6
420 529 > Mc$`b*O5$Pw00T?_*Z=?k
421 530 >
422 531 > EOF
423 532 applying patch from stdin
424 533 abort: "binary2" length is 5 bytes, should be 6
425 534 [255]
426 535
427 536 $ hg revert -aq
428 537 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
429 538 > diff --git a/text2 b/binary2
430 539 > rename from text2
431 540 > rename to binary2
432 541 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
433 542 > GIT binary patch
434 543 > Mc$`b*O5$Pw00T?_*Z=?k
435 544 >
436 545 > EOF
437 546 applying patch from stdin
438 547 abort: could not extract "binary2" binary data
439 548 [255]
440 549
441 550 Simulate a copy/paste turning LF into CRLF (issue2870)
442 551
443 552 $ hg revert -aq
444 553 $ cat > binary.diff <<"EOF"
445 554 > diff --git a/text2 b/binary2
446 555 > rename from text2
447 556 > rename to binary2
448 557 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
449 558 > GIT binary patch
450 559 > literal 5
451 560 > Mc$`b*O5$Pw00T?_*Z=?k
452 561 >
453 562 > EOF
454 563 >>> fp = file('binary.diff', 'rb')
455 564 >>> data = fp.read()
456 565 >>> fp.close()
457 566 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
458 567 $ rm binary2
459 568 $ hg import --no-commit binary.diff
460 569 applying binary.diff
461 570
462 571 $ cd ..
463 572
464 573 Consecutive import with renames (issue2459)
465 574
466 575 $ hg init issue2459
467 576 $ cd issue2459
468 577 $ hg import --no-commit --force - <<EOF
469 578 > diff --git a/a b/a
470 579 > new file mode 100644
471 580 > EOF
472 581 applying patch from stdin
473 582 $ hg import --no-commit --force - <<EOF
474 583 > diff --git a/a b/b
475 584 > rename from a
476 585 > rename to b
477 586 > EOF
478 587 applying patch from stdin
479 588 a has not been committed yet, so no copy data will be stored for b.
480 589 $ hg debugstate
481 590 a 0 -1 unset b
482 591 $ hg ci -m done
483 592 $ cd ..
484 593
485 594 Renames and strip
486 595
487 596 $ hg init renameandstrip
488 597 $ cd renameandstrip
489 598 $ echo a > a
490 599 $ hg ci -Am adda
491 600 adding a
492 601 $ hg import --no-commit -p2 - <<EOF
493 602 > diff --git a/foo/a b/foo/b
494 603 > rename from foo/a
495 604 > rename to foo/b
496 605 > EOF
497 606 applying patch from stdin
498 607 $ hg st --copies
499 608 A b
500 609 a
501 610 R a
502 611
503 612 Renames, similarity and git diff
504 613
505 614 $ hg revert -aC
506 615 undeleting a
507 616 forgetting b
508 617 $ rm b
509 618 $ hg import --similarity 90 --no-commit - <<EOF
510 619 > diff --git a/a b/b
511 620 > rename from a
512 621 > rename to b
513 622 > EOF
514 623 applying patch from stdin
515 624 $ hg st --copies
516 625 A b
517 626 a
518 627 R a
519 628 $ cd ..
520 629
521 630 Pure copy with existing destination
522 631
523 632 $ hg init copytoexisting
524 633 $ cd copytoexisting
525 634 $ echo a > a
526 635 $ echo b > b
527 636 $ hg ci -Am add
528 637 adding a
529 638 adding b
530 639 $ hg import --no-commit - <<EOF
531 640 > diff --git a/a b/b
532 641 > copy from a
533 642 > copy to b
534 643 > EOF
535 644 applying patch from stdin
536 645 abort: cannot create b: destination already exists
537 646 [255]
538 647 $ cat b
539 648 b
540 649
541 650 Copy and changes with existing destination
542 651
543 652 $ hg import --no-commit - <<EOF
544 653 > diff --git a/a b/b
545 654 > copy from a
546 655 > copy to b
547 656 > --- a/a
548 657 > +++ b/b
549 658 > @@ -1,1 +1,2 @@
550 659 > a
551 660 > +b
552 661 > EOF
553 662 applying patch from stdin
554 663 cannot create b: destination already exists
555 664 1 out of 1 hunks FAILED -- saving rejects to file b.rej
556 665 abort: patch failed to apply
557 666 [255]
558 667 $ cat b
559 668 b
560 669
561 670 #if symlink
562 671
563 672 $ ln -s b linkb
564 673 $ hg add linkb
565 674 $ hg ci -m addlinkb
566 675 $ hg import --no-commit - <<EOF
567 676 > diff --git a/linkb b/linkb
568 677 > deleted file mode 120000
569 678 > --- a/linkb
570 679 > +++ /dev/null
571 680 > @@ -1,1 +0,0 @@
572 681 > -badhunk
573 682 > \ No newline at end of file
574 683 > EOF
575 684 applying patch from stdin
576 685 patching file linkb
577 686 Hunk #1 FAILED at 0
578 687 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
579 688 abort: patch failed to apply
580 689 [255]
581 690 $ hg st
582 691 ? b.rej
583 692 ? linkb.rej
584 693
585 694 #endif
586 695
587 696 Test corner case involving copies and multiple hunks (issue3384)
588 697
589 698 $ hg revert -qa
590 699 $ hg import --no-commit - <<EOF
591 700 > diff --git a/a b/c
592 701 > copy from a
593 702 > copy to c
594 703 > --- a/a
595 704 > +++ b/c
596 705 > @@ -1,1 +1,2 @@
597 706 > a
598 707 > +a
599 708 > @@ -2,1 +2,2 @@
600 709 > a
601 710 > +a
602 711 > diff --git a/a b/a
603 712 > --- a/a
604 713 > +++ b/a
605 714 > @@ -1,1 +1,2 @@
606 715 > a
607 716 > +b
608 717 > EOF
609 718 applying patch from stdin
610 719
611 720 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now