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