##// END OF EJS Templates
mq: explicit exit code when patch is already on top
Gilles Moris -
r11439:778377be stable
parent child Browse files
Show More
@@ -1,2998 +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 return
1020 return 0
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 1049 index = self.series.index(patch, start)
1050 1050 fullpatch = self.full_series[index]
1051 1051 del self.full_series[index]
1052 1052 except ValueError:
1053 1053 raise util.Abort(_("patch '%s' not found") % patch)
1054 1054 self.full_series.insert(start, fullpatch)
1055 1055 self.parse_series()
1056 1056 self.series_dirty = 1
1057 1057
1058 1058 self.applied_dirty = 1
1059 1059 if start > 0:
1060 1060 self.check_toppatch(repo)
1061 1061 if not patch:
1062 1062 patch = self.series[start]
1063 1063 end = start + 1
1064 1064 else:
1065 1065 end = self.series.index(patch, start) + 1
1066 1066
1067 1067 s = self.series[start:end]
1068 1068 all_files = set()
1069 1069 try:
1070 1070 if mergeq:
1071 1071 ret = self.mergepatch(repo, mergeq, s, diffopts)
1072 1072 else:
1073 1073 ret = self.apply(repo, s, list, all_files=all_files)
1074 1074 except:
1075 1075 self.ui.warn(_('cleaning up working directory...'))
1076 1076 node = repo.dirstate.parents()[0]
1077 1077 hg.revert(repo, node, None)
1078 1078 # only remove unknown files that we know we touched or
1079 1079 # created while patching
1080 1080 for f in all_files:
1081 1081 if f not in repo.dirstate:
1082 1082 try:
1083 1083 util.unlink(repo.wjoin(f))
1084 1084 except OSError, inst:
1085 1085 if inst.errno != errno.ENOENT:
1086 1086 raise
1087 1087 self.ui.warn(_('done\n'))
1088 1088 raise
1089 1089
1090 1090 if not self.applied:
1091 1091 return ret[0]
1092 1092 top = self.applied[-1].name
1093 1093 if ret[0] and ret[0] > 1:
1094 1094 msg = _("errors during apply, please fix and refresh %s\n")
1095 1095 self.ui.write(msg % top)
1096 1096 else:
1097 1097 self.ui.write(_("now at: %s\n") % top)
1098 1098 return ret[0]
1099 1099
1100 1100 finally:
1101 1101 wlock.release()
1102 1102
1103 1103 def pop(self, repo, patch=None, force=False, update=True, all=False):
1104 1104 wlock = repo.wlock()
1105 1105 try:
1106 1106 if patch:
1107 1107 # index, rev, patch
1108 1108 info = self.isapplied(patch)
1109 1109 if not info:
1110 1110 patch = self.lookup(patch)
1111 1111 info = self.isapplied(patch)
1112 1112 if not info:
1113 1113 raise util.Abort(_("patch %s is not applied") % patch)
1114 1114
1115 1115 if not self.applied:
1116 1116 # Allow qpop -a to work repeatedly,
1117 1117 # but not qpop without an argument
1118 1118 self.ui.warn(_("no patches applied\n"))
1119 1119 return not all
1120 1120
1121 1121 if all:
1122 1122 start = 0
1123 1123 elif patch:
1124 1124 start = info[0] + 1
1125 1125 else:
1126 1126 start = len(self.applied) - 1
1127 1127
1128 1128 if start >= len(self.applied):
1129 1129 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1130 1130 return
1131 1131
1132 1132 if not update:
1133 1133 parents = repo.dirstate.parents()
1134 1134 rr = [x.node for x in self.applied]
1135 1135 for p in parents:
1136 1136 if p in rr:
1137 1137 self.ui.warn(_("qpop: forcing dirstate update\n"))
1138 1138 update = True
1139 1139 else:
1140 1140 parents = [p.node() for p in repo[None].parents()]
1141 1141 needupdate = False
1142 1142 for entry in self.applied[start:]:
1143 1143 if entry.node in parents:
1144 1144 needupdate = True
1145 1145 break
1146 1146 update = needupdate
1147 1147
1148 1148 if not force and update:
1149 1149 self.check_localchanges(repo)
1150 1150
1151 1151 self.applied_dirty = 1
1152 1152 end = len(self.applied)
1153 1153 rev = self.applied[start].node
1154 1154 if update:
1155 1155 top = self.check_toppatch(repo)[0]
1156 1156
1157 1157 try:
1158 1158 heads = repo.changelog.heads(rev)
1159 1159 except error.LookupError:
1160 1160 node = short(rev)
1161 1161 raise util.Abort(_('trying to pop unknown node %s') % node)
1162 1162
1163 1163 if heads != [self.applied[-1].node]:
1164 1164 raise util.Abort(_("popping would remove a revision not "
1165 1165 "managed by this patch queue"))
1166 1166
1167 1167 # we know there are no local changes, so we can make a simplified
1168 1168 # form of hg.update.
1169 1169 if update:
1170 1170 qp = self.qparents(repo, rev)
1171 1171 ctx = repo[qp]
1172 1172 m, a, r, d = repo.status(qp, top)[:4]
1173 1173 if d:
1174 1174 raise util.Abort(_("deletions found between repo revs"))
1175 1175 for f in a:
1176 1176 try:
1177 1177 util.unlink(repo.wjoin(f))
1178 1178 except OSError, e:
1179 1179 if e.errno != errno.ENOENT:
1180 1180 raise
1181 1181 repo.dirstate.forget(f)
1182 1182 for f in m + r:
1183 1183 fctx = ctx[f]
1184 1184 repo.wwrite(f, fctx.data(), fctx.flags())
1185 1185 repo.dirstate.normal(f)
1186 1186 repo.dirstate.setparents(qp, nullid)
1187 1187 for patch in reversed(self.applied[start:end]):
1188 1188 self.ui.status(_("popping %s\n") % patch.name)
1189 1189 del self.applied[start:end]
1190 1190 self.strip(repo, rev, update=False, backup='strip')
1191 1191 if self.applied:
1192 1192 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1193 1193 else:
1194 1194 self.ui.write(_("patch queue now empty\n"))
1195 1195 finally:
1196 1196 wlock.release()
1197 1197
1198 1198 def diff(self, repo, pats, opts):
1199 1199 top, patch = self.check_toppatch(repo)
1200 1200 if not top:
1201 1201 self.ui.write(_("no patches applied\n"))
1202 1202 return
1203 1203 qp = self.qparents(repo, top)
1204 1204 if opts.get('reverse'):
1205 1205 node1, node2 = None, qp
1206 1206 else:
1207 1207 node1, node2 = qp, None
1208 1208 diffopts = self.diffopts(opts, patch)
1209 1209 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1210 1210
1211 1211 def refresh(self, repo, pats=None, **opts):
1212 1212 if not self.applied:
1213 1213 self.ui.write(_("no patches applied\n"))
1214 1214 return 1
1215 1215 msg = opts.get('msg', '').rstrip()
1216 1216 newuser = opts.get('user')
1217 1217 newdate = opts.get('date')
1218 1218 if newdate:
1219 1219 newdate = '%d %d' % util.parsedate(newdate)
1220 1220 wlock = repo.wlock()
1221 1221
1222 1222 try:
1223 1223 self.check_toppatch(repo)
1224 1224 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1225 1225 if repo.changelog.heads(top) != [top]:
1226 1226 raise util.Abort(_("cannot refresh a revision with children"))
1227 1227
1228 1228 cparents = repo.changelog.parents(top)
1229 1229 patchparent = self.qparents(repo, top)
1230 1230 ph = patchheader(self.join(patchfn), self.plainmode)
1231 1231 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1232 1232 if msg:
1233 1233 ph.setmessage(msg)
1234 1234 if newuser:
1235 1235 ph.setuser(newuser)
1236 1236 if newdate:
1237 1237 ph.setdate(newdate)
1238 1238 ph.setparent(hex(patchparent))
1239 1239
1240 1240 # only commit new patch when write is complete
1241 1241 patchf = self.opener(patchfn, 'w', atomictemp=True)
1242 1242
1243 1243 comments = str(ph)
1244 1244 if comments:
1245 1245 patchf.write(comments)
1246 1246
1247 1247 # update the dirstate in place, strip off the qtip commit
1248 1248 # and then commit.
1249 1249 #
1250 1250 # this should really read:
1251 1251 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1252 1252 # but we do it backwards to take advantage of manifest/chlog
1253 1253 # caching against the next repo.status call
1254 1254 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1255 1255 changes = repo.changelog.read(top)
1256 1256 man = repo.manifest.read(changes[0])
1257 1257 aaa = aa[:]
1258 1258 matchfn = cmdutil.match(repo, pats, opts)
1259 1259 # in short mode, we only diff the files included in the
1260 1260 # patch already plus specified files
1261 1261 if opts.get('short'):
1262 1262 # if amending a patch, we start with existing
1263 1263 # files plus specified files - unfiltered
1264 1264 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1265 1265 # filter with inc/exl options
1266 1266 matchfn = cmdutil.match(repo, opts=opts)
1267 1267 else:
1268 1268 match = cmdutil.matchall(repo)
1269 1269 m, a, r, d = repo.status(match=match)[:4]
1270 1270
1271 1271 # we might end up with files that were added between
1272 1272 # qtip and the dirstate parent, but then changed in the
1273 1273 # local dirstate. in this case, we want them to only
1274 1274 # show up in the added section
1275 1275 for x in m:
1276 1276 if x not in aa:
1277 1277 mm.append(x)
1278 1278 # we might end up with files added by the local dirstate that
1279 1279 # were deleted by the patch. In this case, they should only
1280 1280 # show up in the changed section.
1281 1281 for x in a:
1282 1282 if x in dd:
1283 1283 del dd[dd.index(x)]
1284 1284 mm.append(x)
1285 1285 else:
1286 1286 aa.append(x)
1287 1287 # make sure any files deleted in the local dirstate
1288 1288 # are not in the add or change column of the patch
1289 1289 forget = []
1290 1290 for x in d + r:
1291 1291 if x in aa:
1292 1292 del aa[aa.index(x)]
1293 1293 forget.append(x)
1294 1294 continue
1295 1295 elif x in mm:
1296 1296 del mm[mm.index(x)]
1297 1297 dd.append(x)
1298 1298
1299 1299 m = list(set(mm))
1300 1300 r = list(set(dd))
1301 1301 a = list(set(aa))
1302 1302 c = [filter(matchfn, l) for l in (m, a, r)]
1303 1303 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1304 1304 chunks = patch.diff(repo, patchparent, match=match,
1305 1305 changes=c, opts=diffopts)
1306 1306 for chunk in chunks:
1307 1307 patchf.write(chunk)
1308 1308
1309 1309 try:
1310 1310 if diffopts.git or diffopts.upgrade:
1311 1311 copies = {}
1312 1312 for dst in a:
1313 1313 src = repo.dirstate.copied(dst)
1314 1314 # during qfold, the source file for copies may
1315 1315 # be removed. Treat this as a simple add.
1316 1316 if src is not None and src in repo.dirstate:
1317 1317 copies.setdefault(src, []).append(dst)
1318 1318 repo.dirstate.add(dst)
1319 1319 # remember the copies between patchparent and qtip
1320 1320 for dst in aaa:
1321 1321 f = repo.file(dst)
1322 1322 src = f.renamed(man[dst])
1323 1323 if src:
1324 1324 copies.setdefault(src[0], []).extend(
1325 1325 copies.get(dst, []))
1326 1326 if dst in a:
1327 1327 copies[src[0]].append(dst)
1328 1328 # we can't copy a file created by the patch itself
1329 1329 if dst in copies:
1330 1330 del copies[dst]
1331 1331 for src, dsts in copies.iteritems():
1332 1332 for dst in dsts:
1333 1333 repo.dirstate.copy(src, dst)
1334 1334 else:
1335 1335 for dst in a:
1336 1336 repo.dirstate.add(dst)
1337 1337 # Drop useless copy information
1338 1338 for f in list(repo.dirstate.copies()):
1339 1339 repo.dirstate.copy(None, f)
1340 1340 for f in r:
1341 1341 repo.dirstate.remove(f)
1342 1342 # if the patch excludes a modified file, mark that
1343 1343 # file with mtime=0 so status can see it.
1344 1344 mm = []
1345 1345 for i in xrange(len(m)-1, -1, -1):
1346 1346 if not matchfn(m[i]):
1347 1347 mm.append(m[i])
1348 1348 del m[i]
1349 1349 for f in m:
1350 1350 repo.dirstate.normal(f)
1351 1351 for f in mm:
1352 1352 repo.dirstate.normallookup(f)
1353 1353 for f in forget:
1354 1354 repo.dirstate.forget(f)
1355 1355
1356 1356 if not msg:
1357 1357 if not ph.message:
1358 1358 message = "[mq]: %s\n" % patchfn
1359 1359 else:
1360 1360 message = "\n".join(ph.message)
1361 1361 else:
1362 1362 message = msg
1363 1363
1364 1364 user = ph.user or changes[1]
1365 1365
1366 1366 # assumes strip can roll itself back if interrupted
1367 1367 repo.dirstate.setparents(*cparents)
1368 1368 self.applied.pop()
1369 1369 self.applied_dirty = 1
1370 1370 self.strip(repo, top, update=False,
1371 1371 backup='strip')
1372 1372 except:
1373 1373 repo.dirstate.invalidate()
1374 1374 raise
1375 1375
1376 1376 try:
1377 1377 # might be nice to attempt to roll back strip after this
1378 1378 patchf.rename()
1379 1379 n = repo.commit(message, user, ph.date, match=match,
1380 1380 force=True)
1381 1381 self.applied.append(statusentry(n, patchfn))
1382 1382 except:
1383 1383 ctx = repo[cparents[0]]
1384 1384 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1385 1385 self.save_dirty()
1386 1386 self.ui.warn(_('refresh interrupted while patch was popped! '
1387 1387 '(revert --all, qpush to recover)\n'))
1388 1388 raise
1389 1389 finally:
1390 1390 wlock.release()
1391 1391 self.removeundo(repo)
1392 1392
1393 1393 def init(self, repo, create=False):
1394 1394 if not create and os.path.isdir(self.path):
1395 1395 raise util.Abort(_("patch queue directory already exists"))
1396 1396 try:
1397 1397 os.mkdir(self.path)
1398 1398 except OSError, inst:
1399 1399 if inst.errno != errno.EEXIST or not create:
1400 1400 raise
1401 1401 if create:
1402 1402 return self.qrepo(create=True)
1403 1403
1404 1404 def unapplied(self, repo, patch=None):
1405 1405 if patch and patch not in self.series:
1406 1406 raise util.Abort(_("patch %s is not in series file") % patch)
1407 1407 if not patch:
1408 1408 start = self.series_end()
1409 1409 else:
1410 1410 start = self.series.index(patch) + 1
1411 1411 unapplied = []
1412 1412 for i in xrange(start, len(self.series)):
1413 1413 pushable, reason = self.pushable(i)
1414 1414 if pushable:
1415 1415 unapplied.append((i, self.series[i]))
1416 1416 self.explain_pushable(i)
1417 1417 return unapplied
1418 1418
1419 1419 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1420 1420 summary=False):
1421 1421 def displayname(pfx, patchname, state):
1422 1422 if pfx:
1423 1423 self.ui.write(pfx)
1424 1424 if summary:
1425 1425 ph = patchheader(self.join(patchname), self.plainmode)
1426 1426 msg = ph.message and ph.message[0] or ''
1427 1427 if self.ui.formatted():
1428 1428 width = util.termwidth() - len(pfx) - len(patchname) - 2
1429 1429 if width > 0:
1430 1430 msg = util.ellipsis(msg, width)
1431 1431 else:
1432 1432 msg = ''
1433 1433 self.ui.write(patchname, label='qseries.' + state)
1434 1434 self.ui.write(': ')
1435 1435 self.ui.write(msg, label='qseries.message.' + state)
1436 1436 else:
1437 1437 self.ui.write(patchname, label='qseries.' + state)
1438 1438 self.ui.write('\n')
1439 1439
1440 1440 applied = set([p.name for p in self.applied])
1441 1441 if length is None:
1442 1442 length = len(self.series) - start
1443 1443 if not missing:
1444 1444 if self.ui.verbose:
1445 1445 idxwidth = len(str(start + length - 1))
1446 1446 for i in xrange(start, start + length):
1447 1447 patch = self.series[i]
1448 1448 if patch in applied:
1449 1449 char, state = 'A', 'applied'
1450 1450 elif self.pushable(i)[0]:
1451 1451 char, state = 'U', 'unapplied'
1452 1452 else:
1453 1453 char, state = 'G', 'guarded'
1454 1454 pfx = ''
1455 1455 if self.ui.verbose:
1456 1456 pfx = '%*d %s ' % (idxwidth, i, char)
1457 1457 elif status and status != char:
1458 1458 continue
1459 1459 displayname(pfx, patch, state)
1460 1460 else:
1461 1461 msng_list = []
1462 1462 for root, dirs, files in os.walk(self.path):
1463 1463 d = root[len(self.path) + 1:]
1464 1464 for f in files:
1465 1465 fl = os.path.join(d, f)
1466 1466 if (fl not in self.series and
1467 1467 fl not in (self.status_path, self.series_path,
1468 1468 self.guards_path)
1469 1469 and not fl.startswith('.')):
1470 1470 msng_list.append(fl)
1471 1471 for x in sorted(msng_list):
1472 1472 pfx = self.ui.verbose and ('D ') or ''
1473 1473 displayname(pfx, x, 'missing')
1474 1474
1475 1475 def issaveline(self, l):
1476 1476 if l.name == '.hg.patches.save.line':
1477 1477 return True
1478 1478
1479 1479 def qrepo(self, create=False):
1480 1480 if create or os.path.isdir(self.join(".hg")):
1481 1481 return hg.repository(self.ui, path=self.path, create=create)
1482 1482
1483 1483 def restore(self, repo, rev, delete=None, qupdate=None):
1484 1484 desc = repo[rev].description().strip()
1485 1485 lines = desc.splitlines()
1486 1486 i = 0
1487 1487 datastart = None
1488 1488 series = []
1489 1489 applied = []
1490 1490 qpp = None
1491 1491 for i, line in enumerate(lines):
1492 1492 if line == 'Patch Data:':
1493 1493 datastart = i + 1
1494 1494 elif line.startswith('Dirstate:'):
1495 1495 l = line.rstrip()
1496 1496 l = l[10:].split(' ')
1497 1497 qpp = [bin(x) for x in l]
1498 1498 elif datastart != None:
1499 1499 l = line.rstrip()
1500 1500 n, name = l.split(':', 1)
1501 1501 if n:
1502 1502 applied.append(statusentry(bin(n), name))
1503 1503 else:
1504 1504 series.append(l)
1505 1505 if datastart is None:
1506 1506 self.ui.warn(_("No saved patch data found\n"))
1507 1507 return 1
1508 1508 self.ui.warn(_("restoring status: %s\n") % lines[0])
1509 1509 self.full_series = series
1510 1510 self.applied = applied
1511 1511 self.parse_series()
1512 1512 self.series_dirty = 1
1513 1513 self.applied_dirty = 1
1514 1514 heads = repo.changelog.heads()
1515 1515 if delete:
1516 1516 if rev not in heads:
1517 1517 self.ui.warn(_("save entry has children, leaving it alone\n"))
1518 1518 else:
1519 1519 self.ui.warn(_("removing save entry %s\n") % short(rev))
1520 1520 pp = repo.dirstate.parents()
1521 1521 if rev in pp:
1522 1522 update = True
1523 1523 else:
1524 1524 update = False
1525 1525 self.strip(repo, rev, update=update, backup='strip')
1526 1526 if qpp:
1527 1527 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1528 1528 (short(qpp[0]), short(qpp[1])))
1529 1529 if qupdate:
1530 1530 self.ui.status(_("queue directory updating\n"))
1531 1531 r = self.qrepo()
1532 1532 if not r:
1533 1533 self.ui.warn(_("Unable to load queue repository\n"))
1534 1534 return 1
1535 1535 hg.clean(r, qpp[0])
1536 1536
1537 1537 def save(self, repo, msg=None):
1538 1538 if not self.applied:
1539 1539 self.ui.warn(_("save: no patches applied, exiting\n"))
1540 1540 return 1
1541 1541 if self.issaveline(self.applied[-1]):
1542 1542 self.ui.warn(_("status is already saved\n"))
1543 1543 return 1
1544 1544
1545 1545 if not msg:
1546 1546 msg = _("hg patches saved state")
1547 1547 else:
1548 1548 msg = "hg patches: " + msg.rstrip('\r\n')
1549 1549 r = self.qrepo()
1550 1550 if r:
1551 1551 pp = r.dirstate.parents()
1552 1552 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1553 1553 msg += "\n\nPatch Data:\n"
1554 1554 msg += ''.join('%s\n' % x for x in self.applied)
1555 1555 msg += ''.join(':%s\n' % x for x in self.full_series)
1556 1556 n = repo.commit(msg, force=True)
1557 1557 if not n:
1558 1558 self.ui.warn(_("repo commit failed\n"))
1559 1559 return 1
1560 1560 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1561 1561 self.applied_dirty = 1
1562 1562 self.removeundo(repo)
1563 1563
1564 1564 def full_series_end(self):
1565 1565 if self.applied:
1566 1566 p = self.applied[-1].name
1567 1567 end = self.find_series(p)
1568 1568 if end is None:
1569 1569 return len(self.full_series)
1570 1570 return end + 1
1571 1571 return 0
1572 1572
1573 1573 def series_end(self, all_patches=False):
1574 1574 """If all_patches is False, return the index of the next pushable patch
1575 1575 in the series, or the series length. If all_patches is True, return the
1576 1576 index of the first patch past the last applied one.
1577 1577 """
1578 1578 end = 0
1579 1579 def next(start):
1580 1580 if all_patches or start >= len(self.series):
1581 1581 return start
1582 1582 for i in xrange(start, len(self.series)):
1583 1583 p, reason = self.pushable(i)
1584 1584 if p:
1585 1585 break
1586 1586 self.explain_pushable(i)
1587 1587 return i
1588 1588 if self.applied:
1589 1589 p = self.applied[-1].name
1590 1590 try:
1591 1591 end = self.series.index(p)
1592 1592 except ValueError:
1593 1593 return 0
1594 1594 return next(end + 1)
1595 1595 return next(end)
1596 1596
1597 1597 def appliedname(self, index):
1598 1598 pname = self.applied[index].name
1599 1599 if not self.ui.verbose:
1600 1600 p = pname
1601 1601 else:
1602 1602 p = str(self.series.index(pname)) + " " + pname
1603 1603 return p
1604 1604
1605 1605 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1606 1606 force=None, git=False):
1607 1607 def checkseries(patchname):
1608 1608 if patchname in self.series:
1609 1609 raise util.Abort(_('patch %s is already in the series file')
1610 1610 % patchname)
1611 1611 def checkfile(patchname):
1612 1612 if not force and os.path.exists(self.join(patchname)):
1613 1613 raise util.Abort(_('patch "%s" already exists')
1614 1614 % patchname)
1615 1615
1616 1616 if rev:
1617 1617 if files:
1618 1618 raise util.Abort(_('option "-r" not valid when importing '
1619 1619 'files'))
1620 1620 rev = cmdutil.revrange(repo, rev)
1621 1621 rev.sort(reverse=True)
1622 1622 if (len(files) > 1 or len(rev) > 1) and patchname:
1623 1623 raise util.Abort(_('option "-n" not valid when importing multiple '
1624 1624 'patches'))
1625 1625 added = []
1626 1626 if rev:
1627 1627 # If mq patches are applied, we can only import revisions
1628 1628 # that form a linear path to qbase.
1629 1629 # Otherwise, they should form a linear path to a head.
1630 1630 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1631 1631 if len(heads) > 1:
1632 1632 raise util.Abort(_('revision %d is the root of more than one '
1633 1633 'branch') % rev[-1])
1634 1634 if self.applied:
1635 1635 base = repo.changelog.node(rev[0])
1636 1636 if base in [n.node for n in self.applied]:
1637 1637 raise util.Abort(_('revision %d is already managed')
1638 1638 % rev[0])
1639 1639 if heads != [self.applied[-1].node]:
1640 1640 raise util.Abort(_('revision %d is not the parent of '
1641 1641 'the queue') % rev[0])
1642 1642 base = repo.changelog.rev(self.applied[0].node)
1643 1643 lastparent = repo.changelog.parentrevs(base)[0]
1644 1644 else:
1645 1645 if heads != [repo.changelog.node(rev[0])]:
1646 1646 raise util.Abort(_('revision %d has unmanaged children')
1647 1647 % rev[0])
1648 1648 lastparent = None
1649 1649
1650 1650 diffopts = self.diffopts({'git': git})
1651 1651 for r in rev:
1652 1652 p1, p2 = repo.changelog.parentrevs(r)
1653 1653 n = repo.changelog.node(r)
1654 1654 if p2 != nullrev:
1655 1655 raise util.Abort(_('cannot import merge revision %d') % r)
1656 1656 if lastparent and lastparent != r:
1657 1657 raise util.Abort(_('revision %d is not the parent of %d')
1658 1658 % (r, lastparent))
1659 1659 lastparent = p1
1660 1660
1661 1661 if not patchname:
1662 1662 patchname = normname('%d.diff' % r)
1663 1663 self.check_reserved_name(patchname)
1664 1664 checkseries(patchname)
1665 1665 checkfile(patchname)
1666 1666 self.full_series.insert(0, patchname)
1667 1667
1668 1668 patchf = self.opener(patchname, "w")
1669 1669 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1670 1670 patchf.close()
1671 1671
1672 1672 se = statusentry(n, patchname)
1673 1673 self.applied.insert(0, se)
1674 1674
1675 1675 added.append(patchname)
1676 1676 patchname = None
1677 1677 self.parse_series()
1678 1678 self.applied_dirty = 1
1679 1679
1680 1680 for i, filename in enumerate(files):
1681 1681 if existing:
1682 1682 if filename == '-':
1683 1683 raise util.Abort(_('-e is incompatible with import from -'))
1684 1684 if not patchname:
1685 1685 patchname = normname(filename)
1686 1686 self.check_reserved_name(patchname)
1687 1687 if not os.path.isfile(self.join(patchname)):
1688 1688 raise util.Abort(_("patch %s does not exist") % patchname)
1689 1689 else:
1690 1690 try:
1691 1691 if filename == '-':
1692 1692 if not patchname:
1693 1693 raise util.Abort(
1694 1694 _('need --name to import a patch from -'))
1695 1695 text = sys.stdin.read()
1696 1696 else:
1697 1697 text = url.open(self.ui, filename).read()
1698 1698 except (OSError, IOError):
1699 1699 raise util.Abort(_("unable to read %s") % filename)
1700 1700 if not patchname:
1701 1701 patchname = normname(os.path.basename(filename))
1702 1702 self.check_reserved_name(patchname)
1703 1703 checkfile(patchname)
1704 1704 patchf = self.opener(patchname, "w")
1705 1705 patchf.write(text)
1706 1706 if not force:
1707 1707 checkseries(patchname)
1708 1708 if patchname not in self.series:
1709 1709 index = self.full_series_end() + i
1710 1710 self.full_series[index:index] = [patchname]
1711 1711 self.parse_series()
1712 1712 self.ui.warn(_("adding %s to series file\n") % patchname)
1713 1713 added.append(patchname)
1714 1714 patchname = None
1715 1715 self.series_dirty = 1
1716 1716 qrepo = self.qrepo()
1717 1717 if qrepo:
1718 1718 qrepo[None].add(added)
1719 1719
1720 1720 def delete(ui, repo, *patches, **opts):
1721 1721 """remove patches from queue
1722 1722
1723 1723 The patches must not be applied, and at least one patch is required. With
1724 1724 -k/--keep, the patch files are preserved in the patch directory.
1725 1725
1726 1726 To stop managing a patch and move it into permanent history,
1727 1727 use the :hg:`qfinish` command."""
1728 1728 q = repo.mq
1729 1729 q.delete(repo, patches, opts)
1730 1730 q.save_dirty()
1731 1731 return 0
1732 1732
1733 1733 def applied(ui, repo, patch=None, **opts):
1734 1734 """print the patches already applied"""
1735 1735
1736 1736 q = repo.mq
1737 1737 l = len(q.applied)
1738 1738
1739 1739 if patch:
1740 1740 if patch not in q.series:
1741 1741 raise util.Abort(_("patch %s is not in series file") % patch)
1742 1742 end = q.series.index(patch) + 1
1743 1743 else:
1744 1744 end = q.series_end(True)
1745 1745
1746 1746 if opts.get('last') and not end:
1747 1747 ui.write(_("no patches applied\n"))
1748 1748 return 1
1749 1749 elif opts.get('last') and end == 1:
1750 1750 ui.write(_("only one patch applied\n"))
1751 1751 return 1
1752 1752 elif opts.get('last'):
1753 1753 start = end - 2
1754 1754 end = 1
1755 1755 else:
1756 1756 start = 0
1757 1757
1758 1758 return q.qseries(repo, length=end, start=start, status='A',
1759 1759 summary=opts.get('summary'))
1760 1760
1761 1761 def unapplied(ui, repo, patch=None, **opts):
1762 1762 """print the patches not yet applied"""
1763 1763
1764 1764 q = repo.mq
1765 1765 if patch:
1766 1766 if patch not in q.series:
1767 1767 raise util.Abort(_("patch %s is not in series file") % patch)
1768 1768 start = q.series.index(patch) + 1
1769 1769 else:
1770 1770 start = q.series_end(True)
1771 1771
1772 1772 if start == len(q.series) and opts.get('first'):
1773 1773 ui.write(_("all patches applied\n"))
1774 1774 return 1
1775 1775
1776 1776 length = opts.get('first') and 1 or None
1777 1777 return q.qseries(repo, start=start, length=length, status='U',
1778 1778 summary=opts.get('summary'))
1779 1779
1780 1780 def qimport(ui, repo, *filename, **opts):
1781 1781 """import a patch
1782 1782
1783 1783 The patch is inserted into the series after the last applied
1784 1784 patch. If no patches have been applied, qimport prepends the patch
1785 1785 to the series.
1786 1786
1787 1787 The patch will have the same name as its source file unless you
1788 1788 give it a new one with -n/--name.
1789 1789
1790 1790 You can register an existing patch inside the patch directory with
1791 1791 the -e/--existing flag.
1792 1792
1793 1793 With -f/--force, an existing patch of the same name will be
1794 1794 overwritten.
1795 1795
1796 1796 An existing changeset may be placed under mq control with -r/--rev
1797 1797 (e.g. qimport --rev tip -n patch will place tip under mq control).
1798 1798 With -g/--git, patches imported with --rev will use the git diff
1799 1799 format. See the diffs help topic for information on why this is
1800 1800 important for preserving rename/copy information and permission
1801 1801 changes.
1802 1802
1803 1803 To import a patch from standard input, pass - as the patch file.
1804 1804 When importing from standard input, a patch name must be specified
1805 1805 using the --name flag.
1806 1806 """
1807 1807 q = repo.mq
1808 1808 q.qimport(repo, filename, patchname=opts['name'],
1809 1809 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1810 1810 git=opts['git'])
1811 1811 q.save_dirty()
1812 1812
1813 1813 if opts.get('push') and not opts.get('rev'):
1814 1814 return q.push(repo, None)
1815 1815 return 0
1816 1816
1817 1817 def qinit(ui, repo, create):
1818 1818 """initialize a new queue repository
1819 1819
1820 1820 This command also creates a series file for ordering patches, and
1821 1821 an mq-specific .hgignore file in the queue repository, to exclude
1822 1822 the status and guards files (these contain mostly transient state)."""
1823 1823 q = repo.mq
1824 1824 r = q.init(repo, create)
1825 1825 q.save_dirty()
1826 1826 if r:
1827 1827 if not os.path.exists(r.wjoin('.hgignore')):
1828 1828 fp = r.wopener('.hgignore', 'w')
1829 1829 fp.write('^\\.hg\n')
1830 1830 fp.write('^\\.mq\n')
1831 1831 fp.write('syntax: glob\n')
1832 1832 fp.write('status\n')
1833 1833 fp.write('guards\n')
1834 1834 fp.close()
1835 1835 if not os.path.exists(r.wjoin('series')):
1836 1836 r.wopener('series', 'w').close()
1837 1837 r[None].add(['.hgignore', 'series'])
1838 1838 commands.add(ui, r)
1839 1839 return 0
1840 1840
1841 1841 def init(ui, repo, **opts):
1842 1842 """init a new queue repository (DEPRECATED)
1843 1843
1844 1844 The queue repository is unversioned by default. If
1845 1845 -c/--create-repo is specified, qinit will create a separate nested
1846 1846 repository for patches (qinit -c may also be run later to convert
1847 1847 an unversioned patch repository into a versioned one). You can use
1848 1848 qcommit to commit changes to this queue repository.
1849 1849
1850 1850 This command is deprecated. Without -c, it's implied by other relevant
1851 1851 commands. With -c, use :hg:`init --mq` instead."""
1852 1852 return qinit(ui, repo, create=opts['create_repo'])
1853 1853
1854 1854 def clone(ui, source, dest=None, **opts):
1855 1855 '''clone main and patch repository at same time
1856 1856
1857 1857 If source is local, destination will have no patches applied. If
1858 1858 source is remote, this command can not check if patches are
1859 1859 applied in source, so cannot guarantee that patches are not
1860 1860 applied in destination. If you clone remote repository, be sure
1861 1861 before that it has no patches applied.
1862 1862
1863 1863 Source patch repository is looked for in <src>/.hg/patches by
1864 1864 default. Use -p <url> to change.
1865 1865
1866 1866 The patch directory must be a nested Mercurial repository, as
1867 1867 would be created by :hg:`init --mq`.
1868 1868 '''
1869 1869 def patchdir(repo):
1870 1870 url = repo.url()
1871 1871 if url.endswith('/'):
1872 1872 url = url[:-1]
1873 1873 return url + '/.hg/patches'
1874 1874 if dest is None:
1875 1875 dest = hg.defaultdest(source)
1876 1876 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1877 1877 if opts['patches']:
1878 1878 patchespath = ui.expandpath(opts['patches'])
1879 1879 else:
1880 1880 patchespath = patchdir(sr)
1881 1881 try:
1882 1882 hg.repository(ui, patchespath)
1883 1883 except error.RepoError:
1884 1884 raise util.Abort(_('versioned patch repository not found'
1885 1885 ' (see init --mq)'))
1886 1886 qbase, destrev = None, None
1887 1887 if sr.local():
1888 1888 if sr.mq.applied:
1889 1889 qbase = sr.mq.applied[0].node
1890 1890 if not hg.islocal(dest):
1891 1891 heads = set(sr.heads())
1892 1892 destrev = list(heads.difference(sr.heads(qbase)))
1893 1893 destrev.append(sr.changelog.parents(qbase)[0])
1894 1894 elif sr.capable('lookup'):
1895 1895 try:
1896 1896 qbase = sr.lookup('qbase')
1897 1897 except error.RepoError:
1898 1898 pass
1899 1899 ui.note(_('cloning main repository\n'))
1900 1900 sr, dr = hg.clone(ui, sr.url(), dest,
1901 1901 pull=opts['pull'],
1902 1902 rev=destrev,
1903 1903 update=False,
1904 1904 stream=opts['uncompressed'])
1905 1905 ui.note(_('cloning patch repository\n'))
1906 1906 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1907 1907 pull=opts['pull'], update=not opts['noupdate'],
1908 1908 stream=opts['uncompressed'])
1909 1909 if dr.local():
1910 1910 if qbase:
1911 1911 ui.note(_('stripping applied patches from destination '
1912 1912 'repository\n'))
1913 1913 dr.mq.strip(dr, qbase, update=False, backup=None)
1914 1914 if not opts['noupdate']:
1915 1915 ui.note(_('updating destination repository\n'))
1916 1916 hg.update(dr, dr.changelog.tip())
1917 1917
1918 1918 def commit(ui, repo, *pats, **opts):
1919 1919 """commit changes in the queue repository (DEPRECATED)
1920 1920
1921 1921 This command is deprecated; use :hg:`commit --mq` instead."""
1922 1922 q = repo.mq
1923 1923 r = q.qrepo()
1924 1924 if not r:
1925 1925 raise util.Abort('no queue repository')
1926 1926 commands.commit(r.ui, r, *pats, **opts)
1927 1927
1928 1928 def series(ui, repo, **opts):
1929 1929 """print the entire series file"""
1930 1930 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1931 1931 return 0
1932 1932
1933 1933 def top(ui, repo, **opts):
1934 1934 """print the name of the current patch"""
1935 1935 q = repo.mq
1936 1936 t = q.applied and q.series_end(True) or 0
1937 1937 if t:
1938 1938 return q.qseries(repo, start=t - 1, length=1, status='A',
1939 1939 summary=opts.get('summary'))
1940 1940 else:
1941 1941 ui.write(_("no patches applied\n"))
1942 1942 return 1
1943 1943
1944 1944 def next(ui, repo, **opts):
1945 1945 """print the name of the next patch"""
1946 1946 q = repo.mq
1947 1947 end = q.series_end()
1948 1948 if end == len(q.series):
1949 1949 ui.write(_("all patches applied\n"))
1950 1950 return 1
1951 1951 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1952 1952
1953 1953 def prev(ui, repo, **opts):
1954 1954 """print the name of the previous patch"""
1955 1955 q = repo.mq
1956 1956 l = len(q.applied)
1957 1957 if l == 1:
1958 1958 ui.write(_("only one patch applied\n"))
1959 1959 return 1
1960 1960 if not l:
1961 1961 ui.write(_("no patches applied\n"))
1962 1962 return 1
1963 1963 return q.qseries(repo, start=l - 2, length=1, status='A',
1964 1964 summary=opts.get('summary'))
1965 1965
1966 1966 def setupheaderopts(ui, opts):
1967 1967 if not opts.get('user') and opts.get('currentuser'):
1968 1968 opts['user'] = ui.username()
1969 1969 if not opts.get('date') and opts.get('currentdate'):
1970 1970 opts['date'] = "%d %d" % util.makedate()
1971 1971
1972 1972 def new(ui, repo, patch, *args, **opts):
1973 1973 """create a new patch
1974 1974
1975 1975 qnew creates a new patch on top of the currently-applied patch (if
1976 1976 any). The patch will be initialized with any outstanding changes
1977 1977 in the working directory. You may also use -I/--include,
1978 1978 -X/--exclude, and/or a list of files after the patch name to add
1979 1979 only changes to matching files to the new patch, leaving the rest
1980 1980 as uncommitted modifications.
1981 1981
1982 1982 -u/--user and -d/--date can be used to set the (given) user and
1983 1983 date, respectively. -U/--currentuser and -D/--currentdate set user
1984 1984 to current user and date to current date.
1985 1985
1986 1986 -e/--edit, -m/--message or -l/--logfile set the patch header as
1987 1987 well as the commit message. If none is specified, the header is
1988 1988 empty and the commit message is '[mq]: PATCH'.
1989 1989
1990 1990 Use the -g/--git option to keep the patch in the git extended diff
1991 1991 format. Read the diffs help topic for more information on why this
1992 1992 is important for preserving permission changes and copy/rename
1993 1993 information.
1994 1994 """
1995 1995 msg = cmdutil.logmessage(opts)
1996 1996 def getmsg():
1997 1997 return ui.edit(msg, ui.username())
1998 1998 q = repo.mq
1999 1999 opts['msg'] = msg
2000 2000 if opts.get('edit'):
2001 2001 opts['msg'] = getmsg
2002 2002 else:
2003 2003 opts['msg'] = msg
2004 2004 setupheaderopts(ui, opts)
2005 2005 q.new(repo, patch, *args, **opts)
2006 2006 q.save_dirty()
2007 2007 return 0
2008 2008
2009 2009 def refresh(ui, repo, *pats, **opts):
2010 2010 """update the current patch
2011 2011
2012 2012 If any file patterns are provided, the refreshed patch will
2013 2013 contain only the modifications that match those patterns; the
2014 2014 remaining modifications will remain in the working directory.
2015 2015
2016 2016 If -s/--short is specified, files currently included in the patch
2017 2017 will be refreshed just like matched files and remain in the patch.
2018 2018
2019 2019 hg add/remove/copy/rename work as usual, though you might want to
2020 2020 use git-style patches (-g/--git or [diff] git=1) to track copies
2021 2021 and renames. See the diffs help topic for more information on the
2022 2022 git diff format.
2023 2023 """
2024 2024 q = repo.mq
2025 2025 message = cmdutil.logmessage(opts)
2026 2026 if opts['edit']:
2027 2027 if not q.applied:
2028 2028 ui.write(_("no patches applied\n"))
2029 2029 return 1
2030 2030 if message:
2031 2031 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2032 2032 patch = q.applied[-1].name
2033 2033 ph = patchheader(q.join(patch), q.plainmode)
2034 2034 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2035 2035 setupheaderopts(ui, opts)
2036 2036 ret = q.refresh(repo, pats, msg=message, **opts)
2037 2037 q.save_dirty()
2038 2038 return ret
2039 2039
2040 2040 def diff(ui, repo, *pats, **opts):
2041 2041 """diff of the current patch and subsequent modifications
2042 2042
2043 2043 Shows a diff which includes the current patch as well as any
2044 2044 changes which have been made in the working directory since the
2045 2045 last refresh (thus showing what the current patch would become
2046 2046 after a qrefresh).
2047 2047
2048 2048 Use :hg:`diff` if you only want to see the changes made since the
2049 2049 last qrefresh, or :hg:`export qtip` if you want to see changes
2050 2050 made by the current patch without including changes made since the
2051 2051 qrefresh.
2052 2052 """
2053 2053 repo.mq.diff(repo, pats, opts)
2054 2054 return 0
2055 2055
2056 2056 def fold(ui, repo, *files, **opts):
2057 2057 """fold the named patches into the current patch
2058 2058
2059 2059 Patches must not yet be applied. Each patch will be successively
2060 2060 applied to the current patch in the order given. If all the
2061 2061 patches apply successfully, the current patch will be refreshed
2062 2062 with the new cumulative patch, and the folded patches will be
2063 2063 deleted. With -k/--keep, the folded patch files will not be
2064 2064 removed afterwards.
2065 2065
2066 2066 The header for each folded patch will be concatenated with the
2067 2067 current patch header, separated by a line of '* * *'."""
2068 2068
2069 2069 q = repo.mq
2070 2070
2071 2071 if not files:
2072 2072 raise util.Abort(_('qfold requires at least one patch name'))
2073 2073 if not q.check_toppatch(repo)[0]:
2074 2074 raise util.Abort(_('No patches applied'))
2075 2075 q.check_localchanges(repo)
2076 2076
2077 2077 message = cmdutil.logmessage(opts)
2078 2078 if opts['edit']:
2079 2079 if message:
2080 2080 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2081 2081
2082 2082 parent = q.lookup('qtip')
2083 2083 patches = []
2084 2084 messages = []
2085 2085 for f in files:
2086 2086 p = q.lookup(f)
2087 2087 if p in patches or p == parent:
2088 2088 ui.warn(_('Skipping already folded patch %s') % p)
2089 2089 if q.isapplied(p):
2090 2090 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2091 2091 patches.append(p)
2092 2092
2093 2093 for p in patches:
2094 2094 if not message:
2095 2095 ph = patchheader(q.join(p), q.plainmode)
2096 2096 if ph.message:
2097 2097 messages.append(ph.message)
2098 2098 pf = q.join(p)
2099 2099 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2100 2100 if not patchsuccess:
2101 2101 raise util.Abort(_('Error folding patch %s') % p)
2102 2102 patch.updatedir(ui, repo, files)
2103 2103
2104 2104 if not message:
2105 2105 ph = patchheader(q.join(parent), q.plainmode)
2106 2106 message, user = ph.message, ph.user
2107 2107 for msg in messages:
2108 2108 message.append('* * *')
2109 2109 message.extend(msg)
2110 2110 message = '\n'.join(message)
2111 2111
2112 2112 if opts['edit']:
2113 2113 message = ui.edit(message, user or ui.username())
2114 2114
2115 2115 diffopts = q.patchopts(q.diffopts(), *patches)
2116 2116 q.refresh(repo, msg=message, git=diffopts.git)
2117 2117 q.delete(repo, patches, opts)
2118 2118 q.save_dirty()
2119 2119
2120 2120 def goto(ui, repo, patch, **opts):
2121 2121 '''push or pop patches until named patch is at top of stack'''
2122 2122 q = repo.mq
2123 2123 patch = q.lookup(patch)
2124 2124 if q.isapplied(patch):
2125 2125 ret = q.pop(repo, patch, force=opts['force'])
2126 2126 else:
2127 2127 ret = q.push(repo, patch, force=opts['force'])
2128 2128 q.save_dirty()
2129 2129 return ret
2130 2130
2131 2131 def guard(ui, repo, *args, **opts):
2132 2132 '''set or print guards for a patch
2133 2133
2134 2134 Guards control whether a patch can be pushed. A patch with no
2135 2135 guards is always pushed. A patch with a positive guard ("+foo") is
2136 2136 pushed only if the :hg:`qselect` command has activated it. A patch with
2137 2137 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2138 2138 has activated it.
2139 2139
2140 2140 With no arguments, print the currently active guards.
2141 2141 With arguments, set guards for the named patch.
2142 2142 NOTE: Specifying negative guards now requires '--'.
2143 2143
2144 2144 To set guards on another patch::
2145 2145
2146 2146 hg qguard other.patch -- +2.6.17 -stable
2147 2147 '''
2148 2148 def status(idx):
2149 2149 guards = q.series_guards[idx] or ['unguarded']
2150 2150 ui.write('%s: ' % ui.label(q.series[idx], 'qguard.patch'))
2151 2151 for i, guard in enumerate(guards):
2152 2152 if guard.startswith('+'):
2153 2153 ui.write(guard, label='qguard.positive')
2154 2154 elif guard.startswith('-'):
2155 2155 ui.write(guard, label='qguard.negative')
2156 2156 else:
2157 2157 ui.write(guard, label='qguard.unguarded')
2158 2158 if i != len(guards) - 1:
2159 2159 ui.write(' ')
2160 2160 ui.write('\n')
2161 2161 q = repo.mq
2162 2162 patch = None
2163 2163 args = list(args)
2164 2164 if opts['list']:
2165 2165 if args or opts['none']:
2166 2166 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2167 2167 for i in xrange(len(q.series)):
2168 2168 status(i)
2169 2169 return
2170 2170 if not args or args[0][0:1] in '-+':
2171 2171 if not q.applied:
2172 2172 raise util.Abort(_('no patches applied'))
2173 2173 patch = q.applied[-1].name
2174 2174 if patch is None and args[0][0:1] not in '-+':
2175 2175 patch = args.pop(0)
2176 2176 if patch is None:
2177 2177 raise util.Abort(_('no patch to work with'))
2178 2178 if args or opts['none']:
2179 2179 idx = q.find_series(patch)
2180 2180 if idx is None:
2181 2181 raise util.Abort(_('no patch named %s') % patch)
2182 2182 q.set_guards(idx, args)
2183 2183 q.save_dirty()
2184 2184 else:
2185 2185 status(q.series.index(q.lookup(patch)))
2186 2186
2187 2187 def header(ui, repo, patch=None):
2188 2188 """print the header of the topmost or specified patch"""
2189 2189 q = repo.mq
2190 2190
2191 2191 if patch:
2192 2192 patch = q.lookup(patch)
2193 2193 else:
2194 2194 if not q.applied:
2195 2195 ui.write(_('no patches applied\n'))
2196 2196 return 1
2197 2197 patch = q.lookup('qtip')
2198 2198 ph = patchheader(q.join(patch), q.plainmode)
2199 2199
2200 2200 ui.write('\n'.join(ph.message) + '\n')
2201 2201
2202 2202 def lastsavename(path):
2203 2203 (directory, base) = os.path.split(path)
2204 2204 names = os.listdir(directory)
2205 2205 namere = re.compile("%s.([0-9]+)" % base)
2206 2206 maxindex = None
2207 2207 maxname = None
2208 2208 for f in names:
2209 2209 m = namere.match(f)
2210 2210 if m:
2211 2211 index = int(m.group(1))
2212 2212 if maxindex is None or index > maxindex:
2213 2213 maxindex = index
2214 2214 maxname = f
2215 2215 if maxname:
2216 2216 return (os.path.join(directory, maxname), maxindex)
2217 2217 return (None, None)
2218 2218
2219 2219 def savename(path):
2220 2220 (last, index) = lastsavename(path)
2221 2221 if last is None:
2222 2222 index = 0
2223 2223 newpath = path + ".%d" % (index + 1)
2224 2224 return newpath
2225 2225
2226 2226 def push(ui, repo, patch=None, **opts):
2227 2227 """push the next patch onto the stack
2228 2228
2229 2229 When -f/--force is applied, all local changes in patched files
2230 2230 will be lost.
2231 2231 """
2232 2232 q = repo.mq
2233 2233 mergeq = None
2234 2234
2235 2235 if opts['merge']:
2236 2236 if opts['name']:
2237 2237 newpath = repo.join(opts['name'])
2238 2238 else:
2239 2239 newpath, i = lastsavename(q.path)
2240 2240 if not newpath:
2241 2241 ui.warn(_("no saved queues found, please use -n\n"))
2242 2242 return 1
2243 2243 mergeq = queue(ui, repo.join(""), newpath)
2244 2244 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2245 2245 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2246 2246 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2247 2247 return ret
2248 2248
2249 2249 def pop(ui, repo, patch=None, **opts):
2250 2250 """pop the current patch off the stack
2251 2251
2252 2252 By default, pops off the top of the patch stack. If given a patch
2253 2253 name, keeps popping off patches until the named patch is at the
2254 2254 top of the stack.
2255 2255 """
2256 2256 localupdate = True
2257 2257 if opts['name']:
2258 2258 q = queue(ui, repo.join(""), repo.join(opts['name']))
2259 2259 ui.warn(_('using patch queue: %s\n') % q.path)
2260 2260 localupdate = False
2261 2261 else:
2262 2262 q = repo.mq
2263 2263 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2264 2264 all=opts['all'])
2265 2265 q.save_dirty()
2266 2266 return ret
2267 2267
2268 2268 def rename(ui, repo, patch, name=None, **opts):
2269 2269 """rename a patch
2270 2270
2271 2271 With one argument, renames the current patch to PATCH1.
2272 2272 With two arguments, renames PATCH1 to PATCH2."""
2273 2273
2274 2274 q = repo.mq
2275 2275
2276 2276 if not name:
2277 2277 name = patch
2278 2278 patch = None
2279 2279
2280 2280 if patch:
2281 2281 patch = q.lookup(patch)
2282 2282 else:
2283 2283 if not q.applied:
2284 2284 ui.write(_('no patches applied\n'))
2285 2285 return
2286 2286 patch = q.lookup('qtip')
2287 2287 absdest = q.join(name)
2288 2288 if os.path.isdir(absdest):
2289 2289 name = normname(os.path.join(name, os.path.basename(patch)))
2290 2290 absdest = q.join(name)
2291 2291 if os.path.exists(absdest):
2292 2292 raise util.Abort(_('%s already exists') % absdest)
2293 2293
2294 2294 if name in q.series:
2295 2295 raise util.Abort(
2296 2296 _('A patch named %s already exists in the series file') % name)
2297 2297
2298 2298 ui.note(_('renaming %s to %s\n') % (patch, name))
2299 2299 i = q.find_series(patch)
2300 2300 guards = q.guard_re.findall(q.full_series[i])
2301 2301 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2302 2302 q.parse_series()
2303 2303 q.series_dirty = 1
2304 2304
2305 2305 info = q.isapplied(patch)
2306 2306 if info:
2307 2307 q.applied[info[0]] = statusentry(info[1], name)
2308 2308 q.applied_dirty = 1
2309 2309
2310 2310 util.rename(q.join(patch), absdest)
2311 2311 r = q.qrepo()
2312 2312 if r:
2313 2313 wctx = r[None]
2314 2314 wlock = r.wlock()
2315 2315 try:
2316 2316 if r.dirstate[patch] == 'a':
2317 2317 r.dirstate.forget(patch)
2318 2318 r.dirstate.add(name)
2319 2319 else:
2320 2320 if r.dirstate[name] == 'r':
2321 2321 wctx.undelete([name])
2322 2322 wctx.copy(patch, name)
2323 2323 wctx.remove([patch], False)
2324 2324 finally:
2325 2325 wlock.release()
2326 2326
2327 2327 q.save_dirty()
2328 2328
2329 2329 def restore(ui, repo, rev, **opts):
2330 2330 """restore the queue state saved by a revision (DEPRECATED)
2331 2331
2332 2332 This command is deprecated, use rebase --mq instead."""
2333 2333 rev = repo.lookup(rev)
2334 2334 q = repo.mq
2335 2335 q.restore(repo, rev, delete=opts['delete'],
2336 2336 qupdate=opts['update'])
2337 2337 q.save_dirty()
2338 2338 return 0
2339 2339
2340 2340 def save(ui, repo, **opts):
2341 2341 """save current queue state (DEPRECATED)
2342 2342
2343 2343 This command is deprecated, use rebase --mq instead."""
2344 2344 q = repo.mq
2345 2345 message = cmdutil.logmessage(opts)
2346 2346 ret = q.save(repo, msg=message)
2347 2347 if ret:
2348 2348 return ret
2349 2349 q.save_dirty()
2350 2350 if opts['copy']:
2351 2351 path = q.path
2352 2352 if opts['name']:
2353 2353 newpath = os.path.join(q.basepath, opts['name'])
2354 2354 if os.path.exists(newpath):
2355 2355 if not os.path.isdir(newpath):
2356 2356 raise util.Abort(_('destination %s exists and is not '
2357 2357 'a directory') % newpath)
2358 2358 if not opts['force']:
2359 2359 raise util.Abort(_('destination %s exists, '
2360 2360 'use -f to force') % newpath)
2361 2361 else:
2362 2362 newpath = savename(path)
2363 2363 ui.warn(_("copy %s to %s\n") % (path, newpath))
2364 2364 util.copyfiles(path, newpath)
2365 2365 if opts['empty']:
2366 2366 try:
2367 2367 os.unlink(q.join(q.status_path))
2368 2368 except:
2369 2369 pass
2370 2370 return 0
2371 2371
2372 2372 def strip(ui, repo, rev, **opts):
2373 2373 """strip a changeset and all its descendants from the repository
2374 2374
2375 2375 The strip command removes all changesets whose local revision
2376 2376 number is greater than or equal to REV, and then restores any
2377 2377 changesets that are not descendants of REV. If the working
2378 2378 directory has uncommitted changes, the operation is aborted unless
2379 2379 the --force flag is supplied.
2380 2380
2381 2381 If a parent of the working directory is stripped, then the working
2382 2382 directory will automatically be updated to the most recent
2383 2383 available ancestor of the stripped parent after the operation
2384 2384 completes.
2385 2385
2386 2386 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2387 2387 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2388 2388 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2389 2389 where BUNDLE is the bundle file created by the strip. Note that
2390 2390 the local revision numbers will in general be different after the
2391 2391 restore.
2392 2392
2393 2393 Use the --nobackup option to discard the backup bundle once the
2394 2394 operation completes.
2395 2395 """
2396 2396 backup = 'all'
2397 2397 if opts['backup']:
2398 2398 backup = 'strip'
2399 2399 elif opts['nobackup']:
2400 2400 backup = 'none'
2401 2401
2402 2402 rev = repo.lookup(rev)
2403 2403 p = repo.dirstate.parents()
2404 2404 cl = repo.changelog
2405 2405 update = True
2406 2406 if p[0] == nullid:
2407 2407 update = False
2408 2408 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2409 2409 update = False
2410 2410 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2411 2411 update = False
2412 2412
2413 2413 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2414 2414 return 0
2415 2415
2416 2416 def select(ui, repo, *args, **opts):
2417 2417 '''set or print guarded patches to push
2418 2418
2419 2419 Use the :hg:`qguard` command to set or print guards on patch, then use
2420 2420 qselect to tell mq which guards to use. A patch will be pushed if
2421 2421 it has no guards or any positive guards match the currently
2422 2422 selected guard, but will not be pushed if any negative guards
2423 2423 match the current guard. For example::
2424 2424
2425 2425 qguard foo.patch -stable (negative guard)
2426 2426 qguard bar.patch +stable (positive guard)
2427 2427 qselect stable
2428 2428
2429 2429 This activates the "stable" guard. mq will skip foo.patch (because
2430 2430 it has a negative match) but push bar.patch (because it has a
2431 2431 positive match).
2432 2432
2433 2433 With no arguments, prints the currently active guards.
2434 2434 With one argument, sets the active guard.
2435 2435
2436 2436 Use -n/--none to deactivate guards (no other arguments needed).
2437 2437 When no guards are active, patches with positive guards are
2438 2438 skipped and patches with negative guards are pushed.
2439 2439
2440 2440 qselect can change the guards on applied patches. It does not pop
2441 2441 guarded patches by default. Use --pop to pop back to the last
2442 2442 applied patch that is not guarded. Use --reapply (which implies
2443 2443 --pop) to push back to the current patch afterwards, but skip
2444 2444 guarded patches.
2445 2445
2446 2446 Use -s/--series to print a list of all guards in the series file
2447 2447 (no other arguments needed). Use -v for more information.'''
2448 2448
2449 2449 q = repo.mq
2450 2450 guards = q.active()
2451 2451 if args or opts['none']:
2452 2452 old_unapplied = q.unapplied(repo)
2453 2453 old_guarded = [i for i in xrange(len(q.applied)) if
2454 2454 not q.pushable(i)[0]]
2455 2455 q.set_active(args)
2456 2456 q.save_dirty()
2457 2457 if not args:
2458 2458 ui.status(_('guards deactivated\n'))
2459 2459 if not opts['pop'] and not opts['reapply']:
2460 2460 unapplied = q.unapplied(repo)
2461 2461 guarded = [i for i in xrange(len(q.applied))
2462 2462 if not q.pushable(i)[0]]
2463 2463 if len(unapplied) != len(old_unapplied):
2464 2464 ui.status(_('number of unguarded, unapplied patches has '
2465 2465 'changed from %d to %d\n') %
2466 2466 (len(old_unapplied), len(unapplied)))
2467 2467 if len(guarded) != len(old_guarded):
2468 2468 ui.status(_('number of guarded, applied patches has changed '
2469 2469 'from %d to %d\n') %
2470 2470 (len(old_guarded), len(guarded)))
2471 2471 elif opts['series']:
2472 2472 guards = {}
2473 2473 noguards = 0
2474 2474 for gs in q.series_guards:
2475 2475 if not gs:
2476 2476 noguards += 1
2477 2477 for g in gs:
2478 2478 guards.setdefault(g, 0)
2479 2479 guards[g] += 1
2480 2480 if ui.verbose:
2481 2481 guards['NONE'] = noguards
2482 2482 guards = guards.items()
2483 2483 guards.sort(key=lambda x: x[0][1:])
2484 2484 if guards:
2485 2485 ui.note(_('guards in series file:\n'))
2486 2486 for guard, count in guards:
2487 2487 ui.note('%2d ' % count)
2488 2488 ui.write(guard, '\n')
2489 2489 else:
2490 2490 ui.note(_('no guards in series file\n'))
2491 2491 else:
2492 2492 if guards:
2493 2493 ui.note(_('active guards:\n'))
2494 2494 for g in guards:
2495 2495 ui.write(g, '\n')
2496 2496 else:
2497 2497 ui.write(_('no active guards\n'))
2498 2498 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2499 2499 popped = False
2500 2500 if opts['pop'] or opts['reapply']:
2501 2501 for i in xrange(len(q.applied)):
2502 2502 pushable, reason = q.pushable(i)
2503 2503 if not pushable:
2504 2504 ui.status(_('popping guarded patches\n'))
2505 2505 popped = True
2506 2506 if i == 0:
2507 2507 q.pop(repo, all=True)
2508 2508 else:
2509 2509 q.pop(repo, i - 1)
2510 2510 break
2511 2511 if popped:
2512 2512 try:
2513 2513 if reapply:
2514 2514 ui.status(_('reapplying unguarded patches\n'))
2515 2515 q.push(repo, reapply)
2516 2516 finally:
2517 2517 q.save_dirty()
2518 2518
2519 2519 def finish(ui, repo, *revrange, **opts):
2520 2520 """move applied patches into repository history
2521 2521
2522 2522 Finishes the specified revisions (corresponding to applied
2523 2523 patches) by moving them out of mq control into regular repository
2524 2524 history.
2525 2525
2526 2526 Accepts a revision range or the -a/--applied option. If --applied
2527 2527 is specified, all applied mq revisions are removed from mq
2528 2528 control. Otherwise, the given revisions must be at the base of the
2529 2529 stack of applied patches.
2530 2530
2531 2531 This can be especially useful if your changes have been applied to
2532 2532 an upstream repository, or if you are about to push your changes
2533 2533 to upstream.
2534 2534 """
2535 2535 if not opts['applied'] and not revrange:
2536 2536 raise util.Abort(_('no revisions specified'))
2537 2537 elif opts['applied']:
2538 2538 revrange = ('qbase:qtip',) + revrange
2539 2539
2540 2540 q = repo.mq
2541 2541 if not q.applied:
2542 2542 ui.status(_('no patches applied\n'))
2543 2543 return 0
2544 2544
2545 2545 revs = cmdutil.revrange(repo, revrange)
2546 2546 q.finish(repo, revs)
2547 2547 q.save_dirty()
2548 2548 return 0
2549 2549
2550 2550 def qqueue(ui, repo, name=None, **opts):
2551 2551 '''manage multiple patch queues
2552 2552
2553 2553 Supports switching between different patch queues, as well as creating
2554 2554 new patch queues and deleting existing ones.
2555 2555
2556 2556 Omitting a queue name or specifying -l/--list will show you the registered
2557 2557 queues - by default the "normal" patches queue is registered. The currently
2558 2558 active queue will be marked with "(active)".
2559 2559
2560 2560 To create a new queue, use -c/--create. The queue is automatically made
2561 2561 active, except in the case where there are applied patches from the
2562 2562 currently active queue in the repository. Then the queue will only be
2563 2563 created and switching will fail.
2564 2564
2565 2565 To delete an existing queue, use --delete. You cannot delete the currently
2566 2566 active queue.
2567 2567 '''
2568 2568
2569 2569 q = repo.mq
2570 2570
2571 2571 _defaultqueue = 'patches'
2572 2572 _allqueues = 'patches.queues'
2573 2573 _activequeue = 'patches.queue'
2574 2574
2575 2575 def _getcurrent():
2576 2576 cur = os.path.basename(q.path)
2577 2577 if cur.startswith('patches-'):
2578 2578 cur = cur[8:]
2579 2579 return cur
2580 2580
2581 2581 def _noqueues():
2582 2582 try:
2583 2583 fh = repo.opener(_allqueues, 'r')
2584 2584 fh.close()
2585 2585 except IOError:
2586 2586 return True
2587 2587
2588 2588 return False
2589 2589
2590 2590 def _getqueues():
2591 2591 current = _getcurrent()
2592 2592
2593 2593 try:
2594 2594 fh = repo.opener(_allqueues, 'r')
2595 2595 queues = [queue.strip() for queue in fh if queue.strip()]
2596 2596 if current not in queues:
2597 2597 queues.append(current)
2598 2598 except IOError:
2599 2599 queues = [_defaultqueue]
2600 2600
2601 2601 return sorted(queues)
2602 2602
2603 2603 def _setactive(name):
2604 2604 if q.applied:
2605 2605 raise util.Abort(_('patches applied - cannot set new queue active'))
2606 2606
2607 2607 fh = repo.opener(_activequeue, 'w')
2608 2608 if name != 'patches':
2609 2609 fh.write(name)
2610 2610 fh.close()
2611 2611
2612 2612 def _addqueue(name):
2613 2613 fh = repo.opener(_allqueues, 'a')
2614 2614 fh.write('%s\n' % (name,))
2615 2615 fh.close()
2616 2616
2617 2617 def _validname(name):
2618 2618 for n in name:
2619 2619 if n in ':\\/.':
2620 2620 return False
2621 2621 return True
2622 2622
2623 2623 if not name or opts.get('list'):
2624 2624 current = _getcurrent()
2625 2625 for queue in _getqueues():
2626 2626 ui.write('%s' % (queue,))
2627 2627 if queue == current:
2628 2628 ui.write(_(' (active)\n'))
2629 2629 else:
2630 2630 ui.write('\n')
2631 2631 return
2632 2632
2633 2633 if not _validname(name):
2634 2634 raise util.Abort(
2635 2635 _('invalid queue name, may not contain the characters ":\\/."'))
2636 2636
2637 2637 existing = _getqueues()
2638 2638
2639 2639 if opts.get('create'):
2640 2640 if name in existing:
2641 2641 raise util.Abort(_('queue "%s" already exists') % name)
2642 2642 if _noqueues():
2643 2643 _addqueue(_defaultqueue)
2644 2644 _addqueue(name)
2645 2645 _setactive(name)
2646 2646 elif opts.get('delete'):
2647 2647 if name not in existing:
2648 2648 raise util.Abort(_('cannot delete queue that does not exist'))
2649 2649
2650 2650 current = _getcurrent()
2651 2651
2652 2652 if name == current:
2653 2653 raise util.Abort(_('cannot delete currently active queue'))
2654 2654
2655 2655 fh = repo.opener('patches.queues.new', 'w')
2656 2656 for queue in existing:
2657 2657 if queue == name:
2658 2658 continue
2659 2659 fh.write('%s\n' % (queue,))
2660 2660 fh.close()
2661 2661 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2662 2662 else:
2663 2663 if name not in existing:
2664 2664 raise util.Abort(_('use --create to create a new queue'))
2665 2665 _setactive(name)
2666 2666
2667 2667 def reposetup(ui, repo):
2668 2668 class mqrepo(repo.__class__):
2669 2669 @util.propertycache
2670 2670 def mq(self):
2671 2671 return queue(self.ui, self.join(""))
2672 2672
2673 2673 def abort_if_wdir_patched(self, errmsg, force=False):
2674 2674 if self.mq.applied and not force:
2675 2675 parent = self.dirstate.parents()[0]
2676 2676 if parent in [s.node for s in self.mq.applied]:
2677 2677 raise util.Abort(errmsg)
2678 2678
2679 2679 def commit(self, text="", user=None, date=None, match=None,
2680 2680 force=False, editor=False, extra={}):
2681 2681 self.abort_if_wdir_patched(
2682 2682 _('cannot commit over an applied mq patch'),
2683 2683 force)
2684 2684
2685 2685 return super(mqrepo, self).commit(text, user, date, match, force,
2686 2686 editor, extra)
2687 2687
2688 2688 def push(self, remote, force=False, revs=None, newbranch=False):
2689 2689 if self.mq.applied and not force and not revs:
2690 2690 raise util.Abort(_('source has mq patches applied'))
2691 2691 return super(mqrepo, self).push(remote, force, revs, newbranch)
2692 2692
2693 2693 def _findtags(self):
2694 2694 '''augment tags from base class with patch tags'''
2695 2695 result = super(mqrepo, self)._findtags()
2696 2696
2697 2697 q = self.mq
2698 2698 if not q.applied:
2699 2699 return result
2700 2700
2701 2701 mqtags = [(patch.node, patch.name) for patch in q.applied]
2702 2702
2703 2703 if mqtags[-1][0] not in self.changelog.nodemap:
2704 2704 self.ui.warn(_('mq status file refers to unknown node %s\n')
2705 2705 % short(mqtags[-1][0]))
2706 2706 return result
2707 2707
2708 2708 mqtags.append((mqtags[-1][0], 'qtip'))
2709 2709 mqtags.append((mqtags[0][0], 'qbase'))
2710 2710 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2711 2711 tags = result[0]
2712 2712 for patch in mqtags:
2713 2713 if patch[1] in tags:
2714 2714 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2715 2715 % patch[1])
2716 2716 else:
2717 2717 tags[patch[1]] = patch[0]
2718 2718
2719 2719 return result
2720 2720
2721 2721 def _branchtags(self, partial, lrev):
2722 2722 q = self.mq
2723 2723 if not q.applied:
2724 2724 return super(mqrepo, self)._branchtags(partial, lrev)
2725 2725
2726 2726 cl = self.changelog
2727 2727 qbasenode = q.applied[0].node
2728 2728 if qbasenode not in cl.nodemap:
2729 2729 self.ui.warn(_('mq status file refers to unknown node %s\n')
2730 2730 % short(qbasenode))
2731 2731 return super(mqrepo, self)._branchtags(partial, lrev)
2732 2732
2733 2733 qbase = cl.rev(qbasenode)
2734 2734 start = lrev + 1
2735 2735 if start < qbase:
2736 2736 # update the cache (excluding the patches) and save it
2737 2737 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2738 2738 self._updatebranchcache(partial, ctxgen)
2739 2739 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2740 2740 start = qbase
2741 2741 # if start = qbase, the cache is as updated as it should be.
2742 2742 # if start > qbase, the cache includes (part of) the patches.
2743 2743 # we might as well use it, but we won't save it.
2744 2744
2745 2745 # update the cache up to the tip
2746 2746 ctxgen = (self[r] for r in xrange(start, len(cl)))
2747 2747 self._updatebranchcache(partial, ctxgen)
2748 2748
2749 2749 return partial
2750 2750
2751 2751 if repo.local():
2752 2752 repo.__class__ = mqrepo
2753 2753
2754 2754 def mqimport(orig, ui, repo, *args, **kwargs):
2755 2755 if (hasattr(repo, 'abort_if_wdir_patched')
2756 2756 and not kwargs.get('no_commit', False)):
2757 2757 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2758 2758 kwargs.get('force'))
2759 2759 return orig(ui, repo, *args, **kwargs)
2760 2760
2761 2761 def mqinit(orig, ui, *args, **kwargs):
2762 2762 mq = kwargs.pop('mq', None)
2763 2763
2764 2764 if not mq:
2765 2765 return orig(ui, *args, **kwargs)
2766 2766
2767 2767 if args:
2768 2768 repopath = args[0]
2769 2769 if not hg.islocal(repopath):
2770 2770 raise util.Abort(_('only a local queue repository '
2771 2771 'may be initialized'))
2772 2772 else:
2773 2773 repopath = cmdutil.findrepo(os.getcwd())
2774 2774 if not repopath:
2775 2775 raise util.Abort(_('There is no Mercurial repository here '
2776 2776 '(.hg not found)'))
2777 2777 repo = hg.repository(ui, repopath)
2778 2778 return qinit(ui, repo, True)
2779 2779
2780 2780 def mqcommand(orig, ui, repo, *args, **kwargs):
2781 2781 """Add --mq option to operate on patch repository instead of main"""
2782 2782
2783 2783 # some commands do not like getting unknown options
2784 2784 mq = kwargs.pop('mq', None)
2785 2785
2786 2786 if not mq:
2787 2787 return orig(ui, repo, *args, **kwargs)
2788 2788
2789 2789 q = repo.mq
2790 2790 r = q.qrepo()
2791 2791 if not r:
2792 2792 raise util.Abort(_('no queue repository'))
2793 2793 return orig(r.ui, r, *args, **kwargs)
2794 2794
2795 2795 def summary(orig, ui, repo, *args, **kwargs):
2796 2796 r = orig(ui, repo, *args, **kwargs)
2797 2797 q = repo.mq
2798 2798 m = []
2799 2799 a, u = len(q.applied), len(q.unapplied(repo))
2800 2800 if a:
2801 2801 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2802 2802 if u:
2803 2803 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2804 2804 if m:
2805 2805 ui.write("mq: %s\n" % ', '.join(m))
2806 2806 else:
2807 2807 ui.note(_("mq: (empty queue)\n"))
2808 2808 return r
2809 2809
2810 2810 def uisetup(ui):
2811 2811 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2812 2812
2813 2813 extensions.wrapcommand(commands.table, 'import', mqimport)
2814 2814 extensions.wrapcommand(commands.table, 'summary', summary)
2815 2815
2816 2816 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2817 2817 entry[1].extend(mqopt)
2818 2818
2819 2819 norepo = commands.norepo.split(" ")
2820 2820 for cmd in commands.table.keys():
2821 2821 cmd = cmdutil.parsealiases(cmd)[0]
2822 2822 if cmd in norepo:
2823 2823 continue
2824 2824 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2825 2825 entry[1].extend(mqopt)
2826 2826
2827 2827 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2828 2828
2829 2829 cmdtable = {
2830 2830 "qapplied":
2831 2831 (applied,
2832 2832 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2833 2833 _('hg qapplied [-1] [-s] [PATCH]')),
2834 2834 "qclone":
2835 2835 (clone,
2836 2836 [('', 'pull', None, _('use pull protocol to copy metadata')),
2837 2837 ('U', 'noupdate', None, _('do not update the new working directories')),
2838 2838 ('', 'uncompressed', None,
2839 2839 _('use uncompressed transfer (fast over LAN)')),
2840 2840 ('p', 'patches', '',
2841 2841 _('location of source patch repository'), _('REPO')),
2842 2842 ] + commands.remoteopts,
2843 2843 _('hg qclone [OPTION]... SOURCE [DEST]')),
2844 2844 "qcommit|qci":
2845 2845 (commit,
2846 2846 commands.table["^commit|ci"][1],
2847 2847 _('hg qcommit [OPTION]... [FILE]...')),
2848 2848 "^qdiff":
2849 2849 (diff,
2850 2850 commands.diffopts + commands.diffopts2 + commands.walkopts,
2851 2851 _('hg qdiff [OPTION]... [FILE]...')),
2852 2852 "qdelete|qremove|qrm":
2853 2853 (delete,
2854 2854 [('k', 'keep', None, _('keep patch file')),
2855 2855 ('r', 'rev', [],
2856 2856 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2857 2857 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2858 2858 'qfold':
2859 2859 (fold,
2860 2860 [('e', 'edit', None, _('edit patch header')),
2861 2861 ('k', 'keep', None, _('keep folded patch files')),
2862 2862 ] + commands.commitopts,
2863 2863 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2864 2864 'qgoto':
2865 2865 (goto,
2866 2866 [('f', 'force', None, _('overwrite any local changes'))],
2867 2867 _('hg qgoto [OPTION]... PATCH')),
2868 2868 'qguard':
2869 2869 (guard,
2870 2870 [('l', 'list', None, _('list all patches and guards')),
2871 2871 ('n', 'none', None, _('drop all guards'))],
2872 2872 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2873 2873 'qheader': (header, [], _('hg qheader [PATCH]')),
2874 2874 "qimport":
2875 2875 (qimport,
2876 2876 [('e', 'existing', None, _('import file in patch directory')),
2877 2877 ('n', 'name', '',
2878 2878 _('name of patch file'), _('NAME')),
2879 2879 ('f', 'force', None, _('overwrite existing files')),
2880 2880 ('r', 'rev', [],
2881 2881 _('place existing revisions under mq control'), _('REV')),
2882 2882 ('g', 'git', None, _('use git extended diff format')),
2883 2883 ('P', 'push', None, _('qpush after importing'))],
2884 2884 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2885 2885 "^qinit":
2886 2886 (init,
2887 2887 [('c', 'create-repo', None, _('create queue repository'))],
2888 2888 _('hg qinit [-c]')),
2889 2889 "^qnew":
2890 2890 (new,
2891 2891 [('e', 'edit', None, _('edit commit message')),
2892 2892 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2893 2893 ('g', 'git', None, _('use git extended diff format')),
2894 2894 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2895 2895 ('u', 'user', '',
2896 2896 _('add "From: <USER>" to patch'), _('USER')),
2897 2897 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2898 2898 ('d', 'date', '',
2899 2899 _('add "Date: <DATE>" to patch'), _('DATE'))
2900 2900 ] + commands.walkopts + commands.commitopts,
2901 2901 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
2902 2902 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2903 2903 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2904 2904 "^qpop":
2905 2905 (pop,
2906 2906 [('a', 'all', None, _('pop all patches')),
2907 2907 ('n', 'name', '',
2908 2908 _('queue name to pop (DEPRECATED)'), _('NAME')),
2909 2909 ('f', 'force', None, _('forget any local changes to patched files'))],
2910 2910 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2911 2911 "^qpush":
2912 2912 (push,
2913 2913 [('f', 'force', None, _('apply if the patch has rejects')),
2914 2914 ('l', 'list', None, _('list patch name in commit text')),
2915 2915 ('a', 'all', None, _('apply all patches')),
2916 2916 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2917 2917 ('n', 'name', '',
2918 2918 _('merge queue name (DEPRECATED)'), _('NAME')),
2919 2919 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2920 2920 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
2921 2921 "^qrefresh":
2922 2922 (refresh,
2923 2923 [('e', 'edit', None, _('edit commit message')),
2924 2924 ('g', 'git', None, _('use git extended diff format')),
2925 2925 ('s', 'short', None,
2926 2926 _('refresh only files already in the patch and specified files')),
2927 2927 ('U', 'currentuser', None,
2928 2928 _('add/update author field in patch with current user')),
2929 2929 ('u', 'user', '',
2930 2930 _('add/update author field in patch with given user'), _('USER')),
2931 2931 ('D', 'currentdate', None,
2932 2932 _('add/update date field in patch with current date')),
2933 2933 ('d', 'date', '',
2934 2934 _('add/update date field in patch with given date'), _('DATE'))
2935 2935 ] + commands.walkopts + commands.commitopts,
2936 2936 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2937 2937 'qrename|qmv':
2938 2938 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2939 2939 "qrestore":
2940 2940 (restore,
2941 2941 [('d', 'delete', None, _('delete save entry')),
2942 2942 ('u', 'update', None, _('update queue working directory'))],
2943 2943 _('hg qrestore [-d] [-u] REV')),
2944 2944 "qsave":
2945 2945 (save,
2946 2946 [('c', 'copy', None, _('copy patch directory')),
2947 2947 ('n', 'name', '',
2948 2948 _('copy directory name'), _('NAME')),
2949 2949 ('e', 'empty', None, _('clear queue status file')),
2950 2950 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2951 2951 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2952 2952 "qselect":
2953 2953 (select,
2954 2954 [('n', 'none', None, _('disable all guards')),
2955 2955 ('s', 'series', None, _('list all guards in series file')),
2956 2956 ('', 'pop', None, _('pop to before first guarded applied patch')),
2957 2957 ('', 'reapply', None, _('pop, then reapply patches'))],
2958 2958 _('hg qselect [OPTION]... [GUARD]...')),
2959 2959 "qseries":
2960 2960 (series,
2961 2961 [('m', 'missing', None, _('print patches not in series')),
2962 2962 ] + seriesopts,
2963 2963 _('hg qseries [-ms]')),
2964 2964 "strip":
2965 2965 (strip,
2966 2966 [('f', 'force', None, _('force removal of changesets even if the '
2967 2967 'working directory has uncommitted changes')),
2968 2968 ('b', 'backup', None, _('bundle only changesets with local revision'
2969 2969 ' number greater than REV which are not'
2970 2970 ' descendants of REV (DEPRECATED)')),
2971 2971 ('n', 'nobackup', None, _('no backups'))],
2972 2972 _('hg strip [-f] [-n] REV')),
2973 2973 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2974 2974 "qunapplied":
2975 2975 (unapplied,
2976 2976 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2977 2977 _('hg qunapplied [-1] [-s] [PATCH]')),
2978 2978 "qfinish":
2979 2979 (finish,
2980 2980 [('a', 'applied', None, _('finish all applied changesets'))],
2981 2981 _('hg qfinish [-a] [REV]...')),
2982 2982 'qqueue':
2983 2983 (qqueue,
2984 2984 [
2985 2985 ('l', 'list', False, _('list all available queues')),
2986 2986 ('c', 'create', False, _('create new queue')),
2987 2987 ('', 'delete', False, _('delete reference to queue')),
2988 2988 ],
2989 2989 _('[OPTION] [QUEUE]')),
2990 2990 }
2991 2991
2992 2992 colortable = {'qguard.negative': 'red',
2993 2993 'qguard.positive': 'yellow',
2994 2994 'qguard.unguarded': 'green',
2995 2995 'qseries.applied': 'blue bold underline',
2996 2996 'qseries.guarded': 'black bold',
2997 2997 'qseries.missing': 'red bold',
2998 2998 'qseries.unapplied': 'black bold'}
General Comments 0
You need to be logged in to leave comments. Login now