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