##// END OF EJS Templates
patch.pathtransform: prepend prefix even if strip is 0...
Siddharth Agarwal -
r24385:885a573f default
parent child Browse files
Show More
@@ -1,2422 +1,2424 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, copy
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 cStringIO
19 19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
20 20
21 21 gitre = re.compile('diff --git a/(.*) b/(.*)')
22 22 tabsplitter = re.compile(r'(\t+|[^\t]+)')
23 23
24 24 class PatchError(Exception):
25 25 pass
26 26
27 27
28 28 # public functions
29 29
30 30 def split(stream):
31 31 '''return an iterator of individual patches from a stream'''
32 32 def isheader(line, inheader):
33 33 if inheader and line[0] in (' ', '\t'):
34 34 # continuation
35 35 return True
36 36 if line[0] in (' ', '-', '+'):
37 37 # diff line - don't check for header pattern in there
38 38 return False
39 39 l = line.split(': ', 1)
40 40 return len(l) == 2 and ' ' not in l[0]
41 41
42 42 def chunk(lines):
43 43 return cStringIO.StringIO(''.join(lines))
44 44
45 45 def hgsplit(stream, cur):
46 46 inheader = True
47 47
48 48 for line in stream:
49 49 if not line.strip():
50 50 inheader = False
51 51 if not inheader and line.startswith('# HG changeset patch'):
52 52 yield chunk(cur)
53 53 cur = []
54 54 inheader = True
55 55
56 56 cur.append(line)
57 57
58 58 if cur:
59 59 yield chunk(cur)
60 60
61 61 def mboxsplit(stream, cur):
62 62 for line in stream:
63 63 if line.startswith('From '):
64 64 for c in split(chunk(cur[1:])):
65 65 yield c
66 66 cur = []
67 67
68 68 cur.append(line)
69 69
70 70 if cur:
71 71 for c in split(chunk(cur[1:])):
72 72 yield c
73 73
74 74 def mimesplit(stream, cur):
75 75 def msgfp(m):
76 76 fp = cStringIO.StringIO()
77 77 g = email.Generator.Generator(fp, mangle_from_=False)
78 78 g.flatten(m)
79 79 fp.seek(0)
80 80 return fp
81 81
82 82 for line in stream:
83 83 cur.append(line)
84 84 c = chunk(cur)
85 85
86 86 m = email.Parser.Parser().parse(c)
87 87 if not m.is_multipart():
88 88 yield msgfp(m)
89 89 else:
90 90 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
91 91 for part in m.walk():
92 92 ct = part.get_content_type()
93 93 if ct not in ok_types:
94 94 continue
95 95 yield msgfp(part)
96 96
97 97 def headersplit(stream, cur):
98 98 inheader = False
99 99
100 100 for line in stream:
101 101 if not inheader and isheader(line, inheader):
102 102 yield chunk(cur)
103 103 cur = []
104 104 inheader = True
105 105 if inheader and not isheader(line, inheader):
106 106 inheader = False
107 107
108 108 cur.append(line)
109 109
110 110 if cur:
111 111 yield chunk(cur)
112 112
113 113 def remainder(cur):
114 114 yield chunk(cur)
115 115
116 116 class fiter(object):
117 117 def __init__(self, fp):
118 118 self.fp = fp
119 119
120 120 def __iter__(self):
121 121 return self
122 122
123 123 def next(self):
124 124 l = self.fp.readline()
125 125 if not l:
126 126 raise StopIteration
127 127 return l
128 128
129 129 inheader = False
130 130 cur = []
131 131
132 132 mimeheaders = ['content-type']
133 133
134 134 if not util.safehasattr(stream, 'next'):
135 135 # http responses, for example, have readline but not next
136 136 stream = fiter(stream)
137 137
138 138 for line in stream:
139 139 cur.append(line)
140 140 if line.startswith('# HG changeset patch'):
141 141 return hgsplit(stream, cur)
142 142 elif line.startswith('From '):
143 143 return mboxsplit(stream, cur)
144 144 elif isheader(line, inheader):
145 145 inheader = True
146 146 if line.split(':', 1)[0].lower() in mimeheaders:
147 147 # let email parser handle this
148 148 return mimesplit(stream, cur)
149 149 elif line.startswith('--- ') and inheader:
150 150 # No evil headers seen by diff start, split by hand
151 151 return headersplit(stream, cur)
152 152 # Not enough info, keep reading
153 153
154 154 # if we are here, we have a very plain patch
155 155 return remainder(cur)
156 156
157 157 def extract(ui, fileobj):
158 158 '''extract patch from data read from fileobj.
159 159
160 160 patch can be a normal patch or contained in an email message.
161 161
162 162 return tuple (filename, message, user, date, branch, node, p1, p2).
163 163 Any item in the returned tuple can be None. If filename is None,
164 164 fileobj did not contain a patch. Caller must unlink filename when done.'''
165 165
166 166 # attempt to detect the start of a patch
167 167 # (this heuristic is borrowed from quilt)
168 168 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
169 169 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
170 170 r'---[ \t].*?^\+\+\+[ \t]|'
171 171 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
172 172
173 173 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
174 174 tmpfp = os.fdopen(fd, 'w')
175 175 try:
176 176 msg = email.Parser.Parser().parse(fileobj)
177 177
178 178 subject = msg['Subject']
179 179 user = msg['From']
180 180 if not subject and not user:
181 181 # Not an email, restore parsed headers if any
182 182 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
183 183
184 184 # should try to parse msg['Date']
185 185 date = None
186 186 nodeid = None
187 187 branch = None
188 188 parents = []
189 189
190 190 if subject:
191 191 if subject.startswith('[PATCH'):
192 192 pend = subject.find(']')
193 193 if pend >= 0:
194 194 subject = subject[pend + 1:].lstrip()
195 195 subject = re.sub(r'\n[ \t]+', ' ', subject)
196 196 ui.debug('Subject: %s\n' % subject)
197 197 if user:
198 198 ui.debug('From: %s\n' % user)
199 199 diffs_seen = 0
200 200 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
201 201 message = ''
202 202 for part in msg.walk():
203 203 content_type = part.get_content_type()
204 204 ui.debug('Content-Type: %s\n' % content_type)
205 205 if content_type not in ok_types:
206 206 continue
207 207 payload = part.get_payload(decode=True)
208 208 m = diffre.search(payload)
209 209 if m:
210 210 hgpatch = False
211 211 hgpatchheader = False
212 212 ignoretext = False
213 213
214 214 ui.debug('found patch at byte %d\n' % m.start(0))
215 215 diffs_seen += 1
216 216 cfp = cStringIO.StringIO()
217 217 for line in payload[:m.start(0)].splitlines():
218 218 if line.startswith('# HG changeset patch') and not hgpatch:
219 219 ui.debug('patch generated by hg export\n')
220 220 hgpatch = True
221 221 hgpatchheader = True
222 222 # drop earlier commit message content
223 223 cfp.seek(0)
224 224 cfp.truncate()
225 225 subject = None
226 226 elif hgpatchheader:
227 227 if line.startswith('# User '):
228 228 user = line[7:]
229 229 ui.debug('From: %s\n' % user)
230 230 elif line.startswith("# Date "):
231 231 date = line[7:]
232 232 elif line.startswith("# Branch "):
233 233 branch = line[9:]
234 234 elif line.startswith("# Node ID "):
235 235 nodeid = line[10:]
236 236 elif line.startswith("# Parent "):
237 237 parents.append(line[9:].lstrip())
238 238 elif not line.startswith("# "):
239 239 hgpatchheader = False
240 240 elif line == '---':
241 241 ignoretext = True
242 242 if not hgpatchheader and not ignoretext:
243 243 cfp.write(line)
244 244 cfp.write('\n')
245 245 message = cfp.getvalue()
246 246 if tmpfp:
247 247 tmpfp.write(payload)
248 248 if not payload.endswith('\n'):
249 249 tmpfp.write('\n')
250 250 elif not diffs_seen and message and content_type == 'text/plain':
251 251 message += '\n' + payload
252 252 except: # re-raises
253 253 tmpfp.close()
254 254 os.unlink(tmpname)
255 255 raise
256 256
257 257 if subject and not message.startswith(subject):
258 258 message = '%s\n%s' % (subject, message)
259 259 tmpfp.close()
260 260 if not diffs_seen:
261 261 os.unlink(tmpname)
262 262 return None, message, user, date, branch, None, None, None
263 263
264 264 if parents:
265 265 p1 = parents.pop(0)
266 266 else:
267 267 p1 = None
268 268
269 269 if parents:
270 270 p2 = parents.pop(0)
271 271 else:
272 272 p2 = None
273 273
274 274 return tmpname, message, user, date, branch, nodeid, p1, p2
275 275
276 276 class patchmeta(object):
277 277 """Patched file metadata
278 278
279 279 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
280 280 or COPY. 'path' is patched file path. 'oldpath' is set to the
281 281 origin file when 'op' is either COPY or RENAME, None otherwise. If
282 282 file mode is changed, 'mode' is a tuple (islink, isexec) where
283 283 'islink' is True if the file is a symlink and 'isexec' is True if
284 284 the file is executable. Otherwise, 'mode' is None.
285 285 """
286 286 def __init__(self, path):
287 287 self.path = path
288 288 self.oldpath = None
289 289 self.mode = None
290 290 self.op = 'MODIFY'
291 291 self.binary = False
292 292
293 293 def setmode(self, mode):
294 294 islink = mode & 020000
295 295 isexec = mode & 0100
296 296 self.mode = (islink, isexec)
297 297
298 298 def copy(self):
299 299 other = patchmeta(self.path)
300 300 other.oldpath = self.oldpath
301 301 other.mode = self.mode
302 302 other.op = self.op
303 303 other.binary = self.binary
304 304 return other
305 305
306 306 def _ispatchinga(self, afile):
307 307 if afile == '/dev/null':
308 308 return self.op == 'ADD'
309 309 return afile == 'a/' + (self.oldpath or self.path)
310 310
311 311 def _ispatchingb(self, bfile):
312 312 if bfile == '/dev/null':
313 313 return self.op == 'DELETE'
314 314 return bfile == 'b/' + self.path
315 315
316 316 def ispatching(self, afile, bfile):
317 317 return self._ispatchinga(afile) and self._ispatchingb(bfile)
318 318
319 319 def __repr__(self):
320 320 return "<patchmeta %s %r>" % (self.op, self.path)
321 321
322 322 def readgitpatch(lr):
323 323 """extract git-style metadata about patches from <patchname>"""
324 324
325 325 # Filter patch for git information
326 326 gp = None
327 327 gitpatches = []
328 328 for line in lr:
329 329 line = line.rstrip(' \r\n')
330 330 if line.startswith('diff --git a/'):
331 331 m = gitre.match(line)
332 332 if m:
333 333 if gp:
334 334 gitpatches.append(gp)
335 335 dst = m.group(2)
336 336 gp = patchmeta(dst)
337 337 elif gp:
338 338 if line.startswith('--- '):
339 339 gitpatches.append(gp)
340 340 gp = None
341 341 continue
342 342 if line.startswith('rename from '):
343 343 gp.op = 'RENAME'
344 344 gp.oldpath = line[12:]
345 345 elif line.startswith('rename to '):
346 346 gp.path = line[10:]
347 347 elif line.startswith('copy from '):
348 348 gp.op = 'COPY'
349 349 gp.oldpath = line[10:]
350 350 elif line.startswith('copy to '):
351 351 gp.path = line[8:]
352 352 elif line.startswith('deleted file'):
353 353 gp.op = 'DELETE'
354 354 elif line.startswith('new file mode '):
355 355 gp.op = 'ADD'
356 356 gp.setmode(int(line[-6:], 8))
357 357 elif line.startswith('new mode '):
358 358 gp.setmode(int(line[-6:], 8))
359 359 elif line.startswith('GIT binary patch'):
360 360 gp.binary = True
361 361 if gp:
362 362 gitpatches.append(gp)
363 363
364 364 return gitpatches
365 365
366 366 class linereader(object):
367 367 # simple class to allow pushing lines back into the input stream
368 368 def __init__(self, fp):
369 369 self.fp = fp
370 370 self.buf = []
371 371
372 372 def push(self, line):
373 373 if line is not None:
374 374 self.buf.append(line)
375 375
376 376 def readline(self):
377 377 if self.buf:
378 378 l = self.buf[0]
379 379 del self.buf[0]
380 380 return l
381 381 return self.fp.readline()
382 382
383 383 def __iter__(self):
384 384 while True:
385 385 l = self.readline()
386 386 if not l:
387 387 break
388 388 yield l
389 389
390 390 class abstractbackend(object):
391 391 def __init__(self, ui):
392 392 self.ui = ui
393 393
394 394 def getfile(self, fname):
395 395 """Return target file data and flags as a (data, (islink,
396 396 isexec)) tuple. Data is None if file is missing/deleted.
397 397 """
398 398 raise NotImplementedError
399 399
400 400 def setfile(self, fname, data, mode, copysource):
401 401 """Write data to target file fname and set its mode. mode is a
402 402 (islink, isexec) tuple. If data is None, the file content should
403 403 be left unchanged. If the file is modified after being copied,
404 404 copysource is set to the original file name.
405 405 """
406 406 raise NotImplementedError
407 407
408 408 def unlink(self, fname):
409 409 """Unlink target file."""
410 410 raise NotImplementedError
411 411
412 412 def writerej(self, fname, failed, total, lines):
413 413 """Write rejected lines for fname. total is the number of hunks
414 414 which failed to apply and total the total number of hunks for this
415 415 files.
416 416 """
417 417 pass
418 418
419 419 def exists(self, fname):
420 420 raise NotImplementedError
421 421
422 422 class fsbackend(abstractbackend):
423 423 def __init__(self, ui, basedir):
424 424 super(fsbackend, self).__init__(ui)
425 425 self.opener = scmutil.opener(basedir)
426 426
427 427 def _join(self, f):
428 428 return os.path.join(self.opener.base, f)
429 429
430 430 def getfile(self, fname):
431 431 if self.opener.islink(fname):
432 432 return (self.opener.readlink(fname), (True, False))
433 433
434 434 isexec = False
435 435 try:
436 436 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
437 437 except OSError, e:
438 438 if e.errno != errno.ENOENT:
439 439 raise
440 440 try:
441 441 return (self.opener.read(fname), (False, isexec))
442 442 except IOError, e:
443 443 if e.errno != errno.ENOENT:
444 444 raise
445 445 return None, None
446 446
447 447 def setfile(self, fname, data, mode, copysource):
448 448 islink, isexec = mode
449 449 if data is None:
450 450 self.opener.setflags(fname, islink, isexec)
451 451 return
452 452 if islink:
453 453 self.opener.symlink(data, fname)
454 454 else:
455 455 self.opener.write(fname, data)
456 456 if isexec:
457 457 self.opener.setflags(fname, False, True)
458 458
459 459 def unlink(self, fname):
460 460 self.opener.unlinkpath(fname, ignoremissing=True)
461 461
462 462 def writerej(self, fname, failed, total, lines):
463 463 fname = fname + ".rej"
464 464 self.ui.warn(
465 465 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
466 466 (failed, total, fname))
467 467 fp = self.opener(fname, 'w')
468 468 fp.writelines(lines)
469 469 fp.close()
470 470
471 471 def exists(self, fname):
472 472 return self.opener.lexists(fname)
473 473
474 474 class workingbackend(fsbackend):
475 475 def __init__(self, ui, repo, similarity):
476 476 super(workingbackend, self).__init__(ui, repo.root)
477 477 self.repo = repo
478 478 self.similarity = similarity
479 479 self.removed = set()
480 480 self.changed = set()
481 481 self.copied = []
482 482
483 483 def _checkknown(self, fname):
484 484 if self.repo.dirstate[fname] == '?' and self.exists(fname):
485 485 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
486 486
487 487 def setfile(self, fname, data, mode, copysource):
488 488 self._checkknown(fname)
489 489 super(workingbackend, self).setfile(fname, data, mode, copysource)
490 490 if copysource is not None:
491 491 self.copied.append((copysource, fname))
492 492 self.changed.add(fname)
493 493
494 494 def unlink(self, fname):
495 495 self._checkknown(fname)
496 496 super(workingbackend, self).unlink(fname)
497 497 self.removed.add(fname)
498 498 self.changed.add(fname)
499 499
500 500 def close(self):
501 501 wctx = self.repo[None]
502 502 changed = set(self.changed)
503 503 for src, dst in self.copied:
504 504 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
505 505 if self.removed:
506 506 wctx.forget(sorted(self.removed))
507 507 for f in self.removed:
508 508 if f not in self.repo.dirstate:
509 509 # File was deleted and no longer belongs to the
510 510 # dirstate, it was probably marked added then
511 511 # deleted, and should not be considered by
512 512 # marktouched().
513 513 changed.discard(f)
514 514 if changed:
515 515 scmutil.marktouched(self.repo, changed, self.similarity)
516 516 return sorted(self.changed)
517 517
518 518 class filestore(object):
519 519 def __init__(self, maxsize=None):
520 520 self.opener = None
521 521 self.files = {}
522 522 self.created = 0
523 523 self.maxsize = maxsize
524 524 if self.maxsize is None:
525 525 self.maxsize = 4*(2**20)
526 526 self.size = 0
527 527 self.data = {}
528 528
529 529 def setfile(self, fname, data, mode, copied=None):
530 530 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
531 531 self.data[fname] = (data, mode, copied)
532 532 self.size += len(data)
533 533 else:
534 534 if self.opener is None:
535 535 root = tempfile.mkdtemp(prefix='hg-patch-')
536 536 self.opener = scmutil.opener(root)
537 537 # Avoid filename issues with these simple names
538 538 fn = str(self.created)
539 539 self.opener.write(fn, data)
540 540 self.created += 1
541 541 self.files[fname] = (fn, mode, copied)
542 542
543 543 def getfile(self, fname):
544 544 if fname in self.data:
545 545 return self.data[fname]
546 546 if not self.opener or fname not in self.files:
547 547 return None, None, None
548 548 fn, mode, copied = self.files[fname]
549 549 return self.opener.read(fn), mode, copied
550 550
551 551 def close(self):
552 552 if self.opener:
553 553 shutil.rmtree(self.opener.base)
554 554
555 555 class repobackend(abstractbackend):
556 556 def __init__(self, ui, repo, ctx, store):
557 557 super(repobackend, self).__init__(ui)
558 558 self.repo = repo
559 559 self.ctx = ctx
560 560 self.store = store
561 561 self.changed = set()
562 562 self.removed = set()
563 563 self.copied = {}
564 564
565 565 def _checkknown(self, fname):
566 566 if fname not in self.ctx:
567 567 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
568 568
569 569 def getfile(self, fname):
570 570 try:
571 571 fctx = self.ctx[fname]
572 572 except error.LookupError:
573 573 return None, None
574 574 flags = fctx.flags()
575 575 return fctx.data(), ('l' in flags, 'x' in flags)
576 576
577 577 def setfile(self, fname, data, mode, copysource):
578 578 if copysource:
579 579 self._checkknown(copysource)
580 580 if data is None:
581 581 data = self.ctx[fname].data()
582 582 self.store.setfile(fname, data, mode, copysource)
583 583 self.changed.add(fname)
584 584 if copysource:
585 585 self.copied[fname] = copysource
586 586
587 587 def unlink(self, fname):
588 588 self._checkknown(fname)
589 589 self.removed.add(fname)
590 590
591 591 def exists(self, fname):
592 592 return fname in self.ctx
593 593
594 594 def close(self):
595 595 return self.changed | self.removed
596 596
597 597 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
598 598 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
599 599 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
600 600 eolmodes = ['strict', 'crlf', 'lf', 'auto']
601 601
602 602 class patchfile(object):
603 603 def __init__(self, ui, gp, backend, store, eolmode='strict'):
604 604 self.fname = gp.path
605 605 self.eolmode = eolmode
606 606 self.eol = None
607 607 self.backend = backend
608 608 self.ui = ui
609 609 self.lines = []
610 610 self.exists = False
611 611 self.missing = True
612 612 self.mode = gp.mode
613 613 self.copysource = gp.oldpath
614 614 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
615 615 self.remove = gp.op == 'DELETE'
616 616 if self.copysource is None:
617 617 data, mode = backend.getfile(self.fname)
618 618 else:
619 619 data, mode = store.getfile(self.copysource)[:2]
620 620 if data is not None:
621 621 self.exists = self.copysource is None or backend.exists(self.fname)
622 622 self.missing = False
623 623 if data:
624 624 self.lines = mdiff.splitnewlines(data)
625 625 if self.mode is None:
626 626 self.mode = mode
627 627 if self.lines:
628 628 # Normalize line endings
629 629 if self.lines[0].endswith('\r\n'):
630 630 self.eol = '\r\n'
631 631 elif self.lines[0].endswith('\n'):
632 632 self.eol = '\n'
633 633 if eolmode != 'strict':
634 634 nlines = []
635 635 for l in self.lines:
636 636 if l.endswith('\r\n'):
637 637 l = l[:-2] + '\n'
638 638 nlines.append(l)
639 639 self.lines = nlines
640 640 else:
641 641 if self.create:
642 642 self.missing = False
643 643 if self.mode is None:
644 644 self.mode = (False, False)
645 645 if self.missing:
646 646 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
647 647
648 648 self.hash = {}
649 649 self.dirty = 0
650 650 self.offset = 0
651 651 self.skew = 0
652 652 self.rej = []
653 653 self.fileprinted = False
654 654 self.printfile(False)
655 655 self.hunks = 0
656 656
657 657 def writelines(self, fname, lines, mode):
658 658 if self.eolmode == 'auto':
659 659 eol = self.eol
660 660 elif self.eolmode == 'crlf':
661 661 eol = '\r\n'
662 662 else:
663 663 eol = '\n'
664 664
665 665 if self.eolmode != 'strict' and eol and eol != '\n':
666 666 rawlines = []
667 667 for l in lines:
668 668 if l and l[-1] == '\n':
669 669 l = l[:-1] + eol
670 670 rawlines.append(l)
671 671 lines = rawlines
672 672
673 673 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
674 674
675 675 def printfile(self, warn):
676 676 if self.fileprinted:
677 677 return
678 678 if warn or self.ui.verbose:
679 679 self.fileprinted = True
680 680 s = _("patching file %s\n") % self.fname
681 681 if warn:
682 682 self.ui.warn(s)
683 683 else:
684 684 self.ui.note(s)
685 685
686 686
687 687 def findlines(self, l, linenum):
688 688 # looks through the hash and finds candidate lines. The
689 689 # result is a list of line numbers sorted based on distance
690 690 # from linenum
691 691
692 692 cand = self.hash.get(l, [])
693 693 if len(cand) > 1:
694 694 # resort our list of potentials forward then back.
695 695 cand.sort(key=lambda x: abs(x - linenum))
696 696 return cand
697 697
698 698 def write_rej(self):
699 699 # our rejects are a little different from patch(1). This always
700 700 # creates rejects in the same form as the original patch. A file
701 701 # header is inserted so that you can run the reject through patch again
702 702 # without having to type the filename.
703 703 if not self.rej:
704 704 return
705 705 base = os.path.basename(self.fname)
706 706 lines = ["--- %s\n+++ %s\n" % (base, base)]
707 707 for x in self.rej:
708 708 for l in x.hunk:
709 709 lines.append(l)
710 710 if l[-1] != '\n':
711 711 lines.append("\n\ No newline at end of file\n")
712 712 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
713 713
714 714 def apply(self, h):
715 715 if not h.complete():
716 716 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
717 717 (h.number, h.desc, len(h.a), h.lena, len(h.b),
718 718 h.lenb))
719 719
720 720 self.hunks += 1
721 721
722 722 if self.missing:
723 723 self.rej.append(h)
724 724 return -1
725 725
726 726 if self.exists and self.create:
727 727 if self.copysource:
728 728 self.ui.warn(_("cannot create %s: destination already "
729 729 "exists\n") % self.fname)
730 730 else:
731 731 self.ui.warn(_("file %s already exists\n") % self.fname)
732 732 self.rej.append(h)
733 733 return -1
734 734
735 735 if isinstance(h, binhunk):
736 736 if self.remove:
737 737 self.backend.unlink(self.fname)
738 738 else:
739 739 l = h.new(self.lines)
740 740 self.lines[:] = l
741 741 self.offset += len(l)
742 742 self.dirty = True
743 743 return 0
744 744
745 745 horig = h
746 746 if (self.eolmode in ('crlf', 'lf')
747 747 or self.eolmode == 'auto' and self.eol):
748 748 # If new eols are going to be normalized, then normalize
749 749 # hunk data before patching. Otherwise, preserve input
750 750 # line-endings.
751 751 h = h.getnormalized()
752 752
753 753 # fast case first, no offsets, no fuzz
754 754 old, oldstart, new, newstart = h.fuzzit(0, False)
755 755 oldstart += self.offset
756 756 orig_start = oldstart
757 757 # if there's skew we want to emit the "(offset %d lines)" even
758 758 # when the hunk cleanly applies at start + skew, so skip the
759 759 # fast case code
760 760 if (self.skew == 0 and
761 761 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
762 762 if self.remove:
763 763 self.backend.unlink(self.fname)
764 764 else:
765 765 self.lines[oldstart:oldstart + len(old)] = new
766 766 self.offset += len(new) - len(old)
767 767 self.dirty = True
768 768 return 0
769 769
770 770 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
771 771 self.hash = {}
772 772 for x, s in enumerate(self.lines):
773 773 self.hash.setdefault(s, []).append(x)
774 774
775 775 for fuzzlen in xrange(3):
776 776 for toponly in [True, False]:
777 777 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
778 778 oldstart = oldstart + self.offset + self.skew
779 779 oldstart = min(oldstart, len(self.lines))
780 780 if old:
781 781 cand = self.findlines(old[0][1:], oldstart)
782 782 else:
783 783 # Only adding lines with no or fuzzed context, just
784 784 # take the skew in account
785 785 cand = [oldstart]
786 786
787 787 for l in cand:
788 788 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
789 789 self.lines[l : l + len(old)] = new
790 790 self.offset += len(new) - len(old)
791 791 self.skew = l - orig_start
792 792 self.dirty = True
793 793 offset = l - orig_start - fuzzlen
794 794 if fuzzlen:
795 795 msg = _("Hunk #%d succeeded at %d "
796 796 "with fuzz %d "
797 797 "(offset %d lines).\n")
798 798 self.printfile(True)
799 799 self.ui.warn(msg %
800 800 (h.number, l + 1, fuzzlen, offset))
801 801 else:
802 802 msg = _("Hunk #%d succeeded at %d "
803 803 "(offset %d lines).\n")
804 804 self.ui.note(msg % (h.number, l + 1, offset))
805 805 return fuzzlen
806 806 self.printfile(True)
807 807 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
808 808 self.rej.append(horig)
809 809 return -1
810 810
811 811 def close(self):
812 812 if self.dirty:
813 813 self.writelines(self.fname, self.lines, self.mode)
814 814 self.write_rej()
815 815 return len(self.rej)
816 816
817 817 class header(object):
818 818 """patch header
819 819 """
820 820 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
821 821 diff_re = re.compile('diff -r .* (.*)$')
822 822 allhunks_re = re.compile('(?:index|deleted file) ')
823 823 pretty_re = re.compile('(?:new file|deleted file) ')
824 824 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
825 825
826 826 def __init__(self, header):
827 827 self.header = header
828 828 self.hunks = []
829 829
830 830 def binary(self):
831 831 return util.any(h.startswith('index ') for h in self.header)
832 832
833 833 def pretty(self, fp):
834 834 for h in self.header:
835 835 if h.startswith('index '):
836 836 fp.write(_('this modifies a binary file (all or nothing)\n'))
837 837 break
838 838 if self.pretty_re.match(h):
839 839 fp.write(h)
840 840 if self.binary():
841 841 fp.write(_('this is a binary file\n'))
842 842 break
843 843 if h.startswith('---'):
844 844 fp.write(_('%d hunks, %d lines changed\n') %
845 845 (len(self.hunks),
846 846 sum([max(h.added, h.removed) for h in self.hunks])))
847 847 break
848 848 fp.write(h)
849 849
850 850 def write(self, fp):
851 851 fp.write(''.join(self.header))
852 852
853 853 def allhunks(self):
854 854 return util.any(self.allhunks_re.match(h) for h in self.header)
855 855
856 856 def files(self):
857 857 match = self.diffgit_re.match(self.header[0])
858 858 if match:
859 859 fromfile, tofile = match.groups()
860 860 if fromfile == tofile:
861 861 return [fromfile]
862 862 return [fromfile, tofile]
863 863 else:
864 864 return self.diff_re.match(self.header[0]).groups()
865 865
866 866 def filename(self):
867 867 return self.files()[-1]
868 868
869 869 def __repr__(self):
870 870 return '<header %s>' % (' '.join(map(repr, self.files())))
871 871
872 872 def special(self):
873 873 return util.any(self.special_re.match(h) for h in self.header)
874 874
875 875 class recordhunk(object):
876 876 """patch hunk
877 877
878 878 XXX shouldn't we merge this with the other hunk class?
879 879 """
880 880 maxcontext = 3
881 881
882 882 def __init__(self, header, fromline, toline, proc, before, hunk, after):
883 883 def trimcontext(number, lines):
884 884 delta = len(lines) - self.maxcontext
885 885 if False and delta > 0:
886 886 return number + delta, lines[:self.maxcontext]
887 887 return number, lines
888 888
889 889 self.header = header
890 890 self.fromline, self.before = trimcontext(fromline, before)
891 891 self.toline, self.after = trimcontext(toline, after)
892 892 self.proc = proc
893 893 self.hunk = hunk
894 894 self.added, self.removed = self.countchanges(self.hunk)
895 895
896 896 def __eq__(self, v):
897 897 if not isinstance(v, recordhunk):
898 898 return False
899 899
900 900 return ((v.hunk == self.hunk) and
901 901 (v.proc == self.proc) and
902 902 (self.fromline == v.fromline) and
903 903 (self.header.files() == v.header.files()))
904 904
905 905 def __hash__(self):
906 906 return hash((tuple(self.hunk),
907 907 tuple(self.header.files()),
908 908 self.fromline,
909 909 self.proc))
910 910
911 911 def countchanges(self, hunk):
912 912 """hunk -> (n+,n-)"""
913 913 add = len([h for h in hunk if h[0] == '+'])
914 914 rem = len([h for h in hunk if h[0] == '-'])
915 915 return add, rem
916 916
917 917 def write(self, fp):
918 918 delta = len(self.before) + len(self.after)
919 919 if self.after and self.after[-1] == '\\ No newline at end of file\n':
920 920 delta -= 1
921 921 fromlen = delta + self.removed
922 922 tolen = delta + self.added
923 923 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
924 924 (self.fromline, fromlen, self.toline, tolen,
925 925 self.proc and (' ' + self.proc)))
926 926 fp.write(''.join(self.before + self.hunk + self.after))
927 927
928 928 pretty = write
929 929
930 930 def filename(self):
931 931 return self.header.filename()
932 932
933 933 def __repr__(self):
934 934 return '<hunk %r@%d>' % (self.filename(), self.fromline)
935 935
936 936 def filterpatch(ui, headers):
937 937 """Interactively filter patch chunks into applied-only chunks"""
938 938
939 939 def prompt(skipfile, skipall, query, chunk):
940 940 """prompt query, and process base inputs
941 941
942 942 - y/n for the rest of file
943 943 - y/n for the rest
944 944 - ? (help)
945 945 - q (quit)
946 946
947 947 Return True/False and possibly updated skipfile and skipall.
948 948 """
949 949 newpatches = None
950 950 if skipall is not None:
951 951 return skipall, skipfile, skipall, newpatches
952 952 if skipfile is not None:
953 953 return skipfile, skipfile, skipall, newpatches
954 954 while True:
955 955 resps = _('[Ynesfdaq?]'
956 956 '$$ &Yes, record this change'
957 957 '$$ &No, skip this change'
958 958 '$$ &Edit this change manually'
959 959 '$$ &Skip remaining changes to this file'
960 960 '$$ Record remaining changes to this &file'
961 961 '$$ &Done, skip remaining changes and files'
962 962 '$$ Record &all changes to all remaining files'
963 963 '$$ &Quit, recording no changes'
964 964 '$$ &? (display help)')
965 965 r = ui.promptchoice("%s %s" % (query, resps))
966 966 ui.write("\n")
967 967 if r == 8: # ?
968 968 for c, t in ui.extractchoices(resps)[1]:
969 969 ui.write('%s - %s\n' % (c, t.lower()))
970 970 continue
971 971 elif r == 0: # yes
972 972 ret = True
973 973 elif r == 1: # no
974 974 ret = False
975 975 elif r == 2: # Edit patch
976 976 if chunk is None:
977 977 ui.write(_('cannot edit patch for whole file'))
978 978 ui.write("\n")
979 979 continue
980 980 if chunk.header.binary():
981 981 ui.write(_('cannot edit patch for binary file'))
982 982 ui.write("\n")
983 983 continue
984 984 # Patch comment based on the Git one (based on comment at end of
985 985 # http://mercurial.selenic.com/wiki/RecordExtension)
986 986 phelp = '---' + _("""
987 987 To remove '-' lines, make them ' ' lines (context).
988 988 To remove '+' lines, delete them.
989 989 Lines starting with # will be removed from the patch.
990 990
991 991 If the patch applies cleanly, the edited hunk will immediately be
992 992 added to the record list. If it does not apply cleanly, a rejects
993 993 file will be generated: you can use that when you try again. If
994 994 all lines of the hunk are removed, then the edit is aborted and
995 995 the hunk is left unchanged.
996 996 """)
997 997 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
998 998 suffix=".diff", text=True)
999 999 ncpatchfp = None
1000 1000 try:
1001 1001 # Write the initial patch
1002 1002 f = os.fdopen(patchfd, "w")
1003 1003 chunk.header.write(f)
1004 1004 chunk.write(f)
1005 1005 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1006 1006 f.close()
1007 1007 # Start the editor and wait for it to complete
1008 1008 editor = ui.geteditor()
1009 1009 ui.system("%s \"%s\"" % (editor, patchfn),
1010 1010 environ={'HGUSER': ui.username()},
1011 1011 onerr=util.Abort, errprefix=_("edit failed"))
1012 1012 # Remove comment lines
1013 1013 patchfp = open(patchfn)
1014 1014 ncpatchfp = cStringIO.StringIO()
1015 1015 for line in patchfp:
1016 1016 if not line.startswith('#'):
1017 1017 ncpatchfp.write(line)
1018 1018 patchfp.close()
1019 1019 ncpatchfp.seek(0)
1020 1020 newpatches = parsepatch(ncpatchfp)
1021 1021 finally:
1022 1022 os.unlink(patchfn)
1023 1023 del ncpatchfp
1024 1024 # Signal that the chunk shouldn't be applied as-is, but
1025 1025 # provide the new patch to be used instead.
1026 1026 ret = False
1027 1027 elif r == 3: # Skip
1028 1028 ret = skipfile = False
1029 1029 elif r == 4: # file (Record remaining)
1030 1030 ret = skipfile = True
1031 1031 elif r == 5: # done, skip remaining
1032 1032 ret = skipall = False
1033 1033 elif r == 6: # all
1034 1034 ret = skipall = True
1035 1035 elif r == 7: # quit
1036 1036 raise util.Abort(_('user quit'))
1037 1037 return ret, skipfile, skipall, newpatches
1038 1038
1039 1039 seen = set()
1040 1040 applied = {} # 'filename' -> [] of chunks
1041 1041 skipfile, skipall = None, None
1042 1042 pos, total = 1, sum(len(h.hunks) for h in headers)
1043 1043 for h in headers:
1044 1044 pos += len(h.hunks)
1045 1045 skipfile = None
1046 1046 fixoffset = 0
1047 1047 hdr = ''.join(h.header)
1048 1048 if hdr in seen:
1049 1049 continue
1050 1050 seen.add(hdr)
1051 1051 if skipall is None:
1052 1052 h.pretty(ui)
1053 1053 msg = (_('examine changes to %s?') %
1054 1054 _(' and ').join("'%s'" % f for f in h.files()))
1055 1055 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1056 1056 if not r:
1057 1057 continue
1058 1058 applied[h.filename()] = [h]
1059 1059 if h.allhunks():
1060 1060 applied[h.filename()] += h.hunks
1061 1061 continue
1062 1062 for i, chunk in enumerate(h.hunks):
1063 1063 if skipfile is None and skipall is None:
1064 1064 chunk.pretty(ui)
1065 1065 if total == 1:
1066 1066 msg = _("record this change to '%s'?") % chunk.filename()
1067 1067 else:
1068 1068 idx = pos - len(h.hunks) + i
1069 1069 msg = _("record change %d/%d to '%s'?") % (idx, total,
1070 1070 chunk.filename())
1071 1071 r, skipfile, skipall, newpatches = prompt(skipfile,
1072 1072 skipall, msg, chunk)
1073 1073 if r:
1074 1074 if fixoffset:
1075 1075 chunk = copy.copy(chunk)
1076 1076 chunk.toline += fixoffset
1077 1077 applied[chunk.filename()].append(chunk)
1078 1078 elif newpatches is not None:
1079 1079 for newpatch in newpatches:
1080 1080 for newhunk in newpatch.hunks:
1081 1081 if fixoffset:
1082 1082 newhunk.toline += fixoffset
1083 1083 applied[newhunk.filename()].append(newhunk)
1084 1084 else:
1085 1085 fixoffset += chunk.removed - chunk.added
1086 1086 return sum([h for h in applied.itervalues()
1087 1087 if h[0].special() or len(h) > 1], [])
1088 1088 class hunk(object):
1089 1089 def __init__(self, desc, num, lr, context):
1090 1090 self.number = num
1091 1091 self.desc = desc
1092 1092 self.hunk = [desc]
1093 1093 self.a = []
1094 1094 self.b = []
1095 1095 self.starta = self.lena = None
1096 1096 self.startb = self.lenb = None
1097 1097 if lr is not None:
1098 1098 if context:
1099 1099 self.read_context_hunk(lr)
1100 1100 else:
1101 1101 self.read_unified_hunk(lr)
1102 1102
1103 1103 def getnormalized(self):
1104 1104 """Return a copy with line endings normalized to LF."""
1105 1105
1106 1106 def normalize(lines):
1107 1107 nlines = []
1108 1108 for line in lines:
1109 1109 if line.endswith('\r\n'):
1110 1110 line = line[:-2] + '\n'
1111 1111 nlines.append(line)
1112 1112 return nlines
1113 1113
1114 1114 # Dummy object, it is rebuilt manually
1115 1115 nh = hunk(self.desc, self.number, None, None)
1116 1116 nh.number = self.number
1117 1117 nh.desc = self.desc
1118 1118 nh.hunk = self.hunk
1119 1119 nh.a = normalize(self.a)
1120 1120 nh.b = normalize(self.b)
1121 1121 nh.starta = self.starta
1122 1122 nh.startb = self.startb
1123 1123 nh.lena = self.lena
1124 1124 nh.lenb = self.lenb
1125 1125 return nh
1126 1126
1127 1127 def read_unified_hunk(self, lr):
1128 1128 m = unidesc.match(self.desc)
1129 1129 if not m:
1130 1130 raise PatchError(_("bad hunk #%d") % self.number)
1131 1131 self.starta, self.lena, self.startb, self.lenb = m.groups()
1132 1132 if self.lena is None:
1133 1133 self.lena = 1
1134 1134 else:
1135 1135 self.lena = int(self.lena)
1136 1136 if self.lenb is None:
1137 1137 self.lenb = 1
1138 1138 else:
1139 1139 self.lenb = int(self.lenb)
1140 1140 self.starta = int(self.starta)
1141 1141 self.startb = int(self.startb)
1142 1142 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1143 1143 self.b)
1144 1144 # if we hit eof before finishing out the hunk, the last line will
1145 1145 # be zero length. Lets try to fix it up.
1146 1146 while len(self.hunk[-1]) == 0:
1147 1147 del self.hunk[-1]
1148 1148 del self.a[-1]
1149 1149 del self.b[-1]
1150 1150 self.lena -= 1
1151 1151 self.lenb -= 1
1152 1152 self._fixnewline(lr)
1153 1153
1154 1154 def read_context_hunk(self, lr):
1155 1155 self.desc = lr.readline()
1156 1156 m = contextdesc.match(self.desc)
1157 1157 if not m:
1158 1158 raise PatchError(_("bad hunk #%d") % self.number)
1159 1159 self.starta, aend = m.groups()
1160 1160 self.starta = int(self.starta)
1161 1161 if aend is None:
1162 1162 aend = self.starta
1163 1163 self.lena = int(aend) - self.starta
1164 1164 if self.starta:
1165 1165 self.lena += 1
1166 1166 for x in xrange(self.lena):
1167 1167 l = lr.readline()
1168 1168 if l.startswith('---'):
1169 1169 # lines addition, old block is empty
1170 1170 lr.push(l)
1171 1171 break
1172 1172 s = l[2:]
1173 1173 if l.startswith('- ') or l.startswith('! '):
1174 1174 u = '-' + s
1175 1175 elif l.startswith(' '):
1176 1176 u = ' ' + s
1177 1177 else:
1178 1178 raise PatchError(_("bad hunk #%d old text line %d") %
1179 1179 (self.number, x))
1180 1180 self.a.append(u)
1181 1181 self.hunk.append(u)
1182 1182
1183 1183 l = lr.readline()
1184 1184 if l.startswith('\ '):
1185 1185 s = self.a[-1][:-1]
1186 1186 self.a[-1] = s
1187 1187 self.hunk[-1] = s
1188 1188 l = lr.readline()
1189 1189 m = contextdesc.match(l)
1190 1190 if not m:
1191 1191 raise PatchError(_("bad hunk #%d") % self.number)
1192 1192 self.startb, bend = m.groups()
1193 1193 self.startb = int(self.startb)
1194 1194 if bend is None:
1195 1195 bend = self.startb
1196 1196 self.lenb = int(bend) - self.startb
1197 1197 if self.startb:
1198 1198 self.lenb += 1
1199 1199 hunki = 1
1200 1200 for x in xrange(self.lenb):
1201 1201 l = lr.readline()
1202 1202 if l.startswith('\ '):
1203 1203 # XXX: the only way to hit this is with an invalid line range.
1204 1204 # The no-eol marker is not counted in the line range, but I
1205 1205 # guess there are diff(1) out there which behave differently.
1206 1206 s = self.b[-1][:-1]
1207 1207 self.b[-1] = s
1208 1208 self.hunk[hunki - 1] = s
1209 1209 continue
1210 1210 if not l:
1211 1211 # line deletions, new block is empty and we hit EOF
1212 1212 lr.push(l)
1213 1213 break
1214 1214 s = l[2:]
1215 1215 if l.startswith('+ ') or l.startswith('! '):
1216 1216 u = '+' + s
1217 1217 elif l.startswith(' '):
1218 1218 u = ' ' + s
1219 1219 elif len(self.b) == 0:
1220 1220 # line deletions, new block is empty
1221 1221 lr.push(l)
1222 1222 break
1223 1223 else:
1224 1224 raise PatchError(_("bad hunk #%d old text line %d") %
1225 1225 (self.number, x))
1226 1226 self.b.append(s)
1227 1227 while True:
1228 1228 if hunki >= len(self.hunk):
1229 1229 h = ""
1230 1230 else:
1231 1231 h = self.hunk[hunki]
1232 1232 hunki += 1
1233 1233 if h == u:
1234 1234 break
1235 1235 elif h.startswith('-'):
1236 1236 continue
1237 1237 else:
1238 1238 self.hunk.insert(hunki - 1, u)
1239 1239 break
1240 1240
1241 1241 if not self.a:
1242 1242 # this happens when lines were only added to the hunk
1243 1243 for x in self.hunk:
1244 1244 if x.startswith('-') or x.startswith(' '):
1245 1245 self.a.append(x)
1246 1246 if not self.b:
1247 1247 # this happens when lines were only deleted from the hunk
1248 1248 for x in self.hunk:
1249 1249 if x.startswith('+') or x.startswith(' '):
1250 1250 self.b.append(x[1:])
1251 1251 # @@ -start,len +start,len @@
1252 1252 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1253 1253 self.startb, self.lenb)
1254 1254 self.hunk[0] = self.desc
1255 1255 self._fixnewline(lr)
1256 1256
1257 1257 def _fixnewline(self, lr):
1258 1258 l = lr.readline()
1259 1259 if l.startswith('\ '):
1260 1260 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1261 1261 else:
1262 1262 lr.push(l)
1263 1263
1264 1264 def complete(self):
1265 1265 return len(self.a) == self.lena and len(self.b) == self.lenb
1266 1266
1267 1267 def _fuzzit(self, old, new, fuzz, toponly):
1268 1268 # this removes context lines from the top and bottom of list 'l'. It
1269 1269 # checks the hunk to make sure only context lines are removed, and then
1270 1270 # returns a new shortened list of lines.
1271 1271 fuzz = min(fuzz, len(old))
1272 1272 if fuzz:
1273 1273 top = 0
1274 1274 bot = 0
1275 1275 hlen = len(self.hunk)
1276 1276 for x in xrange(hlen - 1):
1277 1277 # the hunk starts with the @@ line, so use x+1
1278 1278 if self.hunk[x + 1][0] == ' ':
1279 1279 top += 1
1280 1280 else:
1281 1281 break
1282 1282 if not toponly:
1283 1283 for x in xrange(hlen - 1):
1284 1284 if self.hunk[hlen - bot - 1][0] == ' ':
1285 1285 bot += 1
1286 1286 else:
1287 1287 break
1288 1288
1289 1289 bot = min(fuzz, bot)
1290 1290 top = min(fuzz, top)
1291 1291 return old[top:len(old) - bot], new[top:len(new) - bot], top
1292 1292 return old, new, 0
1293 1293
1294 1294 def fuzzit(self, fuzz, toponly):
1295 1295 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1296 1296 oldstart = self.starta + top
1297 1297 newstart = self.startb + top
1298 1298 # zero length hunk ranges already have their start decremented
1299 1299 if self.lena and oldstart > 0:
1300 1300 oldstart -= 1
1301 1301 if self.lenb and newstart > 0:
1302 1302 newstart -= 1
1303 1303 return old, oldstart, new, newstart
1304 1304
1305 1305 class binhunk(object):
1306 1306 'A binary patch file.'
1307 1307 def __init__(self, lr, fname):
1308 1308 self.text = None
1309 1309 self.delta = False
1310 1310 self.hunk = ['GIT binary patch\n']
1311 1311 self._fname = fname
1312 1312 self._read(lr)
1313 1313
1314 1314 def complete(self):
1315 1315 return self.text is not None
1316 1316
1317 1317 def new(self, lines):
1318 1318 if self.delta:
1319 1319 return [applybindelta(self.text, ''.join(lines))]
1320 1320 return [self.text]
1321 1321
1322 1322 def _read(self, lr):
1323 1323 def getline(lr, hunk):
1324 1324 l = lr.readline()
1325 1325 hunk.append(l)
1326 1326 return l.rstrip('\r\n')
1327 1327
1328 1328 size = 0
1329 1329 while True:
1330 1330 line = getline(lr, self.hunk)
1331 1331 if not line:
1332 1332 raise PatchError(_('could not extract "%s" binary data')
1333 1333 % self._fname)
1334 1334 if line.startswith('literal '):
1335 1335 size = int(line[8:].rstrip())
1336 1336 break
1337 1337 if line.startswith('delta '):
1338 1338 size = int(line[6:].rstrip())
1339 1339 self.delta = True
1340 1340 break
1341 1341 dec = []
1342 1342 line = getline(lr, self.hunk)
1343 1343 while len(line) > 1:
1344 1344 l = line[0]
1345 1345 if l <= 'Z' and l >= 'A':
1346 1346 l = ord(l) - ord('A') + 1
1347 1347 else:
1348 1348 l = ord(l) - ord('a') + 27
1349 1349 try:
1350 1350 dec.append(base85.b85decode(line[1:])[:l])
1351 1351 except ValueError, e:
1352 1352 raise PatchError(_('could not decode "%s" binary patch: %s')
1353 1353 % (self._fname, str(e)))
1354 1354 line = getline(lr, self.hunk)
1355 1355 text = zlib.decompress(''.join(dec))
1356 1356 if len(text) != size:
1357 1357 raise PatchError(_('"%s" length is %d bytes, should be %d')
1358 1358 % (self._fname, len(text), size))
1359 1359 self.text = text
1360 1360
1361 1361 def parsefilename(str):
1362 1362 # --- filename \t|space stuff
1363 1363 s = str[4:].rstrip('\r\n')
1364 1364 i = s.find('\t')
1365 1365 if i < 0:
1366 1366 i = s.find(' ')
1367 1367 if i < 0:
1368 1368 return s
1369 1369 return s[:i]
1370 1370
1371 1371 def parsepatch(originalchunks):
1372 1372 """patch -> [] of headers -> [] of hunks """
1373 1373 class parser(object):
1374 1374 """patch parsing state machine"""
1375 1375 def __init__(self):
1376 1376 self.fromline = 0
1377 1377 self.toline = 0
1378 1378 self.proc = ''
1379 1379 self.header = None
1380 1380 self.context = []
1381 1381 self.before = []
1382 1382 self.hunk = []
1383 1383 self.headers = []
1384 1384
1385 1385 def addrange(self, limits):
1386 1386 fromstart, fromend, tostart, toend, proc = limits
1387 1387 self.fromline = int(fromstart)
1388 1388 self.toline = int(tostart)
1389 1389 self.proc = proc
1390 1390
1391 1391 def addcontext(self, context):
1392 1392 if self.hunk:
1393 1393 h = recordhunk(self.header, self.fromline, self.toline,
1394 1394 self.proc, self.before, self.hunk, context)
1395 1395 self.header.hunks.append(h)
1396 1396 self.fromline += len(self.before) + h.removed
1397 1397 self.toline += len(self.before) + h.added
1398 1398 self.before = []
1399 1399 self.hunk = []
1400 1400 self.proc = ''
1401 1401 self.context = context
1402 1402
1403 1403 def addhunk(self, hunk):
1404 1404 if self.context:
1405 1405 self.before = self.context
1406 1406 self.context = []
1407 1407 self.hunk = hunk
1408 1408
1409 1409 def newfile(self, hdr):
1410 1410 self.addcontext([])
1411 1411 h = header(hdr)
1412 1412 self.headers.append(h)
1413 1413 self.header = h
1414 1414
1415 1415 def addother(self, line):
1416 1416 pass # 'other' lines are ignored
1417 1417
1418 1418 def finished(self):
1419 1419 self.addcontext([])
1420 1420 return self.headers
1421 1421
1422 1422 transitions = {
1423 1423 'file': {'context': addcontext,
1424 1424 'file': newfile,
1425 1425 'hunk': addhunk,
1426 1426 'range': addrange},
1427 1427 'context': {'file': newfile,
1428 1428 'hunk': addhunk,
1429 1429 'range': addrange,
1430 1430 'other': addother},
1431 1431 'hunk': {'context': addcontext,
1432 1432 'file': newfile,
1433 1433 'range': addrange},
1434 1434 'range': {'context': addcontext,
1435 1435 'hunk': addhunk},
1436 1436 'other': {'other': addother},
1437 1437 }
1438 1438
1439 1439 p = parser()
1440 1440 fp = cStringIO.StringIO()
1441 1441 fp.write(''.join(originalchunks))
1442 1442 fp.seek(0)
1443 1443
1444 1444 state = 'context'
1445 1445 for newstate, data in scanpatch(fp):
1446 1446 try:
1447 1447 p.transitions[state][newstate](p, data)
1448 1448 except KeyError:
1449 1449 raise PatchError('unhandled transition: %s -> %s' %
1450 1450 (state, newstate))
1451 1451 state = newstate
1452 1452 del fp
1453 1453 return p.finished()
1454 1454
1455 1455 def pathtransform(path, strip, prefix):
1456 1456 '''turn a path from a patch into a path suitable for the repository
1457 1457
1458 1458 prefix, if not empty, is expected to be normalized with a / at the end.
1459 1459
1460 1460 Returns (stripped components, path in repository).
1461 1461
1462 1462 >>> pathtransform('a/b/c', 0, '')
1463 1463 ('', 'a/b/c')
1464 1464 >>> pathtransform(' a/b/c ', 0, '')
1465 1465 ('', ' a/b/c')
1466 1466 >>> pathtransform(' a/b/c ', 2, '')
1467 1467 ('a/b/', 'c')
1468 >>> pathtransform('a/b/c', 0, 'd/e/')
1469 ('', 'd/e/a/b/c')
1468 1470 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1469 1471 ('a//b/', 'd/e/c')
1470 1472 >>> pathtransform('a/b/c', 3, '')
1471 1473 Traceback (most recent call last):
1472 1474 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1473 1475 '''
1474 1476 pathlen = len(path)
1475 1477 i = 0
1476 1478 if strip == 0:
1477 return '', path.rstrip()
1479 return '', prefix + path.rstrip()
1478 1480 count = strip
1479 1481 while count > 0:
1480 1482 i = path.find('/', i)
1481 1483 if i == -1:
1482 1484 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1483 1485 (count, strip, path))
1484 1486 i += 1
1485 1487 # consume '//' in the path
1486 1488 while i < pathlen - 1 and path[i] == '/':
1487 1489 i += 1
1488 1490 count -= 1
1489 1491 return path[:i].lstrip(), prefix + path[i:].rstrip()
1490 1492
1491 1493 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1492 1494 nulla = afile_orig == "/dev/null"
1493 1495 nullb = bfile_orig == "/dev/null"
1494 1496 create = nulla and hunk.starta == 0 and hunk.lena == 0
1495 1497 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1496 1498 abase, afile = pathtransform(afile_orig, strip, prefix)
1497 1499 gooda = not nulla and backend.exists(afile)
1498 1500 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1499 1501 if afile == bfile:
1500 1502 goodb = gooda
1501 1503 else:
1502 1504 goodb = not nullb and backend.exists(bfile)
1503 1505 missing = not goodb and not gooda and not create
1504 1506
1505 1507 # some diff programs apparently produce patches where the afile is
1506 1508 # not /dev/null, but afile starts with bfile
1507 1509 abasedir = afile[:afile.rfind('/') + 1]
1508 1510 bbasedir = bfile[:bfile.rfind('/') + 1]
1509 1511 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1510 1512 and hunk.starta == 0 and hunk.lena == 0):
1511 1513 create = True
1512 1514 missing = False
1513 1515
1514 1516 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1515 1517 # diff is between a file and its backup. In this case, the original
1516 1518 # file should be patched (see original mpatch code).
1517 1519 isbackup = (abase == bbase and bfile.startswith(afile))
1518 1520 fname = None
1519 1521 if not missing:
1520 1522 if gooda and goodb:
1521 1523 if isbackup:
1522 1524 fname = afile
1523 1525 else:
1524 1526 fname = bfile
1525 1527 elif gooda:
1526 1528 fname = afile
1527 1529
1528 1530 if not fname:
1529 1531 if not nullb:
1530 1532 if isbackup:
1531 1533 fname = afile
1532 1534 else:
1533 1535 fname = bfile
1534 1536 elif not nulla:
1535 1537 fname = afile
1536 1538 else:
1537 1539 raise PatchError(_("undefined source and destination files"))
1538 1540
1539 1541 gp = patchmeta(fname)
1540 1542 if create:
1541 1543 gp.op = 'ADD'
1542 1544 elif remove:
1543 1545 gp.op = 'DELETE'
1544 1546 return gp
1545 1547
1546 1548 def scanpatch(fp):
1547 1549 """like patch.iterhunks, but yield different events
1548 1550
1549 1551 - ('file', [header_lines + fromfile + tofile])
1550 1552 - ('context', [context_lines])
1551 1553 - ('hunk', [hunk_lines])
1552 1554 - ('range', (-start,len, +start,len, proc))
1553 1555 """
1554 1556 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1555 1557 lr = linereader(fp)
1556 1558
1557 1559 def scanwhile(first, p):
1558 1560 """scan lr while predicate holds"""
1559 1561 lines = [first]
1560 1562 while True:
1561 1563 line = lr.readline()
1562 1564 if not line:
1563 1565 break
1564 1566 if p(line):
1565 1567 lines.append(line)
1566 1568 else:
1567 1569 lr.push(line)
1568 1570 break
1569 1571 return lines
1570 1572
1571 1573 while True:
1572 1574 line = lr.readline()
1573 1575 if not line:
1574 1576 break
1575 1577 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1576 1578 def notheader(line):
1577 1579 s = line.split(None, 1)
1578 1580 return not s or s[0] not in ('---', 'diff')
1579 1581 header = scanwhile(line, notheader)
1580 1582 fromfile = lr.readline()
1581 1583 if fromfile.startswith('---'):
1582 1584 tofile = lr.readline()
1583 1585 header += [fromfile, tofile]
1584 1586 else:
1585 1587 lr.push(fromfile)
1586 1588 yield 'file', header
1587 1589 elif line[0] == ' ':
1588 1590 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1589 1591 elif line[0] in '-+':
1590 1592 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1591 1593 else:
1592 1594 m = lines_re.match(line)
1593 1595 if m:
1594 1596 yield 'range', m.groups()
1595 1597 else:
1596 1598 yield 'other', line
1597 1599
1598 1600 def scangitpatch(lr, firstline):
1599 1601 """
1600 1602 Git patches can emit:
1601 1603 - rename a to b
1602 1604 - change b
1603 1605 - copy a to c
1604 1606 - change c
1605 1607
1606 1608 We cannot apply this sequence as-is, the renamed 'a' could not be
1607 1609 found for it would have been renamed already. And we cannot copy
1608 1610 from 'b' instead because 'b' would have been changed already. So
1609 1611 we scan the git patch for copy and rename commands so we can
1610 1612 perform the copies ahead of time.
1611 1613 """
1612 1614 pos = 0
1613 1615 try:
1614 1616 pos = lr.fp.tell()
1615 1617 fp = lr.fp
1616 1618 except IOError:
1617 1619 fp = cStringIO.StringIO(lr.fp.read())
1618 1620 gitlr = linereader(fp)
1619 1621 gitlr.push(firstline)
1620 1622 gitpatches = readgitpatch(gitlr)
1621 1623 fp.seek(pos)
1622 1624 return gitpatches
1623 1625
1624 1626 def iterhunks(fp):
1625 1627 """Read a patch and yield the following events:
1626 1628 - ("file", afile, bfile, firsthunk): select a new target file.
1627 1629 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1628 1630 "file" event.
1629 1631 - ("git", gitchanges): current diff is in git format, gitchanges
1630 1632 maps filenames to gitpatch records. Unique event.
1631 1633 """
1632 1634 afile = ""
1633 1635 bfile = ""
1634 1636 state = None
1635 1637 hunknum = 0
1636 1638 emitfile = newfile = False
1637 1639 gitpatches = None
1638 1640
1639 1641 # our states
1640 1642 BFILE = 1
1641 1643 context = None
1642 1644 lr = linereader(fp)
1643 1645
1644 1646 while True:
1645 1647 x = lr.readline()
1646 1648 if not x:
1647 1649 break
1648 1650 if state == BFILE and (
1649 1651 (not context and x[0] == '@')
1650 1652 or (context is not False and x.startswith('***************'))
1651 1653 or x.startswith('GIT binary patch')):
1652 1654 gp = None
1653 1655 if (gitpatches and
1654 1656 gitpatches[-1].ispatching(afile, bfile)):
1655 1657 gp = gitpatches.pop()
1656 1658 if x.startswith('GIT binary patch'):
1657 1659 h = binhunk(lr, gp.path)
1658 1660 else:
1659 1661 if context is None and x.startswith('***************'):
1660 1662 context = True
1661 1663 h = hunk(x, hunknum + 1, lr, context)
1662 1664 hunknum += 1
1663 1665 if emitfile:
1664 1666 emitfile = False
1665 1667 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1666 1668 yield 'hunk', h
1667 1669 elif x.startswith('diff --git a/'):
1668 1670 m = gitre.match(x.rstrip(' \r\n'))
1669 1671 if not m:
1670 1672 continue
1671 1673 if gitpatches is None:
1672 1674 # scan whole input for git metadata
1673 1675 gitpatches = scangitpatch(lr, x)
1674 1676 yield 'git', [g.copy() for g in gitpatches
1675 1677 if g.op in ('COPY', 'RENAME')]
1676 1678 gitpatches.reverse()
1677 1679 afile = 'a/' + m.group(1)
1678 1680 bfile = 'b/' + m.group(2)
1679 1681 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1680 1682 gp = gitpatches.pop()
1681 1683 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1682 1684 if not gitpatches:
1683 1685 raise PatchError(_('failed to synchronize metadata for "%s"')
1684 1686 % afile[2:])
1685 1687 gp = gitpatches[-1]
1686 1688 newfile = True
1687 1689 elif x.startswith('---'):
1688 1690 # check for a unified diff
1689 1691 l2 = lr.readline()
1690 1692 if not l2.startswith('+++'):
1691 1693 lr.push(l2)
1692 1694 continue
1693 1695 newfile = True
1694 1696 context = False
1695 1697 afile = parsefilename(x)
1696 1698 bfile = parsefilename(l2)
1697 1699 elif x.startswith('***'):
1698 1700 # check for a context diff
1699 1701 l2 = lr.readline()
1700 1702 if not l2.startswith('---'):
1701 1703 lr.push(l2)
1702 1704 continue
1703 1705 l3 = lr.readline()
1704 1706 lr.push(l3)
1705 1707 if not l3.startswith("***************"):
1706 1708 lr.push(l2)
1707 1709 continue
1708 1710 newfile = True
1709 1711 context = True
1710 1712 afile = parsefilename(x)
1711 1713 bfile = parsefilename(l2)
1712 1714
1713 1715 if newfile:
1714 1716 newfile = False
1715 1717 emitfile = True
1716 1718 state = BFILE
1717 1719 hunknum = 0
1718 1720
1719 1721 while gitpatches:
1720 1722 gp = gitpatches.pop()
1721 1723 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1722 1724
1723 1725 def applybindelta(binchunk, data):
1724 1726 """Apply a binary delta hunk
1725 1727 The algorithm used is the algorithm from git's patch-delta.c
1726 1728 """
1727 1729 def deltahead(binchunk):
1728 1730 i = 0
1729 1731 for c in binchunk:
1730 1732 i += 1
1731 1733 if not (ord(c) & 0x80):
1732 1734 return i
1733 1735 return i
1734 1736 out = ""
1735 1737 s = deltahead(binchunk)
1736 1738 binchunk = binchunk[s:]
1737 1739 s = deltahead(binchunk)
1738 1740 binchunk = binchunk[s:]
1739 1741 i = 0
1740 1742 while i < len(binchunk):
1741 1743 cmd = ord(binchunk[i])
1742 1744 i += 1
1743 1745 if (cmd & 0x80):
1744 1746 offset = 0
1745 1747 size = 0
1746 1748 if (cmd & 0x01):
1747 1749 offset = ord(binchunk[i])
1748 1750 i += 1
1749 1751 if (cmd & 0x02):
1750 1752 offset |= ord(binchunk[i]) << 8
1751 1753 i += 1
1752 1754 if (cmd & 0x04):
1753 1755 offset |= ord(binchunk[i]) << 16
1754 1756 i += 1
1755 1757 if (cmd & 0x08):
1756 1758 offset |= ord(binchunk[i]) << 24
1757 1759 i += 1
1758 1760 if (cmd & 0x10):
1759 1761 size = ord(binchunk[i])
1760 1762 i += 1
1761 1763 if (cmd & 0x20):
1762 1764 size |= ord(binchunk[i]) << 8
1763 1765 i += 1
1764 1766 if (cmd & 0x40):
1765 1767 size |= ord(binchunk[i]) << 16
1766 1768 i += 1
1767 1769 if size == 0:
1768 1770 size = 0x10000
1769 1771 offset_end = offset + size
1770 1772 out += data[offset:offset_end]
1771 1773 elif cmd != 0:
1772 1774 offset_end = i + cmd
1773 1775 out += binchunk[i:offset_end]
1774 1776 i += cmd
1775 1777 else:
1776 1778 raise PatchError(_('unexpected delta opcode 0'))
1777 1779 return out
1778 1780
1779 1781 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1780 1782 """Reads a patch from fp and tries to apply it.
1781 1783
1782 1784 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1783 1785 there was any fuzz.
1784 1786
1785 1787 If 'eolmode' is 'strict', the patch content and patched file are
1786 1788 read in binary mode. Otherwise, line endings are ignored when
1787 1789 patching then normalized according to 'eolmode'.
1788 1790 """
1789 1791 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1790 1792 prefix=prefix, eolmode=eolmode)
1791 1793
1792 1794 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1793 1795 eolmode='strict'):
1794 1796
1795 1797 if prefix:
1796 1798 # clean up double slashes, lack of trailing slashes, etc
1797 1799 prefix = util.normpath(prefix) + '/'
1798 1800 def pstrip(p):
1799 1801 return pathtransform(p, strip - 1, prefix)[1]
1800 1802
1801 1803 rejects = 0
1802 1804 err = 0
1803 1805 current_file = None
1804 1806
1805 1807 for state, values in iterhunks(fp):
1806 1808 if state == 'hunk':
1807 1809 if not current_file:
1808 1810 continue
1809 1811 ret = current_file.apply(values)
1810 1812 if ret > 0:
1811 1813 err = 1
1812 1814 elif state == 'file':
1813 1815 if current_file:
1814 1816 rejects += current_file.close()
1815 1817 current_file = None
1816 1818 afile, bfile, first_hunk, gp = values
1817 1819 if gp:
1818 1820 gp.path = pstrip(gp.path)
1819 1821 if gp.oldpath:
1820 1822 gp.oldpath = pstrip(gp.oldpath)
1821 1823 else:
1822 1824 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1823 1825 prefix)
1824 1826 if gp.op == 'RENAME':
1825 1827 backend.unlink(gp.oldpath)
1826 1828 if not first_hunk:
1827 1829 if gp.op == 'DELETE':
1828 1830 backend.unlink(gp.path)
1829 1831 continue
1830 1832 data, mode = None, None
1831 1833 if gp.op in ('RENAME', 'COPY'):
1832 1834 data, mode = store.getfile(gp.oldpath)[:2]
1833 1835 # FIXME: failing getfile has never been handled here
1834 1836 assert data is not None
1835 1837 if gp.mode:
1836 1838 mode = gp.mode
1837 1839 if gp.op == 'ADD':
1838 1840 # Added files without content have no hunk and
1839 1841 # must be created
1840 1842 data = ''
1841 1843 if data or mode:
1842 1844 if (gp.op in ('ADD', 'RENAME', 'COPY')
1843 1845 and backend.exists(gp.path)):
1844 1846 raise PatchError(_("cannot create %s: destination "
1845 1847 "already exists") % gp.path)
1846 1848 backend.setfile(gp.path, data, mode, gp.oldpath)
1847 1849 continue
1848 1850 try:
1849 1851 current_file = patcher(ui, gp, backend, store,
1850 1852 eolmode=eolmode)
1851 1853 except PatchError, inst:
1852 1854 ui.warn(str(inst) + '\n')
1853 1855 current_file = None
1854 1856 rejects += 1
1855 1857 continue
1856 1858 elif state == 'git':
1857 1859 for gp in values:
1858 1860 path = pstrip(gp.oldpath)
1859 1861 data, mode = backend.getfile(path)
1860 1862 if data is None:
1861 1863 # The error ignored here will trigger a getfile()
1862 1864 # error in a place more appropriate for error
1863 1865 # handling, and will not interrupt the patching
1864 1866 # process.
1865 1867 pass
1866 1868 else:
1867 1869 store.setfile(path, data, mode)
1868 1870 else:
1869 1871 raise util.Abort(_('unsupported parser state: %s') % state)
1870 1872
1871 1873 if current_file:
1872 1874 rejects += current_file.close()
1873 1875
1874 1876 if rejects:
1875 1877 return -1
1876 1878 return err
1877 1879
1878 1880 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1879 1881 similarity):
1880 1882 """use <patcher> to apply <patchname> to the working directory.
1881 1883 returns whether patch was applied with fuzz factor."""
1882 1884
1883 1885 fuzz = False
1884 1886 args = []
1885 1887 cwd = repo.root
1886 1888 if cwd:
1887 1889 args.append('-d %s' % util.shellquote(cwd))
1888 1890 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1889 1891 util.shellquote(patchname)))
1890 1892 try:
1891 1893 for line in fp:
1892 1894 line = line.rstrip()
1893 1895 ui.note(line + '\n')
1894 1896 if line.startswith('patching file '):
1895 1897 pf = util.parsepatchoutput(line)
1896 1898 printed_file = False
1897 1899 files.add(pf)
1898 1900 elif line.find('with fuzz') >= 0:
1899 1901 fuzz = True
1900 1902 if not printed_file:
1901 1903 ui.warn(pf + '\n')
1902 1904 printed_file = True
1903 1905 ui.warn(line + '\n')
1904 1906 elif line.find('saving rejects to file') >= 0:
1905 1907 ui.warn(line + '\n')
1906 1908 elif line.find('FAILED') >= 0:
1907 1909 if not printed_file:
1908 1910 ui.warn(pf + '\n')
1909 1911 printed_file = True
1910 1912 ui.warn(line + '\n')
1911 1913 finally:
1912 1914 if files:
1913 1915 scmutil.marktouched(repo, files, similarity)
1914 1916 code = fp.close()
1915 1917 if code:
1916 1918 raise PatchError(_("patch command failed: %s") %
1917 1919 util.explainexit(code)[0])
1918 1920 return fuzz
1919 1921
1920 1922 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1921 1923 eolmode='strict'):
1922 1924 if files is None:
1923 1925 files = set()
1924 1926 if eolmode is None:
1925 1927 eolmode = ui.config('patch', 'eol', 'strict')
1926 1928 if eolmode.lower() not in eolmodes:
1927 1929 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1928 1930 eolmode = eolmode.lower()
1929 1931
1930 1932 store = filestore()
1931 1933 try:
1932 1934 fp = open(patchobj, 'rb')
1933 1935 except TypeError:
1934 1936 fp = patchobj
1935 1937 try:
1936 1938 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1937 1939 eolmode=eolmode)
1938 1940 finally:
1939 1941 if fp != patchobj:
1940 1942 fp.close()
1941 1943 files.update(backend.close())
1942 1944 store.close()
1943 1945 if ret < 0:
1944 1946 raise PatchError(_('patch failed to apply'))
1945 1947 return ret > 0
1946 1948
1947 1949 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1948 1950 eolmode='strict', similarity=0):
1949 1951 """use builtin patch to apply <patchobj> to the working directory.
1950 1952 returns whether patch was applied with fuzz factor."""
1951 1953 backend = workingbackend(ui, repo, similarity)
1952 1954 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1953 1955
1954 1956 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1955 1957 eolmode='strict'):
1956 1958 backend = repobackend(ui, repo, ctx, store)
1957 1959 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1958 1960
1959 1961 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1960 1962 similarity=0):
1961 1963 """Apply <patchname> to the working directory.
1962 1964
1963 1965 'eolmode' specifies how end of lines should be handled. It can be:
1964 1966 - 'strict': inputs are read in binary mode, EOLs are preserved
1965 1967 - 'crlf': EOLs are ignored when patching and reset to CRLF
1966 1968 - 'lf': EOLs are ignored when patching and reset to LF
1967 1969 - None: get it from user settings, default to 'strict'
1968 1970 'eolmode' is ignored when using an external patcher program.
1969 1971
1970 1972 Returns whether patch was applied with fuzz factor.
1971 1973 """
1972 1974 patcher = ui.config('ui', 'patch')
1973 1975 if files is None:
1974 1976 files = set()
1975 1977 if patcher:
1976 1978 return _externalpatch(ui, repo, patcher, patchname, strip,
1977 1979 files, similarity)
1978 1980 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1979 1981 similarity)
1980 1982
1981 1983 def changedfiles(ui, repo, patchpath, strip=1):
1982 1984 backend = fsbackend(ui, repo.root)
1983 1985 fp = open(patchpath, 'rb')
1984 1986 try:
1985 1987 changed = set()
1986 1988 for state, values in iterhunks(fp):
1987 1989 if state == 'file':
1988 1990 afile, bfile, first_hunk, gp = values
1989 1991 if gp:
1990 1992 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1991 1993 if gp.oldpath:
1992 1994 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1993 1995 else:
1994 1996 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1995 1997 '')
1996 1998 changed.add(gp.path)
1997 1999 if gp.op == 'RENAME':
1998 2000 changed.add(gp.oldpath)
1999 2001 elif state not in ('hunk', 'git'):
2000 2002 raise util.Abort(_('unsupported parser state: %s') % state)
2001 2003 return changed
2002 2004 finally:
2003 2005 fp.close()
2004 2006
2005 2007 class GitDiffRequired(Exception):
2006 2008 pass
2007 2009
2008 2010 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2009 2011 '''return diffopts with all features supported and parsed'''
2010 2012 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2011 2013 git=True, whitespace=True, formatchanging=True)
2012 2014
2013 2015 diffopts = diffallopts
2014 2016
2015 2017 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2016 2018 whitespace=False, formatchanging=False):
2017 2019 '''return diffopts with only opted-in features parsed
2018 2020
2019 2021 Features:
2020 2022 - git: git-style diffs
2021 2023 - whitespace: whitespace options like ignoreblanklines and ignorews
2022 2024 - formatchanging: options that will likely break or cause correctness issues
2023 2025 with most diff parsers
2024 2026 '''
2025 2027 def get(key, name=None, getter=ui.configbool, forceplain=None):
2026 2028 if opts:
2027 2029 v = opts.get(key)
2028 2030 if v:
2029 2031 return v
2030 2032 if forceplain is not None and ui.plain():
2031 2033 return forceplain
2032 2034 return getter(section, name or key, None, untrusted=untrusted)
2033 2035
2034 2036 # core options, expected to be understood by every diff parser
2035 2037 buildopts = {
2036 2038 'nodates': get('nodates'),
2037 2039 'showfunc': get('show_function', 'showfunc'),
2038 2040 'context': get('unified', getter=ui.config),
2039 2041 }
2040 2042
2041 2043 if git:
2042 2044 buildopts['git'] = get('git')
2043 2045 if whitespace:
2044 2046 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2045 2047 buildopts['ignorewsamount'] = get('ignore_space_change',
2046 2048 'ignorewsamount')
2047 2049 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2048 2050 'ignoreblanklines')
2049 2051 if formatchanging:
2050 2052 buildopts['text'] = opts and opts.get('text')
2051 2053 buildopts['nobinary'] = get('nobinary')
2052 2054 buildopts['noprefix'] = get('noprefix', forceplain=False)
2053 2055
2054 2056 return mdiff.diffopts(**buildopts)
2055 2057
2056 2058 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2057 2059 losedatafn=None, prefix=''):
2058 2060 '''yields diff of changes to files between two nodes, or node and
2059 2061 working directory.
2060 2062
2061 2063 if node1 is None, use first dirstate parent instead.
2062 2064 if node2 is None, compare node1 with working directory.
2063 2065
2064 2066 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2065 2067 every time some change cannot be represented with the current
2066 2068 patch format. Return False to upgrade to git patch format, True to
2067 2069 accept the loss or raise an exception to abort the diff. It is
2068 2070 called with the name of current file being diffed as 'fn'. If set
2069 2071 to None, patches will always be upgraded to git format when
2070 2072 necessary.
2071 2073
2072 2074 prefix is a filename prefix that is prepended to all filenames on
2073 2075 display (used for subrepos).
2074 2076 '''
2075 2077
2076 2078 if opts is None:
2077 2079 opts = mdiff.defaultopts
2078 2080
2079 2081 if not node1 and not node2:
2080 2082 node1 = repo.dirstate.p1()
2081 2083
2082 2084 def lrugetfilectx():
2083 2085 cache = {}
2084 2086 order = util.deque()
2085 2087 def getfilectx(f, ctx):
2086 2088 fctx = ctx.filectx(f, filelog=cache.get(f))
2087 2089 if f not in cache:
2088 2090 if len(cache) > 20:
2089 2091 del cache[order.popleft()]
2090 2092 cache[f] = fctx.filelog()
2091 2093 else:
2092 2094 order.remove(f)
2093 2095 order.append(f)
2094 2096 return fctx
2095 2097 return getfilectx
2096 2098 getfilectx = lrugetfilectx()
2097 2099
2098 2100 ctx1 = repo[node1]
2099 2101 ctx2 = repo[node2]
2100 2102
2101 2103 if not changes:
2102 2104 changes = repo.status(ctx1, ctx2, match=match)
2103 2105 modified, added, removed = changes[:3]
2104 2106
2105 2107 if not modified and not added and not removed:
2106 2108 return []
2107 2109
2108 2110 if repo.ui.debugflag:
2109 2111 hexfunc = hex
2110 2112 else:
2111 2113 hexfunc = short
2112 2114 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2113 2115
2114 2116 copy = {}
2115 2117 if opts.git or opts.upgrade:
2116 2118 copy = copies.pathcopies(ctx1, ctx2)
2117 2119
2118 2120 def difffn(opts, losedata):
2119 2121 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2120 2122 copy, getfilectx, opts, losedata, prefix)
2121 2123 if opts.upgrade and not opts.git:
2122 2124 try:
2123 2125 def losedata(fn):
2124 2126 if not losedatafn or not losedatafn(fn=fn):
2125 2127 raise GitDiffRequired
2126 2128 # Buffer the whole output until we are sure it can be generated
2127 2129 return list(difffn(opts.copy(git=False), losedata))
2128 2130 except GitDiffRequired:
2129 2131 return difffn(opts.copy(git=True), None)
2130 2132 else:
2131 2133 return difffn(opts, None)
2132 2134
2133 2135 def difflabel(func, *args, **kw):
2134 2136 '''yields 2-tuples of (output, label) based on the output of func()'''
2135 2137 headprefixes = [('diff', 'diff.diffline'),
2136 2138 ('copy', 'diff.extended'),
2137 2139 ('rename', 'diff.extended'),
2138 2140 ('old', 'diff.extended'),
2139 2141 ('new', 'diff.extended'),
2140 2142 ('deleted', 'diff.extended'),
2141 2143 ('---', 'diff.file_a'),
2142 2144 ('+++', 'diff.file_b')]
2143 2145 textprefixes = [('@', 'diff.hunk'),
2144 2146 ('-', 'diff.deleted'),
2145 2147 ('+', 'diff.inserted')]
2146 2148 head = False
2147 2149 for chunk in func(*args, **kw):
2148 2150 lines = chunk.split('\n')
2149 2151 for i, line in enumerate(lines):
2150 2152 if i != 0:
2151 2153 yield ('\n', '')
2152 2154 if head:
2153 2155 if line.startswith('@'):
2154 2156 head = False
2155 2157 else:
2156 2158 if line and line[0] not in ' +-@\\':
2157 2159 head = True
2158 2160 stripline = line
2159 2161 diffline = False
2160 2162 if not head and line and line[0] in '+-':
2161 2163 # highlight tabs and trailing whitespace, but only in
2162 2164 # changed lines
2163 2165 stripline = line.rstrip()
2164 2166 diffline = True
2165 2167
2166 2168 prefixes = textprefixes
2167 2169 if head:
2168 2170 prefixes = headprefixes
2169 2171 for prefix, label in prefixes:
2170 2172 if stripline.startswith(prefix):
2171 2173 if diffline:
2172 2174 for token in tabsplitter.findall(stripline):
2173 2175 if '\t' == token[0]:
2174 2176 yield (token, 'diff.tab')
2175 2177 else:
2176 2178 yield (token, label)
2177 2179 else:
2178 2180 yield (stripline, label)
2179 2181 break
2180 2182 else:
2181 2183 yield (line, '')
2182 2184 if line != stripline:
2183 2185 yield (line[len(stripline):], 'diff.trailingwhitespace')
2184 2186
2185 2187 def diffui(*args, **kw):
2186 2188 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2187 2189 return difflabel(diff, *args, **kw)
2188 2190
2189 2191 def _filepairs(ctx1, modified, added, removed, copy, opts):
2190 2192 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2191 2193 before and f2 is the the name after. For added files, f1 will be None,
2192 2194 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2193 2195 or 'rename' (the latter two only if opts.git is set).'''
2194 2196 gone = set()
2195 2197
2196 2198 copyto = dict([(v, k) for k, v in copy.items()])
2197 2199
2198 2200 addedset, removedset = set(added), set(removed)
2199 2201 # Fix up added, since merged-in additions appear as
2200 2202 # modifications during merges
2201 2203 for f in modified:
2202 2204 if f not in ctx1:
2203 2205 addedset.add(f)
2204 2206
2205 2207 for f in sorted(modified + added + removed):
2206 2208 copyop = None
2207 2209 f1, f2 = f, f
2208 2210 if f in addedset:
2209 2211 f1 = None
2210 2212 if f in copy:
2211 2213 if opts.git:
2212 2214 f1 = copy[f]
2213 2215 if f1 in removedset and f1 not in gone:
2214 2216 copyop = 'rename'
2215 2217 gone.add(f1)
2216 2218 else:
2217 2219 copyop = 'copy'
2218 2220 elif f in removedset:
2219 2221 f2 = None
2220 2222 if opts.git:
2221 2223 # have we already reported a copy above?
2222 2224 if (f in copyto and copyto[f] in addedset
2223 2225 and copy[copyto[f]] == f):
2224 2226 continue
2225 2227 yield f1, f2, copyop
2226 2228
2227 2229 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2228 2230 copy, getfilectx, opts, losedatafn, prefix):
2229 2231 '''given input data, generate a diff and yield it in blocks
2230 2232
2231 2233 If generating a diff would lose data like flags or binary data and
2232 2234 losedatafn is not None, it will be called.
2233 2235
2234 2236 prefix is added to every path in the diff output.'''
2235 2237
2236 2238 def gitindex(text):
2237 2239 if not text:
2238 2240 text = ""
2239 2241 l = len(text)
2240 2242 s = util.sha1('blob %d\0' % l)
2241 2243 s.update(text)
2242 2244 return s.hexdigest()
2243 2245
2244 2246 if opts.noprefix:
2245 2247 aprefix = bprefix = ''
2246 2248 else:
2247 2249 aprefix = 'a/'
2248 2250 bprefix = 'b/'
2249 2251
2250 2252 def diffline(f, revs):
2251 2253 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2252 2254 return 'diff %s %s' % (revinfo, f)
2253 2255
2254 2256 date1 = util.datestr(ctx1.date())
2255 2257 date2 = util.datestr(ctx2.date())
2256 2258
2257 2259 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2258 2260
2259 2261 for f1, f2, copyop in _filepairs(
2260 2262 ctx1, modified, added, removed, copy, opts):
2261 2263 content1 = None
2262 2264 content2 = None
2263 2265 flag1 = None
2264 2266 flag2 = None
2265 2267 if f1:
2266 2268 content1 = getfilectx(f1, ctx1).data()
2267 2269 if opts.git or losedatafn:
2268 2270 flag1 = ctx1.flags(f1)
2269 2271 if f2:
2270 2272 content2 = getfilectx(f2, ctx2).data()
2271 2273 if opts.git or losedatafn:
2272 2274 flag2 = ctx2.flags(f2)
2273 2275 binary = False
2274 2276 if opts.git or losedatafn:
2275 2277 binary = util.binary(content1) or util.binary(content2)
2276 2278
2277 2279 if losedatafn and not opts.git:
2278 2280 if (binary or
2279 2281 # copy/rename
2280 2282 f2 in copy or
2281 2283 # empty file creation
2282 2284 (not f1 and not content2) or
2283 2285 # empty file deletion
2284 2286 (not content1 and not f2) or
2285 2287 # create with flags
2286 2288 (not f1 and flag2) or
2287 2289 # change flags
2288 2290 (f1 and f2 and flag1 != flag2)):
2289 2291 losedatafn(f2 or f1)
2290 2292
2291 2293 path1 = posixpath.join(prefix, f1 or f2)
2292 2294 path2 = posixpath.join(prefix, f2 or f1)
2293 2295 header = []
2294 2296 if opts.git:
2295 2297 header.append('diff --git %s%s %s%s' %
2296 2298 (aprefix, path1, bprefix, path2))
2297 2299 if not f1: # added
2298 2300 header.append('new file mode %s' % gitmode[flag2])
2299 2301 elif not f2: # removed
2300 2302 header.append('deleted file mode %s' % gitmode[flag1])
2301 2303 else: # modified/copied/renamed
2302 2304 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2303 2305 if mode1 != mode2:
2304 2306 header.append('old mode %s' % mode1)
2305 2307 header.append('new mode %s' % mode2)
2306 2308 if copyop is not None:
2307 2309 header.append('%s from %s' % (copyop, path1))
2308 2310 header.append('%s to %s' % (copyop, path2))
2309 2311 elif revs and not repo.ui.quiet:
2310 2312 header.append(diffline(path1, revs))
2311 2313
2312 2314 if binary and opts.git and not opts.nobinary:
2313 2315 text = mdiff.b85diff(content1, content2)
2314 2316 if text:
2315 2317 header.append('index %s..%s' %
2316 2318 (gitindex(content1), gitindex(content2)))
2317 2319 else:
2318 2320 text = mdiff.unidiff(content1, date1,
2319 2321 content2, date2,
2320 2322 path1, path2, opts=opts)
2321 2323 if header and (text or len(header) > 1):
2322 2324 yield '\n'.join(header) + '\n'
2323 2325 if text:
2324 2326 yield text
2325 2327
2326 2328 def diffstatsum(stats):
2327 2329 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2328 2330 for f, a, r, b in stats:
2329 2331 maxfile = max(maxfile, encoding.colwidth(f))
2330 2332 maxtotal = max(maxtotal, a + r)
2331 2333 addtotal += a
2332 2334 removetotal += r
2333 2335 binary = binary or b
2334 2336
2335 2337 return maxfile, maxtotal, addtotal, removetotal, binary
2336 2338
2337 2339 def diffstatdata(lines):
2338 2340 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2339 2341
2340 2342 results = []
2341 2343 filename, adds, removes, isbinary = None, 0, 0, False
2342 2344
2343 2345 def addresult():
2344 2346 if filename:
2345 2347 results.append((filename, adds, removes, isbinary))
2346 2348
2347 2349 for line in lines:
2348 2350 if line.startswith('diff'):
2349 2351 addresult()
2350 2352 # set numbers to 0 anyway when starting new file
2351 2353 adds, removes, isbinary = 0, 0, False
2352 2354 if line.startswith('diff --git a/'):
2353 2355 filename = gitre.search(line).group(2)
2354 2356 elif line.startswith('diff -r'):
2355 2357 # format: "diff -r ... -r ... filename"
2356 2358 filename = diffre.search(line).group(1)
2357 2359 elif line.startswith('+') and not line.startswith('+++ '):
2358 2360 adds += 1
2359 2361 elif line.startswith('-') and not line.startswith('--- '):
2360 2362 removes += 1
2361 2363 elif (line.startswith('GIT binary patch') or
2362 2364 line.startswith('Binary file')):
2363 2365 isbinary = True
2364 2366 addresult()
2365 2367 return results
2366 2368
2367 2369 def diffstat(lines, width=80, git=False):
2368 2370 output = []
2369 2371 stats = diffstatdata(lines)
2370 2372 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2371 2373
2372 2374 countwidth = len(str(maxtotal))
2373 2375 if hasbinary and countwidth < 3:
2374 2376 countwidth = 3
2375 2377 graphwidth = width - countwidth - maxname - 6
2376 2378 if graphwidth < 10:
2377 2379 graphwidth = 10
2378 2380
2379 2381 def scale(i):
2380 2382 if maxtotal <= graphwidth:
2381 2383 return i
2382 2384 # If diffstat runs out of room it doesn't print anything,
2383 2385 # which isn't very useful, so always print at least one + or -
2384 2386 # if there were at least some changes.
2385 2387 return max(i * graphwidth // maxtotal, int(bool(i)))
2386 2388
2387 2389 for filename, adds, removes, isbinary in stats:
2388 2390 if isbinary:
2389 2391 count = 'Bin'
2390 2392 else:
2391 2393 count = adds + removes
2392 2394 pluses = '+' * scale(adds)
2393 2395 minuses = '-' * scale(removes)
2394 2396 output.append(' %s%s | %*s %s%s\n' %
2395 2397 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2396 2398 countwidth, count, pluses, minuses))
2397 2399
2398 2400 if stats:
2399 2401 output.append(_(' %d files changed, %d insertions(+), '
2400 2402 '%d deletions(-)\n')
2401 2403 % (len(stats), totaladds, totalremoves))
2402 2404
2403 2405 return ''.join(output)
2404 2406
2405 2407 def diffstatui(*args, **kw):
2406 2408 '''like diffstat(), but yields 2-tuples of (output, label) for
2407 2409 ui.write()
2408 2410 '''
2409 2411
2410 2412 for line in diffstat(*args, **kw).splitlines():
2411 2413 if line and line[-1] in '+-':
2412 2414 name, graph = line.rsplit(' ', 1)
2413 2415 yield (name + ' ', '')
2414 2416 m = re.search(r'\++', graph)
2415 2417 if m:
2416 2418 yield (m.group(0), 'diffstat.inserted')
2417 2419 m = re.search(r'-+', graph)
2418 2420 if m:
2419 2421 yield (m.group(0), 'diffstat.deleted')
2420 2422 else:
2421 2423 yield (line, '')
2422 2424 yield ('\n', '')
@@ -1,775 +1,798 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 323 Binary file and delta hunk (we build the patch using this sed hack to
324 324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
325 325
326 326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
327 327 > diff --git a/delta b/delta
328 328 > new file mode 100644
329 329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
330 330 > GIT binary patch
331 331 > literal 3749
332 332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
333 333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
334 334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
335 335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
336 336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
337 337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
338 338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
339 339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
340 340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
341 341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
342 342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
343 343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
344 344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
345 345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
346 346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
347 347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
348 348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
349 349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
350 350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
351 351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
352 352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
353 353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
354 354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
355 355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
356 356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
357 357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
358 358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
359 359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
360 360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
361 361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
362 362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
363 363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
364 364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
365 365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
366 366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
367 367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
368 368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
369 369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
370 370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
371 371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
372 372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
373 373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
374 374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
375 375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
376 376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
377 377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
378 378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
379 379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
380 380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
381 381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
382 382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
383 383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
384 384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
385 385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
386 386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
387 387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
388 388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
389 389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
390 390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
391 391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
392 392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
393 393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
394 394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
395 395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
396 396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
397 397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
398 398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
399 399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
400 400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
401 401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
402 402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
403 403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
404 404 > P00000NkvXXu0mjfZ5|Er
405 405 >
406 406 > literal 0
407 407 > HcmV?d00001
408 408 >
409 409 > EOF
410 410 $ hg import -d "1000000 0" -m delta quote-hack.patch
411 411 applying quote-hack.patch
412 412 $ rm quote-hack.patch
413 413
414 414 $ hg manifest --debug | grep delta
415 415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
416 416
417 417 $ hg import -d "1000000 0" -m delta - <<'EOF'
418 418 > diff --git a/delta b/delta
419 419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
420 420 > GIT binary patch
421 421 > delta 49
422 422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
423 423 > FSO9ZT4bA`n
424 424 >
425 425 > delta 49
426 426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
427 427 > HOA1;9yU-AD
428 428 >
429 429 > EOF
430 430 applying patch from stdin
431 431
432 432 $ hg manifest --debug | grep delta
433 433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
434 434
435 435 Filenames with spaces:
436 436
437 437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
438 438 > diff --git a/foo bar b/foo bar
439 439 > new file mode 100644
440 440 > index 0000000..257cc56
441 441 > --- /dev/null
442 442 > +++ b/foo bar EOL
443 443 > @@ -0,0 +1 @@
444 444 > +foo
445 445 > EOF
446 446 applying patch from stdin
447 447
448 448 $ hg tip -q
449 449 14:4b79479c9a6d
450 450
451 451 $ cat "foo bar"
452 452 foo
453 453
454 454 Copy then modify the original file:
455 455
456 456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
457 457 > diff --git a/foo2 b/foo2
458 458 > index 257cc56..fe08ec6 100644
459 459 > --- a/foo2
460 460 > +++ b/foo2
461 461 > @@ -1 +1,2 @@
462 462 > foo
463 463 > +new line
464 464 > diff --git a/foo2 b/foo3
465 465 > similarity index 100%
466 466 > copy from foo2
467 467 > copy to foo3
468 468 > EOF
469 469 applying patch from stdin
470 470
471 471 $ hg tip -q
472 472 15:9cbe44af4ae9
473 473
474 474 $ cat foo3
475 475 foo
476 476
477 477 Move text file and patch as binary
478 478
479 479 $ echo a > text2
480 480 $ hg ci -Am0
481 481 adding text2
482 482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
483 483 > diff --git a/text2 b/binary2
484 484 > rename from text2
485 485 > rename to binary2
486 486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
487 487 > GIT binary patch
488 488 > literal 5
489 489 > Mc$`b*O5$Pw00T?_*Z=?k
490 490 >
491 491 > EOF
492 492 applying patch from stdin
493 493
494 494 $ cat binary2
495 495 a
496 496 b
497 497 \x00 (no-eol) (esc)
498 498
499 499 $ hg st --copies --change .
500 500 A binary2
501 501 text2
502 502 R text2
503 503
504 504 Invalid base85 content
505 505
506 506 $ hg rollback
507 507 repository tip rolled back to revision 16 (undo import)
508 508 working directory now based on revision 16
509 509 $ hg revert -aq
510 510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
511 511 > diff --git a/text2 b/binary2
512 512 > rename from text2
513 513 > rename to binary2
514 514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
515 515 > GIT binary patch
516 516 > literal 5
517 517 > Mc$`b*O.$Pw00T?_*Z=?k
518 518 >
519 519 > EOF
520 520 applying patch from stdin
521 521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
522 522 [255]
523 523
524 524 $ hg revert -aq
525 525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
526 526 > diff --git a/text2 b/binary2
527 527 > rename from text2
528 528 > rename to binary2
529 529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
530 530 > GIT binary patch
531 531 > literal 6
532 532 > Mc$`b*O5$Pw00T?_*Z=?k
533 533 >
534 534 > EOF
535 535 applying patch from stdin
536 536 abort: "binary2" length is 5 bytes, should be 6
537 537 [255]
538 538
539 539 $ hg revert -aq
540 540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
541 541 > diff --git a/text2 b/binary2
542 542 > rename from text2
543 543 > rename to binary2
544 544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
545 545 > GIT binary patch
546 546 > Mc$`b*O5$Pw00T?_*Z=?k
547 547 >
548 548 > EOF
549 549 applying patch from stdin
550 550 abort: could not extract "binary2" binary data
551 551 [255]
552 552
553 553 Simulate a copy/paste turning LF into CRLF (issue2870)
554 554
555 555 $ hg revert -aq
556 556 $ cat > binary.diff <<"EOF"
557 557 > diff --git a/text2 b/binary2
558 558 > rename from text2
559 559 > rename to binary2
560 560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
561 561 > GIT binary patch
562 562 > literal 5
563 563 > Mc$`b*O5$Pw00T?_*Z=?k
564 564 >
565 565 > EOF
566 566 >>> fp = file('binary.diff', 'rb')
567 567 >>> data = fp.read()
568 568 >>> fp.close()
569 569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
570 570 $ rm binary2
571 571 $ hg import --no-commit binary.diff
572 572 applying binary.diff
573 573
574 574 $ cd ..
575 575
576 576 Consecutive import with renames (issue2459)
577 577
578 578 $ hg init issue2459
579 579 $ cd issue2459
580 580 $ hg import --no-commit --force - <<EOF
581 581 > diff --git a/a b/a
582 582 > new file mode 100644
583 583 > EOF
584 584 applying patch from stdin
585 585 $ hg import --no-commit --force - <<EOF
586 586 > diff --git a/a b/b
587 587 > rename from a
588 588 > rename to b
589 589 > EOF
590 590 applying patch from stdin
591 591 a has not been committed yet, so no copy data will be stored for b.
592 592 $ hg debugstate
593 593 a 0 -1 unset b
594 594 $ hg ci -m done
595 595 $ cd ..
596 596
597 597 Renames and strip
598 598
599 599 $ hg init renameandstrip
600 600 $ cd renameandstrip
601 601 $ echo a > a
602 602 $ hg ci -Am adda
603 603 adding a
604 604 $ hg import --no-commit -p2 - <<EOF
605 605 > diff --git a/foo/a b/foo/b
606 606 > rename from foo/a
607 607 > rename to foo/b
608 608 > EOF
609 609 applying patch from stdin
610 610 $ hg st --copies
611 611 A b
612 612 a
613 613 R a
614 614
615 615 Prefix with strip, renames, creates etc
616 616
617 617 $ hg revert -aC
618 618 undeleting a
619 619 forgetting b
620 620 $ rm b
621 621 $ mkdir -p dir/dir2
622 622 $ echo b > dir/dir2/b
623 623 $ echo c > dir/dir2/c
624 624 $ echo d > dir/d
625 625 $ hg ci -Am addbcd
626 626 adding dir/d
627 627 adding dir/dir2/b
628 628 adding dir/dir2/c
629 $ hg import --no-commit --prefix dir/ - <<EOF
630 > diff --git a/a b/a
631 > --- /dev/null
632 > +++ b/a
633 > @@ -0,0 +1 @@
634 > +aaa
635 > diff --git a/d b/d
636 > --- a/d
637 > +++ b/d
638 > @@ -1,1 +1,2 @@
639 > d
640 > +dd
641 > EOF
642 applying patch from stdin
643 $ cat dir/a
644 aaa
645 $ cat dir/d
646 d
647 dd
648 $ hg revert -aC
649 forgetting dir/a (glob)
650 reverting dir/d (glob)
651 $ rm dir/a
629 652 (test that prefixes are relative to the root)
630 653 $ mkdir tmpdir
631 654 $ cd tmpdir
632 655 $ hg import --no-commit -p2 --prefix dir/ - <<EOF
633 656 > diff --git a/foo/a b/foo/a
634 657 > new file mode 100644
635 658 > --- /dev/null
636 659 > +++ b/foo/a
637 660 > @@ -0,0 +1 @@
638 661 > +a
639 662 > diff --git a/foo/dir2/b b/foo/dir2/b2
640 663 > rename from foo/dir2/b
641 664 > rename to foo/dir2/b2
642 665 > diff --git a/foo/dir2/c b/foo/dir2/c
643 666 > --- a/foo/dir2/c
644 667 > +++ b/foo/dir2/c
645 668 > @@ -0,0 +1 @@
646 669 > +cc
647 670 > diff --git a/foo/d b/foo/d
648 671 > deleted file mode 100644
649 672 > --- a/foo/d
650 673 > +++ /dev/null
651 674 > @@ -1,1 +0,0 @@
652 675 > -d
653 676 > EOF
654 677 applying patch from stdin
655 678 $ hg st --copies
656 679 M dir/dir2/c
657 680 A dir/a
658 681 A dir/dir2/b2
659 682 dir/dir2/b
660 683 R dir/d
661 684 R dir/dir2/b
662 685 $ cd ..
663 686
664 687 Renames, similarity and git diff
665 688
666 689 $ hg revert -aC
667 690 forgetting dir/a (glob)
668 691 undeleting dir/d (glob)
669 692 undeleting dir/dir2/b (glob)
670 693 forgetting dir/dir2/b2 (glob)
671 694 reverting dir/dir2/c (glob)
672 695 $ rm dir/a dir/dir2/b2
673 696 $ hg import --similarity 90 --no-commit - <<EOF
674 697 > diff --git a/a b/b
675 698 > rename from a
676 699 > rename to b
677 700 > EOF
678 701 applying patch from stdin
679 702 $ hg st --copies
680 703 A b
681 704 a
682 705 R a
683 706 $ cd ..
684 707
685 708 Pure copy with existing destination
686 709
687 710 $ hg init copytoexisting
688 711 $ cd copytoexisting
689 712 $ echo a > a
690 713 $ echo b > b
691 714 $ hg ci -Am add
692 715 adding a
693 716 adding b
694 717 $ hg import --no-commit - <<EOF
695 718 > diff --git a/a b/b
696 719 > copy from a
697 720 > copy to b
698 721 > EOF
699 722 applying patch from stdin
700 723 abort: cannot create b: destination already exists
701 724 [255]
702 725 $ cat b
703 726 b
704 727
705 728 Copy and changes with existing destination
706 729
707 730 $ hg import --no-commit - <<EOF
708 731 > diff --git a/a b/b
709 732 > copy from a
710 733 > copy to b
711 734 > --- a/a
712 735 > +++ b/b
713 736 > @@ -1,1 +1,2 @@
714 737 > a
715 738 > +b
716 739 > EOF
717 740 applying patch from stdin
718 741 cannot create b: destination already exists
719 742 1 out of 1 hunks FAILED -- saving rejects to file b.rej
720 743 abort: patch failed to apply
721 744 [255]
722 745 $ cat b
723 746 b
724 747
725 748 #if symlink
726 749
727 750 $ ln -s b linkb
728 751 $ hg add linkb
729 752 $ hg ci -m addlinkb
730 753 $ hg import --no-commit - <<EOF
731 754 > diff --git a/linkb b/linkb
732 755 > deleted file mode 120000
733 756 > --- a/linkb
734 757 > +++ /dev/null
735 758 > @@ -1,1 +0,0 @@
736 759 > -badhunk
737 760 > \ No newline at end of file
738 761 > EOF
739 762 applying patch from stdin
740 763 patching file linkb
741 764 Hunk #1 FAILED at 0
742 765 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
743 766 abort: patch failed to apply
744 767 [255]
745 768 $ hg st
746 769 ? b.rej
747 770 ? linkb.rej
748 771
749 772 #endif
750 773
751 774 Test corner case involving copies and multiple hunks (issue3384)
752 775
753 776 $ hg revert -qa
754 777 $ hg import --no-commit - <<EOF
755 778 > diff --git a/a b/c
756 779 > copy from a
757 780 > copy to c
758 781 > --- a/a
759 782 > +++ b/c
760 783 > @@ -1,1 +1,2 @@
761 784 > a
762 785 > +a
763 786 > @@ -2,1 +2,2 @@
764 787 > a
765 788 > +a
766 789 > diff --git a/a b/a
767 790 > --- a/a
768 791 > +++ b/a
769 792 > @@ -1,1 +1,2 @@
770 793 > a
771 794 > +b
772 795 > EOF
773 796 applying patch from stdin
774 797
775 798 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now