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