##// END OF EJS Templates
i18n: avoid naive plural tricks
Martin Geisler -
r6952:3fffba1c default
parent child Browse files
Show More
@@ -1,1334 +1,1330
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
7 7 # of the GNU General Public License, 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, revlog, diffhelpers, copies
12 12 import cStringIO, email.Parser, os, popen2, re, errno
13 13 import sys, tempfile, zlib
14 14
15 15 class PatchError(Exception):
16 16 pass
17 17
18 18 class NoHunks(PatchError):
19 19 pass
20 20
21 21 # helper functions
22 22
23 23 def copyfile(src, dst, basedir=None):
24 24 if not basedir:
25 25 basedir = os.getcwd()
26 26
27 27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
28 28 if os.path.exists(absdst):
29 29 raise util.Abort(_("cannot create %s: destination already exists") %
30 30 dst)
31 31
32 32 targetdir = os.path.dirname(absdst)
33 33 if not os.path.isdir(targetdir):
34 34 os.makedirs(targetdir)
35 35
36 36 util.copyfile(abssrc, absdst)
37 37
38 38 # public functions
39 39
40 40 def extract(ui, fileobj):
41 41 '''extract patch from data read from fileobj.
42 42
43 43 patch can be a normal patch or contained in an email message.
44 44
45 45 return tuple (filename, message, user, date, node, p1, p2).
46 46 Any item in the returned tuple can be None. If filename is None,
47 47 fileobj did not contain a patch. Caller must unlink filename when done.'''
48 48
49 49 # attempt to detect the start of a patch
50 50 # (this heuristic is borrowed from quilt)
51 51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
52 52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
53 53 '(---|\*\*\*)[ \t])', re.MULTILINE)
54 54
55 55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
56 56 tmpfp = os.fdopen(fd, 'w')
57 57 try:
58 58 msg = email.Parser.Parser().parse(fileobj)
59 59
60 60 subject = msg['Subject']
61 61 user = msg['From']
62 62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
63 63 # should try to parse msg['Date']
64 64 date = None
65 65 nodeid = None
66 66 branch = None
67 67 parents = []
68 68
69 69 if subject:
70 70 if subject.startswith('[PATCH'):
71 71 pend = subject.find(']')
72 72 if pend >= 0:
73 73 subject = subject[pend+1:].lstrip()
74 74 subject = subject.replace('\n\t', ' ')
75 75 ui.debug('Subject: %s\n' % subject)
76 76 if user:
77 77 ui.debug('From: %s\n' % user)
78 78 diffs_seen = 0
79 79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
80 80 message = ''
81 81 for part in msg.walk():
82 82 content_type = part.get_content_type()
83 83 ui.debug('Content-Type: %s\n' % content_type)
84 84 if content_type not in ok_types:
85 85 continue
86 86 payload = part.get_payload(decode=True)
87 87 m = diffre.search(payload)
88 88 if m:
89 89 hgpatch = False
90 90 ignoretext = False
91 91
92 92 ui.debug(_('found patch at byte %d\n') % m.start(0))
93 93 diffs_seen += 1
94 94 cfp = cStringIO.StringIO()
95 95 for line in payload[:m.start(0)].splitlines():
96 96 if line.startswith('# HG changeset patch'):
97 97 ui.debug(_('patch generated by hg export\n'))
98 98 hgpatch = True
99 99 # drop earlier commit message content
100 100 cfp.seek(0)
101 101 cfp.truncate()
102 102 subject = None
103 103 elif hgpatch:
104 104 if line.startswith('# User '):
105 105 user = line[7:]
106 106 ui.debug('From: %s\n' % user)
107 107 elif line.startswith("# Date "):
108 108 date = line[7:]
109 109 elif line.startswith("# Branch "):
110 110 branch = line[9:]
111 111 elif line.startswith("# Node ID "):
112 112 nodeid = line[10:]
113 113 elif line.startswith("# Parent "):
114 114 parents.append(line[10:])
115 115 elif line == '---' and gitsendmail:
116 116 ignoretext = True
117 117 if not line.startswith('# ') and not ignoretext:
118 118 cfp.write(line)
119 119 cfp.write('\n')
120 120 message = cfp.getvalue()
121 121 if tmpfp:
122 122 tmpfp.write(payload)
123 123 if not payload.endswith('\n'):
124 124 tmpfp.write('\n')
125 125 elif not diffs_seen and message and content_type == 'text/plain':
126 126 message += '\n' + payload
127 127 except:
128 128 tmpfp.close()
129 129 os.unlink(tmpname)
130 130 raise
131 131
132 132 if subject and not message.startswith(subject):
133 133 message = '%s\n%s' % (subject, message)
134 134 tmpfp.close()
135 135 if not diffs_seen:
136 136 os.unlink(tmpname)
137 137 return None, message, user, date, branch, None, None, None
138 138 p1 = parents and parents.pop(0) or None
139 139 p2 = parents and parents.pop(0) or None
140 140 return tmpname, message, user, date, branch, nodeid, p1, p2
141 141
142 142 GP_PATCH = 1 << 0 # we have to run patch
143 143 GP_FILTER = 1 << 1 # there's some copy/rename operation
144 144 GP_BINARY = 1 << 2 # there's a binary patch
145 145
146 146 def readgitpatch(fp, firstline=None):
147 147 """extract git-style metadata about patches from <patchname>"""
148 148 class gitpatch:
149 149 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
150 150 def __init__(self, path):
151 151 self.path = path
152 152 self.oldpath = None
153 153 self.mode = None
154 154 self.op = 'MODIFY'
155 155 self.lineno = 0
156 156 self.binary = False
157 157
158 158 def reader(fp, firstline):
159 159 if firstline is not None:
160 160 yield firstline
161 161 for line in fp:
162 162 yield line
163 163
164 164 # Filter patch for git information
165 165 gitre = re.compile('diff --git a/(.*) b/(.*)')
166 166 gp = None
167 167 gitpatches = []
168 168 # Can have a git patch with only metadata, causing patch to complain
169 169 dopatch = 0
170 170
171 171 lineno = 0
172 172 for line in reader(fp, firstline):
173 173 lineno += 1
174 174 if line.startswith('diff --git'):
175 175 m = gitre.match(line)
176 176 if m:
177 177 if gp:
178 178 gitpatches.append(gp)
179 179 src, dst = m.group(1, 2)
180 180 gp = gitpatch(dst)
181 181 gp.lineno = lineno
182 182 elif gp:
183 183 if line.startswith('--- '):
184 184 if gp.op in ('COPY', 'RENAME'):
185 185 dopatch |= GP_FILTER
186 186 gitpatches.append(gp)
187 187 gp = None
188 188 dopatch |= GP_PATCH
189 189 continue
190 190 if line.startswith('rename from '):
191 191 gp.op = 'RENAME'
192 192 gp.oldpath = line[12:].rstrip()
193 193 elif line.startswith('rename to '):
194 194 gp.path = line[10:].rstrip()
195 195 elif line.startswith('copy from '):
196 196 gp.op = 'COPY'
197 197 gp.oldpath = line[10:].rstrip()
198 198 elif line.startswith('copy to '):
199 199 gp.path = line[8:].rstrip()
200 200 elif line.startswith('deleted file'):
201 201 gp.op = 'DELETE'
202 202 elif line.startswith('new file mode '):
203 203 gp.op = 'ADD'
204 204 gp.mode = int(line.rstrip()[-6:], 8)
205 205 elif line.startswith('new mode '):
206 206 gp.mode = int(line.rstrip()[-6:], 8)
207 207 elif line.startswith('GIT binary patch'):
208 208 dopatch |= GP_BINARY
209 209 gp.binary = True
210 210 if gp:
211 211 gitpatches.append(gp)
212 212
213 213 if not gitpatches:
214 214 dopatch = GP_PATCH
215 215
216 216 return (dopatch, gitpatches)
217 217
218 218 def patch(patchname, ui, strip=1, cwd=None, files={}):
219 219 """apply <patchname> to the working directory.
220 220 returns whether patch was applied with fuzz factor."""
221 221 patcher = ui.config('ui', 'patch')
222 222 args = []
223 223 try:
224 224 if patcher:
225 225 return externalpatch(patcher, args, patchname, ui, strip, cwd,
226 226 files)
227 227 else:
228 228 try:
229 229 return internalpatch(patchname, ui, strip, cwd, files)
230 230 except NoHunks:
231 231 patcher = util.find_exe('gpatch') or util.find_exe('patch')
232 232 ui.debug('no valid hunks found; trying with %r instead\n' %
233 233 patcher)
234 234 if util.needbinarypatch():
235 235 args.append('--binary')
236 236 return externalpatch(patcher, args, patchname, ui, strip, cwd,
237 237 files)
238 238 except PatchError, err:
239 239 s = str(err)
240 240 if s:
241 241 raise util.Abort(s)
242 242 else:
243 243 raise util.Abort(_('patch failed to apply'))
244 244
245 245 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
246 246 """use <patcher> to apply <patchname> to the working directory.
247 247 returns whether patch was applied with fuzz factor."""
248 248
249 249 fuzz = False
250 250 if cwd:
251 251 args.append('-d %s' % util.shellquote(cwd))
252 252 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
253 253 util.shellquote(patchname)))
254 254
255 255 for line in fp:
256 256 line = line.rstrip()
257 257 ui.note(line + '\n')
258 258 if line.startswith('patching file '):
259 259 pf = util.parse_patch_output(line)
260 260 printed_file = False
261 261 files.setdefault(pf, (None, None))
262 262 elif line.find('with fuzz') >= 0:
263 263 fuzz = True
264 264 if not printed_file:
265 265 ui.warn(pf + '\n')
266 266 printed_file = True
267 267 ui.warn(line + '\n')
268 268 elif line.find('saving rejects to file') >= 0:
269 269 ui.warn(line + '\n')
270 270 elif line.find('FAILED') >= 0:
271 271 if not printed_file:
272 272 ui.warn(pf + '\n')
273 273 printed_file = True
274 274 ui.warn(line + '\n')
275 275 code = fp.close()
276 276 if code:
277 277 raise PatchError(_("patch command failed: %s") %
278 278 util.explain_exit(code)[0])
279 279 return fuzz
280 280
281 281 def internalpatch(patchobj, ui, strip, cwd, files={}):
282 282 """use builtin patch to apply <patchobj> to the working directory.
283 283 returns whether patch was applied with fuzz factor."""
284 284 try:
285 285 fp = file(patchobj, 'rb')
286 286 except TypeError:
287 287 fp = patchobj
288 288 if cwd:
289 289 curdir = os.getcwd()
290 290 os.chdir(cwd)
291 291 try:
292 292 ret = applydiff(ui, fp, files, strip=strip)
293 293 finally:
294 294 if cwd:
295 295 os.chdir(curdir)
296 296 if ret < 0:
297 297 raise PatchError
298 298 return ret > 0
299 299
300 300 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
301 301 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
302 302 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
303 303
304 304 class patchfile:
305 305 def __init__(self, ui, fname, missing=False):
306 306 self.fname = fname
307 307 self.ui = ui
308 308 self.lines = []
309 309 self.exists = False
310 310 self.missing = missing
311 311 if not missing:
312 312 try:
313 313 fp = file(fname, 'rb')
314 314 self.lines = fp.readlines()
315 315 self.exists = True
316 316 except IOError:
317 317 pass
318 318 else:
319 319 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
320 320
321 321 if not self.exists:
322 322 dirname = os.path.dirname(fname)
323 323 if dirname and not os.path.isdir(dirname):
324 324 os.makedirs(dirname)
325 325
326 326 self.hash = {}
327 327 self.dirty = 0
328 328 self.offset = 0
329 329 self.rej = []
330 330 self.fileprinted = False
331 331 self.printfile(False)
332 332 self.hunks = 0
333 333
334 334 def printfile(self, warn):
335 335 if self.fileprinted:
336 336 return
337 337 if warn or self.ui.verbose:
338 338 self.fileprinted = True
339 339 s = _("patching file %s\n") % self.fname
340 340 if warn:
341 341 self.ui.warn(s)
342 342 else:
343 343 self.ui.note(s)
344 344
345 345
346 346 def findlines(self, l, linenum):
347 347 # looks through the hash and finds candidate lines. The
348 348 # result is a list of line numbers sorted based on distance
349 349 # from linenum
350 350 def sorter(a, b):
351 351 vala = abs(a - linenum)
352 352 valb = abs(b - linenum)
353 353 return cmp(vala, valb)
354 354
355 355 try:
356 356 cand = self.hash[l]
357 357 except:
358 358 return []
359 359
360 360 if len(cand) > 1:
361 361 # resort our list of potentials forward then back.
362 362 cand.sort(sorter)
363 363 return cand
364 364
365 365 def hashlines(self):
366 366 self.hash = {}
367 367 for x in xrange(len(self.lines)):
368 368 s = self.lines[x]
369 369 self.hash.setdefault(s, []).append(x)
370 370
371 371 def write_rej(self):
372 372 # our rejects are a little different from patch(1). This always
373 373 # creates rejects in the same form as the original patch. A file
374 374 # header is inserted so that you can run the reject through patch again
375 375 # without having to type the filename.
376 376
377 377 if not self.rej:
378 378 return
379 if self.hunks != 1:
380 hunkstr = "s"
381 else:
382 hunkstr = ""
383 379
384 380 fname = self.fname + ".rej"
385 381 self.ui.warn(
386 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
387 (len(self.rej), self.hunks, hunkstr, fname))
382 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
383 (len(self.rej), self.hunks, fname))
388 384 try: os.unlink(fname)
389 385 except:
390 386 pass
391 387 fp = file(fname, 'wb')
392 388 base = os.path.basename(self.fname)
393 389 fp.write("--- %s\n+++ %s\n" % (base, base))
394 390 for x in self.rej:
395 391 for l in x.hunk:
396 392 fp.write(l)
397 393 if l[-1] != '\n':
398 394 fp.write("\n\ No newline at end of file\n")
399 395
400 396 def write(self, dest=None):
401 397 if self.dirty:
402 398 if not dest:
403 399 dest = self.fname
404 400 st = None
405 401 try:
406 402 st = os.lstat(dest)
407 403 except OSError, inst:
408 404 if inst.errno != errno.ENOENT:
409 405 raise
410 406 if st and st.st_nlink > 1:
411 407 os.unlink(dest)
412 408 fp = file(dest, 'wb')
413 409 if st and st.st_nlink > 1:
414 410 os.chmod(dest, st.st_mode)
415 411 fp.writelines(self.lines)
416 412 fp.close()
417 413
418 414 def close(self):
419 415 self.write()
420 416 self.write_rej()
421 417
422 418 def apply(self, h, reverse):
423 419 if not h.complete():
424 420 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
425 421 (h.number, h.desc, len(h.a), h.lena, len(h.b),
426 422 h.lenb))
427 423
428 424 self.hunks += 1
429 425 if reverse:
430 426 h.reverse()
431 427
432 428 if self.missing:
433 429 self.rej.append(h)
434 430 return -1
435 431
436 432 if self.exists and h.createfile():
437 433 self.ui.warn(_("file %s already exists\n") % self.fname)
438 434 self.rej.append(h)
439 435 return -1
440 436
441 437 if isinstance(h, binhunk):
442 438 if h.rmfile():
443 439 os.unlink(self.fname)
444 440 else:
445 441 self.lines[:] = h.new()
446 442 self.offset += len(h.new())
447 443 self.dirty = 1
448 444 return 0
449 445
450 446 # fast case first, no offsets, no fuzz
451 447 old = h.old()
452 448 # patch starts counting at 1 unless we are adding the file
453 449 if h.starta == 0:
454 450 start = 0
455 451 else:
456 452 start = h.starta + self.offset - 1
457 453 orig_start = start
458 454 if diffhelpers.testhunk(old, self.lines, start) == 0:
459 455 if h.rmfile():
460 456 os.unlink(self.fname)
461 457 else:
462 458 self.lines[start : start + h.lena] = h.new()
463 459 self.offset += h.lenb - h.lena
464 460 self.dirty = 1
465 461 return 0
466 462
467 463 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
468 464 self.hashlines()
469 465 if h.hunk[-1][0] != ' ':
470 466 # if the hunk tried to put something at the bottom of the file
471 467 # override the start line and use eof here
472 468 search_start = len(self.lines)
473 469 else:
474 470 search_start = orig_start
475 471
476 472 for fuzzlen in xrange(3):
477 473 for toponly in [ True, False ]:
478 474 old = h.old(fuzzlen, toponly)
479 475
480 476 cand = self.findlines(old[0][1:], search_start)
481 477 for l in cand:
482 478 if diffhelpers.testhunk(old, self.lines, l) == 0:
483 479 newlines = h.new(fuzzlen, toponly)
484 480 self.lines[l : l + len(old)] = newlines
485 481 self.offset += len(newlines) - len(old)
486 482 self.dirty = 1
487 483 if fuzzlen:
488 484 fuzzstr = "with fuzz %d " % fuzzlen
489 485 f = self.ui.warn
490 486 self.printfile(True)
491 487 else:
492 488 fuzzstr = ""
493 489 f = self.ui.note
494 490 offset = l - orig_start - fuzzlen
495 491 if offset == 1:
496 492 linestr = "line"
497 493 else:
498 494 linestr = "lines"
499 495 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
500 496 (h.number, l+1, fuzzstr, offset, linestr))
501 497 return fuzzlen
502 498 self.printfile(True)
503 499 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
504 500 self.rej.append(h)
505 501 return -1
506 502
507 503 class hunk:
508 504 def __init__(self, desc, num, lr, context, create=False, remove=False):
509 505 self.number = num
510 506 self.desc = desc
511 507 self.hunk = [ desc ]
512 508 self.a = []
513 509 self.b = []
514 510 if context:
515 511 self.read_context_hunk(lr)
516 512 else:
517 513 self.read_unified_hunk(lr)
518 514 self.create = create
519 515 self.remove = remove and not create
520 516
521 517 def read_unified_hunk(self, lr):
522 518 m = unidesc.match(self.desc)
523 519 if not m:
524 520 raise PatchError(_("bad hunk #%d") % self.number)
525 521 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
526 522 if self.lena == None:
527 523 self.lena = 1
528 524 else:
529 525 self.lena = int(self.lena)
530 526 if self.lenb == None:
531 527 self.lenb = 1
532 528 else:
533 529 self.lenb = int(self.lenb)
534 530 self.starta = int(self.starta)
535 531 self.startb = int(self.startb)
536 532 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
537 533 # if we hit eof before finishing out the hunk, the last line will
538 534 # be zero length. Lets try to fix it up.
539 535 while len(self.hunk[-1]) == 0:
540 536 del self.hunk[-1]
541 537 del self.a[-1]
542 538 del self.b[-1]
543 539 self.lena -= 1
544 540 self.lenb -= 1
545 541
546 542 def read_context_hunk(self, lr):
547 543 self.desc = lr.readline()
548 544 m = contextdesc.match(self.desc)
549 545 if not m:
550 546 raise PatchError(_("bad hunk #%d") % self.number)
551 547 foo, self.starta, foo2, aend, foo3 = m.groups()
552 548 self.starta = int(self.starta)
553 549 if aend == None:
554 550 aend = self.starta
555 551 self.lena = int(aend) - self.starta
556 552 if self.starta:
557 553 self.lena += 1
558 554 for x in xrange(self.lena):
559 555 l = lr.readline()
560 556 if l.startswith('---'):
561 557 lr.push(l)
562 558 break
563 559 s = l[2:]
564 560 if l.startswith('- ') or l.startswith('! '):
565 561 u = '-' + s
566 562 elif l.startswith(' '):
567 563 u = ' ' + s
568 564 else:
569 565 raise PatchError(_("bad hunk #%d old text line %d") %
570 566 (self.number, x))
571 567 self.a.append(u)
572 568 self.hunk.append(u)
573 569
574 570 l = lr.readline()
575 571 if l.startswith('\ '):
576 572 s = self.a[-1][:-1]
577 573 self.a[-1] = s
578 574 self.hunk[-1] = s
579 575 l = lr.readline()
580 576 m = contextdesc.match(l)
581 577 if not m:
582 578 raise PatchError(_("bad hunk #%d") % self.number)
583 579 foo, self.startb, foo2, bend, foo3 = m.groups()
584 580 self.startb = int(self.startb)
585 581 if bend == None:
586 582 bend = self.startb
587 583 self.lenb = int(bend) - self.startb
588 584 if self.startb:
589 585 self.lenb += 1
590 586 hunki = 1
591 587 for x in xrange(self.lenb):
592 588 l = lr.readline()
593 589 if l.startswith('\ '):
594 590 s = self.b[-1][:-1]
595 591 self.b[-1] = s
596 592 self.hunk[hunki-1] = s
597 593 continue
598 594 if not l:
599 595 lr.push(l)
600 596 break
601 597 s = l[2:]
602 598 if l.startswith('+ ') or l.startswith('! '):
603 599 u = '+' + s
604 600 elif l.startswith(' '):
605 601 u = ' ' + s
606 602 elif len(self.b) == 0:
607 603 # this can happen when the hunk does not add any lines
608 604 lr.push(l)
609 605 break
610 606 else:
611 607 raise PatchError(_("bad hunk #%d old text line %d") %
612 608 (self.number, x))
613 609 self.b.append(s)
614 610 while True:
615 611 if hunki >= len(self.hunk):
616 612 h = ""
617 613 else:
618 614 h = self.hunk[hunki]
619 615 hunki += 1
620 616 if h == u:
621 617 break
622 618 elif h.startswith('-'):
623 619 continue
624 620 else:
625 621 self.hunk.insert(hunki-1, u)
626 622 break
627 623
628 624 if not self.a:
629 625 # this happens when lines were only added to the hunk
630 626 for x in self.hunk:
631 627 if x.startswith('-') or x.startswith(' '):
632 628 self.a.append(x)
633 629 if not self.b:
634 630 # this happens when lines were only deleted from the hunk
635 631 for x in self.hunk:
636 632 if x.startswith('+') or x.startswith(' '):
637 633 self.b.append(x[1:])
638 634 # @@ -start,len +start,len @@
639 635 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
640 636 self.startb, self.lenb)
641 637 self.hunk[0] = self.desc
642 638
643 639 def reverse(self):
644 640 self.create, self.remove = self.remove, self.create
645 641 origlena = self.lena
646 642 origstarta = self.starta
647 643 self.lena = self.lenb
648 644 self.starta = self.startb
649 645 self.lenb = origlena
650 646 self.startb = origstarta
651 647 self.a = []
652 648 self.b = []
653 649 # self.hunk[0] is the @@ description
654 650 for x in xrange(1, len(self.hunk)):
655 651 o = self.hunk[x]
656 652 if o.startswith('-'):
657 653 n = '+' + o[1:]
658 654 self.b.append(o[1:])
659 655 elif o.startswith('+'):
660 656 n = '-' + o[1:]
661 657 self.a.append(n)
662 658 else:
663 659 n = o
664 660 self.b.append(o[1:])
665 661 self.a.append(o)
666 662 self.hunk[x] = o
667 663
668 664 def fix_newline(self):
669 665 diffhelpers.fix_newline(self.hunk, self.a, self.b)
670 666
671 667 def complete(self):
672 668 return len(self.a) == self.lena and len(self.b) == self.lenb
673 669
674 670 def createfile(self):
675 671 return self.starta == 0 and self.lena == 0 and self.create
676 672
677 673 def rmfile(self):
678 674 return self.startb == 0 and self.lenb == 0 and self.remove
679 675
680 676 def fuzzit(self, l, fuzz, toponly):
681 677 # this removes context lines from the top and bottom of list 'l'. It
682 678 # checks the hunk to make sure only context lines are removed, and then
683 679 # returns a new shortened list of lines.
684 680 fuzz = min(fuzz, len(l)-1)
685 681 if fuzz:
686 682 top = 0
687 683 bot = 0
688 684 hlen = len(self.hunk)
689 685 for x in xrange(hlen-1):
690 686 # the hunk starts with the @@ line, so use x+1
691 687 if self.hunk[x+1][0] == ' ':
692 688 top += 1
693 689 else:
694 690 break
695 691 if not toponly:
696 692 for x in xrange(hlen-1):
697 693 if self.hunk[hlen-bot-1][0] == ' ':
698 694 bot += 1
699 695 else:
700 696 break
701 697
702 698 # top and bot now count context in the hunk
703 699 # adjust them if either one is short
704 700 context = max(top, bot, 3)
705 701 if bot < context:
706 702 bot = max(0, fuzz - (context - bot))
707 703 else:
708 704 bot = min(fuzz, bot)
709 705 if top < context:
710 706 top = max(0, fuzz - (context - top))
711 707 else:
712 708 top = min(fuzz, top)
713 709
714 710 return l[top:len(l)-bot]
715 711 return l
716 712
717 713 def old(self, fuzz=0, toponly=False):
718 714 return self.fuzzit(self.a, fuzz, toponly)
719 715
720 716 def newctrl(self):
721 717 res = []
722 718 for x in self.hunk:
723 719 c = x[0]
724 720 if c == ' ' or c == '+':
725 721 res.append(x)
726 722 return res
727 723
728 724 def new(self, fuzz=0, toponly=False):
729 725 return self.fuzzit(self.b, fuzz, toponly)
730 726
731 727 class binhunk:
732 728 'A binary patch file. Only understands literals so far.'
733 729 def __init__(self, gitpatch):
734 730 self.gitpatch = gitpatch
735 731 self.text = None
736 732 self.hunk = ['GIT binary patch\n']
737 733
738 734 def createfile(self):
739 735 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
740 736
741 737 def rmfile(self):
742 738 return self.gitpatch.op == 'DELETE'
743 739
744 740 def complete(self):
745 741 return self.text is not None
746 742
747 743 def new(self):
748 744 return [self.text]
749 745
750 746 def extract(self, fp):
751 747 line = fp.readline()
752 748 self.hunk.append(line)
753 749 while line and not line.startswith('literal '):
754 750 line = fp.readline()
755 751 self.hunk.append(line)
756 752 if not line:
757 753 raise PatchError(_('could not extract binary patch'))
758 754 size = int(line[8:].rstrip())
759 755 dec = []
760 756 line = fp.readline()
761 757 self.hunk.append(line)
762 758 while len(line) > 1:
763 759 l = line[0]
764 760 if l <= 'Z' and l >= 'A':
765 761 l = ord(l) - ord('A') + 1
766 762 else:
767 763 l = ord(l) - ord('a') + 27
768 764 dec.append(base85.b85decode(line[1:-1])[:l])
769 765 line = fp.readline()
770 766 self.hunk.append(line)
771 767 text = zlib.decompress(''.join(dec))
772 768 if len(text) != size:
773 769 raise PatchError(_('binary patch is %d bytes, not %d') %
774 770 len(text), size)
775 771 self.text = text
776 772
777 773 def parsefilename(str):
778 774 # --- filename \t|space stuff
779 775 s = str[4:].rstrip('\r\n')
780 776 i = s.find('\t')
781 777 if i < 0:
782 778 i = s.find(' ')
783 779 if i < 0:
784 780 return s
785 781 return s[:i]
786 782
787 783 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
788 784 def pathstrip(path, count=1):
789 785 pathlen = len(path)
790 786 i = 0
791 787 if count == 0:
792 788 return '', path.rstrip()
793 789 while count > 0:
794 790 i = path.find('/', i)
795 791 if i == -1:
796 792 raise PatchError(_("unable to strip away %d dirs from %s") %
797 793 (count, path))
798 794 i += 1
799 795 # consume '//' in the path
800 796 while i < pathlen - 1 and path[i] == '/':
801 797 i += 1
802 798 count -= 1
803 799 return path[:i].lstrip(), path[i:].rstrip()
804 800
805 801 nulla = afile_orig == "/dev/null"
806 802 nullb = bfile_orig == "/dev/null"
807 803 abase, afile = pathstrip(afile_orig, strip)
808 804 gooda = not nulla and os.path.exists(afile)
809 805 bbase, bfile = pathstrip(bfile_orig, strip)
810 806 if afile == bfile:
811 807 goodb = gooda
812 808 else:
813 809 goodb = not nullb and os.path.exists(bfile)
814 810 createfunc = hunk.createfile
815 811 if reverse:
816 812 createfunc = hunk.rmfile
817 813 missing = not goodb and not gooda and not createfunc()
818 814 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
819 815 # diff is between a file and its backup. In this case, the original
820 816 # file should be patched (see original mpatch code).
821 817 isbackup = (abase == bbase and bfile.startswith(afile))
822 818 fname = None
823 819 if not missing:
824 820 if gooda and goodb:
825 821 fname = isbackup and afile or bfile
826 822 elif gooda:
827 823 fname = afile
828 824
829 825 if not fname:
830 826 if not nullb:
831 827 fname = isbackup and afile or bfile
832 828 elif not nulla:
833 829 fname = afile
834 830 else:
835 831 raise PatchError(_("undefined source and destination files"))
836 832
837 833 return fname, missing
838 834
839 835 class linereader:
840 836 # simple class to allow pushing lines back into the input stream
841 837 def __init__(self, fp):
842 838 self.fp = fp
843 839 self.buf = []
844 840
845 841 def push(self, line):
846 842 self.buf.append(line)
847 843
848 844 def readline(self):
849 845 if self.buf:
850 846 l = self.buf[0]
851 847 del self.buf[0]
852 848 return l
853 849 return self.fp.readline()
854 850
855 851 def iterhunks(ui, fp, sourcefile=None):
856 852 """Read a patch and yield the following events:
857 853 - ("file", afile, bfile, firsthunk): select a new target file.
858 854 - ("hunk", hunk): a new hunk is ready to be applied, follows a
859 855 "file" event.
860 856 - ("git", gitchanges): current diff is in git format, gitchanges
861 857 maps filenames to gitpatch records. Unique event.
862 858 """
863 859
864 860 def scangitpatch(fp, firstline):
865 861 '''git patches can modify a file, then copy that file to
866 862 a new file, but expect the source to be the unmodified form.
867 863 So we scan the patch looking for that case so we can do
868 864 the copies ahead of time.'''
869 865
870 866 pos = 0
871 867 try:
872 868 pos = fp.tell()
873 869 except IOError:
874 870 fp = cStringIO.StringIO(fp.read())
875 871
876 872 (dopatch, gitpatches) = readgitpatch(fp, firstline)
877 873 fp.seek(pos)
878 874
879 875 return fp, dopatch, gitpatches
880 876
881 877 changed = {}
882 878 current_hunk = None
883 879 afile = ""
884 880 bfile = ""
885 881 state = None
886 882 hunknum = 0
887 883 emitfile = False
888 884
889 885 git = False
890 886 gitre = re.compile('diff --git (a/.*) (b/.*)')
891 887
892 888 # our states
893 889 BFILE = 1
894 890 context = None
895 891 lr = linereader(fp)
896 892 dopatch = True
897 893 # gitworkdone is True if a git operation (copy, rename, ...) was
898 894 # performed already for the current file. Useful when the file
899 895 # section may have no hunk.
900 896 gitworkdone = False
901 897
902 898 while True:
903 899 newfile = False
904 900 x = lr.readline()
905 901 if not x:
906 902 break
907 903 if current_hunk:
908 904 if x.startswith('\ '):
909 905 current_hunk.fix_newline()
910 906 yield 'hunk', current_hunk
911 907 current_hunk = None
912 908 gitworkdone = False
913 909 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
914 910 ((context or context == None) and x.startswith('***************')))):
915 911 try:
916 912 if context == None and x.startswith('***************'):
917 913 context = True
918 914 gpatch = changed.get(bfile[2:], (None, None))[1]
919 915 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
920 916 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
921 917 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
922 918 except PatchError, err:
923 919 ui.debug(err)
924 920 current_hunk = None
925 921 continue
926 922 hunknum += 1
927 923 if emitfile:
928 924 emitfile = False
929 925 yield 'file', (afile, bfile, current_hunk)
930 926 elif state == BFILE and x.startswith('GIT binary patch'):
931 927 current_hunk = binhunk(changed[bfile[2:]][1])
932 928 hunknum += 1
933 929 if emitfile:
934 930 emitfile = False
935 931 yield 'file', (afile, bfile, current_hunk)
936 932 current_hunk.extract(fp)
937 933 elif x.startswith('diff --git'):
938 934 # check for git diff, scanning the whole patch file if needed
939 935 m = gitre.match(x)
940 936 if m:
941 937 afile, bfile = m.group(1, 2)
942 938 if not git:
943 939 git = True
944 940 fp, dopatch, gitpatches = scangitpatch(fp, x)
945 941 yield 'git', gitpatches
946 942 for gp in gitpatches:
947 943 changed[gp.path] = (gp.op, gp)
948 944 # else error?
949 945 # copy/rename + modify should modify target, not source
950 946 gitop = changed.get(bfile[2:], (None, None))[0]
951 947 if gitop in ('COPY', 'DELETE', 'RENAME'):
952 948 afile = bfile
953 949 gitworkdone = True
954 950 newfile = True
955 951 elif x.startswith('---'):
956 952 # check for a unified diff
957 953 l2 = lr.readline()
958 954 if not l2.startswith('+++'):
959 955 lr.push(l2)
960 956 continue
961 957 newfile = True
962 958 context = False
963 959 afile = parsefilename(x)
964 960 bfile = parsefilename(l2)
965 961 elif x.startswith('***'):
966 962 # check for a context diff
967 963 l2 = lr.readline()
968 964 if not l2.startswith('---'):
969 965 lr.push(l2)
970 966 continue
971 967 l3 = lr.readline()
972 968 lr.push(l3)
973 969 if not l3.startswith("***************"):
974 970 lr.push(l2)
975 971 continue
976 972 newfile = True
977 973 context = True
978 974 afile = parsefilename(x)
979 975 bfile = parsefilename(l2)
980 976
981 977 if newfile:
982 978 emitfile = True
983 979 state = BFILE
984 980 hunknum = 0
985 981 if current_hunk:
986 982 if current_hunk.complete():
987 983 yield 'hunk', current_hunk
988 984 else:
989 985 raise PatchError(_("malformed patch %s %s") % (afile,
990 986 current_hunk.desc))
991 987
992 988 if hunknum == 0 and dopatch and not gitworkdone:
993 989 raise NoHunks
994 990
995 991 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
996 992 rejmerge=None, updatedir=None):
997 993 """reads a patch from fp and tries to apply it. The dict 'changed' is
998 994 filled in with all of the filenames changed by the patch. Returns 0
999 995 for a clean patch, -1 if any rejects were found and 1 if there was
1000 996 any fuzz."""
1001 997
1002 998 rejects = 0
1003 999 err = 0
1004 1000 current_file = None
1005 1001 gitpatches = None
1006 1002
1007 1003 def closefile():
1008 1004 if not current_file:
1009 1005 return 0
1010 1006 current_file.close()
1011 1007 if rejmerge:
1012 1008 rejmerge(current_file)
1013 1009 return len(current_file.rej)
1014 1010
1015 1011 for state, values in iterhunks(ui, fp, sourcefile):
1016 1012 if state == 'hunk':
1017 1013 if not current_file:
1018 1014 continue
1019 1015 current_hunk = values
1020 1016 ret = current_file.apply(current_hunk, reverse)
1021 1017 if ret >= 0:
1022 1018 changed.setdefault(current_file.fname, (None, None))
1023 1019 if ret > 0:
1024 1020 err = 1
1025 1021 elif state == 'file':
1026 1022 rejects += closefile()
1027 1023 afile, bfile, first_hunk = values
1028 1024 try:
1029 1025 if sourcefile:
1030 1026 current_file = patchfile(ui, sourcefile)
1031 1027 else:
1032 1028 current_file, missing = selectfile(afile, bfile, first_hunk,
1033 1029 strip, reverse)
1034 1030 current_file = patchfile(ui, current_file, missing)
1035 1031 except PatchError, err:
1036 1032 ui.warn(str(err) + '\n')
1037 1033 current_file, current_hunk = None, None
1038 1034 rejects += 1
1039 1035 continue
1040 1036 elif state == 'git':
1041 1037 gitpatches = values
1042 1038 cwd = os.getcwd()
1043 1039 for gp in gitpatches:
1044 1040 if gp.op in ('COPY', 'RENAME'):
1045 1041 src, dst = [util.canonpath(cwd, cwd, x)
1046 1042 for x in [gp.oldpath, gp.path]]
1047 1043 copyfile(src, dst)
1048 1044 changed[gp.path] = (gp.op, gp)
1049 1045 else:
1050 1046 raise util.Abort(_('unsupported parser state: %s') % state)
1051 1047
1052 1048 rejects += closefile()
1053 1049
1054 1050 if updatedir and gitpatches:
1055 1051 updatedir(gitpatches)
1056 1052 if rejects:
1057 1053 return -1
1058 1054 return err
1059 1055
1060 1056 def diffopts(ui, opts={}, untrusted=False):
1061 1057 def get(key, name=None, getter=ui.configbool):
1062 1058 return (opts.get(key) or
1063 1059 getter('diff', name or key, None, untrusted=untrusted))
1064 1060 return mdiff.diffopts(
1065 1061 text=opts.get('text'),
1066 1062 git=get('git'),
1067 1063 nodates=get('nodates'),
1068 1064 showfunc=get('show_function', 'showfunc'),
1069 1065 ignorews=get('ignore_all_space', 'ignorews'),
1070 1066 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1071 1067 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1072 1068 context=get('unified', getter=ui.config))
1073 1069
1074 1070 def updatedir(ui, repo, patches):
1075 1071 '''Update dirstate after patch application according to metadata'''
1076 1072 if not patches:
1077 1073 return
1078 1074 copies = []
1079 1075 removes = {}
1080 1076 cfiles = patches.keys()
1081 1077 cwd = repo.getcwd()
1082 1078 if cwd:
1083 1079 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1084 1080 for f in patches:
1085 1081 ctype, gp = patches[f]
1086 1082 if ctype == 'RENAME':
1087 1083 copies.append((gp.oldpath, gp.path))
1088 1084 removes[gp.oldpath] = 1
1089 1085 elif ctype == 'COPY':
1090 1086 copies.append((gp.oldpath, gp.path))
1091 1087 elif ctype == 'DELETE':
1092 1088 removes[gp.path] = 1
1093 1089 for src, dst in copies:
1094 1090 repo.copy(src, dst)
1095 1091 removes = removes.keys()
1096 1092 if removes:
1097 1093 repo.remove(util.sort(removes), True)
1098 1094 for f in patches:
1099 1095 ctype, gp = patches[f]
1100 1096 if gp and gp.mode:
1101 1097 flags = ''
1102 1098 if gp.mode & 0100:
1103 1099 flags = 'x'
1104 1100 elif gp.mode & 020000:
1105 1101 flags = 'l'
1106 1102 dst = os.path.join(repo.root, gp.path)
1107 1103 # patch won't create empty files
1108 1104 if ctype == 'ADD' and not os.path.exists(dst):
1109 1105 repo.wwrite(gp.path, '', flags)
1110 1106 else:
1111 1107 util.set_flags(dst, 'l' in flags, 'x' in flags)
1112 1108 cmdutil.addremove(repo, cfiles)
1113 1109 files = patches.keys()
1114 1110 files.extend([r for r in removes if r not in files])
1115 1111 return util.sort(files)
1116 1112
1117 1113 def b85diff(to, tn):
1118 1114 '''print base85-encoded binary diff'''
1119 1115 def gitindex(text):
1120 1116 if not text:
1121 1117 return '0' * 40
1122 1118 l = len(text)
1123 1119 s = util.sha1('blob %d\0' % l)
1124 1120 s.update(text)
1125 1121 return s.hexdigest()
1126 1122
1127 1123 def fmtline(line):
1128 1124 l = len(line)
1129 1125 if l <= 26:
1130 1126 l = chr(ord('A') + l - 1)
1131 1127 else:
1132 1128 l = chr(l - 26 + ord('a') - 1)
1133 1129 return '%c%s\n' % (l, base85.b85encode(line, True))
1134 1130
1135 1131 def chunk(text, csize=52):
1136 1132 l = len(text)
1137 1133 i = 0
1138 1134 while i < l:
1139 1135 yield text[i:i+csize]
1140 1136 i += csize
1141 1137
1142 1138 tohash = gitindex(to)
1143 1139 tnhash = gitindex(tn)
1144 1140 if tohash == tnhash:
1145 1141 return ""
1146 1142
1147 1143 # TODO: deltas
1148 1144 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1149 1145 (tohash, tnhash, len(tn))]
1150 1146 for l in chunk(zlib.compress(tn)):
1151 1147 ret.append(fmtline(l))
1152 1148 ret.append('\n')
1153 1149 return ''.join(ret)
1154 1150
1155 1151 def diff(repo, node1=None, node2=None, match=None,
1156 1152 fp=None, changes=None, opts=None):
1157 1153 '''print diff of changes to files between two nodes, or node and
1158 1154 working directory.
1159 1155
1160 1156 if node1 is None, use first dirstate parent instead.
1161 1157 if node2 is None, compare node1 with working directory.'''
1162 1158
1163 1159 if not match:
1164 1160 match = cmdutil.matchall(repo)
1165 1161
1166 1162 if opts is None:
1167 1163 opts = mdiff.defaultopts
1168 1164 if fp is None:
1169 1165 fp = repo.ui
1170 1166
1171 1167 if not node1:
1172 1168 node1 = repo.dirstate.parents()[0]
1173 1169
1174 1170 flcache = {}
1175 1171 def getfilectx(f, ctx):
1176 1172 flctx = ctx.filectx(f, filelog=flcache.get(f))
1177 1173 if f not in flcache:
1178 1174 flcache[f] = flctx._filelog
1179 1175 return flctx
1180 1176
1181 1177 # reading the data for node1 early allows it to play nicely
1182 1178 # with repo.status and the revlog cache.
1183 1179 ctx1 = repo[node1]
1184 1180 # force manifest reading
1185 1181 man1 = ctx1.manifest()
1186 1182 date1 = util.datestr(ctx1.date())
1187 1183
1188 1184 if not changes:
1189 1185 changes = repo.status(node1, node2, match=match)
1190 1186 modified, added, removed = changes[:3]
1191 1187
1192 1188 if not modified and not added and not removed:
1193 1189 return
1194 1190
1195 1191 ctx2 = repo[node2]
1196 1192
1197 1193 if repo.ui.quiet:
1198 1194 r = None
1199 1195 else:
1200 1196 hexfunc = repo.ui.debugflag and hex or short
1201 1197 r = [hexfunc(node) for node in [node1, node2] if node]
1202 1198
1203 1199 if opts.git:
1204 1200 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1205 1201 for k, v in copy.items():
1206 1202 copy[v] = k
1207 1203
1208 1204 gone = {}
1209 1205 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1210 1206
1211 1207 for f in util.sort(modified + added + removed):
1212 1208 to = None
1213 1209 tn = None
1214 1210 dodiff = True
1215 1211 header = []
1216 1212 if f in man1:
1217 1213 to = getfilectx(f, ctx1).data()
1218 1214 if f not in removed:
1219 1215 tn = getfilectx(f, ctx2).data()
1220 1216 a, b = f, f
1221 1217 if opts.git:
1222 1218 def addmodehdr(header, omode, nmode):
1223 1219 if omode != nmode:
1224 1220 header.append('old mode %s\n' % omode)
1225 1221 header.append('new mode %s\n' % nmode)
1226 1222
1227 1223 if f in added:
1228 1224 mode = gitmode[ctx2.flags(f)]
1229 1225 if f in copy:
1230 1226 a = copy[f]
1231 1227 omode = gitmode[man1.flags(a)]
1232 1228 addmodehdr(header, omode, mode)
1233 1229 if a in removed and a not in gone:
1234 1230 op = 'rename'
1235 1231 gone[a] = 1
1236 1232 else:
1237 1233 op = 'copy'
1238 1234 header.append('%s from %s\n' % (op, a))
1239 1235 header.append('%s to %s\n' % (op, f))
1240 1236 to = getfilectx(a, ctx1).data()
1241 1237 else:
1242 1238 header.append('new file mode %s\n' % mode)
1243 1239 if util.binary(tn):
1244 1240 dodiff = 'binary'
1245 1241 elif f in removed:
1246 1242 # have we already reported a copy above?
1247 1243 if f in copy and copy[f] in added and copy[copy[f]] == f:
1248 1244 dodiff = False
1249 1245 else:
1250 1246 header.append('deleted file mode %s\n' %
1251 1247 gitmode[man1.flags(f)])
1252 1248 else:
1253 1249 omode = gitmode[man1.flags(f)]
1254 1250 nmode = gitmode[ctx2.flags(f)]
1255 1251 addmodehdr(header, omode, nmode)
1256 1252 if util.binary(to) or util.binary(tn):
1257 1253 dodiff = 'binary'
1258 1254 r = None
1259 1255 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1260 1256 if dodiff:
1261 1257 if dodiff == 'binary':
1262 1258 text = b85diff(to, tn)
1263 1259 else:
1264 1260 text = mdiff.unidiff(to, date1,
1265 1261 # ctx2 date may be dynamic
1266 1262 tn, util.datestr(ctx2.date()),
1267 1263 a, b, r, opts=opts)
1268 1264 if text or len(header) > 1:
1269 1265 fp.write(''.join(header))
1270 1266 fp.write(text)
1271 1267
1272 1268 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1273 1269 opts=None):
1274 1270 '''export changesets as hg patches.'''
1275 1271
1276 1272 total = len(revs)
1277 1273 revwidth = max([len(str(rev)) for rev in revs])
1278 1274
1279 1275 def single(rev, seqno, fp):
1280 1276 ctx = repo[rev]
1281 1277 node = ctx.node()
1282 1278 parents = [p.node() for p in ctx.parents() if p]
1283 1279 branch = ctx.branch()
1284 1280 if switch_parent:
1285 1281 parents.reverse()
1286 1282 prev = (parents and parents[0]) or nullid
1287 1283
1288 1284 if not fp:
1289 1285 fp = cmdutil.make_file(repo, template, node, total=total,
1290 1286 seqno=seqno, revwidth=revwidth)
1291 1287 if fp != sys.stdout and hasattr(fp, 'name'):
1292 1288 repo.ui.note("%s\n" % fp.name)
1293 1289
1294 1290 fp.write("# HG changeset patch\n")
1295 1291 fp.write("# User %s\n" % ctx.user())
1296 1292 fp.write("# Date %d %d\n" % ctx.date())
1297 1293 if branch and (branch != 'default'):
1298 1294 fp.write("# Branch %s\n" % branch)
1299 1295 fp.write("# Node ID %s\n" % hex(node))
1300 1296 fp.write("# Parent %s\n" % hex(prev))
1301 1297 if len(parents) > 1:
1302 1298 fp.write("# Parent %s\n" % hex(parents[1]))
1303 1299 fp.write(ctx.description().rstrip())
1304 1300 fp.write("\n\n")
1305 1301
1306 1302 diff(repo, prev, node, fp=fp, opts=opts)
1307 1303 if fp not in (sys.stdout, repo.ui):
1308 1304 fp.close()
1309 1305
1310 1306 for seqno, rev in enumerate(revs):
1311 1307 single(rev, seqno+1, fp)
1312 1308
1313 1309 def diffstat(patchlines):
1314 1310 if not util.find_exe('diffstat'):
1315 1311 return
1316 1312 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1317 1313 try:
1318 1314 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1319 1315 try:
1320 1316 for line in patchlines:
1321 1317 p.tochild.write(line + "\n")
1322 1318 p.tochild.close()
1323 1319 if p.wait(): return
1324 1320 fp = os.fdopen(fd, 'r')
1325 1321 stat = []
1326 1322 for line in fp: stat.append(line.lstrip())
1327 1323 last = stat.pop()
1328 1324 stat.insert(0, last)
1329 1325 stat = ''.join(stat)
1330 1326 return stat
1331 1327 except: raise
1332 1328 finally:
1333 1329 try: os.unlink(name)
1334 1330 except: pass
@@ -1,49 +1,49
1 1 adding b
2 2 Patch queue now empty
3 3 % push patch with missing target
4 4 applying changeb
5 5 unable to find 'b' for patching
6 6 2 out of 2 hunks FAILED -- saving rejects to file b.rej
7 7 patch failed, unable to continue (try -v)
8 8 patch failed, rejects left in working dir
9 9 Errors during apply, please fix and refresh changeb
10 10 % display added files
11 11 a
12 12 c
13 13 % display rejections
14 14 --- b
15 15 +++ b
16 16 @@ -1,3 +1,5 @@
17 17 +b
18 18 +b
19 19 a
20 20 a
21 21 a
22 22 @@ -8,3 +10,5 @@
23 23 a
24 24 a
25 25 a
26 26 +c
27 27 +c
28 28 adding b
29 29 Patch queue now empty
30 30 % push git patch with missing target
31 31 applying changeb
32 32 unable to find 'b' for patching
33 1 out of 1 hunk FAILED -- saving rejects to file b.rej
33 1 out of 1 hunks FAILED -- saving rejects to file b.rej
34 34 patch failed, unable to continue (try -v)
35 35 b: No such file or directory
36 36 b not tracked!
37 37 patch failed, rejects left in working dir
38 38 Errors during apply, please fix and refresh changeb
39 39 ? b.rej
40 40 % display added files
41 41 a
42 42 c
43 43 % display rejections
44 44 --- b
45 45 +++ b
46 46 GIT binary patch
47 47 literal 2
48 48 Jc${No0000400IC2
49 49
@@ -1,554 +1,554
1 1 % help
2 2 mq extension - patch management and development
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and
6 6 applied patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches
9 9 directory. Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 prepare repository to work with patches qinit
14 14 create new patch qnew
15 15 import existing patch qimport
16 16
17 17 print patch series qseries
18 18 print applied patches qapplied
19 19 print name of top applied patch qtop
20 20
21 21 add known patch to applied stack qpush
22 22 remove patch from applied stack qpop
23 23 refresh contents of top applied patch qrefresh
24 24
25 25 list of commands:
26 26
27 27 qapplied print the patches already applied
28 28 qclone clone main and patch repository at same time
29 29 qcommit commit changes in the queue repository
30 30 qdelete remove patches from queue
31 31 qdiff diff of the current patch and subsequent modifications
32 32 qfinish move applied patches into repository history
33 33 qfold fold the named patches into the current patch
34 34 qgoto push or pop patches until named patch is at top of stack
35 35 qguard set or print guards for a patch
36 36 qheader Print the header of the topmost or specified patch
37 37 qimport import a patch
38 38 qinit init a new queue repository
39 39 qnew create a new patch
40 40 qnext print the name of the next patch
41 41 qpop pop the current patch off the stack
42 42 qprev print the name of the previous patch
43 43 qpush push the next patch onto the stack
44 44 qrefresh update the current patch
45 45 qrename rename a patch
46 46 qrestore restore the queue state saved by a rev
47 47 qsave save current queue state
48 48 qselect set or print guarded patches to push
49 49 qseries print the entire series file
50 50 qtop print the name of the current patch
51 51 qunapplied print the patches not yet applied
52 52 strip strip a revision and all its descendants from the repository
53 53
54 54 use "hg -v help mq" to show aliases and global options
55 55 adding a
56 56 updating working directory
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 adding b/z
59 59 % qinit
60 60 % -R qinit
61 61 % qinit -c
62 62 A .hgignore
63 63 A series
64 64 % qnew should refuse bad patch names
65 65 abort: "series" cannot be used as the name of a patch
66 66 abort: "status" cannot be used as the name of a patch
67 67 abort: "guards" cannot be used as the name of a patch
68 68 abort: ".hgignore" cannot be used as the name of a patch
69 69 % qnew implies add
70 70 A .hgignore
71 71 A series
72 72 A test.patch
73 73 % qinit; qinit -c
74 74 .hgignore:
75 75 ^\.hg
76 76 ^\.mq
77 77 syntax: glob
78 78 status
79 79 guards
80 80 series:
81 81 abort: repository already exists!
82 82 % qinit; <stuff>; qinit -c
83 83 adding .hg/patches/A
84 84 adding .hg/patches/B
85 85 A .hgignore
86 86 A A
87 87 A B
88 88 A series
89 89 .hgignore:
90 90 status
91 91 bleh
92 92 series:
93 93 A
94 94 B
95 95 % qnew with uncommitted changes
96 96 abort: local changes found, refresh first
97 97 A somefile
98 98 % qnew with uncommitted changes and missing file (issue 803)
99 99 someotherfile: No such file or directory
100 100 someotherfile: No such file or directory
101 101 A somefile
102 102 issue803.patch
103 103 Patch queue now empty
104 104 % qnew -m
105 105 foo bar
106 106 % qrefresh
107 107 foo bar
108 108
109 109 diff -r xa
110 110 --- a/a
111 111 +++ b/a
112 112 @@ -1,1 +1,2 @@
113 113 a
114 114 +a
115 115 % empty qrefresh
116 116 revision:
117 117 patch:
118 118 foo bar
119 119
120 120 working dir diff:
121 121 --- a/a
122 122 +++ b/a
123 123 @@ -1,1 +1,2 @@
124 124 a
125 125 +a
126 126 % qpop
127 127 Patch queue now empty
128 128 % qpush
129 129 applying test.patch
130 130 Now at: test.patch
131 131 % pop/push outside repo
132 132 Patch queue now empty
133 133 applying test.patch
134 134 Now at: test.patch
135 135 % qrefresh in subdir
136 136 % pop/push -a in subdir
137 137 Patch queue now empty
138 138 applying test.patch
139 139 applying test2.patch
140 140 Now at: test2.patch
141 141 % qseries
142 142 test.patch
143 143 test2.patch
144 144 Now at: test.patch
145 145 0 A test.patch: foo bar
146 146 1 U test2.patch:
147 147 applying test2.patch
148 148 Now at: test2.patch
149 149 % qapplied
150 150 test.patch
151 151 test2.patch
152 152 % qtop
153 153 test2.patch
154 154 % qprev
155 155 test.patch
156 156 % qnext
157 157 All patches applied
158 158 % pop, qnext, qprev, qapplied
159 159 Now at: test.patch
160 160 test2.patch
161 161 Only one patch applied
162 162 test.patch
163 163 % commit should fail
164 164 abort: cannot commit over an applied mq patch
165 165 % push should fail
166 166 pushing to ../../k
167 167 abort: source has mq patches applied
168 168 % qunapplied
169 169 test2.patch
170 170 % qpush/qpop with index
171 171 applying test2.patch
172 172 Now at: test2.patch
173 173 Now at: test.patch
174 174 applying test1b.patch
175 175 Now at: test1b.patch
176 176 applying test2.patch
177 177 Now at: test2.patch
178 178 Now at: test1b.patch
179 179 Now at: test.patch
180 180 applying test1b.patch
181 181 applying test2.patch
182 182 Now at: test2.patch
183 183 % push should succeed
184 184 Patch queue now empty
185 185 pushing to ../../k
186 186 searching for changes
187 187 adding changesets
188 188 adding manifests
189 189 adding file changes
190 190 added 1 changesets with 1 changes to 1 files
191 191 % qpush/qpop error codes
192 192 applying test.patch
193 193 applying test1b.patch
194 194 applying test2.patch
195 195 Now at: test2.patch
196 196 % pops all patches and succeeds
197 197 Patch queue now empty
198 198 qpop -a succeeds
199 199 % does nothing and succeeds
200 200 no patches applied
201 201 qpop -a succeeds
202 202 % fails - nothing else to pop
203 203 no patches applied
204 204 qpop fails
205 205 % pushes a patch and succeeds
206 206 applying test.patch
207 207 Now at: test.patch
208 208 qpush succeeds
209 209 % pops a patch and succeeds
210 210 Patch queue now empty
211 211 qpop succeeds
212 212 % pushes up to test1b.patch and succeeds
213 213 applying test.patch
214 214 applying test1b.patch
215 215 Now at: test1b.patch
216 216 qpush test1b.patch succeeds
217 217 % does nothing and succeeds
218 218 qpush: test1b.patch is already at the top
219 219 qpush test1b.patch succeeds
220 220 % does nothing and succeeds
221 221 qpop: test1b.patch is already at the top
222 222 qpop test1b.patch succeeds
223 223 % fails - can't push to this patch
224 224 abort: cannot push to a previous patch: test.patch
225 225 qpush test.patch fails
226 226 % fails - can't pop to this patch
227 227 abort: patch test2.patch is not applied
228 228 qpop test2.patch fails
229 229 % pops up to test.patch and succeeds
230 230 Now at: test.patch
231 231 qpop test.patch succeeds
232 232 % pushes all patches and succeeds
233 233 applying test1b.patch
234 234 applying test2.patch
235 235 Now at: test2.patch
236 236 qpush -a succeeds
237 237 % does nothing and succeeds
238 238 all patches are currently applied
239 239 qpush -a succeeds
240 240 % fails - nothing else to push
241 241 patch series already fully applied
242 242 qpush fails
243 243 % does nothing and succeeds
244 244 all patches are currently applied
245 245 qpush test2.patch succeeds
246 246 % strip
247 247 adding x
248 248 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
249 249 saving bundle to
250 250 adding changesets
251 251 adding manifests
252 252 adding file changes
253 253 added 1 changesets with 1 changes to 1 files
254 254 (run 'hg update' to get a working copy)
255 255 % strip with local changes, should complain
256 256 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 abort: local changes found
258 258 % --force strip with local changes
259 259 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
260 260 saving bundle to
261 261 % cd b; hg qrefresh
262 262 adding a
263 263 foo
264 264
265 265 diff -r cb9a9f314b8b a
266 266 --- a/a
267 267 +++ b/a
268 268 @@ -1,1 +1,2 @@
269 269 a
270 270 +a
271 271 diff -r cb9a9f314b8b b/f
272 272 --- /dev/null
273 273 +++ b/b/f
274 274 @@ -0,0 +1,1 @@
275 275 +f
276 276 % hg qrefresh .
277 277 foo
278 278
279 279 diff -r cb9a9f314b8b b/f
280 280 --- /dev/null
281 281 +++ b/b/f
282 282 @@ -0,0 +1,1 @@
283 283 +f
284 284 M a
285 285 % qpush failure
286 286 Patch queue now empty
287 287 applying foo
288 288 applying bar
289 289 file foo already exists
290 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
290 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
291 291 patch failed, unable to continue (try -v)
292 292 patch failed, rejects left in working dir
293 293 Errors during apply, please fix and refresh bar
294 294 ? foo
295 295 ? foo.rej
296 296 % mq tags
297 297 0 qparent
298 298 1 qbase foo
299 299 2 qtip bar tip
300 300 % bad node in status
301 301 Now at: foo
302 302 changeset: 0:cb9a9f314b8b
303 303 mq status file refers to unknown node
304 304 tag: tip
305 305 user: test
306 306 date: Thu Jan 01 00:00:00 1970 +0000
307 307 summary: a
308 308
309 309 mq status file refers to unknown node
310 310 default 0:cb9a9f314b8b
311 311 abort: working directory revision is not qtip
312 312 new file
313 313
314 314 diff --git a/new b/new
315 315 new file mode 100755
316 316 --- /dev/null
317 317 +++ b/new
318 318 @@ -0,0 +1,1 @@
319 319 +foo
320 320 copy file
321 321
322 322 diff --git a/new b/copy
323 323 copy from new
324 324 copy to copy
325 325 Now at: new
326 326 applying copy
327 327 Now at: copy
328 328 diff --git a/new b/copy
329 329 copy from new
330 330 copy to copy
331 331 diff --git a/new b/copy
332 332 copy from new
333 333 copy to copy
334 334 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
335 335 created new head
336 336 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
337 337 adding branch
338 338 adding changesets
339 339 adding manifests
340 340 adding file changes
341 341 added 1 changesets with 1 changes to 1 files
342 342 Patch queue now empty
343 343 (working directory not at tip)
344 344 applying bar
345 345 Now at: bar
346 346 diff --git a/bar b/bar
347 347 new file mode 100644
348 348 --- /dev/null
349 349 +++ b/bar
350 350 @@ -0,0 +1,1 @@
351 351 +bar
352 352 diff --git a/foo b/baz
353 353 rename from foo
354 354 rename to baz
355 355 2 baz (foo)
356 356 diff --git a/bar b/bar
357 357 new file mode 100644
358 358 --- /dev/null
359 359 +++ b/bar
360 360 @@ -0,0 +1,1 @@
361 361 +bar
362 362 diff --git a/foo b/baz
363 363 rename from foo
364 364 rename to baz
365 365 2 baz (foo)
366 366 diff --git a/bar b/bar
367 367 diff --git a/foo b/baz
368 368
369 369 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
370 370 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
371 371 adding branch
372 372 adding changesets
373 373 adding manifests
374 374 adding file changes
375 375 added 1 changesets with 1 changes to 1 files
376 376 Patch queue now empty
377 377 (working directory not at tip)
378 378 applying bar
379 379 Now at: bar
380 380 diff --git a/foo b/bleh
381 381 rename from foo
382 382 rename to bleh
383 383 diff --git a/quux b/quux
384 384 new file mode 100644
385 385 --- /dev/null
386 386 +++ b/quux
387 387 @@ -0,0 +1,1 @@
388 388 +bar
389 389 3 bleh (foo)
390 390 diff --git a/foo b/barney
391 391 rename from foo
392 392 rename to barney
393 393 diff --git a/fred b/fred
394 394 new file mode 100644
395 395 --- /dev/null
396 396 +++ b/fred
397 397 @@ -0,0 +1,1 @@
398 398 +bar
399 399 3 barney (foo)
400 400 % refresh omitting an added file
401 401 C newfile
402 402 A newfile
403 403 Now at: bar
404 404 % create a git patch
405 405 diff --git a/alexander b/alexander
406 406 % create a git binary patch
407 407 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
408 408 diff --git a/bucephalus b/bucephalus
409 409 % check binary patches can be popped and pushed
410 410 Now at: addalexander
411 411 applying addbucephalus
412 412 Now at: addbucephalus
413 413 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
414 414 % strip again
415 415 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
416 416 created new head
417 417 merging foo
418 418 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
419 419 (branch merge, don't forget to commit)
420 420 changeset: 3:99615015637b
421 421 tag: tip
422 422 parent: 2:20cbbe65cff7
423 423 parent: 1:d2871fc282d4
424 424 user: test
425 425 date: Thu Jan 01 00:00:00 1970 +0000
426 426 summary: merge
427 427
428 428 changeset: 2:20cbbe65cff7
429 429 parent: 0:53245c60e682
430 430 user: test
431 431 date: Thu Jan 01 00:00:00 1970 +0000
432 432 summary: change foo 2
433 433
434 434 changeset: 1:d2871fc282d4
435 435 user: test
436 436 date: Thu Jan 01 00:00:00 1970 +0000
437 437 summary: change foo 1
438 438
439 439 changeset: 0:53245c60e682
440 440 user: test
441 441 date: Thu Jan 01 00:00:00 1970 +0000
442 442 summary: add foo
443 443
444 444 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
445 445 saving bundle to
446 446 saving bundle to
447 447 adding branch
448 448 adding changesets
449 449 adding manifests
450 450 adding file changes
451 451 added 1 changesets with 1 changes to 1 files
452 452 changeset: 1:20cbbe65cff7
453 453 tag: tip
454 454 user: test
455 455 date: Thu Jan 01 00:00:00 1970 +0000
456 456 summary: change foo 2
457 457
458 458 changeset: 0:53245c60e682
459 459 user: test
460 460 date: Thu Jan 01 00:00:00 1970 +0000
461 461 summary: add foo
462 462
463 463 % qclone
464 464 abort: versioned patch repository not found (see qinit -c)
465 465 adding .hg/patches/patch1
466 466 main repo:
467 467 rev 1: change foo
468 468 rev 0: add foo
469 469 patch repo:
470 470 rev 0: checkpoint
471 471 updating working directory
472 472 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 473 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
474 474 main repo:
475 475 rev 0: add foo
476 476 patch repo:
477 477 rev 0: checkpoint
478 478 Patch queue now empty
479 479 main repo:
480 480 rev 0: add foo
481 481 patch repo:
482 482 rev 0: checkpoint
483 483 updating working directory
484 484 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 485 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 486 main repo:
487 487 rev 0: add foo
488 488 patch repo:
489 489 rev 0: checkpoint
490 490 % test applying on an empty file (issue 1033)
491 491 adding a
492 492 Patch queue now empty
493 493 applying changea
494 494 Now at: changea
495 495 % test qpush with --force, issue1087
496 496 adding bye.txt
497 497 adding hello.txt
498 498 Patch queue now empty
499 499 % qpush should fail, local changes
500 500 abort: local changes found, refresh first
501 501 % apply force, should not discard changes with empty patch
502 502 applying empty
503 503 /usr/bin/patch: **** Only garbage was found in the patch input.
504 504 patch failed, unable to continue (try -v)
505 505 patch empty is empty
506 506 Now at: empty
507 507 diff -r bf5fc3f07a0a hello.txt
508 508 --- a/hello.txt
509 509 +++ b/hello.txt
510 510 @@ -1,1 +1,2 @@
511 511 hello
512 512 +world
513 513 diff -r 9ecee4f634e3 hello.txt
514 514 --- a/hello.txt
515 515 +++ b/hello.txt
516 516 @@ -1,1 +1,2 @@
517 517 hello
518 518 +world
519 519 changeset: 1:bf5fc3f07a0a
520 520 tag: qtip
521 521 tag: tip
522 522 tag: empty
523 523 tag: qbase
524 524 user: test
525 525 date: Thu Jan 01 00:00:00 1970 +0000
526 526 summary: imported patch empty
527 527
528 528
529 529 Patch queue now empty
530 530 % qpush should fail, local changes
531 531 abort: local changes found, refresh first
532 532 % apply force, should discard changes in hello, but not bye
533 533 applying empty
534 534 Now at: empty
535 535 M bye.txt
536 536 diff -r ba252371dbc1 bye.txt
537 537 --- a/bye.txt
538 538 +++ b/bye.txt
539 539 @@ -1,1 +1,2 @@
540 540 bye
541 541 +universe
542 542 diff -r 9ecee4f634e3 bye.txt
543 543 --- a/bye.txt
544 544 +++ b/bye.txt
545 545 @@ -1,1 +1,2 @@
546 546 bye
547 547 +universe
548 548 diff -r 9ecee4f634e3 hello.txt
549 549 --- a/hello.txt
550 550 +++ b/hello.txt
551 551 @@ -1,1 +1,3 @@
552 552 hello
553 553 +world
554 554 +universe
@@ -1,138 +1,138
1 1 adding r1
2 2 adding r2
3 3 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 4 adding b1
5 5 created new head
6 6 adding b2
7 7 adding b3
8 8 4 b3
9 9 3 b2
10 10 2 0:17ab29e464c6 b1
11 11 1 r2
12 12 0 r1
13 13 updating working directory
14 14 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 15 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
16 16 % rebase b onto r1
17 17 applying 37a1297eb21b
18 18 37a1297eb21b transplanted to e234d668f844
19 19 applying 722f4667af76
20 20 722f4667af76 transplanted to 539f377d78df
21 21 applying a53251cdf717
22 22 a53251cdf717 transplanted to ffd6818a3975
23 23 7 b3
24 24 6 b2
25 25 5 1:d11e3596cc1a b1
26 26 4 b3
27 27 3 b2
28 28 2 0:17ab29e464c6 b1
29 29 1 r2
30 30 0 r1
31 31 updating working directory
32 32 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 33 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
34 34 % rebase b onto r1, skipping b2
35 35 applying 37a1297eb21b
36 36 37a1297eb21b transplanted to e234d668f844
37 37 applying a53251cdf717
38 38 a53251cdf717 transplanted to 7275fda4d04f
39 39 6 b3
40 40 5 1:d11e3596cc1a b1
41 41 4 b3
42 42 3 b2
43 43 2 0:17ab29e464c6 b1
44 44 1 r2
45 45 0 r1
46 46 % remote transplant
47 47 requesting all changes
48 48 adding changesets
49 49 adding manifests
50 50 adding file changes
51 51 added 2 changesets with 2 changes to 2 files
52 52 updating working directory
53 53 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54 searching for changes
55 55 applying 37a1297eb21b
56 56 37a1297eb21b transplanted to c19cf0ccb069
57 57 applying a53251cdf717
58 58 a53251cdf717 transplanted to f7fe5bf98525
59 59 3 b3
60 60 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
61 61 2 b1
62 62 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
63 63 1 r2
64 64 0 r1
65 65 % skip previous transplants
66 66 searching for changes
67 67 applying 722f4667af76
68 68 722f4667af76 transplanted to 47156cd86c0b
69 69 4 b2
70 70 3 b3
71 71 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
72 72 2 b1
73 73 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
74 74 1 r2
75 75 0 r1
76 76 % skip local changes transplanted to the source
77 77 adding b4
78 78 updating working directory
79 79 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 80 searching for changes
81 81 applying 4333daefcb15
82 82 4333daefcb15 transplanted to 5f42c04e07cc
83 83 % remote transplant with pull
84 84 requesting all changes
85 85 adding changesets
86 86 adding manifests
87 87 adding file changes
88 88 added 1 changesets with 1 changes to 1 files
89 89 updating working directory
90 90 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91 searching for changes
92 92 searching for changes
93 93 adding changesets
94 94 adding manifests
95 95 adding file changes
96 96 added 1 changesets with 1 changes to 1 files
97 97 applying a53251cdf717
98 98 a53251cdf717 transplanted to 8d9279348abb
99 99 2 b3
100 100 1 b1
101 101 0 r1
102 102 % transplant --continue
103 103 adding foo
104 104 adding toremove
105 105 adding added
106 106 removing toremove
107 107 adding bar
108 108 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
109 109 created new head
110 110 applying a1e30dd1b8e7
111 111 patching file foo
112 112 Hunk #1 FAILED at 0
113 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
113 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
114 114 patch failed to apply
115 115 abort: Fix up the merge and run hg transplant --continue
116 116 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
117 117 applying a1e30dd1b8e7
118 118 patching file foo
119 119 Hunk #1 FAILED at 0
120 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
120 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
121 121 patch failed to apply
122 122 abort: Fix up the merge and run hg transplant --continue
123 123 a1e30dd1b8e7 transplanted as f1563cf27039
124 124 skipping already applied revision 1:a1e30dd1b8e7
125 125 applying 1739ac5f6139
126 126 1739ac5f6139 transplanted to d649c221319f
127 127 applying 0282d5fbbe02
128 128 0282d5fbbe02 transplanted to 77418277ccb3
129 129 added
130 130 bar
131 131 foo
132 132 % test transplant merge
133 133 adding a
134 134 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
135 135 created new head
136 136 % tranplant
137 137 applying 42dc4432fd35
138 138 1:42dc4432fd35 merged at a9f4acbac129
General Comments 0
You need to be logged in to leave comments. Login now