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