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