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