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