##// END OF EJS Templates
mq: add function to check if a header exists in a patch
Yann E. MORIN -
r9335:6eaadd77 default
parent child Browse files
Show More
@@ -1,2632 +1,2640 b''
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''manage a stack of patches
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details)::
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 33 from mercurial.node import bin, hex, short, nullid, nullrev
34 34 from mercurial.lock import release
35 35 from mercurial import commands, cmdutil, hg, patch, util
36 36 from mercurial import repair, extensions, url, error
37 37 import os, sys, re, errno
38 38
39 39 commands.norepo += " qclone"
40 40
41 41 # Patch names looks like unix-file names.
42 42 # They must be joinable with queue directory and result in the patch path.
43 43 normname = util.normpath
44 44
45 45 class statusentry(object):
46 46 def __init__(self, rev, name=None):
47 47 if not name:
48 48 fields = rev.split(':', 1)
49 49 if len(fields) == 2:
50 50 self.rev, self.name = fields
51 51 else:
52 52 self.rev, self.name = None, None
53 53 else:
54 54 self.rev, self.name = rev, name
55 55
56 56 def __str__(self):
57 57 return self.rev + ':' + self.name
58 58
59 59 class patchheader(object):
60 60 def __init__(self, pf):
61 61 def eatdiff(lines):
62 62 while lines:
63 63 l = lines[-1]
64 64 if (l.startswith("diff -") or
65 65 l.startswith("Index:") or
66 66 l.startswith("===========")):
67 67 del lines[-1]
68 68 else:
69 69 break
70 70 def eatempty(lines):
71 71 while lines:
72 72 l = lines[-1]
73 73 if re.match('\s*$', l):
74 74 del lines[-1]
75 75 else:
76 76 break
77 77
78 78 message = []
79 79 comments = []
80 80 user = None
81 81 date = None
82 82 format = None
83 83 subject = None
84 84 diffstart = 0
85 85
86 86 for line in file(pf):
87 87 line = line.rstrip()
88 88 if line.startswith('diff --git'):
89 89 diffstart = 2
90 90 break
91 91 if diffstart:
92 92 if line.startswith('+++ '):
93 93 diffstart = 2
94 94 break
95 95 if line.startswith("--- "):
96 96 diffstart = 1
97 97 continue
98 98 elif format == "hgpatch":
99 99 # parse values when importing the result of an hg export
100 100 if line.startswith("# User "):
101 101 user = line[7:]
102 102 elif line.startswith("# Date "):
103 103 date = line[7:]
104 104 elif not line.startswith("# ") and line:
105 105 message.append(line)
106 106 format = None
107 107 elif line == '# HG changeset patch':
108 108 message = []
109 109 format = "hgpatch"
110 110 elif (format != "tagdone" and (line.startswith("Subject: ") or
111 111 line.startswith("subject: "))):
112 112 subject = line[9:]
113 113 format = "tag"
114 114 elif (format != "tagdone" and (line.startswith("From: ") or
115 115 line.startswith("from: "))):
116 116 user = line[6:]
117 117 format = "tag"
118 118 elif format == "tag" and line == "":
119 119 # when looking for tags (subject: from: etc) they
120 120 # end once you find a blank line in the source
121 121 format = "tagdone"
122 122 elif message or line:
123 123 message.append(line)
124 124 comments.append(line)
125 125
126 126 eatdiff(message)
127 127 eatdiff(comments)
128 128 eatempty(message)
129 129 eatempty(comments)
130 130
131 131 # make sure message isn't empty
132 132 if format and format.startswith("tag") and subject:
133 133 message.insert(0, "")
134 134 message.insert(0, subject)
135 135
136 136 self.message = message
137 137 self.comments = comments
138 138 self.user = user
139 139 self.date = date
140 140 self.haspatch = diffstart > 1
141 141
142 142 def setuser(self, user):
143 143 if not self.updateheader(['From: ', '# User '], user):
144 144 try:
145 145 patchheaderat = self.comments.index('# HG changeset patch')
146 146 self.comments.insert(patchheaderat + 1, '# User ' + user)
147 147 except ValueError:
148 148 self.comments = ['From: ' + user, ''] + self.comments
149 149 self.user = user
150 150
151 151 def setdate(self, date):
152 152 if self.updateheader(['# Date '], date):
153 153 self.date = date
154 154
155 155 def setmessage(self, message):
156 156 if self.comments:
157 157 self._delmsg()
158 158 self.message = [message]
159 159 self.comments += self.message
160 160
161 161 def updateheader(self, prefixes, new):
162 162 '''Update all references to a field in the patch header.
163 163 Return whether the field is present.'''
164 164 res = False
165 165 for prefix in prefixes:
166 166 for i in xrange(len(self.comments)):
167 167 if self.comments[i].startswith(prefix):
168 168 self.comments[i] = prefix + new
169 169 res = True
170 170 break
171 171 return res
172 172
173 def _hasheader(self, prefixes):
174 '''Check if a header starts with any of the given prefixes.'''
175 for prefix in prefixes:
176 for comment in self.comments:
177 if comment.startswith(prefix):
178 return True
179 return False
180
173 181 def __str__(self):
174 182 if not self.comments:
175 183 return ''
176 184 return '\n'.join(self.comments) + '\n\n'
177 185
178 186 def _delmsg(self):
179 187 '''Remove existing message, keeping the rest of the comments fields.
180 188 If comments contains 'subject: ', message will prepend
181 189 the field and a blank line.'''
182 190 if self.message:
183 191 subj = 'subject: ' + self.message[0].lower()
184 192 for i in xrange(len(self.comments)):
185 193 if subj == self.comments[i].lower():
186 194 del self.comments[i]
187 195 self.message = self.message[2:]
188 196 break
189 197 ci = 0
190 198 for mi in self.message:
191 199 while mi != self.comments[ci]:
192 200 ci += 1
193 201 del self.comments[ci]
194 202
195 203 class queue(object):
196 204 def __init__(self, ui, path, patchdir=None):
197 205 self.basepath = path
198 206 self.path = patchdir or os.path.join(path, "patches")
199 207 self.opener = util.opener(self.path)
200 208 self.ui = ui
201 209 self.applied_dirty = 0
202 210 self.series_dirty = 0
203 211 self.series_path = "series"
204 212 self.status_path = "status"
205 213 self.guards_path = "guards"
206 214 self.active_guards = None
207 215 self.guards_dirty = False
208 216 self._diffopts = None
209 217
210 218 @util.propertycache
211 219 def applied(self):
212 220 if os.path.exists(self.join(self.status_path)):
213 221 lines = self.opener(self.status_path).read().splitlines()
214 222 return [statusentry(l) for l in lines]
215 223 return []
216 224
217 225 @util.propertycache
218 226 def full_series(self):
219 227 if os.path.exists(self.join(self.series_path)):
220 228 return self.opener(self.series_path).read().splitlines()
221 229 return []
222 230
223 231 @util.propertycache
224 232 def series(self):
225 233 self.parse_series()
226 234 return self.series
227 235
228 236 @util.propertycache
229 237 def series_guards(self):
230 238 self.parse_series()
231 239 return self.series_guards
232 240
233 241 def invalidate(self):
234 242 for a in 'applied full_series series series_guards'.split():
235 243 if a in self.__dict__:
236 244 delattr(self, a)
237 245 self.applied_dirty = 0
238 246 self.series_dirty = 0
239 247 self.guards_dirty = False
240 248 self.active_guards = None
241 249
242 250 def diffopts(self):
243 251 if self._diffopts is None:
244 252 self._diffopts = patch.diffopts(self.ui)
245 253 return self._diffopts
246 254
247 255 def join(self, *p):
248 256 return os.path.join(self.path, *p)
249 257
250 258 def find_series(self, patch):
251 259 pre = re.compile("(\s*)([^#]+)")
252 260 index = 0
253 261 for l in self.full_series:
254 262 m = pre.match(l)
255 263 if m:
256 264 s = m.group(2)
257 265 s = s.rstrip()
258 266 if s == patch:
259 267 return index
260 268 index += 1
261 269 return None
262 270
263 271 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
264 272
265 273 def parse_series(self):
266 274 self.series = []
267 275 self.series_guards = []
268 276 for l in self.full_series:
269 277 h = l.find('#')
270 278 if h == -1:
271 279 patch = l
272 280 comment = ''
273 281 elif h == 0:
274 282 continue
275 283 else:
276 284 patch = l[:h]
277 285 comment = l[h:]
278 286 patch = patch.strip()
279 287 if patch:
280 288 if patch in self.series:
281 289 raise util.Abort(_('%s appears more than once in %s') %
282 290 (patch, self.join(self.series_path)))
283 291 self.series.append(patch)
284 292 self.series_guards.append(self.guard_re.findall(comment))
285 293
286 294 def check_guard(self, guard):
287 295 if not guard:
288 296 return _('guard cannot be an empty string')
289 297 bad_chars = '# \t\r\n\f'
290 298 first = guard[0]
291 299 if first in '-+':
292 300 return (_('guard %r starts with invalid character: %r') %
293 301 (guard, first))
294 302 for c in bad_chars:
295 303 if c in guard:
296 304 return _('invalid character in guard %r: %r') % (guard, c)
297 305
298 306 def set_active(self, guards):
299 307 for guard in guards:
300 308 bad = self.check_guard(guard)
301 309 if bad:
302 310 raise util.Abort(bad)
303 311 guards = sorted(set(guards))
304 312 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
305 313 self.active_guards = guards
306 314 self.guards_dirty = True
307 315
308 316 def active(self):
309 317 if self.active_guards is None:
310 318 self.active_guards = []
311 319 try:
312 320 guards = self.opener(self.guards_path).read().split()
313 321 except IOError, err:
314 322 if err.errno != errno.ENOENT: raise
315 323 guards = []
316 324 for i, guard in enumerate(guards):
317 325 bad = self.check_guard(guard)
318 326 if bad:
319 327 self.ui.warn('%s:%d: %s\n' %
320 328 (self.join(self.guards_path), i + 1, bad))
321 329 else:
322 330 self.active_guards.append(guard)
323 331 return self.active_guards
324 332
325 333 def set_guards(self, idx, guards):
326 334 for g in guards:
327 335 if len(g) < 2:
328 336 raise util.Abort(_('guard %r too short') % g)
329 337 if g[0] not in '-+':
330 338 raise util.Abort(_('guard %r starts with invalid char') % g)
331 339 bad = self.check_guard(g[1:])
332 340 if bad:
333 341 raise util.Abort(bad)
334 342 drop = self.guard_re.sub('', self.full_series[idx])
335 343 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
336 344 self.parse_series()
337 345 self.series_dirty = True
338 346
339 347 def pushable(self, idx):
340 348 if isinstance(idx, str):
341 349 idx = self.series.index(idx)
342 350 patchguards = self.series_guards[idx]
343 351 if not patchguards:
344 352 return True, None
345 353 guards = self.active()
346 354 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
347 355 if exactneg:
348 356 return False, exactneg[0]
349 357 pos = [g for g in patchguards if g[0] == '+']
350 358 exactpos = [g for g in pos if g[1:] in guards]
351 359 if pos:
352 360 if exactpos:
353 361 return True, exactpos[0]
354 362 return False, pos
355 363 return True, ''
356 364
357 365 def explain_pushable(self, idx, all_patches=False):
358 366 write = all_patches and self.ui.write or self.ui.warn
359 367 if all_patches or self.ui.verbose:
360 368 if isinstance(idx, str):
361 369 idx = self.series.index(idx)
362 370 pushable, why = self.pushable(idx)
363 371 if all_patches and pushable:
364 372 if why is None:
365 373 write(_('allowing %s - no guards in effect\n') %
366 374 self.series[idx])
367 375 else:
368 376 if not why:
369 377 write(_('allowing %s - no matching negative guards\n') %
370 378 self.series[idx])
371 379 else:
372 380 write(_('allowing %s - guarded by %r\n') %
373 381 (self.series[idx], why))
374 382 if not pushable:
375 383 if why:
376 384 write(_('skipping %s - guarded by %r\n') %
377 385 (self.series[idx], why))
378 386 else:
379 387 write(_('skipping %s - no matching guards\n') %
380 388 self.series[idx])
381 389
382 390 def save_dirty(self):
383 391 def write_list(items, path):
384 392 fp = self.opener(path, 'w')
385 393 for i in items:
386 394 fp.write("%s\n" % i)
387 395 fp.close()
388 396 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
389 397 if self.series_dirty: write_list(self.full_series, self.series_path)
390 398 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
391 399
392 400 def removeundo(self, repo):
393 401 undo = repo.sjoin('undo')
394 402 if not os.path.exists(undo):
395 403 return
396 404 try:
397 405 os.unlink(undo)
398 406 except OSError, inst:
399 407 self.ui.warn(_('error removing undo: %s\n') % str(inst))
400 408
401 409 def printdiff(self, repo, node1, node2=None, files=None,
402 410 fp=None, changes=None, opts={}):
403 411 m = cmdutil.match(repo, files, opts)
404 412 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
405 413 write = fp is None and repo.ui.write or fp.write
406 414 for chunk in chunks:
407 415 write(chunk)
408 416
409 417 def mergeone(self, repo, mergeq, head, patch, rev):
410 418 # first try just applying the patch
411 419 (err, n) = self.apply(repo, [ patch ], update_status=False,
412 420 strict=True, merge=rev)
413 421
414 422 if err == 0:
415 423 return (err, n)
416 424
417 425 if n is None:
418 426 raise util.Abort(_("apply failed for patch %s") % patch)
419 427
420 428 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
421 429
422 430 # apply failed, strip away that rev and merge.
423 431 hg.clean(repo, head)
424 432 self.strip(repo, n, update=False, backup='strip')
425 433
426 434 ctx = repo[rev]
427 435 ret = hg.merge(repo, rev)
428 436 if ret:
429 437 raise util.Abort(_("update returned %d") % ret)
430 438 n = repo.commit(ctx.description(), ctx.user(), force=True)
431 439 if n is None:
432 440 raise util.Abort(_("repo commit failed"))
433 441 try:
434 442 ph = patchheader(mergeq.join(patch))
435 443 except:
436 444 raise util.Abort(_("unable to read %s") % patch)
437 445
438 446 patchf = self.opener(patch, "w")
439 447 comments = str(ph)
440 448 if comments:
441 449 patchf.write(comments)
442 450 self.printdiff(repo, head, n, fp=patchf)
443 451 patchf.close()
444 452 self.removeundo(repo)
445 453 return (0, n)
446 454
447 455 def qparents(self, repo, rev=None):
448 456 if rev is None:
449 457 (p1, p2) = repo.dirstate.parents()
450 458 if p2 == nullid:
451 459 return p1
452 460 if len(self.applied) == 0:
453 461 return None
454 462 return bin(self.applied[-1].rev)
455 463 pp = repo.changelog.parents(rev)
456 464 if pp[1] != nullid:
457 465 arevs = [ x.rev for x in self.applied ]
458 466 p0 = hex(pp[0])
459 467 p1 = hex(pp[1])
460 468 if p0 in arevs:
461 469 return pp[0]
462 470 if p1 in arevs:
463 471 return pp[1]
464 472 return pp[0]
465 473
466 474 def mergepatch(self, repo, mergeq, series):
467 475 if len(self.applied) == 0:
468 476 # each of the patches merged in will have two parents. This
469 477 # can confuse the qrefresh, qdiff, and strip code because it
470 478 # needs to know which parent is actually in the patch queue.
471 479 # so, we insert a merge marker with only one parent. This way
472 480 # the first patch in the queue is never a merge patch
473 481 #
474 482 pname = ".hg.patches.merge.marker"
475 483 n = repo.commit('[mq]: merge marker', force=True)
476 484 self.removeundo(repo)
477 485 self.applied.append(statusentry(hex(n), pname))
478 486 self.applied_dirty = 1
479 487
480 488 head = self.qparents(repo)
481 489
482 490 for patch in series:
483 491 patch = mergeq.lookup(patch, strict=True)
484 492 if not patch:
485 493 self.ui.warn(_("patch %s does not exist\n") % patch)
486 494 return (1, None)
487 495 pushable, reason = self.pushable(patch)
488 496 if not pushable:
489 497 self.explain_pushable(patch, all_patches=True)
490 498 continue
491 499 info = mergeq.isapplied(patch)
492 500 if not info:
493 501 self.ui.warn(_("patch %s is not applied\n") % patch)
494 502 return (1, None)
495 503 rev = bin(info[1])
496 504 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
497 505 if head:
498 506 self.applied.append(statusentry(hex(head), patch))
499 507 self.applied_dirty = 1
500 508 if err:
501 509 return (err, head)
502 510 self.save_dirty()
503 511 return (0, head)
504 512
505 513 def patch(self, repo, patchfile):
506 514 '''Apply patchfile to the working directory.
507 515 patchfile: name of patch file'''
508 516 files = {}
509 517 try:
510 518 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
511 519 files=files, eolmode=None)
512 520 except Exception, inst:
513 521 self.ui.note(str(inst) + '\n')
514 522 if not self.ui.verbose:
515 523 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
516 524 return (False, files, False)
517 525
518 526 return (True, files, fuzz)
519 527
520 528 def apply(self, repo, series, list=False, update_status=True,
521 529 strict=False, patchdir=None, merge=None, all_files={}):
522 530 wlock = lock = tr = None
523 531 try:
524 532 wlock = repo.wlock()
525 533 lock = repo.lock()
526 534 tr = repo.transaction()
527 535 try:
528 536 ret = self._apply(repo, series, list, update_status,
529 537 strict, patchdir, merge, all_files=all_files)
530 538 tr.close()
531 539 self.save_dirty()
532 540 return ret
533 541 except:
534 542 try:
535 543 tr.abort()
536 544 finally:
537 545 repo.invalidate()
538 546 repo.dirstate.invalidate()
539 547 raise
540 548 finally:
541 549 del tr
542 550 release(lock, wlock)
543 551 self.removeundo(repo)
544 552
545 553 def _apply(self, repo, series, list=False, update_status=True,
546 554 strict=False, patchdir=None, merge=None, all_files={}):
547 555 '''returns (error, hash)
548 556 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
549 557 # TODO unify with commands.py
550 558 if not patchdir:
551 559 patchdir = self.path
552 560 err = 0
553 561 n = None
554 562 for patchname in series:
555 563 pushable, reason = self.pushable(patchname)
556 564 if not pushable:
557 565 self.explain_pushable(patchname, all_patches=True)
558 566 continue
559 567 self.ui.status(_("applying %s\n") % patchname)
560 568 pf = os.path.join(patchdir, patchname)
561 569
562 570 try:
563 571 ph = patchheader(self.join(patchname))
564 572 except:
565 573 self.ui.warn(_("unable to read %s\n") % patchname)
566 574 err = 1
567 575 break
568 576
569 577 message = ph.message
570 578 if not message:
571 579 message = _("imported patch %s\n") % patchname
572 580 else:
573 581 if list:
574 582 message.append(_("\nimported patch %s") % patchname)
575 583 message = '\n'.join(message)
576 584
577 585 if ph.haspatch:
578 586 (patcherr, files, fuzz) = self.patch(repo, pf)
579 587 all_files.update(files)
580 588 patcherr = not patcherr
581 589 else:
582 590 self.ui.warn(_("patch %s is empty\n") % patchname)
583 591 patcherr, files, fuzz = 0, [], 0
584 592
585 593 if merge and files:
586 594 # Mark as removed/merged and update dirstate parent info
587 595 removed = []
588 596 merged = []
589 597 for f in files:
590 598 if os.path.exists(repo.wjoin(f)):
591 599 merged.append(f)
592 600 else:
593 601 removed.append(f)
594 602 for f in removed:
595 603 repo.dirstate.remove(f)
596 604 for f in merged:
597 605 repo.dirstate.merge(f)
598 606 p1, p2 = repo.dirstate.parents()
599 607 repo.dirstate.setparents(p1, merge)
600 608
601 609 files = patch.updatedir(self.ui, repo, files)
602 610 match = cmdutil.matchfiles(repo, files or [])
603 611 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
604 612
605 613 if n is None:
606 614 raise util.Abort(_("repo commit failed"))
607 615
608 616 if update_status:
609 617 self.applied.append(statusentry(hex(n), patchname))
610 618
611 619 if patcherr:
612 620 self.ui.warn(_("patch failed, rejects left in working dir\n"))
613 621 err = 2
614 622 break
615 623
616 624 if fuzz and strict:
617 625 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
618 626 err = 3
619 627 break
620 628 return (err, n)
621 629
622 630 def _cleanup(self, patches, numrevs, keep=False):
623 631 if not keep:
624 632 r = self.qrepo()
625 633 if r:
626 634 r.remove(patches, True)
627 635 else:
628 636 for p in patches:
629 637 os.unlink(self.join(p))
630 638
631 639 if numrevs:
632 640 del self.applied[:numrevs]
633 641 self.applied_dirty = 1
634 642
635 643 for i in sorted([self.find_series(p) for p in patches], reverse=True):
636 644 del self.full_series[i]
637 645 self.parse_series()
638 646 self.series_dirty = 1
639 647
640 648 def _revpatches(self, repo, revs):
641 649 firstrev = repo[self.applied[0].rev].rev()
642 650 patches = []
643 651 for i, rev in enumerate(revs):
644 652
645 653 if rev < firstrev:
646 654 raise util.Abort(_('revision %d is not managed') % rev)
647 655
648 656 ctx = repo[rev]
649 657 base = bin(self.applied[i].rev)
650 658 if ctx.node() != base:
651 659 msg = _('cannot delete revision %d above applied patches')
652 660 raise util.Abort(msg % rev)
653 661
654 662 patch = self.applied[i].name
655 663 for fmt in ('[mq]: %s', 'imported patch %s'):
656 664 if ctx.description() == fmt % patch:
657 665 msg = _('patch %s finalized without changeset message\n')
658 666 repo.ui.status(msg % patch)
659 667 break
660 668
661 669 patches.append(patch)
662 670 return patches
663 671
664 672 def finish(self, repo, revs):
665 673 patches = self._revpatches(repo, sorted(revs))
666 674 self._cleanup(patches, len(patches))
667 675
668 676 def delete(self, repo, patches, opts):
669 677 if not patches and not opts.get('rev'):
670 678 raise util.Abort(_('qdelete requires at least one revision or '
671 679 'patch name'))
672 680
673 681 realpatches = []
674 682 for patch in patches:
675 683 patch = self.lookup(patch, strict=True)
676 684 info = self.isapplied(patch)
677 685 if info:
678 686 raise util.Abort(_("cannot delete applied patch %s") % patch)
679 687 if patch not in self.series:
680 688 raise util.Abort(_("patch %s not in series file") % patch)
681 689 realpatches.append(patch)
682 690
683 691 numrevs = 0
684 692 if opts.get('rev'):
685 693 if not self.applied:
686 694 raise util.Abort(_('no patches applied'))
687 695 revs = cmdutil.revrange(repo, opts['rev'])
688 696 if len(revs) > 1 and revs[0] > revs[1]:
689 697 revs.reverse()
690 698 revpatches = self._revpatches(repo, revs)
691 699 realpatches += revpatches
692 700 numrevs = len(revpatches)
693 701
694 702 self._cleanup(realpatches, numrevs, opts.get('keep'))
695 703
696 704 def check_toppatch(self, repo):
697 705 if len(self.applied) > 0:
698 706 top = bin(self.applied[-1].rev)
699 707 pp = repo.dirstate.parents()
700 708 if top not in pp:
701 709 raise util.Abort(_("working directory revision is not qtip"))
702 710 return top
703 711 return None
704 712 def check_localchanges(self, repo, force=False, refresh=True):
705 713 m, a, r, d = repo.status()[:4]
706 714 if m or a or r or d:
707 715 if not force:
708 716 if refresh:
709 717 raise util.Abort(_("local changes found, refresh first"))
710 718 else:
711 719 raise util.Abort(_("local changes found"))
712 720 return m, a, r, d
713 721
714 722 _reserved = ('series', 'status', 'guards')
715 723 def check_reserved_name(self, name):
716 724 if (name in self._reserved or name.startswith('.hg')
717 725 or name.startswith('.mq')):
718 726 raise util.Abort(_('"%s" cannot be used as the name of a patch')
719 727 % name)
720 728
721 729 def new(self, repo, patchfn, *pats, **opts):
722 730 """options:
723 731 msg: a string or a no-argument function returning a string
724 732 """
725 733 msg = opts.get('msg')
726 734 force = opts.get('force')
727 735 user = opts.get('user')
728 736 date = opts.get('date')
729 737 if date:
730 738 date = util.parsedate(date)
731 739 self.check_reserved_name(patchfn)
732 740 if os.path.exists(self.join(patchfn)):
733 741 raise util.Abort(_('patch "%s" already exists') % patchfn)
734 742 if opts.get('include') or opts.get('exclude') or pats:
735 743 match = cmdutil.match(repo, pats, opts)
736 744 # detect missing files in pats
737 745 def badfn(f, msg):
738 746 raise util.Abort('%s: %s' % (f, msg))
739 747 match.bad = badfn
740 748 m, a, r, d = repo.status(match=match)[:4]
741 749 else:
742 750 m, a, r, d = self.check_localchanges(repo, force)
743 751 match = cmdutil.matchfiles(repo, m + a + r)
744 752 commitfiles = m + a + r
745 753 self.check_toppatch(repo)
746 754 insert = self.full_series_end()
747 755 wlock = repo.wlock()
748 756 try:
749 757 # if patch file write fails, abort early
750 758 p = self.opener(patchfn, "w")
751 759 try:
752 760 if date:
753 761 p.write("# HG changeset patch\n")
754 762 if user:
755 763 p.write("# User " + user + "\n")
756 764 p.write("# Date %d %d\n\n" % date)
757 765 elif user:
758 766 p.write("From: " + user + "\n\n")
759 767
760 768 if hasattr(msg, '__call__'):
761 769 msg = msg()
762 770 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
763 771 n = repo.commit(commitmsg, user, date, match=match, force=True)
764 772 if n is None:
765 773 raise util.Abort(_("repo commit failed"))
766 774 try:
767 775 self.full_series[insert:insert] = [patchfn]
768 776 self.applied.append(statusentry(hex(n), patchfn))
769 777 self.parse_series()
770 778 self.series_dirty = 1
771 779 self.applied_dirty = 1
772 780 if msg:
773 781 msg = msg + "\n\n"
774 782 p.write(msg)
775 783 if commitfiles:
776 784 diffopts = self.diffopts()
777 785 if opts.get('git'): diffopts.git = True
778 786 parent = self.qparents(repo, n)
779 787 chunks = patch.diff(repo, node1=parent, node2=n,
780 788 match=match, opts=diffopts)
781 789 for chunk in chunks:
782 790 p.write(chunk)
783 791 p.close()
784 792 wlock.release()
785 793 wlock = None
786 794 r = self.qrepo()
787 795 if r: r.add([patchfn])
788 796 except:
789 797 repo.rollback()
790 798 raise
791 799 except Exception:
792 800 patchpath = self.join(patchfn)
793 801 try:
794 802 os.unlink(patchpath)
795 803 except:
796 804 self.ui.warn(_('error unlinking %s\n') % patchpath)
797 805 raise
798 806 self.removeundo(repo)
799 807 finally:
800 808 release(wlock)
801 809
802 810 def strip(self, repo, rev, update=True, backup="all", force=None):
803 811 wlock = lock = None
804 812 try:
805 813 wlock = repo.wlock()
806 814 lock = repo.lock()
807 815
808 816 if update:
809 817 self.check_localchanges(repo, force=force, refresh=False)
810 818 urev = self.qparents(repo, rev)
811 819 hg.clean(repo, urev)
812 820 repo.dirstate.write()
813 821
814 822 self.removeundo(repo)
815 823 repair.strip(self.ui, repo, rev, backup)
816 824 # strip may have unbundled a set of backed up revisions after
817 825 # the actual strip
818 826 self.removeundo(repo)
819 827 finally:
820 828 release(lock, wlock)
821 829
822 830 def isapplied(self, patch):
823 831 """returns (index, rev, patch)"""
824 832 for i, a in enumerate(self.applied):
825 833 if a.name == patch:
826 834 return (i, a.rev, a.name)
827 835 return None
828 836
829 837 # if the exact patch name does not exist, we try a few
830 838 # variations. If strict is passed, we try only #1
831 839 #
832 840 # 1) a number to indicate an offset in the series file
833 841 # 2) a unique substring of the patch name was given
834 842 # 3) patchname[-+]num to indicate an offset in the series file
835 843 def lookup(self, patch, strict=False):
836 844 patch = patch and str(patch)
837 845
838 846 def partial_name(s):
839 847 if s in self.series:
840 848 return s
841 849 matches = [x for x in self.series if s in x]
842 850 if len(matches) > 1:
843 851 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
844 852 for m in matches:
845 853 self.ui.warn(' %s\n' % m)
846 854 return None
847 855 if matches:
848 856 return matches[0]
849 857 if len(self.series) > 0 and len(self.applied) > 0:
850 858 if s == 'qtip':
851 859 return self.series[self.series_end(True)-1]
852 860 if s == 'qbase':
853 861 return self.series[0]
854 862 return None
855 863
856 864 if patch is None:
857 865 return None
858 866 if patch in self.series:
859 867 return patch
860 868
861 869 if not os.path.isfile(self.join(patch)):
862 870 try:
863 871 sno = int(patch)
864 872 except(ValueError, OverflowError):
865 873 pass
866 874 else:
867 875 if -len(self.series) <= sno < len(self.series):
868 876 return self.series[sno]
869 877
870 878 if not strict:
871 879 res = partial_name(patch)
872 880 if res:
873 881 return res
874 882 minus = patch.rfind('-')
875 883 if minus >= 0:
876 884 res = partial_name(patch[:minus])
877 885 if res:
878 886 i = self.series.index(res)
879 887 try:
880 888 off = int(patch[minus+1:] or 1)
881 889 except(ValueError, OverflowError):
882 890 pass
883 891 else:
884 892 if i - off >= 0:
885 893 return self.series[i - off]
886 894 plus = patch.rfind('+')
887 895 if plus >= 0:
888 896 res = partial_name(patch[:plus])
889 897 if res:
890 898 i = self.series.index(res)
891 899 try:
892 900 off = int(patch[plus+1:] or 1)
893 901 except(ValueError, OverflowError):
894 902 pass
895 903 else:
896 904 if i + off < len(self.series):
897 905 return self.series[i + off]
898 906 raise util.Abort(_("patch %s not in series") % patch)
899 907
900 908 def push(self, repo, patch=None, force=False, list=False,
901 909 mergeq=None, all=False):
902 910 wlock = repo.wlock()
903 911 try:
904 912 if repo.dirstate.parents()[0] not in repo.heads():
905 913 self.ui.status(_("(working directory not at a head)\n"))
906 914
907 915 if not self.series:
908 916 self.ui.warn(_('no patches in series\n'))
909 917 return 0
910 918
911 919 patch = self.lookup(patch)
912 920 # Suppose our series file is: A B C and the current 'top'
913 921 # patch is B. qpush C should be performed (moving forward)
914 922 # qpush B is a NOP (no change) qpush A is an error (can't
915 923 # go backwards with qpush)
916 924 if patch:
917 925 info = self.isapplied(patch)
918 926 if info:
919 927 if info[0] < len(self.applied) - 1:
920 928 raise util.Abort(
921 929 _("cannot push to a previous patch: %s") % patch)
922 930 self.ui.warn(
923 931 _('qpush: %s is already at the top\n') % patch)
924 932 return
925 933 pushable, reason = self.pushable(patch)
926 934 if not pushable:
927 935 if reason:
928 936 reason = _('guarded by %r') % reason
929 937 else:
930 938 reason = _('no matching guards')
931 939 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
932 940 return 1
933 941 elif all:
934 942 patch = self.series[-1]
935 943 if self.isapplied(patch):
936 944 self.ui.warn(_('all patches are currently applied\n'))
937 945 return 0
938 946
939 947 # Following the above example, starting at 'top' of B:
940 948 # qpush should be performed (pushes C), but a subsequent
941 949 # qpush without an argument is an error (nothing to
942 950 # apply). This allows a loop of "...while hg qpush..." to
943 951 # work as it detects an error when done
944 952 start = self.series_end()
945 953 if start == len(self.series):
946 954 self.ui.warn(_('patch series already fully applied\n'))
947 955 return 1
948 956 if not force:
949 957 self.check_localchanges(repo)
950 958
951 959 self.applied_dirty = 1
952 960 if start > 0:
953 961 self.check_toppatch(repo)
954 962 if not patch:
955 963 patch = self.series[start]
956 964 end = start + 1
957 965 else:
958 966 end = self.series.index(patch, start) + 1
959 967
960 968 s = self.series[start:end]
961 969 all_files = {}
962 970 try:
963 971 if mergeq:
964 972 ret = self.mergepatch(repo, mergeq, s)
965 973 else:
966 974 ret = self.apply(repo, s, list, all_files=all_files)
967 975 except:
968 976 self.ui.warn(_('cleaning up working directory...'))
969 977 node = repo.dirstate.parents()[0]
970 978 hg.revert(repo, node, None)
971 979 unknown = repo.status(unknown=True)[4]
972 980 # only remove unknown files that we know we touched or
973 981 # created while patching
974 982 for f in unknown:
975 983 if f in all_files:
976 984 util.unlink(repo.wjoin(f))
977 985 self.ui.warn(_('done\n'))
978 986 raise
979 987
980 988 top = self.applied[-1].name
981 989 if ret[0] and ret[0] > 1:
982 990 msg = _("errors during apply, please fix and refresh %s\n")
983 991 self.ui.write(msg % top)
984 992 else:
985 993 self.ui.write(_("now at: %s\n") % top)
986 994 return ret[0]
987 995
988 996 finally:
989 997 wlock.release()
990 998
991 999 def pop(self, repo, patch=None, force=False, update=True, all=False):
992 1000 def getfile(f, rev, flags):
993 1001 t = repo.file(f).read(rev)
994 1002 repo.wwrite(f, t, flags)
995 1003
996 1004 wlock = repo.wlock()
997 1005 try:
998 1006 if patch:
999 1007 # index, rev, patch
1000 1008 info = self.isapplied(patch)
1001 1009 if not info:
1002 1010 patch = self.lookup(patch)
1003 1011 info = self.isapplied(patch)
1004 1012 if not info:
1005 1013 raise util.Abort(_("patch %s is not applied") % patch)
1006 1014
1007 1015 if len(self.applied) == 0:
1008 1016 # Allow qpop -a to work repeatedly,
1009 1017 # but not qpop without an argument
1010 1018 self.ui.warn(_("no patches applied\n"))
1011 1019 return not all
1012 1020
1013 1021 if all:
1014 1022 start = 0
1015 1023 elif patch:
1016 1024 start = info[0] + 1
1017 1025 else:
1018 1026 start = len(self.applied) - 1
1019 1027
1020 1028 if start >= len(self.applied):
1021 1029 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1022 1030 return
1023 1031
1024 1032 if not update:
1025 1033 parents = repo.dirstate.parents()
1026 1034 rr = [ bin(x.rev) for x in self.applied ]
1027 1035 for p in parents:
1028 1036 if p in rr:
1029 1037 self.ui.warn(_("qpop: forcing dirstate update\n"))
1030 1038 update = True
1031 1039 else:
1032 1040 parents = [p.hex() for p in repo[None].parents()]
1033 1041 needupdate = False
1034 1042 for entry in self.applied[start:]:
1035 1043 if entry.rev in parents:
1036 1044 needupdate = True
1037 1045 break
1038 1046 update = needupdate
1039 1047
1040 1048 if not force and update:
1041 1049 self.check_localchanges(repo)
1042 1050
1043 1051 self.applied_dirty = 1
1044 1052 end = len(self.applied)
1045 1053 rev = bin(self.applied[start].rev)
1046 1054 if update:
1047 1055 top = self.check_toppatch(repo)
1048 1056
1049 1057 try:
1050 1058 heads = repo.changelog.heads(rev)
1051 1059 except error.LookupError:
1052 1060 node = short(rev)
1053 1061 raise util.Abort(_('trying to pop unknown node %s') % node)
1054 1062
1055 1063 if heads != [bin(self.applied[-1].rev)]:
1056 1064 raise util.Abort(_("popping would remove a revision not "
1057 1065 "managed by this patch queue"))
1058 1066
1059 1067 # we know there are no local changes, so we can make a simplified
1060 1068 # form of hg.update.
1061 1069 if update:
1062 1070 qp = self.qparents(repo, rev)
1063 1071 changes = repo.changelog.read(qp)
1064 1072 mmap = repo.manifest.read(changes[0])
1065 1073 m, a, r, d = repo.status(qp, top)[:4]
1066 1074 if d:
1067 1075 raise util.Abort(_("deletions found between repo revs"))
1068 1076 for f in m:
1069 1077 getfile(f, mmap[f], mmap.flags(f))
1070 1078 for f in r:
1071 1079 getfile(f, mmap[f], mmap.flags(f))
1072 1080 for f in m + r:
1073 1081 repo.dirstate.normal(f)
1074 1082 for f in a:
1075 1083 try:
1076 1084 os.unlink(repo.wjoin(f))
1077 1085 except OSError, e:
1078 1086 if e.errno != errno.ENOENT:
1079 1087 raise
1080 1088 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1081 1089 except: pass
1082 1090 repo.dirstate.forget(f)
1083 1091 repo.dirstate.setparents(qp, nullid)
1084 1092 for patch in reversed(self.applied[start:end]):
1085 1093 self.ui.status(_("popping %s\n") % patch.name)
1086 1094 del self.applied[start:end]
1087 1095 self.strip(repo, rev, update=False, backup='strip')
1088 1096 if len(self.applied):
1089 1097 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1090 1098 else:
1091 1099 self.ui.write(_("patch queue now empty\n"))
1092 1100 finally:
1093 1101 wlock.release()
1094 1102
1095 1103 def diff(self, repo, pats, opts):
1096 1104 top = self.check_toppatch(repo)
1097 1105 if not top:
1098 1106 self.ui.write(_("no patches applied\n"))
1099 1107 return
1100 1108 qp = self.qparents(repo, top)
1101 1109 self._diffopts = patch.diffopts(self.ui, opts)
1102 1110 self.printdiff(repo, qp, files=pats, opts=opts)
1103 1111
1104 1112 def refresh(self, repo, pats=None, **opts):
1105 1113 if len(self.applied) == 0:
1106 1114 self.ui.write(_("no patches applied\n"))
1107 1115 return 1
1108 1116 msg = opts.get('msg', '').rstrip()
1109 1117 newuser = opts.get('user')
1110 1118 newdate = opts.get('date')
1111 1119 if newdate:
1112 1120 newdate = '%d %d' % util.parsedate(newdate)
1113 1121 wlock = repo.wlock()
1114 1122 try:
1115 1123 self.check_toppatch(repo)
1116 1124 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1117 1125 top = bin(top)
1118 1126 if repo.changelog.heads(top) != [top]:
1119 1127 raise util.Abort(_("cannot refresh a revision with children"))
1120 1128 cparents = repo.changelog.parents(top)
1121 1129 patchparent = self.qparents(repo, top)
1122 1130 ph = patchheader(self.join(patchfn))
1123 1131
1124 1132 patchf = self.opener(patchfn, 'r')
1125 1133
1126 1134 # if the patch was a git patch, refresh it as a git patch
1127 1135 for line in patchf:
1128 1136 if line.startswith('diff --git'):
1129 1137 self.diffopts().git = True
1130 1138 break
1131 1139
1132 1140 if msg:
1133 1141 ph.setmessage(msg)
1134 1142 if newuser:
1135 1143 ph.setuser(newuser)
1136 1144 if newdate:
1137 1145 ph.setdate(newdate)
1138 1146
1139 1147 # only commit new patch when write is complete
1140 1148 patchf = self.opener(patchfn, 'w', atomictemp=True)
1141 1149
1142 1150 patchf.seek(0)
1143 1151 patchf.truncate()
1144 1152
1145 1153 comments = str(ph)
1146 1154 if comments:
1147 1155 patchf.write(comments)
1148 1156
1149 1157 if opts.get('git'):
1150 1158 self.diffopts().git = True
1151 1159 tip = repo.changelog.tip()
1152 1160 if top == tip:
1153 1161 # if the top of our patch queue is also the tip, there is an
1154 1162 # optimization here. We update the dirstate in place and strip
1155 1163 # off the tip commit. Then just commit the current directory
1156 1164 # tree. We can also send repo.commit the list of files
1157 1165 # changed to speed up the diff
1158 1166 #
1159 1167 # in short mode, we only diff the files included in the
1160 1168 # patch already plus specified files
1161 1169 #
1162 1170 # this should really read:
1163 1171 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1164 1172 # but we do it backwards to take advantage of manifest/chlog
1165 1173 # caching against the next repo.status call
1166 1174 #
1167 1175 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1168 1176 changes = repo.changelog.read(tip)
1169 1177 man = repo.manifest.read(changes[0])
1170 1178 aaa = aa[:]
1171 1179 matchfn = cmdutil.match(repo, pats, opts)
1172 1180 if opts.get('short'):
1173 1181 # if amending a patch, we start with existing
1174 1182 # files plus specified files - unfiltered
1175 1183 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1176 1184 # filter with inc/exl options
1177 1185 matchfn = cmdutil.match(repo, opts=opts)
1178 1186 else:
1179 1187 match = cmdutil.matchall(repo)
1180 1188 m, a, r, d = repo.status(match=match)[:4]
1181 1189
1182 1190 # we might end up with files that were added between
1183 1191 # tip and the dirstate parent, but then changed in the
1184 1192 # local dirstate. in this case, we want them to only
1185 1193 # show up in the added section
1186 1194 for x in m:
1187 1195 if x not in aa:
1188 1196 mm.append(x)
1189 1197 # we might end up with files added by the local dirstate that
1190 1198 # were deleted by the patch. In this case, they should only
1191 1199 # show up in the changed section.
1192 1200 for x in a:
1193 1201 if x in dd:
1194 1202 del dd[dd.index(x)]
1195 1203 mm.append(x)
1196 1204 else:
1197 1205 aa.append(x)
1198 1206 # make sure any files deleted in the local dirstate
1199 1207 # are not in the add or change column of the patch
1200 1208 forget = []
1201 1209 for x in d + r:
1202 1210 if x in aa:
1203 1211 del aa[aa.index(x)]
1204 1212 forget.append(x)
1205 1213 continue
1206 1214 elif x in mm:
1207 1215 del mm[mm.index(x)]
1208 1216 dd.append(x)
1209 1217
1210 1218 m = list(set(mm))
1211 1219 r = list(set(dd))
1212 1220 a = list(set(aa))
1213 1221 c = [filter(matchfn, l) for l in (m, a, r)]
1214 1222 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1215 1223 chunks = patch.diff(repo, patchparent, match=match,
1216 1224 changes=c, opts=self.diffopts())
1217 1225 for chunk in chunks:
1218 1226 patchf.write(chunk)
1219 1227
1220 1228 try:
1221 1229 if self.diffopts().git:
1222 1230 copies = {}
1223 1231 for dst in a:
1224 1232 src = repo.dirstate.copied(dst)
1225 1233 # during qfold, the source file for copies may
1226 1234 # be removed. Treat this as a simple add.
1227 1235 if src is not None and src in repo.dirstate:
1228 1236 copies.setdefault(src, []).append(dst)
1229 1237 repo.dirstate.add(dst)
1230 1238 # remember the copies between patchparent and tip
1231 1239 for dst in aaa:
1232 1240 f = repo.file(dst)
1233 1241 src = f.renamed(man[dst])
1234 1242 if src:
1235 1243 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1236 1244 if dst in a:
1237 1245 copies[src[0]].append(dst)
1238 1246 # we can't copy a file created by the patch itself
1239 1247 if dst in copies:
1240 1248 del copies[dst]
1241 1249 for src, dsts in copies.iteritems():
1242 1250 for dst in dsts:
1243 1251 repo.dirstate.copy(src, dst)
1244 1252 else:
1245 1253 for dst in a:
1246 1254 repo.dirstate.add(dst)
1247 1255 # Drop useless copy information
1248 1256 for f in list(repo.dirstate.copies()):
1249 1257 repo.dirstate.copy(None, f)
1250 1258 for f in r:
1251 1259 repo.dirstate.remove(f)
1252 1260 # if the patch excludes a modified file, mark that
1253 1261 # file with mtime=0 so status can see it.
1254 1262 mm = []
1255 1263 for i in xrange(len(m)-1, -1, -1):
1256 1264 if not matchfn(m[i]):
1257 1265 mm.append(m[i])
1258 1266 del m[i]
1259 1267 for f in m:
1260 1268 repo.dirstate.normal(f)
1261 1269 for f in mm:
1262 1270 repo.dirstate.normallookup(f)
1263 1271 for f in forget:
1264 1272 repo.dirstate.forget(f)
1265 1273
1266 1274 if not msg:
1267 1275 if not ph.message:
1268 1276 message = "[mq]: %s\n" % patchfn
1269 1277 else:
1270 1278 message = "\n".join(ph.message)
1271 1279 else:
1272 1280 message = msg
1273 1281
1274 1282 user = ph.user or changes[1]
1275 1283
1276 1284 # assumes strip can roll itself back if interrupted
1277 1285 repo.dirstate.setparents(*cparents)
1278 1286 self.applied.pop()
1279 1287 self.applied_dirty = 1
1280 1288 self.strip(repo, top, update=False,
1281 1289 backup='strip')
1282 1290 except:
1283 1291 repo.dirstate.invalidate()
1284 1292 raise
1285 1293
1286 1294 try:
1287 1295 # might be nice to attempt to roll back strip after this
1288 1296 patchf.rename()
1289 1297 n = repo.commit(message, user, ph.date, match=match,
1290 1298 force=True)
1291 1299 self.applied.append(statusentry(hex(n), patchfn))
1292 1300 except:
1293 1301 ctx = repo[cparents[0]]
1294 1302 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1295 1303 self.save_dirty()
1296 1304 self.ui.warn(_('refresh interrupted while patch was popped! '
1297 1305 '(revert --all, qpush to recover)\n'))
1298 1306 raise
1299 1307 else:
1300 1308 self.printdiff(repo, patchparent, fp=patchf)
1301 1309 patchf.rename()
1302 1310 added = repo.status()[1]
1303 1311 for a in added:
1304 1312 f = repo.wjoin(a)
1305 1313 try:
1306 1314 os.unlink(f)
1307 1315 except OSError, e:
1308 1316 if e.errno != errno.ENOENT:
1309 1317 raise
1310 1318 try: os.removedirs(os.path.dirname(f))
1311 1319 except: pass
1312 1320 # forget the file copies in the dirstate
1313 1321 # push should readd the files later on
1314 1322 repo.dirstate.forget(a)
1315 1323 self.pop(repo, force=True)
1316 1324 self.push(repo, force=True)
1317 1325 finally:
1318 1326 wlock.release()
1319 1327 self.removeundo(repo)
1320 1328
1321 1329 def init(self, repo, create=False):
1322 1330 if not create and os.path.isdir(self.path):
1323 1331 raise util.Abort(_("patch queue directory already exists"))
1324 1332 try:
1325 1333 os.mkdir(self.path)
1326 1334 except OSError, inst:
1327 1335 if inst.errno != errno.EEXIST or not create:
1328 1336 raise
1329 1337 if create:
1330 1338 return self.qrepo(create=True)
1331 1339
1332 1340 def unapplied(self, repo, patch=None):
1333 1341 if patch and patch not in self.series:
1334 1342 raise util.Abort(_("patch %s is not in series file") % patch)
1335 1343 if not patch:
1336 1344 start = self.series_end()
1337 1345 else:
1338 1346 start = self.series.index(patch) + 1
1339 1347 unapplied = []
1340 1348 for i in xrange(start, len(self.series)):
1341 1349 pushable, reason = self.pushable(i)
1342 1350 if pushable:
1343 1351 unapplied.append((i, self.series[i]))
1344 1352 self.explain_pushable(i)
1345 1353 return unapplied
1346 1354
1347 1355 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1348 1356 summary=False):
1349 1357 def displayname(pfx, patchname):
1350 1358 if summary:
1351 1359 ph = patchheader(self.join(patchname))
1352 1360 msg = ph.message
1353 1361 msg = msg and ': ' + msg[0] or ': '
1354 1362 else:
1355 1363 msg = ''
1356 1364 msg = "%s%s%s" % (pfx, patchname, msg)
1357 1365 if self.ui.interactive():
1358 1366 msg = util.ellipsis(msg, util.termwidth())
1359 1367 self.ui.write(msg + '\n')
1360 1368
1361 1369 applied = set([p.name for p in self.applied])
1362 1370 if length is None:
1363 1371 length = len(self.series) - start
1364 1372 if not missing:
1365 1373 if self.ui.verbose:
1366 1374 idxwidth = len(str(start+length - 1))
1367 1375 for i in xrange(start, start+length):
1368 1376 patch = self.series[i]
1369 1377 if patch in applied:
1370 1378 stat = 'A'
1371 1379 elif self.pushable(i)[0]:
1372 1380 stat = 'U'
1373 1381 else:
1374 1382 stat = 'G'
1375 1383 pfx = ''
1376 1384 if self.ui.verbose:
1377 1385 pfx = '%*d %s ' % (idxwidth, i, stat)
1378 1386 elif status and status != stat:
1379 1387 continue
1380 1388 displayname(pfx, patch)
1381 1389 else:
1382 1390 msng_list = []
1383 1391 for root, dirs, files in os.walk(self.path):
1384 1392 d = root[len(self.path) + 1:]
1385 1393 for f in files:
1386 1394 fl = os.path.join(d, f)
1387 1395 if (fl not in self.series and
1388 1396 fl not in (self.status_path, self.series_path,
1389 1397 self.guards_path)
1390 1398 and not fl.startswith('.')):
1391 1399 msng_list.append(fl)
1392 1400 for x in sorted(msng_list):
1393 1401 pfx = self.ui.verbose and ('D ') or ''
1394 1402 displayname(pfx, x)
1395 1403
1396 1404 def issaveline(self, l):
1397 1405 if l.name == '.hg.patches.save.line':
1398 1406 return True
1399 1407
1400 1408 def qrepo(self, create=False):
1401 1409 if create or os.path.isdir(self.join(".hg")):
1402 1410 return hg.repository(self.ui, path=self.path, create=create)
1403 1411
1404 1412 def restore(self, repo, rev, delete=None, qupdate=None):
1405 1413 c = repo.changelog.read(rev)
1406 1414 desc = c[4].strip()
1407 1415 lines = desc.splitlines()
1408 1416 i = 0
1409 1417 datastart = None
1410 1418 series = []
1411 1419 applied = []
1412 1420 qpp = None
1413 1421 for i, line in enumerate(lines):
1414 1422 if line == 'Patch Data:':
1415 1423 datastart = i + 1
1416 1424 elif line.startswith('Dirstate:'):
1417 1425 l = line.rstrip()
1418 1426 l = l[10:].split(' ')
1419 1427 qpp = [ bin(x) for x in l ]
1420 1428 elif datastart != None:
1421 1429 l = line.rstrip()
1422 1430 se = statusentry(l)
1423 1431 file_ = se.name
1424 1432 if se.rev:
1425 1433 applied.append(se)
1426 1434 else:
1427 1435 series.append(file_)
1428 1436 if datastart is None:
1429 1437 self.ui.warn(_("No saved patch data found\n"))
1430 1438 return 1
1431 1439 self.ui.warn(_("restoring status: %s\n") % lines[0])
1432 1440 self.full_series = series
1433 1441 self.applied = applied
1434 1442 self.parse_series()
1435 1443 self.series_dirty = 1
1436 1444 self.applied_dirty = 1
1437 1445 heads = repo.changelog.heads()
1438 1446 if delete:
1439 1447 if rev not in heads:
1440 1448 self.ui.warn(_("save entry has children, leaving it alone\n"))
1441 1449 else:
1442 1450 self.ui.warn(_("removing save entry %s\n") % short(rev))
1443 1451 pp = repo.dirstate.parents()
1444 1452 if rev in pp:
1445 1453 update = True
1446 1454 else:
1447 1455 update = False
1448 1456 self.strip(repo, rev, update=update, backup='strip')
1449 1457 if qpp:
1450 1458 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1451 1459 (short(qpp[0]), short(qpp[1])))
1452 1460 if qupdate:
1453 1461 self.ui.status(_("queue directory updating\n"))
1454 1462 r = self.qrepo()
1455 1463 if not r:
1456 1464 self.ui.warn(_("Unable to load queue repository\n"))
1457 1465 return 1
1458 1466 hg.clean(r, qpp[0])
1459 1467
1460 1468 def save(self, repo, msg=None):
1461 1469 if len(self.applied) == 0:
1462 1470 self.ui.warn(_("save: no patches applied, exiting\n"))
1463 1471 return 1
1464 1472 if self.issaveline(self.applied[-1]):
1465 1473 self.ui.warn(_("status is already saved\n"))
1466 1474 return 1
1467 1475
1468 1476 ar = [ ':' + x for x in self.full_series ]
1469 1477 if not msg:
1470 1478 msg = _("hg patches saved state")
1471 1479 else:
1472 1480 msg = "hg patches: " + msg.rstrip('\r\n')
1473 1481 r = self.qrepo()
1474 1482 if r:
1475 1483 pp = r.dirstate.parents()
1476 1484 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1477 1485 msg += "\n\nPatch Data:\n"
1478 1486 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1479 1487 "\n".join(ar) + '\n' or "")
1480 1488 n = repo.commit(text, force=True)
1481 1489 if not n:
1482 1490 self.ui.warn(_("repo commit failed\n"))
1483 1491 return 1
1484 1492 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1485 1493 self.applied_dirty = 1
1486 1494 self.removeundo(repo)
1487 1495
1488 1496 def full_series_end(self):
1489 1497 if len(self.applied) > 0:
1490 1498 p = self.applied[-1].name
1491 1499 end = self.find_series(p)
1492 1500 if end is None:
1493 1501 return len(self.full_series)
1494 1502 return end + 1
1495 1503 return 0
1496 1504
1497 1505 def series_end(self, all_patches=False):
1498 1506 """If all_patches is False, return the index of the next pushable patch
1499 1507 in the series, or the series length. If all_patches is True, return the
1500 1508 index of the first patch past the last applied one.
1501 1509 """
1502 1510 end = 0
1503 1511 def next(start):
1504 1512 if all_patches:
1505 1513 return start
1506 1514 i = start
1507 1515 while i < len(self.series):
1508 1516 p, reason = self.pushable(i)
1509 1517 if p:
1510 1518 break
1511 1519 self.explain_pushable(i)
1512 1520 i += 1
1513 1521 return i
1514 1522 if len(self.applied) > 0:
1515 1523 p = self.applied[-1].name
1516 1524 try:
1517 1525 end = self.series.index(p)
1518 1526 except ValueError:
1519 1527 return 0
1520 1528 return next(end + 1)
1521 1529 return next(end)
1522 1530
1523 1531 def appliedname(self, index):
1524 1532 pname = self.applied[index].name
1525 1533 if not self.ui.verbose:
1526 1534 p = pname
1527 1535 else:
1528 1536 p = str(self.series.index(pname)) + " " + pname
1529 1537 return p
1530 1538
1531 1539 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1532 1540 force=None, git=False):
1533 1541 def checkseries(patchname):
1534 1542 if patchname in self.series:
1535 1543 raise util.Abort(_('patch %s is already in the series file')
1536 1544 % patchname)
1537 1545 def checkfile(patchname):
1538 1546 if not force and os.path.exists(self.join(patchname)):
1539 1547 raise util.Abort(_('patch "%s" already exists')
1540 1548 % patchname)
1541 1549
1542 1550 if rev:
1543 1551 if files:
1544 1552 raise util.Abort(_('option "-r" not valid when importing '
1545 1553 'files'))
1546 1554 rev = cmdutil.revrange(repo, rev)
1547 1555 rev.sort(reverse=True)
1548 1556 if (len(files) > 1 or len(rev) > 1) and patchname:
1549 1557 raise util.Abort(_('option "-n" not valid when importing multiple '
1550 1558 'patches'))
1551 1559 i = 0
1552 1560 added = []
1553 1561 if rev:
1554 1562 # If mq patches are applied, we can only import revisions
1555 1563 # that form a linear path to qbase.
1556 1564 # Otherwise, they should form a linear path to a head.
1557 1565 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1558 1566 if len(heads) > 1:
1559 1567 raise util.Abort(_('revision %d is the root of more than one '
1560 1568 'branch') % rev[-1])
1561 1569 if self.applied:
1562 1570 base = hex(repo.changelog.node(rev[0]))
1563 1571 if base in [n.rev for n in self.applied]:
1564 1572 raise util.Abort(_('revision %d is already managed')
1565 1573 % rev[0])
1566 1574 if heads != [bin(self.applied[-1].rev)]:
1567 1575 raise util.Abort(_('revision %d is not the parent of '
1568 1576 'the queue') % rev[0])
1569 1577 base = repo.changelog.rev(bin(self.applied[0].rev))
1570 1578 lastparent = repo.changelog.parentrevs(base)[0]
1571 1579 else:
1572 1580 if heads != [repo.changelog.node(rev[0])]:
1573 1581 raise util.Abort(_('revision %d has unmanaged children')
1574 1582 % rev[0])
1575 1583 lastparent = None
1576 1584
1577 1585 if git:
1578 1586 self.diffopts().git = True
1579 1587
1580 1588 for r in rev:
1581 1589 p1, p2 = repo.changelog.parentrevs(r)
1582 1590 n = repo.changelog.node(r)
1583 1591 if p2 != nullrev:
1584 1592 raise util.Abort(_('cannot import merge revision %d') % r)
1585 1593 if lastparent and lastparent != r:
1586 1594 raise util.Abort(_('revision %d is not the parent of %d')
1587 1595 % (r, lastparent))
1588 1596 lastparent = p1
1589 1597
1590 1598 if not patchname:
1591 1599 patchname = normname('%d.diff' % r)
1592 1600 self.check_reserved_name(patchname)
1593 1601 checkseries(patchname)
1594 1602 checkfile(patchname)
1595 1603 self.full_series.insert(0, patchname)
1596 1604
1597 1605 patchf = self.opener(patchname, "w")
1598 1606 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1599 1607 patchf.close()
1600 1608
1601 1609 se = statusentry(hex(n), patchname)
1602 1610 self.applied.insert(0, se)
1603 1611
1604 1612 added.append(patchname)
1605 1613 patchname = None
1606 1614 self.parse_series()
1607 1615 self.applied_dirty = 1
1608 1616
1609 1617 for filename in files:
1610 1618 if existing:
1611 1619 if filename == '-':
1612 1620 raise util.Abort(_('-e is incompatible with import from -'))
1613 1621 if not patchname:
1614 1622 patchname = normname(filename)
1615 1623 self.check_reserved_name(patchname)
1616 1624 if not os.path.isfile(self.join(patchname)):
1617 1625 raise util.Abort(_("patch %s does not exist") % patchname)
1618 1626 else:
1619 1627 try:
1620 1628 if filename == '-':
1621 1629 if not patchname:
1622 1630 raise util.Abort(_('need --name to import a patch from -'))
1623 1631 text = sys.stdin.read()
1624 1632 else:
1625 1633 text = url.open(self.ui, filename).read()
1626 1634 except (OSError, IOError):
1627 1635 raise util.Abort(_("unable to read %s") % filename)
1628 1636 if not patchname:
1629 1637 patchname = normname(os.path.basename(filename))
1630 1638 self.check_reserved_name(patchname)
1631 1639 checkfile(patchname)
1632 1640 patchf = self.opener(patchname, "w")
1633 1641 patchf.write(text)
1634 1642 if not force:
1635 1643 checkseries(patchname)
1636 1644 if patchname not in self.series:
1637 1645 index = self.full_series_end() + i
1638 1646 self.full_series[index:index] = [patchname]
1639 1647 self.parse_series()
1640 1648 self.ui.warn(_("adding %s to series file\n") % patchname)
1641 1649 i += 1
1642 1650 added.append(patchname)
1643 1651 patchname = None
1644 1652 self.series_dirty = 1
1645 1653 qrepo = self.qrepo()
1646 1654 if qrepo:
1647 1655 qrepo.add(added)
1648 1656
1649 1657 def delete(ui, repo, *patches, **opts):
1650 1658 """remove patches from queue
1651 1659
1652 1660 The patches must not be applied, and at least one patch is required. With
1653 1661 -k/--keep, the patch files are preserved in the patch directory.
1654 1662
1655 1663 To stop managing a patch and move it into permanent history,
1656 1664 use the qfinish command."""
1657 1665 q = repo.mq
1658 1666 q.delete(repo, patches, opts)
1659 1667 q.save_dirty()
1660 1668 return 0
1661 1669
1662 1670 def applied(ui, repo, patch=None, **opts):
1663 1671 """print the patches already applied"""
1664 1672 q = repo.mq
1665 1673 if patch:
1666 1674 if patch not in q.series:
1667 1675 raise util.Abort(_("patch %s is not in series file") % patch)
1668 1676 end = q.series.index(patch) + 1
1669 1677 else:
1670 1678 end = q.series_end(True)
1671 1679 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1672 1680
1673 1681 def unapplied(ui, repo, patch=None, **opts):
1674 1682 """print the patches not yet applied"""
1675 1683 q = repo.mq
1676 1684 if patch:
1677 1685 if patch not in q.series:
1678 1686 raise util.Abort(_("patch %s is not in series file") % patch)
1679 1687 start = q.series.index(patch) + 1
1680 1688 else:
1681 1689 start = q.series_end(True)
1682 1690 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1683 1691
1684 1692 def qimport(ui, repo, *filename, **opts):
1685 1693 """import a patch
1686 1694
1687 1695 The patch is inserted into the series after the last applied
1688 1696 patch. If no patches have been applied, qimport prepends the patch
1689 1697 to the series.
1690 1698
1691 1699 The patch will have the same name as its source file unless you
1692 1700 give it a new one with -n/--name.
1693 1701
1694 1702 You can register an existing patch inside the patch directory with
1695 1703 the -e/--existing flag.
1696 1704
1697 1705 With -f/--force, an existing patch of the same name will be
1698 1706 overwritten.
1699 1707
1700 1708 An existing changeset may be placed under mq control with -r/--rev
1701 1709 (e.g. qimport --rev tip -n patch will place tip under mq control).
1702 1710 With -g/--git, patches imported with --rev will use the git diff
1703 1711 format. See the diffs help topic for information on why this is
1704 1712 important for preserving rename/copy information and permission
1705 1713 changes.
1706 1714
1707 1715 To import a patch from standard input, pass - as the patch file.
1708 1716 When importing from standard input, a patch name must be specified
1709 1717 using the --name flag.
1710 1718 """
1711 1719 q = repo.mq
1712 1720 q.qimport(repo, filename, patchname=opts['name'],
1713 1721 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1714 1722 git=opts['git'])
1715 1723 q.save_dirty()
1716 1724
1717 1725 if opts.get('push') and not opts.get('rev'):
1718 1726 return q.push(repo, None)
1719 1727 return 0
1720 1728
1721 1729 def init(ui, repo, **opts):
1722 1730 """init a new queue repository
1723 1731
1724 1732 The queue repository is unversioned by default. If
1725 1733 -c/--create-repo is specified, qinit will create a separate nested
1726 1734 repository for patches (qinit -c may also be run later to convert
1727 1735 an unversioned patch repository into a versioned one). You can use
1728 1736 qcommit to commit changes to this queue repository."""
1729 1737 q = repo.mq
1730 1738 r = q.init(repo, create=opts['create_repo'])
1731 1739 q.save_dirty()
1732 1740 if r:
1733 1741 if not os.path.exists(r.wjoin('.hgignore')):
1734 1742 fp = r.wopener('.hgignore', 'w')
1735 1743 fp.write('^\\.hg\n')
1736 1744 fp.write('^\\.mq\n')
1737 1745 fp.write('syntax: glob\n')
1738 1746 fp.write('status\n')
1739 1747 fp.write('guards\n')
1740 1748 fp.close()
1741 1749 if not os.path.exists(r.wjoin('series')):
1742 1750 r.wopener('series', 'w').close()
1743 1751 r.add(['.hgignore', 'series'])
1744 1752 commands.add(ui, r)
1745 1753 return 0
1746 1754
1747 1755 def clone(ui, source, dest=None, **opts):
1748 1756 '''clone main and patch repository at same time
1749 1757
1750 1758 If source is local, destination will have no patches applied. If
1751 1759 source is remote, this command can not check if patches are
1752 1760 applied in source, so cannot guarantee that patches are not
1753 1761 applied in destination. If you clone remote repository, be sure
1754 1762 before that it has no patches applied.
1755 1763
1756 1764 Source patch repository is looked for in <src>/.hg/patches by
1757 1765 default. Use -p <url> to change.
1758 1766
1759 1767 The patch directory must be a nested Mercurial repository, as
1760 1768 would be created by qinit -c.
1761 1769 '''
1762 1770 def patchdir(repo):
1763 1771 url = repo.url()
1764 1772 if url.endswith('/'):
1765 1773 url = url[:-1]
1766 1774 return url + '/.hg/patches'
1767 1775 if dest is None:
1768 1776 dest = hg.defaultdest(source)
1769 1777 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1770 1778 if opts['patches']:
1771 1779 patchespath = ui.expandpath(opts['patches'])
1772 1780 else:
1773 1781 patchespath = patchdir(sr)
1774 1782 try:
1775 1783 hg.repository(ui, patchespath)
1776 1784 except error.RepoError:
1777 1785 raise util.Abort(_('versioned patch repository not found'
1778 1786 ' (see qinit -c)'))
1779 1787 qbase, destrev = None, None
1780 1788 if sr.local():
1781 1789 if sr.mq.applied:
1782 1790 qbase = bin(sr.mq.applied[0].rev)
1783 1791 if not hg.islocal(dest):
1784 1792 heads = set(sr.heads())
1785 1793 destrev = list(heads.difference(sr.heads(qbase)))
1786 1794 destrev.append(sr.changelog.parents(qbase)[0])
1787 1795 elif sr.capable('lookup'):
1788 1796 try:
1789 1797 qbase = sr.lookup('qbase')
1790 1798 except error.RepoError:
1791 1799 pass
1792 1800 ui.note(_('cloning main repository\n'))
1793 1801 sr, dr = hg.clone(ui, sr.url(), dest,
1794 1802 pull=opts['pull'],
1795 1803 rev=destrev,
1796 1804 update=False,
1797 1805 stream=opts['uncompressed'])
1798 1806 ui.note(_('cloning patch repository\n'))
1799 1807 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1800 1808 pull=opts['pull'], update=not opts['noupdate'],
1801 1809 stream=opts['uncompressed'])
1802 1810 if dr.local():
1803 1811 if qbase:
1804 1812 ui.note(_('stripping applied patches from destination '
1805 1813 'repository\n'))
1806 1814 dr.mq.strip(dr, qbase, update=False, backup=None)
1807 1815 if not opts['noupdate']:
1808 1816 ui.note(_('updating destination repository\n'))
1809 1817 hg.update(dr, dr.changelog.tip())
1810 1818
1811 1819 def commit(ui, repo, *pats, **opts):
1812 1820 """commit changes in the queue repository"""
1813 1821 q = repo.mq
1814 1822 r = q.qrepo()
1815 1823 if not r: raise util.Abort('no queue repository')
1816 1824 commands.commit(r.ui, r, *pats, **opts)
1817 1825
1818 1826 def series(ui, repo, **opts):
1819 1827 """print the entire series file"""
1820 1828 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1821 1829 return 0
1822 1830
1823 1831 def top(ui, repo, **opts):
1824 1832 """print the name of the current patch"""
1825 1833 q = repo.mq
1826 1834 t = q.applied and q.series_end(True) or 0
1827 1835 if t:
1828 1836 return q.qseries(repo, start=t-1, length=1, status='A',
1829 1837 summary=opts.get('summary'))
1830 1838 else:
1831 1839 ui.write(_("no patches applied\n"))
1832 1840 return 1
1833 1841
1834 1842 def next(ui, repo, **opts):
1835 1843 """print the name of the next patch"""
1836 1844 q = repo.mq
1837 1845 end = q.series_end()
1838 1846 if end == len(q.series):
1839 1847 ui.write(_("all patches applied\n"))
1840 1848 return 1
1841 1849 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1842 1850
1843 1851 def prev(ui, repo, **opts):
1844 1852 """print the name of the previous patch"""
1845 1853 q = repo.mq
1846 1854 l = len(q.applied)
1847 1855 if l == 1:
1848 1856 ui.write(_("only one patch applied\n"))
1849 1857 return 1
1850 1858 if not l:
1851 1859 ui.write(_("no patches applied\n"))
1852 1860 return 1
1853 1861 return q.qseries(repo, start=l-2, length=1, status='A',
1854 1862 summary=opts.get('summary'))
1855 1863
1856 1864 def setupheaderopts(ui, opts):
1857 1865 def do(opt, val):
1858 1866 if not opts[opt] and opts['current' + opt]:
1859 1867 opts[opt] = val
1860 1868 do('user', ui.username())
1861 1869 do('date', "%d %d" % util.makedate())
1862 1870
1863 1871 def new(ui, repo, patch, *args, **opts):
1864 1872 """create a new patch
1865 1873
1866 1874 qnew creates a new patch on top of the currently-applied patch (if
1867 1875 any). It will refuse to run if there are any outstanding changes
1868 1876 unless -f/--force is specified, in which case the patch will be
1869 1877 initialized with them. You may also use -I/--include,
1870 1878 -X/--exclude, and/or a list of files after the patch name to add
1871 1879 only changes to matching files to the new patch, leaving the rest
1872 1880 as uncommitted modifications.
1873 1881
1874 1882 -u/--user and -d/--date can be used to set the (given) user and
1875 1883 date, respectively. -U/--currentuser and -D/--currentdate set user
1876 1884 to current user and date to current date.
1877 1885
1878 1886 -e/--edit, -m/--message or -l/--logfile set the patch header as
1879 1887 well as the commit message. If none is specified, the header is
1880 1888 empty and the commit message is '[mq]: PATCH'.
1881 1889
1882 1890 Use the -g/--git option to keep the patch in the git extended diff
1883 1891 format. Read the diffs help topic for more information on why this
1884 1892 is important for preserving permission changes and copy/rename
1885 1893 information.
1886 1894 """
1887 1895 msg = cmdutil.logmessage(opts)
1888 1896 def getmsg(): return ui.edit(msg, ui.username())
1889 1897 q = repo.mq
1890 1898 opts['msg'] = msg
1891 1899 if opts.get('edit'):
1892 1900 opts['msg'] = getmsg
1893 1901 else:
1894 1902 opts['msg'] = msg
1895 1903 setupheaderopts(ui, opts)
1896 1904 q.new(repo, patch, *args, **opts)
1897 1905 q.save_dirty()
1898 1906 return 0
1899 1907
1900 1908 def refresh(ui, repo, *pats, **opts):
1901 1909 """update the current patch
1902 1910
1903 1911 If any file patterns are provided, the refreshed patch will
1904 1912 contain only the modifications that match those patterns; the
1905 1913 remaining modifications will remain in the working directory.
1906 1914
1907 1915 If -s/--short is specified, files currently included in the patch
1908 1916 will be refreshed just like matched files and remain in the patch.
1909 1917
1910 1918 hg add/remove/copy/rename work as usual, though you might want to
1911 1919 use git-style patches (-g/--git or [diff] git=1) to track copies
1912 1920 and renames. See the diffs help topic for more information on the
1913 1921 git diff format.
1914 1922 """
1915 1923 q = repo.mq
1916 1924 message = cmdutil.logmessage(opts)
1917 1925 if opts['edit']:
1918 1926 if not q.applied:
1919 1927 ui.write(_("no patches applied\n"))
1920 1928 return 1
1921 1929 if message:
1922 1930 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1923 1931 patch = q.applied[-1].name
1924 1932 ph = patchheader(q.join(patch))
1925 1933 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1926 1934 setupheaderopts(ui, opts)
1927 1935 ret = q.refresh(repo, pats, msg=message, **opts)
1928 1936 q.save_dirty()
1929 1937 return ret
1930 1938
1931 1939 def diff(ui, repo, *pats, **opts):
1932 1940 """diff of the current patch and subsequent modifications
1933 1941
1934 1942 Shows a diff which includes the current patch as well as any
1935 1943 changes which have been made in the working directory since the
1936 1944 last refresh (thus showing what the current patch would become
1937 1945 after a qrefresh).
1938 1946
1939 1947 Use 'hg diff' if you only want to see the changes made since the
1940 1948 last qrefresh, or 'hg export qtip' if you want to see changes made
1941 1949 by the current patch without including changes made since the
1942 1950 qrefresh.
1943 1951 """
1944 1952 repo.mq.diff(repo, pats, opts)
1945 1953 return 0
1946 1954
1947 1955 def fold(ui, repo, *files, **opts):
1948 1956 """fold the named patches into the current patch
1949 1957
1950 1958 Patches must not yet be applied. Each patch will be successively
1951 1959 applied to the current patch in the order given. If all the
1952 1960 patches apply successfully, the current patch will be refreshed
1953 1961 with the new cumulative patch, and the folded patches will be
1954 1962 deleted. With -k/--keep, the folded patch files will not be
1955 1963 removed afterwards.
1956 1964
1957 1965 The header for each folded patch will be concatenated with the
1958 1966 current patch header, separated by a line of '* * *'."""
1959 1967
1960 1968 q = repo.mq
1961 1969
1962 1970 if not files:
1963 1971 raise util.Abort(_('qfold requires at least one patch name'))
1964 1972 if not q.check_toppatch(repo):
1965 1973 raise util.Abort(_('No patches applied'))
1966 1974 q.check_localchanges(repo)
1967 1975
1968 1976 message = cmdutil.logmessage(opts)
1969 1977 if opts['edit']:
1970 1978 if message:
1971 1979 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1972 1980
1973 1981 parent = q.lookup('qtip')
1974 1982 patches = []
1975 1983 messages = []
1976 1984 for f in files:
1977 1985 p = q.lookup(f)
1978 1986 if p in patches or p == parent:
1979 1987 ui.warn(_('Skipping already folded patch %s') % p)
1980 1988 if q.isapplied(p):
1981 1989 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1982 1990 patches.append(p)
1983 1991
1984 1992 for p in patches:
1985 1993 if not message:
1986 1994 ph = patchheader(q.join(p))
1987 1995 if ph.message:
1988 1996 messages.append(ph.message)
1989 1997 pf = q.join(p)
1990 1998 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1991 1999 if not patchsuccess:
1992 2000 raise util.Abort(_('Error folding patch %s') % p)
1993 2001 patch.updatedir(ui, repo, files)
1994 2002
1995 2003 if not message:
1996 2004 ph = patchheader(q.join(parent))
1997 2005 message, user = ph.message, ph.user
1998 2006 for msg in messages:
1999 2007 message.append('* * *')
2000 2008 message.extend(msg)
2001 2009 message = '\n'.join(message)
2002 2010
2003 2011 if opts['edit']:
2004 2012 message = ui.edit(message, user or ui.username())
2005 2013
2006 2014 q.refresh(repo, msg=message)
2007 2015 q.delete(repo, patches, opts)
2008 2016 q.save_dirty()
2009 2017
2010 2018 def goto(ui, repo, patch, **opts):
2011 2019 '''push or pop patches until named patch is at top of stack'''
2012 2020 q = repo.mq
2013 2021 patch = q.lookup(patch)
2014 2022 if q.isapplied(patch):
2015 2023 ret = q.pop(repo, patch, force=opts['force'])
2016 2024 else:
2017 2025 ret = q.push(repo, patch, force=opts['force'])
2018 2026 q.save_dirty()
2019 2027 return ret
2020 2028
2021 2029 def guard(ui, repo, *args, **opts):
2022 2030 '''set or print guards for a patch
2023 2031
2024 2032 Guards control whether a patch can be pushed. A patch with no
2025 2033 guards is always pushed. A patch with a positive guard ("+foo") is
2026 2034 pushed only if the qselect command has activated it. A patch with
2027 2035 a negative guard ("-foo") is never pushed if the qselect command
2028 2036 has activated it.
2029 2037
2030 2038 With no arguments, print the currently active guards.
2031 2039 With arguments, set guards for the named patch.
2032 2040 NOTE: Specifying negative guards now requires '--'.
2033 2041
2034 2042 To set guards on another patch:
2035 2043 hg qguard -- other.patch +2.6.17 -stable
2036 2044 '''
2037 2045 def status(idx):
2038 2046 guards = q.series_guards[idx] or ['unguarded']
2039 2047 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2040 2048 q = repo.mq
2041 2049 patch = None
2042 2050 args = list(args)
2043 2051 if opts['list']:
2044 2052 if args or opts['none']:
2045 2053 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2046 2054 for i in xrange(len(q.series)):
2047 2055 status(i)
2048 2056 return
2049 2057 if not args or args[0][0:1] in '-+':
2050 2058 if not q.applied:
2051 2059 raise util.Abort(_('no patches applied'))
2052 2060 patch = q.applied[-1].name
2053 2061 if patch is None and args[0][0:1] not in '-+':
2054 2062 patch = args.pop(0)
2055 2063 if patch is None:
2056 2064 raise util.Abort(_('no patch to work with'))
2057 2065 if args or opts['none']:
2058 2066 idx = q.find_series(patch)
2059 2067 if idx is None:
2060 2068 raise util.Abort(_('no patch named %s') % patch)
2061 2069 q.set_guards(idx, args)
2062 2070 q.save_dirty()
2063 2071 else:
2064 2072 status(q.series.index(q.lookup(patch)))
2065 2073
2066 2074 def header(ui, repo, patch=None):
2067 2075 """print the header of the topmost or specified patch"""
2068 2076 q = repo.mq
2069 2077
2070 2078 if patch:
2071 2079 patch = q.lookup(patch)
2072 2080 else:
2073 2081 if not q.applied:
2074 2082 ui.write('no patches applied\n')
2075 2083 return 1
2076 2084 patch = q.lookup('qtip')
2077 2085 ph = patchheader(repo.mq.join(patch))
2078 2086
2079 2087 ui.write('\n'.join(ph.message) + '\n')
2080 2088
2081 2089 def lastsavename(path):
2082 2090 (directory, base) = os.path.split(path)
2083 2091 names = os.listdir(directory)
2084 2092 namere = re.compile("%s.([0-9]+)" % base)
2085 2093 maxindex = None
2086 2094 maxname = None
2087 2095 for f in names:
2088 2096 m = namere.match(f)
2089 2097 if m:
2090 2098 index = int(m.group(1))
2091 2099 if maxindex is None or index > maxindex:
2092 2100 maxindex = index
2093 2101 maxname = f
2094 2102 if maxname:
2095 2103 return (os.path.join(directory, maxname), maxindex)
2096 2104 return (None, None)
2097 2105
2098 2106 def savename(path):
2099 2107 (last, index) = lastsavename(path)
2100 2108 if last is None:
2101 2109 index = 0
2102 2110 newpath = path + ".%d" % (index + 1)
2103 2111 return newpath
2104 2112
2105 2113 def push(ui, repo, patch=None, **opts):
2106 2114 """push the next patch onto the stack
2107 2115
2108 2116 When -f/--force is applied, all local changes in patched files
2109 2117 will be lost.
2110 2118 """
2111 2119 q = repo.mq
2112 2120 mergeq = None
2113 2121
2114 2122 if opts['merge']:
2115 2123 if opts['name']:
2116 2124 newpath = repo.join(opts['name'])
2117 2125 else:
2118 2126 newpath, i = lastsavename(q.path)
2119 2127 if not newpath:
2120 2128 ui.warn(_("no saved queues found, please use -n\n"))
2121 2129 return 1
2122 2130 mergeq = queue(ui, repo.join(""), newpath)
2123 2131 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2124 2132 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2125 2133 mergeq=mergeq, all=opts.get('all'))
2126 2134 return ret
2127 2135
2128 2136 def pop(ui, repo, patch=None, **opts):
2129 2137 """pop the current patch off the stack
2130 2138
2131 2139 By default, pops off the top of the patch stack. If given a patch
2132 2140 name, keeps popping off patches until the named patch is at the
2133 2141 top of the stack.
2134 2142 """
2135 2143 localupdate = True
2136 2144 if opts['name']:
2137 2145 q = queue(ui, repo.join(""), repo.join(opts['name']))
2138 2146 ui.warn(_('using patch queue: %s\n') % q.path)
2139 2147 localupdate = False
2140 2148 else:
2141 2149 q = repo.mq
2142 2150 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2143 2151 all=opts['all'])
2144 2152 q.save_dirty()
2145 2153 return ret
2146 2154
2147 2155 def rename(ui, repo, patch, name=None, **opts):
2148 2156 """rename a patch
2149 2157
2150 2158 With one argument, renames the current patch to PATCH1.
2151 2159 With two arguments, renames PATCH1 to PATCH2."""
2152 2160
2153 2161 q = repo.mq
2154 2162
2155 2163 if not name:
2156 2164 name = patch
2157 2165 patch = None
2158 2166
2159 2167 if patch:
2160 2168 patch = q.lookup(patch)
2161 2169 else:
2162 2170 if not q.applied:
2163 2171 ui.write(_('no patches applied\n'))
2164 2172 return
2165 2173 patch = q.lookup('qtip')
2166 2174 absdest = q.join(name)
2167 2175 if os.path.isdir(absdest):
2168 2176 name = normname(os.path.join(name, os.path.basename(patch)))
2169 2177 absdest = q.join(name)
2170 2178 if os.path.exists(absdest):
2171 2179 raise util.Abort(_('%s already exists') % absdest)
2172 2180
2173 2181 if name in q.series:
2174 2182 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2175 2183
2176 2184 if ui.verbose:
2177 2185 ui.write('renaming %s to %s\n' % (patch, name))
2178 2186 i = q.find_series(patch)
2179 2187 guards = q.guard_re.findall(q.full_series[i])
2180 2188 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2181 2189 q.parse_series()
2182 2190 q.series_dirty = 1
2183 2191
2184 2192 info = q.isapplied(patch)
2185 2193 if info:
2186 2194 q.applied[info[0]] = statusentry(info[1], name)
2187 2195 q.applied_dirty = 1
2188 2196
2189 2197 util.rename(q.join(patch), absdest)
2190 2198 r = q.qrepo()
2191 2199 if r:
2192 2200 wlock = r.wlock()
2193 2201 try:
2194 2202 if r.dirstate[patch] == 'a':
2195 2203 r.dirstate.forget(patch)
2196 2204 r.dirstate.add(name)
2197 2205 else:
2198 2206 if r.dirstate[name] == 'r':
2199 2207 r.undelete([name])
2200 2208 r.copy(patch, name)
2201 2209 r.remove([patch], False)
2202 2210 finally:
2203 2211 wlock.release()
2204 2212
2205 2213 q.save_dirty()
2206 2214
2207 2215 def restore(ui, repo, rev, **opts):
2208 2216 """restore the queue state saved by a revision"""
2209 2217 rev = repo.lookup(rev)
2210 2218 q = repo.mq
2211 2219 q.restore(repo, rev, delete=opts['delete'],
2212 2220 qupdate=opts['update'])
2213 2221 q.save_dirty()
2214 2222 return 0
2215 2223
2216 2224 def save(ui, repo, **opts):
2217 2225 """save current queue state"""
2218 2226 q = repo.mq
2219 2227 message = cmdutil.logmessage(opts)
2220 2228 ret = q.save(repo, msg=message)
2221 2229 if ret:
2222 2230 return ret
2223 2231 q.save_dirty()
2224 2232 if opts['copy']:
2225 2233 path = q.path
2226 2234 if opts['name']:
2227 2235 newpath = os.path.join(q.basepath, opts['name'])
2228 2236 if os.path.exists(newpath):
2229 2237 if not os.path.isdir(newpath):
2230 2238 raise util.Abort(_('destination %s exists and is not '
2231 2239 'a directory') % newpath)
2232 2240 if not opts['force']:
2233 2241 raise util.Abort(_('destination %s exists, '
2234 2242 'use -f to force') % newpath)
2235 2243 else:
2236 2244 newpath = savename(path)
2237 2245 ui.warn(_("copy %s to %s\n") % (path, newpath))
2238 2246 util.copyfiles(path, newpath)
2239 2247 if opts['empty']:
2240 2248 try:
2241 2249 os.unlink(q.join(q.status_path))
2242 2250 except:
2243 2251 pass
2244 2252 return 0
2245 2253
2246 2254 def strip(ui, repo, rev, **opts):
2247 2255 """strip a revision and all its descendants from the repository
2248 2256
2249 2257 If one of the working directory's parent revisions is stripped, the
2250 2258 working directory will be updated to the parent of the stripped
2251 2259 revision.
2252 2260 """
2253 2261 backup = 'all'
2254 2262 if opts['backup']:
2255 2263 backup = 'strip'
2256 2264 elif opts['nobackup']:
2257 2265 backup = 'none'
2258 2266
2259 2267 rev = repo.lookup(rev)
2260 2268 p = repo.dirstate.parents()
2261 2269 cl = repo.changelog
2262 2270 update = True
2263 2271 if p[0] == nullid:
2264 2272 update = False
2265 2273 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2266 2274 update = False
2267 2275 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2268 2276 update = False
2269 2277
2270 2278 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2271 2279 return 0
2272 2280
2273 2281 def select(ui, repo, *args, **opts):
2274 2282 '''set or print guarded patches to push
2275 2283
2276 2284 Use the qguard command to set or print guards on patch, then use
2277 2285 qselect to tell mq which guards to use. A patch will be pushed if
2278 2286 it has no guards or any positive guards match the currently
2279 2287 selected guard, but will not be pushed if any negative guards
2280 2288 match the current guard. For example:
2281 2289
2282 2290 qguard foo.patch -stable (negative guard)
2283 2291 qguard bar.patch +stable (positive guard)
2284 2292 qselect stable
2285 2293
2286 2294 This activates the "stable" guard. mq will skip foo.patch (because
2287 2295 it has a negative match) but push bar.patch (because it has a
2288 2296 positive match).
2289 2297
2290 2298 With no arguments, prints the currently active guards.
2291 2299 With one argument, sets the active guard.
2292 2300
2293 2301 Use -n/--none to deactivate guards (no other arguments needed).
2294 2302 When no guards are active, patches with positive guards are
2295 2303 skipped and patches with negative guards are pushed.
2296 2304
2297 2305 qselect can change the guards on applied patches. It does not pop
2298 2306 guarded patches by default. Use --pop to pop back to the last
2299 2307 applied patch that is not guarded. Use --reapply (which implies
2300 2308 --pop) to push back to the current patch afterwards, but skip
2301 2309 guarded patches.
2302 2310
2303 2311 Use -s/--series to print a list of all guards in the series file
2304 2312 (no other arguments needed). Use -v for more information.'''
2305 2313
2306 2314 q = repo.mq
2307 2315 guards = q.active()
2308 2316 if args or opts['none']:
2309 2317 old_unapplied = q.unapplied(repo)
2310 2318 old_guarded = [i for i in xrange(len(q.applied)) if
2311 2319 not q.pushable(i)[0]]
2312 2320 q.set_active(args)
2313 2321 q.save_dirty()
2314 2322 if not args:
2315 2323 ui.status(_('guards deactivated\n'))
2316 2324 if not opts['pop'] and not opts['reapply']:
2317 2325 unapplied = q.unapplied(repo)
2318 2326 guarded = [i for i in xrange(len(q.applied))
2319 2327 if not q.pushable(i)[0]]
2320 2328 if len(unapplied) != len(old_unapplied):
2321 2329 ui.status(_('number of unguarded, unapplied patches has '
2322 2330 'changed from %d to %d\n') %
2323 2331 (len(old_unapplied), len(unapplied)))
2324 2332 if len(guarded) != len(old_guarded):
2325 2333 ui.status(_('number of guarded, applied patches has changed '
2326 2334 'from %d to %d\n') %
2327 2335 (len(old_guarded), len(guarded)))
2328 2336 elif opts['series']:
2329 2337 guards = {}
2330 2338 noguards = 0
2331 2339 for gs in q.series_guards:
2332 2340 if not gs:
2333 2341 noguards += 1
2334 2342 for g in gs:
2335 2343 guards.setdefault(g, 0)
2336 2344 guards[g] += 1
2337 2345 if ui.verbose:
2338 2346 guards['NONE'] = noguards
2339 2347 guards = guards.items()
2340 2348 guards.sort(key=lambda x: x[0][1:])
2341 2349 if guards:
2342 2350 ui.note(_('guards in series file:\n'))
2343 2351 for guard, count in guards:
2344 2352 ui.note('%2d ' % count)
2345 2353 ui.write(guard, '\n')
2346 2354 else:
2347 2355 ui.note(_('no guards in series file\n'))
2348 2356 else:
2349 2357 if guards:
2350 2358 ui.note(_('active guards:\n'))
2351 2359 for g in guards:
2352 2360 ui.write(g, '\n')
2353 2361 else:
2354 2362 ui.write(_('no active guards\n'))
2355 2363 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2356 2364 popped = False
2357 2365 if opts['pop'] or opts['reapply']:
2358 2366 for i in xrange(len(q.applied)):
2359 2367 pushable, reason = q.pushable(i)
2360 2368 if not pushable:
2361 2369 ui.status(_('popping guarded patches\n'))
2362 2370 popped = True
2363 2371 if i == 0:
2364 2372 q.pop(repo, all=True)
2365 2373 else:
2366 2374 q.pop(repo, i-1)
2367 2375 break
2368 2376 if popped:
2369 2377 try:
2370 2378 if reapply:
2371 2379 ui.status(_('reapplying unguarded patches\n'))
2372 2380 q.push(repo, reapply)
2373 2381 finally:
2374 2382 q.save_dirty()
2375 2383
2376 2384 def finish(ui, repo, *revrange, **opts):
2377 2385 """move applied patches into repository history
2378 2386
2379 2387 Finishes the specified revisions (corresponding to applied
2380 2388 patches) by moving them out of mq control into regular repository
2381 2389 history.
2382 2390
2383 2391 Accepts a revision range or the -a/--applied option. If --applied
2384 2392 is specified, all applied mq revisions are removed from mq
2385 2393 control. Otherwise, the given revisions must be at the base of the
2386 2394 stack of applied patches.
2387 2395
2388 2396 This can be especially useful if your changes have been applied to
2389 2397 an upstream repository, or if you are about to push your changes
2390 2398 to upstream.
2391 2399 """
2392 2400 if not opts['applied'] and not revrange:
2393 2401 raise util.Abort(_('no revisions specified'))
2394 2402 elif opts['applied']:
2395 2403 revrange = ('qbase:qtip',) + revrange
2396 2404
2397 2405 q = repo.mq
2398 2406 if not q.applied:
2399 2407 ui.status(_('no patches applied\n'))
2400 2408 return 0
2401 2409
2402 2410 revs = cmdutil.revrange(repo, revrange)
2403 2411 q.finish(repo, revs)
2404 2412 q.save_dirty()
2405 2413 return 0
2406 2414
2407 2415 def reposetup(ui, repo):
2408 2416 class mqrepo(repo.__class__):
2409 2417 @util.propertycache
2410 2418 def mq(self):
2411 2419 return queue(self.ui, self.join(""))
2412 2420
2413 2421 def abort_if_wdir_patched(self, errmsg, force=False):
2414 2422 if self.mq.applied and not force:
2415 2423 parent = hex(self.dirstate.parents()[0])
2416 2424 if parent in [s.rev for s in self.mq.applied]:
2417 2425 raise util.Abort(errmsg)
2418 2426
2419 2427 def commit(self, text="", user=None, date=None, match=None,
2420 2428 force=False, editor=False, extra={}):
2421 2429 self.abort_if_wdir_patched(
2422 2430 _('cannot commit over an applied mq patch'),
2423 2431 force)
2424 2432
2425 2433 return super(mqrepo, self).commit(text, user, date, match, force,
2426 2434 editor, extra)
2427 2435
2428 2436 def push(self, remote, force=False, revs=None):
2429 2437 if self.mq.applied and not force and not revs:
2430 2438 raise util.Abort(_('source has mq patches applied'))
2431 2439 return super(mqrepo, self).push(remote, force, revs)
2432 2440
2433 2441 def _findtags(self):
2434 2442 '''augment tags from base class with patch tags'''
2435 2443 result = super(mqrepo, self)._findtags()
2436 2444
2437 2445 q = self.mq
2438 2446 if not q.applied:
2439 2447 return result
2440 2448
2441 2449 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2442 2450
2443 2451 if mqtags[-1][0] not in self.changelog.nodemap:
2444 2452 self.ui.warn(_('mq status file refers to unknown node %s\n')
2445 2453 % short(mqtags[-1][0]))
2446 2454 return result
2447 2455
2448 2456 mqtags.append((mqtags[-1][0], 'qtip'))
2449 2457 mqtags.append((mqtags[0][0], 'qbase'))
2450 2458 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2451 2459 tags = result[0]
2452 2460 for patch in mqtags:
2453 2461 if patch[1] in tags:
2454 2462 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2455 2463 % patch[1])
2456 2464 else:
2457 2465 tags[patch[1]] = patch[0]
2458 2466
2459 2467 return result
2460 2468
2461 2469 def _branchtags(self, partial, lrev):
2462 2470 q = self.mq
2463 2471 if not q.applied:
2464 2472 return super(mqrepo, self)._branchtags(partial, lrev)
2465 2473
2466 2474 cl = self.changelog
2467 2475 qbasenode = bin(q.applied[0].rev)
2468 2476 if qbasenode not in cl.nodemap:
2469 2477 self.ui.warn(_('mq status file refers to unknown node %s\n')
2470 2478 % short(qbasenode))
2471 2479 return super(mqrepo, self)._branchtags(partial, lrev)
2472 2480
2473 2481 qbase = cl.rev(qbasenode)
2474 2482 start = lrev + 1
2475 2483 if start < qbase:
2476 2484 # update the cache (excluding the patches) and save it
2477 2485 self._updatebranchcache(partial, lrev+1, qbase)
2478 2486 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2479 2487 start = qbase
2480 2488 # if start = qbase, the cache is as updated as it should be.
2481 2489 # if start > qbase, the cache includes (part of) the patches.
2482 2490 # we might as well use it, but we won't save it.
2483 2491
2484 2492 # update the cache up to the tip
2485 2493 self._updatebranchcache(partial, start, len(cl))
2486 2494
2487 2495 return partial
2488 2496
2489 2497 if repo.local():
2490 2498 repo.__class__ = mqrepo
2491 2499
2492 2500 def mqimport(orig, ui, repo, *args, **kwargs):
2493 2501 if hasattr(repo, 'abort_if_wdir_patched'):
2494 2502 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2495 2503 kwargs.get('force'))
2496 2504 return orig(ui, repo, *args, **kwargs)
2497 2505
2498 2506 def uisetup(ui):
2499 2507 extensions.wrapcommand(commands.table, 'import', mqimport)
2500 2508
2501 2509 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2502 2510
2503 2511 cmdtable = {
2504 2512 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2505 2513 "qclone":
2506 2514 (clone,
2507 2515 [('', 'pull', None, _('use pull protocol to copy metadata')),
2508 2516 ('U', 'noupdate', None, _('do not update the new working directories')),
2509 2517 ('', 'uncompressed', None,
2510 2518 _('use uncompressed transfer (fast over LAN)')),
2511 2519 ('p', 'patches', '', _('location of source patch repository')),
2512 2520 ] + commands.remoteopts,
2513 2521 _('hg qclone [OPTION]... SOURCE [DEST]')),
2514 2522 "qcommit|qci":
2515 2523 (commit,
2516 2524 commands.table["^commit|ci"][1],
2517 2525 _('hg qcommit [OPTION]... [FILE]...')),
2518 2526 "^qdiff":
2519 2527 (diff,
2520 2528 commands.diffopts + commands.diffopts2 + commands.walkopts,
2521 2529 _('hg qdiff [OPTION]... [FILE]...')),
2522 2530 "qdelete|qremove|qrm":
2523 2531 (delete,
2524 2532 [('k', 'keep', None, _('keep patch file')),
2525 2533 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2526 2534 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2527 2535 'qfold':
2528 2536 (fold,
2529 2537 [('e', 'edit', None, _('edit patch header')),
2530 2538 ('k', 'keep', None, _('keep folded patch files')),
2531 2539 ] + commands.commitopts,
2532 2540 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2533 2541 'qgoto':
2534 2542 (goto,
2535 2543 [('f', 'force', None, _('overwrite any local changes'))],
2536 2544 _('hg qgoto [OPTION]... PATCH')),
2537 2545 'qguard':
2538 2546 (guard,
2539 2547 [('l', 'list', None, _('list all patches and guards')),
2540 2548 ('n', 'none', None, _('drop all guards'))],
2541 2549 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2542 2550 'qheader': (header, [], _('hg qheader [PATCH]')),
2543 2551 "^qimport":
2544 2552 (qimport,
2545 2553 [('e', 'existing', None, _('import file in patch directory')),
2546 2554 ('n', 'name', '', _('name of patch file')),
2547 2555 ('f', 'force', None, _('overwrite existing files')),
2548 2556 ('r', 'rev', [], _('place existing revisions under mq control')),
2549 2557 ('g', 'git', None, _('use git extended diff format')),
2550 2558 ('P', 'push', None, _('qpush after importing'))],
2551 2559 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2552 2560 "^qinit":
2553 2561 (init,
2554 2562 [('c', 'create-repo', None, _('create queue repository'))],
2555 2563 _('hg qinit [-c]')),
2556 2564 "qnew":
2557 2565 (new,
2558 2566 [('e', 'edit', None, _('edit commit message')),
2559 2567 ('f', 'force', None, _('import uncommitted changes into patch')),
2560 2568 ('g', 'git', None, _('use git extended diff format')),
2561 2569 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2562 2570 ('u', 'user', '', _('add "From: <given user>" to patch')),
2563 2571 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2564 2572 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2565 2573 ] + commands.walkopts + commands.commitopts,
2566 2574 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2567 2575 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2568 2576 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2569 2577 "^qpop":
2570 2578 (pop,
2571 2579 [('a', 'all', None, _('pop all patches')),
2572 2580 ('n', 'name', '', _('queue name to pop')),
2573 2581 ('f', 'force', None, _('forget any local changes'))],
2574 2582 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2575 2583 "^qpush":
2576 2584 (push,
2577 2585 [('f', 'force', None, _('apply if the patch has rejects')),
2578 2586 ('l', 'list', None, _('list patch name in commit text')),
2579 2587 ('a', 'all', None, _('apply all patches')),
2580 2588 ('m', 'merge', None, _('merge from another queue')),
2581 2589 ('n', 'name', '', _('merge queue name'))],
2582 2590 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2583 2591 "^qrefresh":
2584 2592 (refresh,
2585 2593 [('e', 'edit', None, _('edit commit message')),
2586 2594 ('g', 'git', None, _('use git extended diff format')),
2587 2595 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2588 2596 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2589 2597 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2590 2598 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2591 2599 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2592 2600 ] + commands.walkopts + commands.commitopts,
2593 2601 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2594 2602 'qrename|qmv':
2595 2603 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2596 2604 "qrestore":
2597 2605 (restore,
2598 2606 [('d', 'delete', None, _('delete save entry')),
2599 2607 ('u', 'update', None, _('update queue working directory'))],
2600 2608 _('hg qrestore [-d] [-u] REV')),
2601 2609 "qsave":
2602 2610 (save,
2603 2611 [('c', 'copy', None, _('copy patch directory')),
2604 2612 ('n', 'name', '', _('copy directory name')),
2605 2613 ('e', 'empty', None, _('clear queue status file')),
2606 2614 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2607 2615 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2608 2616 "qselect":
2609 2617 (select,
2610 2618 [('n', 'none', None, _('disable all guards')),
2611 2619 ('s', 'series', None, _('list all guards in series file')),
2612 2620 ('', 'pop', None, _('pop to before first guarded applied patch')),
2613 2621 ('', 'reapply', None, _('pop, then reapply patches'))],
2614 2622 _('hg qselect [OPTION]... [GUARD]...')),
2615 2623 "qseries":
2616 2624 (series,
2617 2625 [('m', 'missing', None, _('print patches not in series')),
2618 2626 ] + seriesopts,
2619 2627 _('hg qseries [-ms]')),
2620 2628 "^strip":
2621 2629 (strip,
2622 2630 [('f', 'force', None, _('force removal with local changes')),
2623 2631 ('b', 'backup', None, _('bundle unrelated changesets')),
2624 2632 ('n', 'nobackup', None, _('no backups'))],
2625 2633 _('hg strip [-f] [-b] [-n] REV')),
2626 2634 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2627 2635 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2628 2636 "qfinish":
2629 2637 (finish,
2630 2638 [('a', 'applied', None, _('finish all applied changesets'))],
2631 2639 _('hg qfinish [-a] [REV]...')),
2632 2640 }
General Comments 0
You need to be logged in to leave comments. Login now