##// END OF EJS Templates
patch: create file even if source is not /dev/null...
Brendan Cully -
r9328:648d6a1a default
parent child Browse files
Show More
@@ -1,1440 +1,1451 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, incorporated herein by reference.
8 8
9 9 from i18n import _
10 10 from node import hex, nullid, short
11 11 import base85, cmdutil, mdiff, util, diffhelpers, copies
12 12 import cStringIO, email.Parser, os, re, math
13 13 import sys, tempfile, zlib
14 14
15 15 gitre = re.compile('diff --git a/(.*) b/(.*)')
16 16
17 17 class PatchError(Exception):
18 18 pass
19 19
20 20 class NoHunks(PatchError):
21 21 pass
22 22
23 23 # helper functions
24 24
25 25 def copyfile(src, dst, basedir):
26 26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 27 if os.path.exists(absdst):
28 28 raise util.Abort(_("cannot create %s: destination already exists") %
29 29 dst)
30 30
31 31 dstdir = os.path.dirname(absdst)
32 32 if dstdir and not os.path.isdir(dstdir):
33 33 try:
34 34 os.makedirs(dstdir)
35 35 except IOError:
36 36 raise util.Abort(
37 37 _("cannot create %s: unable to create destination directory")
38 38 % dst)
39 39
40 40 util.copyfile(abssrc, absdst)
41 41
42 42 # public functions
43 43
44 44 def extract(ui, fileobj):
45 45 '''extract patch from data read from fileobj.
46 46
47 47 patch can be a normal patch or contained in an email message.
48 48
49 49 return tuple (filename, message, user, date, node, p1, p2).
50 50 Any item in the returned tuple can be None. If filename is None,
51 51 fileobj did not contain a patch. Caller must unlink filename when done.'''
52 52
53 53 # attempt to detect the start of a patch
54 54 # (this heuristic is borrowed from quilt)
55 55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
56 56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
57 57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
58 58
59 59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
60 60 tmpfp = os.fdopen(fd, 'w')
61 61 try:
62 62 msg = email.Parser.Parser().parse(fileobj)
63 63
64 64 subject = msg['Subject']
65 65 user = msg['From']
66 66 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
67 67 # should try to parse msg['Date']
68 68 date = None
69 69 nodeid = None
70 70 branch = None
71 71 parents = []
72 72
73 73 if subject:
74 74 if subject.startswith('[PATCH'):
75 75 pend = subject.find(']')
76 76 if pend >= 0:
77 77 subject = subject[pend+1:].lstrip()
78 78 subject = subject.replace('\n\t', ' ')
79 79 ui.debug('Subject: %s\n' % subject)
80 80 if user:
81 81 ui.debug('From: %s\n' % user)
82 82 diffs_seen = 0
83 83 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
84 84 message = ''
85 85 for part in msg.walk():
86 86 content_type = part.get_content_type()
87 87 ui.debug('Content-Type: %s\n' % content_type)
88 88 if content_type not in ok_types:
89 89 continue
90 90 payload = part.get_payload(decode=True)
91 91 m = diffre.search(payload)
92 92 if m:
93 93 hgpatch = False
94 94 ignoretext = False
95 95
96 96 ui.debug(_('found patch at byte %d\n') % m.start(0))
97 97 diffs_seen += 1
98 98 cfp = cStringIO.StringIO()
99 99 for line in payload[:m.start(0)].splitlines():
100 100 if line.startswith('# HG changeset patch'):
101 101 ui.debug(_('patch generated by hg export\n'))
102 102 hgpatch = True
103 103 # drop earlier commit message content
104 104 cfp.seek(0)
105 105 cfp.truncate()
106 106 subject = None
107 107 elif hgpatch:
108 108 if line.startswith('# User '):
109 109 user = line[7:]
110 110 ui.debug('From: %s\n' % user)
111 111 elif line.startswith("# Date "):
112 112 date = line[7:]
113 113 elif line.startswith("# Branch "):
114 114 branch = line[9:]
115 115 elif line.startswith("# Node ID "):
116 116 nodeid = line[10:]
117 117 elif line.startswith("# Parent "):
118 118 parents.append(line[10:])
119 119 elif line == '---' and gitsendmail:
120 120 ignoretext = True
121 121 if not line.startswith('# ') and not ignoretext:
122 122 cfp.write(line)
123 123 cfp.write('\n')
124 124 message = cfp.getvalue()
125 125 if tmpfp:
126 126 tmpfp.write(payload)
127 127 if not payload.endswith('\n'):
128 128 tmpfp.write('\n')
129 129 elif not diffs_seen and message and content_type == 'text/plain':
130 130 message += '\n' + payload
131 131 except:
132 132 tmpfp.close()
133 133 os.unlink(tmpname)
134 134 raise
135 135
136 136 if subject and not message.startswith(subject):
137 137 message = '%s\n%s' % (subject, message)
138 138 tmpfp.close()
139 139 if not diffs_seen:
140 140 os.unlink(tmpname)
141 141 return None, message, user, date, branch, None, None, None
142 142 p1 = parents and parents.pop(0) or None
143 143 p2 = parents and parents.pop(0) or None
144 144 return tmpname, message, user, date, branch, nodeid, p1, p2
145 145
146 146 GP_PATCH = 1 << 0 # we have to run patch
147 147 GP_FILTER = 1 << 1 # there's some copy/rename operation
148 148 GP_BINARY = 1 << 2 # there's a binary patch
149 149
150 150 class patchmeta(object):
151 151 """Patched file metadata
152 152
153 153 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
154 154 or COPY. 'path' is patched file path. 'oldpath' is set to the
155 155 origin file when 'op' is either COPY or RENAME, None otherwise. If
156 156 file mode is changed, 'mode' is a tuple (islink, isexec) where
157 157 'islink' is True if the file is a symlink and 'isexec' is True if
158 158 the file is executable. Otherwise, 'mode' is None.
159 159 """
160 160 def __init__(self, path):
161 161 self.path = path
162 162 self.oldpath = None
163 163 self.mode = None
164 164 self.op = 'MODIFY'
165 165 self.lineno = 0
166 166 self.binary = False
167 167
168 168 def setmode(self, mode):
169 169 islink = mode & 020000
170 170 isexec = mode & 0100
171 171 self.mode = (islink, isexec)
172 172
173 173 def readgitpatch(lr):
174 174 """extract git-style metadata about patches from <patchname>"""
175 175
176 176 # Filter patch for git information
177 177 gp = None
178 178 gitpatches = []
179 179 # Can have a git patch with only metadata, causing patch to complain
180 180 dopatch = 0
181 181
182 182 lineno = 0
183 183 for line in lr:
184 184 lineno += 1
185 185 line = line.rstrip(' \r\n')
186 186 if line.startswith('diff --git'):
187 187 m = gitre.match(line)
188 188 if m:
189 189 if gp:
190 190 gitpatches.append(gp)
191 191 src, dst = m.group(1, 2)
192 192 gp = patchmeta(dst)
193 193 gp.lineno = lineno
194 194 elif gp:
195 195 if line.startswith('--- '):
196 196 if gp.op in ('COPY', 'RENAME'):
197 197 dopatch |= GP_FILTER
198 198 gitpatches.append(gp)
199 199 gp = None
200 200 dopatch |= GP_PATCH
201 201 continue
202 202 if line.startswith('rename from '):
203 203 gp.op = 'RENAME'
204 204 gp.oldpath = line[12:]
205 205 elif line.startswith('rename to '):
206 206 gp.path = line[10:]
207 207 elif line.startswith('copy from '):
208 208 gp.op = 'COPY'
209 209 gp.oldpath = line[10:]
210 210 elif line.startswith('copy to '):
211 211 gp.path = line[8:]
212 212 elif line.startswith('deleted file'):
213 213 gp.op = 'DELETE'
214 214 # is the deleted file a symlink?
215 215 gp.setmode(int(line[-6:], 8))
216 216 elif line.startswith('new file mode '):
217 217 gp.op = 'ADD'
218 218 gp.setmode(int(line[-6:], 8))
219 219 elif line.startswith('new mode '):
220 220 gp.setmode(int(line[-6:], 8))
221 221 elif line.startswith('GIT binary patch'):
222 222 dopatch |= GP_BINARY
223 223 gp.binary = True
224 224 if gp:
225 225 gitpatches.append(gp)
226 226
227 227 if not gitpatches:
228 228 dopatch = GP_PATCH
229 229
230 230 return (dopatch, gitpatches)
231 231
232 232 class linereader(object):
233 233 # simple class to allow pushing lines back into the input stream
234 234 def __init__(self, fp, textmode=False):
235 235 self.fp = fp
236 236 self.buf = []
237 237 self.textmode = textmode
238 238
239 239 def push(self, line):
240 240 if line is not None:
241 241 self.buf.append(line)
242 242
243 243 def readline(self):
244 244 if self.buf:
245 245 l = self.buf[0]
246 246 del self.buf[0]
247 247 return l
248 248 l = self.fp.readline()
249 249 if self.textmode and l.endswith('\r\n'):
250 250 l = l[:-2] + '\n'
251 251 return l
252 252
253 253 def __iter__(self):
254 254 while 1:
255 255 l = self.readline()
256 256 if not l:
257 257 break
258 258 yield l
259 259
260 260 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
261 261 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
262 262 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
263 263
264 264 class patchfile(object):
265 265 def __init__(self, ui, fname, opener, missing=False, eol=None):
266 266 self.fname = fname
267 267 self.eol = eol
268 268 self.opener = opener
269 269 self.ui = ui
270 270 self.lines = []
271 271 self.exists = False
272 272 self.missing = missing
273 273 if not missing:
274 274 try:
275 275 self.lines = self.readlines(fname)
276 276 self.exists = True
277 277 except IOError:
278 278 pass
279 279 else:
280 280 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
281 281
282 282 self.hash = {}
283 283 self.dirty = 0
284 284 self.offset = 0
285 285 self.rej = []
286 286 self.fileprinted = False
287 287 self.printfile(False)
288 288 self.hunks = 0
289 289
290 290 def readlines(self, fname):
291 291 fp = self.opener(fname, 'r')
292 292 try:
293 293 return list(linereader(fp, self.eol is not None))
294 294 finally:
295 295 fp.close()
296 296
297 297 def writelines(self, fname, lines):
298 298 fp = self.opener(fname, 'w')
299 299 try:
300 300 if self.eol and self.eol != '\n':
301 301 for l in lines:
302 302 if l and l[-1] == '\n':
303 303 l = l[:-1] + self.eol
304 304 fp.write(l)
305 305 else:
306 306 fp.writelines(lines)
307 307 finally:
308 308 fp.close()
309 309
310 310 def unlink(self, fname):
311 311 os.unlink(fname)
312 312
313 313 def printfile(self, warn):
314 314 if self.fileprinted:
315 315 return
316 316 if warn or self.ui.verbose:
317 317 self.fileprinted = True
318 318 s = _("patching file %s\n") % self.fname
319 319 if warn:
320 320 self.ui.warn(s)
321 321 else:
322 322 self.ui.note(s)
323 323
324 324
325 325 def findlines(self, l, linenum):
326 326 # looks through the hash and finds candidate lines. The
327 327 # result is a list of line numbers sorted based on distance
328 328 # from linenum
329 329
330 330 try:
331 331 cand = self.hash[l]
332 332 except:
333 333 return []
334 334
335 335 if len(cand) > 1:
336 336 # resort our list of potentials forward then back.
337 337 cand.sort(key=lambda x: abs(x - linenum))
338 338 return cand
339 339
340 340 def hashlines(self):
341 341 self.hash = {}
342 342 for x, s in enumerate(self.lines):
343 343 self.hash.setdefault(s, []).append(x)
344 344
345 345 def write_rej(self):
346 346 # our rejects are a little different from patch(1). This always
347 347 # creates rejects in the same form as the original patch. A file
348 348 # header is inserted so that you can run the reject through patch again
349 349 # without having to type the filename.
350 350
351 351 if not self.rej:
352 352 return
353 353
354 354 fname = self.fname + ".rej"
355 355 self.ui.warn(
356 356 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
357 357 (len(self.rej), self.hunks, fname))
358 358
359 359 def rejlines():
360 360 base = os.path.basename(self.fname)
361 361 yield "--- %s\n+++ %s\n" % (base, base)
362 362 for x in self.rej:
363 363 for l in x.hunk:
364 364 yield l
365 365 if l[-1] != '\n':
366 366 yield "\n\ No newline at end of file\n"
367 367
368 368 self.writelines(fname, rejlines())
369 369
370 370 def write(self, dest=None):
371 371 if not self.dirty:
372 372 return
373 373 if not dest:
374 374 dest = self.fname
375 375 self.writelines(dest, self.lines)
376 376
377 377 def close(self):
378 378 self.write()
379 379 self.write_rej()
380 380
381 381 def apply(self, h, reverse):
382 382 if not h.complete():
383 383 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
384 384 (h.number, h.desc, len(h.a), h.lena, len(h.b),
385 385 h.lenb))
386 386
387 387 self.hunks += 1
388 388 if reverse:
389 389 h.reverse()
390 390
391 391 if self.missing:
392 392 self.rej.append(h)
393 393 return -1
394 394
395 395 if self.exists and h.createfile():
396 396 self.ui.warn(_("file %s already exists\n") % self.fname)
397 397 self.rej.append(h)
398 398 return -1
399 399
400 400 if isinstance(h, githunk):
401 401 if h.rmfile():
402 402 self.unlink(self.fname)
403 403 else:
404 404 self.lines[:] = h.new()
405 405 self.offset += len(h.new())
406 406 self.dirty = 1
407 407 return 0
408 408
409 409 # fast case first, no offsets, no fuzz
410 410 old = h.old()
411 411 # patch starts counting at 1 unless we are adding the file
412 412 if h.starta == 0:
413 413 start = 0
414 414 else:
415 415 start = h.starta + self.offset - 1
416 416 orig_start = start
417 417 if diffhelpers.testhunk(old, self.lines, start) == 0:
418 418 if h.rmfile():
419 419 self.unlink(self.fname)
420 420 else:
421 421 self.lines[start : start + h.lena] = h.new()
422 422 self.offset += h.lenb - h.lena
423 423 self.dirty = 1
424 424 return 0
425 425
426 426 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
427 427 self.hashlines()
428 428 if h.hunk[-1][0] != ' ':
429 429 # if the hunk tried to put something at the bottom of the file
430 430 # override the start line and use eof here
431 431 search_start = len(self.lines)
432 432 else:
433 433 search_start = orig_start
434 434
435 435 for fuzzlen in xrange(3):
436 436 for toponly in [ True, False ]:
437 437 old = h.old(fuzzlen, toponly)
438 438
439 439 cand = self.findlines(old[0][1:], search_start)
440 440 for l in cand:
441 441 if diffhelpers.testhunk(old, self.lines, l) == 0:
442 442 newlines = h.new(fuzzlen, toponly)
443 443 self.lines[l : l + len(old)] = newlines
444 444 self.offset += len(newlines) - len(old)
445 445 self.dirty = 1
446 446 if fuzzlen:
447 447 fuzzstr = "with fuzz %d " % fuzzlen
448 448 f = self.ui.warn
449 449 self.printfile(True)
450 450 else:
451 451 fuzzstr = ""
452 452 f = self.ui.note
453 453 offset = l - orig_start - fuzzlen
454 454 if offset == 1:
455 455 msg = _("Hunk #%d succeeded at %d %s"
456 456 "(offset %d line).\n")
457 457 else:
458 458 msg = _("Hunk #%d succeeded at %d %s"
459 459 "(offset %d lines).\n")
460 460 f(msg % (h.number, l+1, fuzzstr, offset))
461 461 return fuzzlen
462 462 self.printfile(True)
463 463 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
464 464 self.rej.append(h)
465 465 return -1
466 466
467 467 class hunk(object):
468 468 def __init__(self, desc, num, lr, context, create=False, remove=False):
469 469 self.number = num
470 470 self.desc = desc
471 471 self.hunk = [ desc ]
472 472 self.a = []
473 473 self.b = []
474 474 if context:
475 475 self.read_context_hunk(lr)
476 476 else:
477 477 self.read_unified_hunk(lr)
478 478 self.create = create
479 479 self.remove = remove and not create
480 480
481 481 def read_unified_hunk(self, lr):
482 482 m = unidesc.match(self.desc)
483 483 if not m:
484 484 raise PatchError(_("bad hunk #%d") % self.number)
485 485 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
486 486 if self.lena is None:
487 487 self.lena = 1
488 488 else:
489 489 self.lena = int(self.lena)
490 490 if self.lenb is None:
491 491 self.lenb = 1
492 492 else:
493 493 self.lenb = int(self.lenb)
494 494 self.starta = int(self.starta)
495 495 self.startb = int(self.startb)
496 496 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
497 497 # if we hit eof before finishing out the hunk, the last line will
498 498 # be zero length. Lets try to fix it up.
499 499 while len(self.hunk[-1]) == 0:
500 500 del self.hunk[-1]
501 501 del self.a[-1]
502 502 del self.b[-1]
503 503 self.lena -= 1
504 504 self.lenb -= 1
505 505
506 506 def read_context_hunk(self, lr):
507 507 self.desc = lr.readline()
508 508 m = contextdesc.match(self.desc)
509 509 if not m:
510 510 raise PatchError(_("bad hunk #%d") % self.number)
511 511 foo, self.starta, foo2, aend, foo3 = m.groups()
512 512 self.starta = int(self.starta)
513 513 if aend is None:
514 514 aend = self.starta
515 515 self.lena = int(aend) - self.starta
516 516 if self.starta:
517 517 self.lena += 1
518 518 for x in xrange(self.lena):
519 519 l = lr.readline()
520 520 if l.startswith('---'):
521 521 lr.push(l)
522 522 break
523 523 s = l[2:]
524 524 if l.startswith('- ') or l.startswith('! '):
525 525 u = '-' + s
526 526 elif l.startswith(' '):
527 527 u = ' ' + s
528 528 else:
529 529 raise PatchError(_("bad hunk #%d old text line %d") %
530 530 (self.number, x))
531 531 self.a.append(u)
532 532 self.hunk.append(u)
533 533
534 534 l = lr.readline()
535 535 if l.startswith('\ '):
536 536 s = self.a[-1][:-1]
537 537 self.a[-1] = s
538 538 self.hunk[-1] = s
539 539 l = lr.readline()
540 540 m = contextdesc.match(l)
541 541 if not m:
542 542 raise PatchError(_("bad hunk #%d") % self.number)
543 543 foo, self.startb, foo2, bend, foo3 = m.groups()
544 544 self.startb = int(self.startb)
545 545 if bend is None:
546 546 bend = self.startb
547 547 self.lenb = int(bend) - self.startb
548 548 if self.startb:
549 549 self.lenb += 1
550 550 hunki = 1
551 551 for x in xrange(self.lenb):
552 552 l = lr.readline()
553 553 if l.startswith('\ '):
554 554 s = self.b[-1][:-1]
555 555 self.b[-1] = s
556 556 self.hunk[hunki-1] = s
557 557 continue
558 558 if not l:
559 559 lr.push(l)
560 560 break
561 561 s = l[2:]
562 562 if l.startswith('+ ') or l.startswith('! '):
563 563 u = '+' + s
564 564 elif l.startswith(' '):
565 565 u = ' ' + s
566 566 elif len(self.b) == 0:
567 567 # this can happen when the hunk does not add any lines
568 568 lr.push(l)
569 569 break
570 570 else:
571 571 raise PatchError(_("bad hunk #%d old text line %d") %
572 572 (self.number, x))
573 573 self.b.append(s)
574 574 while True:
575 575 if hunki >= len(self.hunk):
576 576 h = ""
577 577 else:
578 578 h = self.hunk[hunki]
579 579 hunki += 1
580 580 if h == u:
581 581 break
582 582 elif h.startswith('-'):
583 583 continue
584 584 else:
585 585 self.hunk.insert(hunki-1, u)
586 586 break
587 587
588 588 if not self.a:
589 589 # this happens when lines were only added to the hunk
590 590 for x in self.hunk:
591 591 if x.startswith('-') or x.startswith(' '):
592 592 self.a.append(x)
593 593 if not self.b:
594 594 # this happens when lines were only deleted from the hunk
595 595 for x in self.hunk:
596 596 if x.startswith('+') or x.startswith(' '):
597 597 self.b.append(x[1:])
598 598 # @@ -start,len +start,len @@
599 599 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
600 600 self.startb, self.lenb)
601 601 self.hunk[0] = self.desc
602 602
603 603 def reverse(self):
604 604 self.create, self.remove = self.remove, self.create
605 605 origlena = self.lena
606 606 origstarta = self.starta
607 607 self.lena = self.lenb
608 608 self.starta = self.startb
609 609 self.lenb = origlena
610 610 self.startb = origstarta
611 611 self.a = []
612 612 self.b = []
613 613 # self.hunk[0] is the @@ description
614 614 for x in xrange(1, len(self.hunk)):
615 615 o = self.hunk[x]
616 616 if o.startswith('-'):
617 617 n = '+' + o[1:]
618 618 self.b.append(o[1:])
619 619 elif o.startswith('+'):
620 620 n = '-' + o[1:]
621 621 self.a.append(n)
622 622 else:
623 623 n = o
624 624 self.b.append(o[1:])
625 625 self.a.append(o)
626 626 self.hunk[x] = o
627 627
628 628 def fix_newline(self):
629 629 diffhelpers.fix_newline(self.hunk, self.a, self.b)
630 630
631 631 def complete(self):
632 632 return len(self.a) == self.lena and len(self.b) == self.lenb
633 633
634 634 def createfile(self):
635 635 return self.starta == 0 and self.lena == 0 and self.create
636 636
637 637 def rmfile(self):
638 638 return self.startb == 0 and self.lenb == 0 and self.remove
639 639
640 640 def fuzzit(self, l, fuzz, toponly):
641 641 # this removes context lines from the top and bottom of list 'l'. It
642 642 # checks the hunk to make sure only context lines are removed, and then
643 643 # returns a new shortened list of lines.
644 644 fuzz = min(fuzz, len(l)-1)
645 645 if fuzz:
646 646 top = 0
647 647 bot = 0
648 648 hlen = len(self.hunk)
649 649 for x in xrange(hlen-1):
650 650 # the hunk starts with the @@ line, so use x+1
651 651 if self.hunk[x+1][0] == ' ':
652 652 top += 1
653 653 else:
654 654 break
655 655 if not toponly:
656 656 for x in xrange(hlen-1):
657 657 if self.hunk[hlen-bot-1][0] == ' ':
658 658 bot += 1
659 659 else:
660 660 break
661 661
662 662 # top and bot now count context in the hunk
663 663 # adjust them if either one is short
664 664 context = max(top, bot, 3)
665 665 if bot < context:
666 666 bot = max(0, fuzz - (context - bot))
667 667 else:
668 668 bot = min(fuzz, bot)
669 669 if top < context:
670 670 top = max(0, fuzz - (context - top))
671 671 else:
672 672 top = min(fuzz, top)
673 673
674 674 return l[top:len(l)-bot]
675 675 return l
676 676
677 677 def old(self, fuzz=0, toponly=False):
678 678 return self.fuzzit(self.a, fuzz, toponly)
679 679
680 680 def newctrl(self):
681 681 res = []
682 682 for x in self.hunk:
683 683 c = x[0]
684 684 if c == ' ' or c == '+':
685 685 res.append(x)
686 686 return res
687 687
688 688 def new(self, fuzz=0, toponly=False):
689 689 return self.fuzzit(self.b, fuzz, toponly)
690 690
691 691 class githunk(object):
692 692 """A git hunk"""
693 693 def __init__(self, gitpatch):
694 694 self.gitpatch = gitpatch
695 695 self.text = None
696 696 self.hunk = []
697 697
698 698 def createfile(self):
699 699 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
700 700
701 701 def rmfile(self):
702 702 return self.gitpatch.op == 'DELETE'
703 703
704 704 def complete(self):
705 705 return self.text is not None
706 706
707 707 def new(self):
708 708 return [self.text]
709 709
710 710 class binhunk(githunk):
711 711 'A binary patch file. Only understands literals so far.'
712 712 def __init__(self, gitpatch):
713 713 super(binhunk, self).__init__(gitpatch)
714 714 self.hunk = ['GIT binary patch\n']
715 715
716 716 def extract(self, lr):
717 717 line = lr.readline()
718 718 self.hunk.append(line)
719 719 while line and not line.startswith('literal '):
720 720 line = lr.readline()
721 721 self.hunk.append(line)
722 722 if not line:
723 723 raise PatchError(_('could not extract binary patch'))
724 724 size = int(line[8:].rstrip())
725 725 dec = []
726 726 line = lr.readline()
727 727 self.hunk.append(line)
728 728 while len(line) > 1:
729 729 l = line[0]
730 730 if l <= 'Z' and l >= 'A':
731 731 l = ord(l) - ord('A') + 1
732 732 else:
733 733 l = ord(l) - ord('a') + 27
734 734 dec.append(base85.b85decode(line[1:-1])[:l])
735 735 line = lr.readline()
736 736 self.hunk.append(line)
737 737 text = zlib.decompress(''.join(dec))
738 738 if len(text) != size:
739 739 raise PatchError(_('binary patch is %d bytes, not %d') %
740 740 len(text), size)
741 741 self.text = text
742 742
743 743 class symlinkhunk(githunk):
744 744 """A git symlink hunk"""
745 745 def __init__(self, gitpatch, hunk):
746 746 super(symlinkhunk, self).__init__(gitpatch)
747 747 self.hunk = hunk
748 748
749 749 def complete(self):
750 750 return True
751 751
752 752 def fix_newline(self):
753 753 return
754 754
755 755 def parsefilename(str):
756 756 # --- filename \t|space stuff
757 757 s = str[4:].rstrip('\r\n')
758 758 i = s.find('\t')
759 759 if i < 0:
760 760 i = s.find(' ')
761 761 if i < 0:
762 762 return s
763 763 return s[:i]
764 764
765 765 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
766 766 def pathstrip(path, count=1):
767 767 pathlen = len(path)
768 768 i = 0
769 769 if count == 0:
770 770 return '', path.rstrip()
771 771 while count > 0:
772 772 i = path.find('/', i)
773 773 if i == -1:
774 774 raise PatchError(_("unable to strip away %d dirs from %s") %
775 775 (count, path))
776 776 i += 1
777 777 # consume '//' in the path
778 778 while i < pathlen - 1 and path[i] == '/':
779 779 i += 1
780 780 count -= 1
781 781 return path[:i].lstrip(), path[i:].rstrip()
782 782
783 783 nulla = afile_orig == "/dev/null"
784 784 nullb = bfile_orig == "/dev/null"
785 785 abase, afile = pathstrip(afile_orig, strip)
786 786 gooda = not nulla and util.lexists(afile)
787 787 bbase, bfile = pathstrip(bfile_orig, strip)
788 788 if afile == bfile:
789 789 goodb = gooda
790 790 else:
791 791 goodb = not nullb and os.path.exists(bfile)
792 792 createfunc = hunk.createfile
793 793 if reverse:
794 794 createfunc = hunk.rmfile
795 795 missing = not goodb and not gooda and not createfunc()
796
797 # some diff programs apparently produce create patches where the
798 # afile is not /dev/null, but rather the same name as the bfile
799 if missing and afile == bfile:
800 # this isn't very pretty
801 hunk.create = True
802 if createfunc():
803 missing = False
804 else:
805 hunk.create = False
806
796 807 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
797 808 # diff is between a file and its backup. In this case, the original
798 809 # file should be patched (see original mpatch code).
799 810 isbackup = (abase == bbase and bfile.startswith(afile))
800 811 fname = None
801 812 if not missing:
802 813 if gooda and goodb:
803 814 fname = isbackup and afile or bfile
804 815 elif gooda:
805 816 fname = afile
806 817
807 818 if not fname:
808 819 if not nullb:
809 820 fname = isbackup and afile or bfile
810 821 elif not nulla:
811 822 fname = afile
812 823 else:
813 824 raise PatchError(_("undefined source and destination files"))
814 825
815 826 return fname, missing
816 827
817 828 def scangitpatch(lr, firstline):
818 829 """
819 830 Git patches can emit:
820 831 - rename a to b
821 832 - change b
822 833 - copy a to c
823 834 - change c
824 835
825 836 We cannot apply this sequence as-is, the renamed 'a' could not be
826 837 found for it would have been renamed already. And we cannot copy
827 838 from 'b' instead because 'b' would have been changed already. So
828 839 we scan the git patch for copy and rename commands so we can
829 840 perform the copies ahead of time.
830 841 """
831 842 pos = 0
832 843 try:
833 844 pos = lr.fp.tell()
834 845 fp = lr.fp
835 846 except IOError:
836 847 fp = cStringIO.StringIO(lr.fp.read())
837 848 gitlr = linereader(fp, lr.textmode)
838 849 gitlr.push(firstline)
839 850 (dopatch, gitpatches) = readgitpatch(gitlr)
840 851 fp.seek(pos)
841 852 return dopatch, gitpatches
842 853
843 854 def iterhunks(ui, fp, sourcefile=None, textmode=False):
844 855 """Read a patch and yield the following events:
845 856 - ("file", afile, bfile, firsthunk): select a new target file.
846 857 - ("hunk", hunk): a new hunk is ready to be applied, follows a
847 858 "file" event.
848 859 - ("git", gitchanges): current diff is in git format, gitchanges
849 860 maps filenames to gitpatch records. Unique event.
850 861
851 862 If textmode is True, input line-endings are normalized to LF.
852 863 """
853 864 changed = {}
854 865 current_hunk = None
855 866 afile = ""
856 867 bfile = ""
857 868 state = None
858 869 hunknum = 0
859 870 emitfile = False
860 871 git = False
861 872
862 873 # our states
863 874 BFILE = 1
864 875 context = None
865 876 lr = linereader(fp, textmode)
866 877 dopatch = True
867 878 # gitworkdone is True if a git operation (copy, rename, ...) was
868 879 # performed already for the current file. Useful when the file
869 880 # section may have no hunk.
870 881 gitworkdone = False
871 882
872 883 while True:
873 884 newfile = False
874 885 x = lr.readline()
875 886 if not x:
876 887 break
877 888 if current_hunk:
878 889 if x.startswith('\ '):
879 890 current_hunk.fix_newline()
880 891 yield 'hunk', current_hunk
881 892 current_hunk = None
882 893 gitworkdone = False
883 894 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
884 895 ((context is not False) and x.startswith('***************')))):
885 896 try:
886 897 if context is None and x.startswith('***************'):
887 898 context = True
888 899 gpatch = changed.get(bfile)
889 900 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
890 901 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
891 902 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
892 903 if remove:
893 904 gpatch = changed.get(afile[2:])
894 905 if gpatch and gpatch.mode[0]:
895 906 current_hunk = symlinkhunk(gpatch, current_hunk)
896 907 except PatchError, err:
897 908 ui.debug(err)
898 909 current_hunk = None
899 910 continue
900 911 hunknum += 1
901 912 if emitfile:
902 913 emitfile = False
903 914 yield 'file', (afile, bfile, current_hunk)
904 915 elif state == BFILE and x.startswith('GIT binary patch'):
905 916 current_hunk = binhunk(changed[bfile])
906 917 hunknum += 1
907 918 if emitfile:
908 919 emitfile = False
909 920 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
910 921 current_hunk.extract(lr)
911 922 elif x.startswith('diff --git'):
912 923 # check for git diff, scanning the whole patch file if needed
913 924 m = gitre.match(x)
914 925 if m:
915 926 afile, bfile = m.group(1, 2)
916 927 if not git:
917 928 git = True
918 929 dopatch, gitpatches = scangitpatch(lr, x)
919 930 yield 'git', gitpatches
920 931 for gp in gitpatches:
921 932 changed[gp.path] = gp
922 933 # else error?
923 934 # copy/rename + modify should modify target, not source
924 935 gp = changed.get(bfile)
925 936 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
926 937 afile = bfile
927 938 gitworkdone = True
928 939 newfile = True
929 940 elif x.startswith('---'):
930 941 # check for a unified diff
931 942 l2 = lr.readline()
932 943 if not l2.startswith('+++'):
933 944 lr.push(l2)
934 945 continue
935 946 newfile = True
936 947 context = False
937 948 afile = parsefilename(x)
938 949 bfile = parsefilename(l2)
939 950 elif x.startswith('***'):
940 951 # check for a context diff
941 952 l2 = lr.readline()
942 953 if not l2.startswith('---'):
943 954 lr.push(l2)
944 955 continue
945 956 l3 = lr.readline()
946 957 lr.push(l3)
947 958 if not l3.startswith("***************"):
948 959 lr.push(l2)
949 960 continue
950 961 newfile = True
951 962 context = True
952 963 afile = parsefilename(x)
953 964 bfile = parsefilename(l2)
954 965
955 966 if newfile:
956 967 emitfile = True
957 968 state = BFILE
958 969 hunknum = 0
959 970 if current_hunk:
960 971 if current_hunk.complete():
961 972 yield 'hunk', current_hunk
962 973 else:
963 974 raise PatchError(_("malformed patch %s %s") % (afile,
964 975 current_hunk.desc))
965 976
966 977 if hunknum == 0 and dopatch and not gitworkdone:
967 978 raise NoHunks
968 979
969 980 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
970 981 eol=None):
971 982 """
972 983 Reads a patch from fp and tries to apply it.
973 984
974 985 The dict 'changed' is filled in with all of the filenames changed
975 986 by the patch. Returns 0 for a clean patch, -1 if any rejects were
976 987 found and 1 if there was any fuzz.
977 988
978 989 If 'eol' is None, the patch content and patched file are read in
979 990 binary mode. Otherwise, line endings are ignored when patching then
980 991 normalized to 'eol' (usually '\n' or \r\n').
981 992 """
982 993 rejects = 0
983 994 err = 0
984 995 current_file = None
985 996 gitpatches = None
986 997 opener = util.opener(os.getcwd())
987 998 textmode = eol is not None
988 999
989 1000 def closefile():
990 1001 if not current_file:
991 1002 return 0
992 1003 current_file.close()
993 1004 return len(current_file.rej)
994 1005
995 1006 for state, values in iterhunks(ui, fp, sourcefile, textmode):
996 1007 if state == 'hunk':
997 1008 if not current_file:
998 1009 continue
999 1010 current_hunk = values
1000 1011 ret = current_file.apply(current_hunk, reverse)
1001 1012 if ret >= 0:
1002 1013 changed.setdefault(current_file.fname, None)
1003 1014 if ret > 0:
1004 1015 err = 1
1005 1016 elif state == 'file':
1006 1017 rejects += closefile()
1007 1018 afile, bfile, first_hunk = values
1008 1019 try:
1009 1020 if sourcefile:
1010 1021 current_file = patchfile(ui, sourcefile, opener, eol=eol)
1011 1022 else:
1012 1023 current_file, missing = selectfile(afile, bfile, first_hunk,
1013 1024 strip, reverse)
1014 1025 current_file = patchfile(ui, current_file, opener, missing, eol)
1015 1026 except PatchError, err:
1016 1027 ui.warn(str(err) + '\n')
1017 1028 current_file, current_hunk = None, None
1018 1029 rejects += 1
1019 1030 continue
1020 1031 elif state == 'git':
1021 1032 gitpatches = values
1022 1033 cwd = os.getcwd()
1023 1034 for gp in gitpatches:
1024 1035 if gp.op in ('COPY', 'RENAME'):
1025 1036 copyfile(gp.oldpath, gp.path, cwd)
1026 1037 changed[gp.path] = gp
1027 1038 else:
1028 1039 raise util.Abort(_('unsupported parser state: %s') % state)
1029 1040
1030 1041 rejects += closefile()
1031 1042
1032 1043 if rejects:
1033 1044 return -1
1034 1045 return err
1035 1046
1036 1047 def diffopts(ui, opts={}, untrusted=False):
1037 1048 def get(key, name=None, getter=ui.configbool):
1038 1049 return (opts.get(key) or
1039 1050 getter('diff', name or key, None, untrusted=untrusted))
1040 1051 return mdiff.diffopts(
1041 1052 text=opts.get('text'),
1042 1053 git=get('git'),
1043 1054 nodates=get('nodates'),
1044 1055 showfunc=get('show_function', 'showfunc'),
1045 1056 ignorews=get('ignore_all_space', 'ignorews'),
1046 1057 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1047 1058 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1048 1059 context=get('unified', getter=ui.config))
1049 1060
1050 1061 def updatedir(ui, repo, patches, similarity=0):
1051 1062 '''Update dirstate after patch application according to metadata'''
1052 1063 if not patches:
1053 1064 return
1054 1065 copies = []
1055 1066 removes = set()
1056 1067 cfiles = patches.keys()
1057 1068 cwd = repo.getcwd()
1058 1069 if cwd:
1059 1070 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1060 1071 for f in patches:
1061 1072 gp = patches[f]
1062 1073 if not gp:
1063 1074 continue
1064 1075 if gp.op == 'RENAME':
1065 1076 copies.append((gp.oldpath, gp.path))
1066 1077 removes.add(gp.oldpath)
1067 1078 elif gp.op == 'COPY':
1068 1079 copies.append((gp.oldpath, gp.path))
1069 1080 elif gp.op == 'DELETE':
1070 1081 removes.add(gp.path)
1071 1082 for src, dst in copies:
1072 1083 repo.copy(src, dst)
1073 1084 if (not similarity) and removes:
1074 1085 repo.remove(sorted(removes), True)
1075 1086 for f in patches:
1076 1087 gp = patches[f]
1077 1088 if gp and gp.mode:
1078 1089 islink, isexec = gp.mode
1079 1090 dst = repo.wjoin(gp.path)
1080 1091 # patch won't create empty files
1081 1092 if gp.op == 'ADD' and not os.path.exists(dst):
1082 1093 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1083 1094 repo.wwrite(gp.path, '', flags)
1084 1095 elif gp.op != 'DELETE':
1085 1096 util.set_flags(dst, islink, isexec)
1086 1097 cmdutil.addremove(repo, cfiles, similarity=similarity)
1087 1098 files = patches.keys()
1088 1099 files.extend([r for r in removes if r not in files])
1089 1100 return sorted(files)
1090 1101
1091 1102 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1092 1103 """use <patcher> to apply <patchname> to the working directory.
1093 1104 returns whether patch was applied with fuzz factor."""
1094 1105
1095 1106 fuzz = False
1096 1107 if cwd:
1097 1108 args.append('-d %s' % util.shellquote(cwd))
1098 1109 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1099 1110 util.shellquote(patchname)))
1100 1111
1101 1112 for line in fp:
1102 1113 line = line.rstrip()
1103 1114 ui.note(line + '\n')
1104 1115 if line.startswith('patching file '):
1105 1116 pf = util.parse_patch_output(line)
1106 1117 printed_file = False
1107 1118 files.setdefault(pf, None)
1108 1119 elif line.find('with fuzz') >= 0:
1109 1120 fuzz = True
1110 1121 if not printed_file:
1111 1122 ui.warn(pf + '\n')
1112 1123 printed_file = True
1113 1124 ui.warn(line + '\n')
1114 1125 elif line.find('saving rejects to file') >= 0:
1115 1126 ui.warn(line + '\n')
1116 1127 elif line.find('FAILED') >= 0:
1117 1128 if not printed_file:
1118 1129 ui.warn(pf + '\n')
1119 1130 printed_file = True
1120 1131 ui.warn(line + '\n')
1121 1132 code = fp.close()
1122 1133 if code:
1123 1134 raise PatchError(_("patch command failed: %s") %
1124 1135 util.explain_exit(code)[0])
1125 1136 return fuzz
1126 1137
1127 1138 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
1128 1139 """use builtin patch to apply <patchobj> to the working directory.
1129 1140 returns whether patch was applied with fuzz factor."""
1130 1141
1131 1142 if eolmode is None:
1132 1143 eolmode = ui.config('patch', 'eol', 'strict')
1133 1144 try:
1134 1145 eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
1135 1146 except KeyError:
1136 1147 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1137 1148
1138 1149 try:
1139 1150 fp = open(patchobj, 'rb')
1140 1151 except TypeError:
1141 1152 fp = patchobj
1142 1153 if cwd:
1143 1154 curdir = os.getcwd()
1144 1155 os.chdir(cwd)
1145 1156 try:
1146 1157 ret = applydiff(ui, fp, files, strip=strip, eol=eol)
1147 1158 finally:
1148 1159 if cwd:
1149 1160 os.chdir(curdir)
1150 1161 if ret < 0:
1151 1162 raise PatchError
1152 1163 return ret > 0
1153 1164
1154 1165 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
1155 1166 """Apply <patchname> to the working directory.
1156 1167
1157 1168 'eolmode' specifies how end of lines should be handled. It can be:
1158 1169 - 'strict': inputs are read in binary mode, EOLs are preserved
1159 1170 - 'crlf': EOLs are ignored when patching and reset to CRLF
1160 1171 - 'lf': EOLs are ignored when patching and reset to LF
1161 1172 - None: get it from user settings, default to 'strict'
1162 1173 'eolmode' is ignored when using an external patcher program.
1163 1174
1164 1175 Returns whether patch was applied with fuzz factor.
1165 1176 """
1166 1177 patcher = ui.config('ui', 'patch')
1167 1178 args = []
1168 1179 try:
1169 1180 if patcher:
1170 1181 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1171 1182 files)
1172 1183 else:
1173 1184 try:
1174 1185 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1175 1186 except NoHunks:
1176 1187 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1177 1188 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1178 1189 patcher)
1179 1190 if util.needbinarypatch():
1180 1191 args.append('--binary')
1181 1192 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1182 1193 files)
1183 1194 except PatchError, err:
1184 1195 s = str(err)
1185 1196 if s:
1186 1197 raise util.Abort(s)
1187 1198 else:
1188 1199 raise util.Abort(_('patch failed to apply'))
1189 1200
1190 1201 def b85diff(to, tn):
1191 1202 '''print base85-encoded binary diff'''
1192 1203 def gitindex(text):
1193 1204 if not text:
1194 1205 return '0' * 40
1195 1206 l = len(text)
1196 1207 s = util.sha1('blob %d\0' % l)
1197 1208 s.update(text)
1198 1209 return s.hexdigest()
1199 1210
1200 1211 def fmtline(line):
1201 1212 l = len(line)
1202 1213 if l <= 26:
1203 1214 l = chr(ord('A') + l - 1)
1204 1215 else:
1205 1216 l = chr(l - 26 + ord('a') - 1)
1206 1217 return '%c%s\n' % (l, base85.b85encode(line, True))
1207 1218
1208 1219 def chunk(text, csize=52):
1209 1220 l = len(text)
1210 1221 i = 0
1211 1222 while i < l:
1212 1223 yield text[i:i+csize]
1213 1224 i += csize
1214 1225
1215 1226 tohash = gitindex(to)
1216 1227 tnhash = gitindex(tn)
1217 1228 if tohash == tnhash:
1218 1229 return ""
1219 1230
1220 1231 # TODO: deltas
1221 1232 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1222 1233 (tohash, tnhash, len(tn))]
1223 1234 for l in chunk(zlib.compress(tn)):
1224 1235 ret.append(fmtline(l))
1225 1236 ret.append('\n')
1226 1237 return ''.join(ret)
1227 1238
1228 1239 def _addmodehdr(header, omode, nmode):
1229 1240 if omode != nmode:
1230 1241 header.append('old mode %s\n' % omode)
1231 1242 header.append('new mode %s\n' % nmode)
1232 1243
1233 1244 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1234 1245 '''yields diff of changes to files between two nodes, or node and
1235 1246 working directory.
1236 1247
1237 1248 if node1 is None, use first dirstate parent instead.
1238 1249 if node2 is None, compare node1 with working directory.'''
1239 1250
1240 1251 if opts is None:
1241 1252 opts = mdiff.defaultopts
1242 1253
1243 1254 if not node1:
1244 1255 node1 = repo.dirstate.parents()[0]
1245 1256
1246 1257 def lrugetfilectx():
1247 1258 cache = {}
1248 1259 order = []
1249 1260 def getfilectx(f, ctx):
1250 1261 fctx = ctx.filectx(f, filelog=cache.get(f))
1251 1262 if f not in cache:
1252 1263 if len(cache) > 20:
1253 1264 del cache[order.pop(0)]
1254 1265 cache[f] = fctx._filelog
1255 1266 else:
1256 1267 order.remove(f)
1257 1268 order.append(f)
1258 1269 return fctx
1259 1270 return getfilectx
1260 1271 getfilectx = lrugetfilectx()
1261 1272
1262 1273 ctx1 = repo[node1]
1263 1274 ctx2 = repo[node2]
1264 1275
1265 1276 if not changes:
1266 1277 changes = repo.status(ctx1, ctx2, match=match)
1267 1278 modified, added, removed = changes[:3]
1268 1279
1269 1280 if not modified and not added and not removed:
1270 1281 return
1271 1282
1272 1283 date1 = util.datestr(ctx1.date())
1273 1284 man1 = ctx1.manifest()
1274 1285
1275 1286 if repo.ui.quiet:
1276 1287 r = None
1277 1288 else:
1278 1289 hexfunc = repo.ui.debugflag and hex or short
1279 1290 r = [hexfunc(node) for node in [node1, node2] if node]
1280 1291
1281 1292 if opts.git:
1282 1293 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1283 1294 copy = copy.copy()
1284 1295 for k, v in copy.items():
1285 1296 copy[v] = k
1286 1297
1287 1298 gone = set()
1288 1299 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1289 1300
1290 1301 for f in sorted(modified + added + removed):
1291 1302 to = None
1292 1303 tn = None
1293 1304 dodiff = True
1294 1305 header = []
1295 1306 if f in man1:
1296 1307 to = getfilectx(f, ctx1).data()
1297 1308 if f not in removed:
1298 1309 tn = getfilectx(f, ctx2).data()
1299 1310 a, b = f, f
1300 1311 if opts.git:
1301 1312 if f in added:
1302 1313 mode = gitmode[ctx2.flags(f)]
1303 1314 if f in copy:
1304 1315 a = copy[f]
1305 1316 omode = gitmode[man1.flags(a)]
1306 1317 _addmodehdr(header, omode, mode)
1307 1318 if a in removed and a not in gone:
1308 1319 op = 'rename'
1309 1320 gone.add(a)
1310 1321 else:
1311 1322 op = 'copy'
1312 1323 header.append('%s from %s\n' % (op, a))
1313 1324 header.append('%s to %s\n' % (op, f))
1314 1325 to = getfilectx(a, ctx1).data()
1315 1326 else:
1316 1327 header.append('new file mode %s\n' % mode)
1317 1328 if util.binary(tn):
1318 1329 dodiff = 'binary'
1319 1330 elif f in removed:
1320 1331 # have we already reported a copy above?
1321 1332 if f in copy and copy[f] in added and copy[copy[f]] == f:
1322 1333 dodiff = False
1323 1334 else:
1324 1335 header.append('deleted file mode %s\n' %
1325 1336 gitmode[man1.flags(f)])
1326 1337 else:
1327 1338 omode = gitmode[man1.flags(f)]
1328 1339 nmode = gitmode[ctx2.flags(f)]
1329 1340 _addmodehdr(header, omode, nmode)
1330 1341 if util.binary(to) or util.binary(tn):
1331 1342 dodiff = 'binary'
1332 1343 r = None
1333 1344 header.insert(0, mdiff.diffline(r, a, b, opts))
1334 1345 if dodiff:
1335 1346 if dodiff == 'binary':
1336 1347 text = b85diff(to, tn)
1337 1348 else:
1338 1349 text = mdiff.unidiff(to, date1,
1339 1350 # ctx2 date may be dynamic
1340 1351 tn, util.datestr(ctx2.date()),
1341 1352 a, b, r, opts=opts)
1342 1353 if header and (text or len(header) > 1):
1343 1354 yield ''.join(header)
1344 1355 if text:
1345 1356 yield text
1346 1357
1347 1358 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1348 1359 opts=None):
1349 1360 '''export changesets as hg patches.'''
1350 1361
1351 1362 total = len(revs)
1352 1363 revwidth = max([len(str(rev)) for rev in revs])
1353 1364
1354 1365 def single(rev, seqno, fp):
1355 1366 ctx = repo[rev]
1356 1367 node = ctx.node()
1357 1368 parents = [p.node() for p in ctx.parents() if p]
1358 1369 branch = ctx.branch()
1359 1370 if switch_parent:
1360 1371 parents.reverse()
1361 1372 prev = (parents and parents[0]) or nullid
1362 1373
1363 1374 if not fp:
1364 1375 fp = cmdutil.make_file(repo, template, node, total=total,
1365 1376 seqno=seqno, revwidth=revwidth,
1366 1377 mode='ab')
1367 1378 if fp != sys.stdout and hasattr(fp, 'name'):
1368 1379 repo.ui.note("%s\n" % fp.name)
1369 1380
1370 1381 fp.write("# HG changeset patch\n")
1371 1382 fp.write("# User %s\n" % ctx.user())
1372 1383 fp.write("# Date %d %d\n" % ctx.date())
1373 1384 if branch and (branch != 'default'):
1374 1385 fp.write("# Branch %s\n" % branch)
1375 1386 fp.write("# Node ID %s\n" % hex(node))
1376 1387 fp.write("# Parent %s\n" % hex(prev))
1377 1388 if len(parents) > 1:
1378 1389 fp.write("# Parent %s\n" % hex(parents[1]))
1379 1390 fp.write(ctx.description().rstrip())
1380 1391 fp.write("\n\n")
1381 1392
1382 1393 for chunk in diff(repo, prev, node, opts=opts):
1383 1394 fp.write(chunk)
1384 1395
1385 1396 for seqno, rev in enumerate(revs):
1386 1397 single(rev, seqno+1, fp)
1387 1398
1388 1399 def diffstatdata(lines):
1389 1400 filename, adds, removes = None, 0, 0
1390 1401 for line in lines:
1391 1402 if line.startswith('diff'):
1392 1403 if filename:
1393 1404 yield (filename, adds, removes)
1394 1405 # set numbers to 0 anyway when starting new file
1395 1406 adds, removes = 0, 0
1396 1407 if line.startswith('diff --git'):
1397 1408 filename = gitre.search(line).group(1)
1398 1409 else:
1399 1410 # format: "diff -r ... -r ... filename"
1400 1411 filename = line.split(None, 5)[-1]
1401 1412 elif line.startswith('+') and not line.startswith('+++'):
1402 1413 adds += 1
1403 1414 elif line.startswith('-') and not line.startswith('---'):
1404 1415 removes += 1
1405 1416 if filename:
1406 1417 yield (filename, adds, removes)
1407 1418
1408 1419 def diffstat(lines, width=80):
1409 1420 output = []
1410 1421 stats = list(diffstatdata(lines))
1411 1422
1412 1423 maxtotal, maxname = 0, 0
1413 1424 totaladds, totalremoves = 0, 0
1414 1425 for filename, adds, removes in stats:
1415 1426 totaladds += adds
1416 1427 totalremoves += removes
1417 1428 maxname = max(maxname, len(filename))
1418 1429 maxtotal = max(maxtotal, adds+removes)
1419 1430
1420 1431 countwidth = len(str(maxtotal))
1421 1432 graphwidth = width - countwidth - maxname
1422 1433 if graphwidth < 10:
1423 1434 graphwidth = 10
1424 1435
1425 1436 factor = max(int(math.ceil(float(maxtotal) / graphwidth)), 1)
1426 1437
1427 1438 for filename, adds, removes in stats:
1428 1439 # If diffstat runs out of room it doesn't print anything, which
1429 1440 # isn't very useful, so always print at least one + or - if there
1430 1441 # were at least some changes
1431 1442 pluses = '+' * max(adds // factor, int(bool(adds)))
1432 1443 minuses = '-' * max(removes // factor, int(bool(removes)))
1433 1444 output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
1434 1445 adds+removes, pluses, minuses))
1435 1446
1436 1447 if stats:
1437 1448 output.append(' %d files changed, %d insertions(+), %d deletions(-)\n'
1438 1449 % (len(stats), totaladds, totalremoves))
1439 1450
1440 1451 return ''.join(output)
@@ -1,334 +1,347 b''
1 1 #!/bin/sh
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
10 10 echo line 2 >> a/a
11 11 hg --cwd a ci -u someone -d '1 0' -m'second change'
12 12
13 13 echo % import exported patch
14 14 hg clone -r0 a b
15 15 hg --cwd a export tip > tip.patch
16 16 hg --cwd b import ../tip.patch
17 17 echo % message should be same
18 18 hg --cwd b tip | grep 'second change'
19 19 echo % committer should be same
20 20 hg --cwd b tip | grep someone
21 21 rm -r b
22 22
23 23 echo % import exported patch with external patcher
24 24 cat > dummypatch.py <<EOF
25 25 print 'patching file a'
26 26 file('a', 'wb').write('line2\n')
27 27 EOF
28 28 chmod +x dummypatch.py
29 29 hg clone -r0 a b
30 30 hg --cwd a export tip > tip.patch
31 31 hg --config ui.patch='python ../dummypatch.py' --cwd b import ../tip.patch
32 32 cat b/a
33 33 rm -r b
34 34
35 35 echo % import of plain diff should fail without message
36 36 hg clone -r0 a b
37 37 hg --cwd a diff -r0:1 > tip.patch
38 38 hg --cwd b import ../tip.patch
39 39 rm -r b
40 40
41 41 echo % import of plain diff should be ok with message
42 42 hg clone -r0 a b
43 43 hg --cwd a diff -r0:1 > tip.patch
44 44 hg --cwd b import -mpatch ../tip.patch
45 45 rm -r b
46 46
47 47 echo % import of plain diff with specific date and user
48 48 hg clone -r0 a b
49 49 hg --cwd a diff -r0:1 > tip.patch
50 50 hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../tip.patch
51 51 hg -R b tip -pv
52 52 rm -r b
53 53
54 54 echo % import of plain diff should be ok with --no-commit
55 55 hg clone -r0 a b
56 56 hg --cwd a diff -r0:1 > tip.patch
57 57 hg --cwd b import --no-commit ../tip.patch
58 58 hg --cwd b diff --nodates
59 59 rm -r b
60 60
61 61 echo % hg -R repo import
62 62 # put the clone in a subdir - having a directory named "a"
63 63 # used to hide a bug.
64 64 mkdir dir
65 65 hg clone -r0 a dir/b
66 66 hg --cwd a export tip > dir/tip.patch
67 67 cd dir
68 68 hg -R b import tip.patch
69 69 cd ..
70 70 rm -r dir
71 71
72 72 echo % import from stdin
73 73 hg clone -r0 a b
74 74 hg --cwd a export tip | hg --cwd b import -
75 75 rm -r b
76 76
77 77 echo % override commit message
78 78 hg clone -r0 a b
79 79 hg --cwd a export tip | hg --cwd b import -m 'override' -
80 80 hg --cwd b tip | grep override
81 81 rm -r b
82 82
83 83 cat > mkmsg.py <<EOF
84 84 import email.Message, sys
85 85 msg = email.Message.Message()
86 86 msg.set_payload('email commit message\n' + open('tip.patch', 'rb').read())
87 87 msg['Subject'] = 'email patch'
88 88 msg['From'] = 'email patcher'
89 89 sys.stdout.write(msg.as_string())
90 90 EOF
91 91
92 92 echo % plain diff in email, subject, message body
93 93 hg clone -r0 a b
94 94 hg --cwd a diff -r0:1 > tip.patch
95 95 python mkmsg.py > msg.patch
96 96 hg --cwd b import ../msg.patch
97 97 hg --cwd b tip | grep email
98 98 rm -r b
99 99
100 100 echo % plain diff in email, no subject, message body
101 101 hg clone -r0 a b
102 102 grep -v '^Subject:' msg.patch | hg --cwd b import -
103 103 rm -r b
104 104
105 105 echo % plain diff in email, subject, no message body
106 106 hg clone -r0 a b
107 107 grep -v '^email ' msg.patch | hg --cwd b import -
108 108 rm -r b
109 109
110 110 echo % plain diff in email, no subject, no message body, should fail
111 111 hg clone -r0 a b
112 112 egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
113 113 rm -r b
114 114
115 115 echo % hg export in email, should use patch header
116 116 hg clone -r0 a b
117 117 hg --cwd a export tip > tip.patch
118 118 python mkmsg.py | hg --cwd b import -
119 119 hg --cwd b tip | grep second
120 120 rm -r b
121 121
122 122 # subject: duplicate detection, removal of [PATCH]
123 123 # The '---' tests the gitsendmail handling without proper mail headers
124 124 cat > mkmsg2.py <<EOF
125 125 import email.Message, sys
126 126 msg = email.Message.Message()
127 127 msg.set_payload('email patch\n\nnext line\n---\n' + open('tip.patch').read())
128 128 msg['Subject'] = '[PATCH] email patch'
129 129 msg['From'] = 'email patcher'
130 130 sys.stdout.write(msg.as_string())
131 131 EOF
132 132
133 133 echo '% plain diff in email, [PATCH] subject, message body with subject'
134 134 hg clone -r0 a b
135 135 hg --cwd a diff -r0:1 > tip.patch
136 136 python mkmsg2.py | hg --cwd b import -
137 137 hg --cwd b tip --template '{desc}\n'
138 138 rm -r b
139 139
140 140 # We weren't backing up the correct dirstate file when importing many patches
141 141 # (issue963)
142 142 echo '% import patch1 patch2; rollback'
143 143 echo line 3 >> a/a
144 144 hg --cwd a ci -m'third change'
145 145 hg --cwd a export -o '../patch%R' 1 2
146 146 hg clone -qr0 a b
147 147 hg --cwd b parents --template 'parent: {rev}\n'
148 148 hg --cwd b import ../patch1 ../patch2
149 149 hg --cwd b rollback
150 150 hg --cwd b parents --template 'parent: {rev}\n'
151 151 rm -r b
152 152
153 153 # bug non regression test
154 154 # importing a patch in a subdirectory failed at the commit stage
155 155 echo line 2 >> a/d1/d2/a
156 156 hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
157 157 echo % hg import in a subdirectory
158 158 hg clone -r0 a b
159 159 hg --cwd a export tip | sed -e 's/d1\/d2\///' > tip.patch
160 160 dir=`pwd`
161 161 cd b/d1/d2 2>&1 > /dev/null
162 162 hg import ../../../tip.patch
163 163 cd $dir
164 164 echo "% message should be 'subdir change'"
165 165 hg --cwd b tip | grep 'subdir change'
166 166 echo "% committer should be 'someoneelse'"
167 167 hg --cwd b tip | grep someoneelse
168 168 echo "% should be empty"
169 169 hg --cwd b status
170 170
171 171
172 172 # Test fuzziness (ambiguous patch location, fuzz=2)
173 173 echo % test fuzziness
174 174 hg init fuzzy
175 175 cd fuzzy
176 176 echo line1 > a
177 177 echo line0 >> a
178 178 echo line3 >> a
179 179 hg ci -Am adda
180 180 echo line1 > a
181 181 echo line2 >> a
182 182 echo line0 >> a
183 183 echo line3 >> a
184 184 hg ci -m change a
185 185 hg export tip > tip.patch
186 186 hg up -C 0
187 187 echo line1 > a
188 188 echo line0 >> a
189 189 echo line1 >> a
190 190 echo line0 >> a
191 191 hg ci -m brancha
192 192 hg import -v tip.patch
193 193 cd ..
194 194
195 195 # Test hunk touching empty files (issue906)
196 196 hg init empty
197 197 cd empty
198 198 touch a
199 199 touch b1
200 200 touch c1
201 201 echo d > d
202 202 hg ci -Am init
203 203 echo a > a
204 204 echo b > b1
205 205 hg mv b1 b2
206 206 echo c > c1
207 207 hg copy c1 c2
208 208 rm d
209 209 touch d
210 210 hg diff --git
211 211 hg ci -m empty
212 212 hg export --git tip > empty.diff
213 213 hg up -C 0
214 214 hg import empty.diff
215 215 for name in a b1 b2 c1 c2 d;
216 216 do
217 217 echo % $name file
218 218 test -f $name && cat $name
219 219 done
220 220 cd ..
221 221
222 222 # Test importing a patch ending with a binary file removal
223 223 echo % test trailing binary removal
224 224 hg init binaryremoval
225 225 cd binaryremoval
226 226 echo a > a
227 227 python -c "file('b', 'wb').write('a\x00b')"
228 228 hg ci -Am addall
229 229 hg rm a
230 230 hg rm b
231 231 hg st
232 232 hg ci -m remove
233 233 hg export --git . > remove.diff
234 234 cat remove.diff | grep git
235 235 hg up -C 0
236 236 hg import remove.diff
237 237 hg manifest
238 238 cd ..
239 239
240 240 echo % 'test update+rename with common name (issue 927)'
241 241 hg init t
242 242 cd t
243 243 touch a
244 244 hg ci -Am t
245 245 echo a > a
246 246 # Here, bfile.startswith(afile)
247 247 hg copy a a2
248 248 hg ci -m copya
249 249 hg export --git tip > copy.diff
250 250 hg up -C 0
251 251 hg import copy.diff
252 252 echo % view a
253 253 # a should contain an 'a'
254 254 cat a
255 255 echo % view a2
256 256 # and a2 should have duplicated it
257 257 cat a2
258 258 cd ..
259 259
260 260 echo % 'test -p0'
261 261 hg init p0
262 262 cd p0
263 263 echo a > a
264 264 hg ci -Am t
265 265 hg import -p0 - << EOF
266 266 foobar
267 267 --- a Sat Apr 12 22:43:58 2008 -0400
268 268 +++ a Sat Apr 12 22:44:05 2008 -0400
269 269 @@ -1,1 +1,1 @@
270 270 -a
271 271 +bb
272 272 EOF
273 273 hg status
274 274 cat a
275 275 cd ..
276 276
277 277 echo % 'test paths outside repo root'
278 278 mkdir outside
279 279 touch outside/foo
280 280 hg init inside
281 281 cd inside
282 282 hg import - <<EOF
283 283 diff --git a/a b/b
284 284 rename from ../outside/foo
285 285 rename to bar
286 286 EOF
287 287 cd ..
288 288
289 289 echo '% test import with similarity (issue295)'
290 290 hg init sim
291 291 cd sim
292 292 echo 'this is a test' > a
293 293 hg ci -Ama
294 294 cat > ../rename.diff <<EOF
295 295 diff --git a/a b/a
296 296 deleted file mode 100644
297 297 --- a/a
298 298 +++ /dev/null
299 299 @@ -1,1 +0,0 @@
300 300 -this is a test
301 301 diff --git a/b b/b
302 302 new file mode 100644
303 303 --- /dev/null
304 304 +++ b/b
305 305 @@ -0,0 +1,2 @@
306 306 +this is a test
307 307 +foo
308 308 EOF
309 309 hg import --no-commit -v -s 1 ../rename.diff
310 310 hg st -C
311 311 hg revert -a
312 312 rm b
313 313 hg import --no-commit -v -s 100 ../rename.diff
314 314 hg st -C
315 315 cd ..
316 316
317 317
318 318 echo '% add empty file from the end of patch (issue 1495)'
319 319 hg init addemptyend
320 320 cd addemptyend
321 321 touch a
322 322 hg addremove
323 323 hg ci -m "commit"
324 324 cat > a.patch <<EOF
325 325 diff --git a/a b/a
326 326 --- a/a
327 327 +++ b/a
328 328 @@ -0,0 +1,1 @@
329 329 +a
330 330 diff --git a/b b/b
331 331 new file mode 100644
332 332 EOF
333 333 hg import --no-commit a.patch
334 334 cd ..
335
336 echo '% create file when source is not /dev/null'
337 cat > create.patch <<EOF
338 diff -Naur proj-orig/foo proj-new/foo
339 --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
340 +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
341 @@ -0,0 +1,1 @@
342 +a
343 EOF
344 hg init oddcreate
345 cd oddcreate
346 hg import --no-commit ../create.patch
347 cat foo
@@ -1,294 +1,297 b''
1 1 adding a
2 2 adding d1/d2/a
3 3 % import exported patch
4 4 requesting all changes
5 5 adding changesets
6 6 adding manifests
7 7 adding file changes
8 8 added 1 changesets with 2 changes to 2 files
9 9 updating working directory
10 10 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 11 applying ../tip.patch
12 12 % message should be same
13 13 summary: second change
14 14 % committer should be same
15 15 user: someone
16 16 % import exported patch with external patcher
17 17 requesting all changes
18 18 adding changesets
19 19 adding manifests
20 20 adding file changes
21 21 added 1 changesets with 2 changes to 2 files
22 22 updating working directory
23 23 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24 applying ../tip.patch
25 25 line2
26 26 % import of plain diff should fail without message
27 27 requesting all changes
28 28 adding changesets
29 29 adding manifests
30 30 adding file changes
31 31 added 1 changesets with 2 changes to 2 files
32 32 updating working directory
33 33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 applying ../tip.patch
35 35 abort: empty commit message
36 36 % import of plain diff should be ok with message
37 37 requesting all changes
38 38 adding changesets
39 39 adding manifests
40 40 adding file changes
41 41 added 1 changesets with 2 changes to 2 files
42 42 updating working directory
43 43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 44 applying ../tip.patch
45 45 % import of plain diff with specific date and user
46 46 requesting all changes
47 47 adding changesets
48 48 adding manifests
49 49 adding file changes
50 50 added 1 changesets with 2 changes to 2 files
51 51 updating working directory
52 52 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 53 applying ../tip.patch
54 54 changeset: 1:ca68f19f3a40
55 55 tag: tip
56 56 user: user@nowhere.net
57 57 date: Thu Jan 01 00:00:01 1970 +0000
58 58 files: a
59 59 description:
60 60 patch
61 61
62 62
63 63 diff -r 80971e65b431 -r ca68f19f3a40 a
64 64 --- a/a Thu Jan 01 00:00:00 1970 +0000
65 65 +++ b/a Thu Jan 01 00:00:01 1970 +0000
66 66 @@ -1,1 +1,2 @@
67 67 line 1
68 68 +line 2
69 69
70 70 % import of plain diff should be ok with --no-commit
71 71 requesting all changes
72 72 adding changesets
73 73 adding manifests
74 74 adding file changes
75 75 added 1 changesets with 2 changes to 2 files
76 76 updating working directory
77 77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 78 applying ../tip.patch
79 79 diff -r 80971e65b431 a
80 80 --- a/a
81 81 +++ b/a
82 82 @@ -1,1 +1,2 @@
83 83 line 1
84 84 +line 2
85 85 % hg -R repo import
86 86 requesting all changes
87 87 adding changesets
88 88 adding manifests
89 89 adding file changes
90 90 added 1 changesets with 2 changes to 2 files
91 91 updating working directory
92 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 applying tip.patch
94 94 % import from stdin
95 95 requesting all changes
96 96 adding changesets
97 97 adding manifests
98 98 adding file changes
99 99 added 1 changesets with 2 changes to 2 files
100 100 updating working directory
101 101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 applying patch from stdin
103 103 % override commit message
104 104 requesting all changes
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 working directory
110 110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 applying patch from stdin
112 112 summary: override
113 113 % plain diff in email, subject, message body
114 114 requesting all changes
115 115 adding changesets
116 116 adding manifests
117 117 adding file changes
118 118 added 1 changesets with 2 changes to 2 files
119 119 updating working directory
120 120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 121 applying ../msg.patch
122 122 user: email patcher
123 123 summary: email patch
124 124 % plain diff in email, no subject, message body
125 125 requesting all changes
126 126 adding changesets
127 127 adding manifests
128 128 adding file changes
129 129 added 1 changesets with 2 changes to 2 files
130 130 updating working directory
131 131 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
132 132 applying patch from stdin
133 133 % plain diff in email, subject, no message body
134 134 requesting all changes
135 135 adding changesets
136 136 adding manifests
137 137 adding file changes
138 138 added 1 changesets with 2 changes to 2 files
139 139 updating working directory
140 140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 applying patch from stdin
142 142 % plain diff in email, no subject, no message body, should fail
143 143 requesting all changes
144 144 adding changesets
145 145 adding manifests
146 146 adding file changes
147 147 added 1 changesets with 2 changes to 2 files
148 148 updating working directory
149 149 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 150 applying patch from stdin
151 151 abort: empty commit message
152 152 % hg export in email, should use patch header
153 153 requesting all changes
154 154 adding changesets
155 155 adding manifests
156 156 adding file changes
157 157 added 1 changesets with 2 changes to 2 files
158 158 updating working directory
159 159 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 160 applying patch from stdin
161 161 summary: second change
162 162 % plain diff in email, [PATCH] subject, message body with subject
163 163 requesting all changes
164 164 adding changesets
165 165 adding manifests
166 166 adding file changes
167 167 added 1 changesets with 2 changes to 2 files
168 168 updating working directory
169 169 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 170 applying patch from stdin
171 171 email patch
172 172
173 173 next line
174 174 ---
175 175 % import patch1 patch2; rollback
176 176 parent: 0
177 177 applying ../patch1
178 178 applying ../patch2
179 179 rolling back last transaction
180 180 parent: 1
181 181 % hg import in a subdirectory
182 182 requesting all changes
183 183 adding changesets
184 184 adding manifests
185 185 adding file changes
186 186 added 1 changesets with 2 changes to 2 files
187 187 updating working directory
188 188 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 189 applying ../../../tip.patch
190 190 % message should be 'subdir change'
191 191 summary: subdir change
192 192 % committer should be 'someoneelse'
193 193 user: someoneelse
194 194 % should be empty
195 195 % test fuzziness
196 196 adding a
197 197 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 198 created new head
199 199 applying tip.patch
200 200 patching file a
201 201 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
202 202 a
203 203 adding a
204 204 adding b1
205 205 adding c1
206 206 adding d
207 207 diff --git a/a b/a
208 208 --- a/a
209 209 +++ b/a
210 210 @@ -0,0 +1,1 @@
211 211 +a
212 212 diff --git a/b1 b/b2
213 213 rename from b1
214 214 rename to b2
215 215 --- a/b1
216 216 +++ b/b2
217 217 @@ -0,0 +1,1 @@
218 218 +b
219 219 diff --git a/c1 b/c1
220 220 --- a/c1
221 221 +++ b/c1
222 222 @@ -0,0 +1,1 @@
223 223 +c
224 224 diff --git a/c1 b/c2
225 225 copy from c1
226 226 copy to c2
227 227 --- a/c1
228 228 +++ b/c2
229 229 @@ -0,0 +1,1 @@
230 230 +c
231 231 diff --git a/d b/d
232 232 --- a/d
233 233 +++ b/d
234 234 @@ -1,1 +0,0 @@
235 235 -d
236 236 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
237 237 applying empty.diff
238 238 % a file
239 239 a
240 240 % b1 file
241 241 % b2 file
242 242 b
243 243 % c1 file
244 244 c
245 245 % c2 file
246 246 c
247 247 % d file
248 248 % test trailing binary removal
249 249 adding a
250 250 adding b
251 251 R a
252 252 R b
253 253 diff --git a/a b/a
254 254 diff --git a/b b/b
255 255 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 256 applying remove.diff
257 257 % test update+rename with common name (issue 927)
258 258 adding a
259 259 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
260 260 applying copy.diff
261 261 % view a
262 262 a
263 263 % view a2
264 264 a
265 265 % test -p0
266 266 adding a
267 267 applying patch from stdin
268 268 bb
269 269 % test paths outside repo root
270 270 applying patch from stdin
271 271 abort: ../outside/foo not under root
272 272 % test import with similarity (issue295)
273 273 adding a
274 274 applying ../rename.diff
275 275 patching file a
276 276 patching file b
277 277 removing a
278 278 adding b
279 279 recording removal of a as rename to b (88% similar)
280 280 A b
281 281 a
282 282 R a
283 283 undeleting a
284 284 forgetting b
285 285 applying ../rename.diff
286 286 patching file a
287 287 patching file b
288 288 removing a
289 289 adding b
290 290 A b
291 291 R a
292 292 % add empty file from the end of patch (issue 1495)
293 293 adding a
294 294 applying a.patch
295 % create file when source is not /dev/null
296 applying ../create.patch
297 a
General Comments 0
You need to be logged in to leave comments. Login now