##// END OF EJS Templates
mq: use the new checklocalchange in the strip command...
Pierre-Yves David -
r19815:378567bf default
parent child Browse files
Show More
@@ -1,3620 +1,3620 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, error, phases, bookmarks
67 67 from mercurial import patch as patchmod
68 68 from mercurial import localrepo
69 69 from mercurial import subrepo
70 70 import os, re, errno, shutil
71 71
72 72 commands.norepo += " qclone"
73 73
74 74 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
75 75
76 76 cmdtable = {}
77 77 command = cmdutil.command(cmdtable)
78 78 testedwith = 'internal'
79 79
80 80 # Patch names looks like unix-file names.
81 81 # They must be joinable with queue directory and result in the patch path.
82 82 normname = util.normpath
83 83
84 84 class statusentry(object):
85 85 def __init__(self, node, name):
86 86 self.node, self.name = node, name
87 87 def __repr__(self):
88 88 return hex(self.node) + ':' + self.name
89 89
90 90 class patchheader(object):
91 91 def __init__(self, pf, plainmode=False):
92 92 def eatdiff(lines):
93 93 while lines:
94 94 l = lines[-1]
95 95 if (l.startswith("diff -") or
96 96 l.startswith("Index:") or
97 97 l.startswith("===========")):
98 98 del lines[-1]
99 99 else:
100 100 break
101 101 def eatempty(lines):
102 102 while lines:
103 103 if not lines[-1].strip():
104 104 del lines[-1]
105 105 else:
106 106 break
107 107
108 108 message = []
109 109 comments = []
110 110 user = None
111 111 date = None
112 112 parent = None
113 113 format = None
114 114 subject = None
115 115 branch = None
116 116 nodeid = None
117 117 diffstart = 0
118 118
119 119 for line in file(pf):
120 120 line = line.rstrip()
121 121 if (line.startswith('diff --git')
122 122 or (diffstart and line.startswith('+++ '))):
123 123 diffstart = 2
124 124 break
125 125 diffstart = 0 # reset
126 126 if line.startswith("--- "):
127 127 diffstart = 1
128 128 continue
129 129 elif format == "hgpatch":
130 130 # parse values when importing the result of an hg export
131 131 if line.startswith("# User "):
132 132 user = line[7:]
133 133 elif line.startswith("# Date "):
134 134 date = line[7:]
135 135 elif line.startswith("# Parent "):
136 136 parent = line[9:].lstrip()
137 137 elif line.startswith("# Branch "):
138 138 branch = line[9:]
139 139 elif line.startswith("# Node ID "):
140 140 nodeid = line[10:]
141 141 elif not line.startswith("# ") and line:
142 142 message.append(line)
143 143 format = None
144 144 elif line == '# HG changeset patch':
145 145 message = []
146 146 format = "hgpatch"
147 147 elif (format != "tagdone" and (line.startswith("Subject: ") or
148 148 line.startswith("subject: "))):
149 149 subject = line[9:]
150 150 format = "tag"
151 151 elif (format != "tagdone" and (line.startswith("From: ") or
152 152 line.startswith("from: "))):
153 153 user = line[6:]
154 154 format = "tag"
155 155 elif (format != "tagdone" and (line.startswith("Date: ") or
156 156 line.startswith("date: "))):
157 157 date = line[6:]
158 158 format = "tag"
159 159 elif format == "tag" and line == "":
160 160 # when looking for tags (subject: from: etc) they
161 161 # end once you find a blank line in the source
162 162 format = "tagdone"
163 163 elif message or line:
164 164 message.append(line)
165 165 comments.append(line)
166 166
167 167 eatdiff(message)
168 168 eatdiff(comments)
169 169 # Remember the exact starting line of the patch diffs before consuming
170 170 # empty lines, for external use by TortoiseHg and others
171 171 self.diffstartline = len(comments)
172 172 eatempty(message)
173 173 eatempty(comments)
174 174
175 175 # make sure message isn't empty
176 176 if format and format.startswith("tag") and subject:
177 177 message.insert(0, "")
178 178 message.insert(0, subject)
179 179
180 180 self.message = message
181 181 self.comments = comments
182 182 self.user = user
183 183 self.date = date
184 184 self.parent = parent
185 185 # nodeid and branch are for external use by TortoiseHg and others
186 186 self.nodeid = nodeid
187 187 self.branch = branch
188 188 self.haspatch = diffstart > 1
189 189 self.plainmode = plainmode
190 190
191 191 def setuser(self, user):
192 192 if not self.updateheader(['From: ', '# User '], user):
193 193 try:
194 194 patchheaderat = self.comments.index('# HG changeset patch')
195 195 self.comments.insert(patchheaderat + 1, '# User ' + user)
196 196 except ValueError:
197 197 if self.plainmode or self._hasheader(['Date: ']):
198 198 self.comments = ['From: ' + user] + self.comments
199 199 else:
200 200 tmp = ['# HG changeset patch', '# User ' + user, '']
201 201 self.comments = tmp + self.comments
202 202 self.user = user
203 203
204 204 def setdate(self, date):
205 205 if not self.updateheader(['Date: ', '# Date '], date):
206 206 try:
207 207 patchheaderat = self.comments.index('# HG changeset patch')
208 208 self.comments.insert(patchheaderat + 1, '# Date ' + date)
209 209 except ValueError:
210 210 if self.plainmode or self._hasheader(['From: ']):
211 211 self.comments = ['Date: ' + date] + self.comments
212 212 else:
213 213 tmp = ['# HG changeset patch', '# Date ' + date, '']
214 214 self.comments = tmp + self.comments
215 215 self.date = date
216 216
217 217 def setparent(self, parent):
218 218 if not self.updateheader(['# Parent '], parent):
219 219 try:
220 220 patchheaderat = self.comments.index('# HG changeset patch')
221 221 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
222 222 except ValueError:
223 223 pass
224 224 self.parent = parent
225 225
226 226 def setmessage(self, message):
227 227 if self.comments:
228 228 self._delmsg()
229 229 self.message = [message]
230 230 self.comments += self.message
231 231
232 232 def updateheader(self, prefixes, new):
233 233 '''Update all references to a field in the patch header.
234 234 Return whether the field is present.'''
235 235 res = False
236 236 for prefix in prefixes:
237 237 for i in xrange(len(self.comments)):
238 238 if self.comments[i].startswith(prefix):
239 239 self.comments[i] = prefix + new
240 240 res = True
241 241 break
242 242 return res
243 243
244 244 def _hasheader(self, prefixes):
245 245 '''Check if a header starts with any of the given prefixes.'''
246 246 for prefix in prefixes:
247 247 for comment in self.comments:
248 248 if comment.startswith(prefix):
249 249 return True
250 250 return False
251 251
252 252 def __str__(self):
253 253 if not self.comments:
254 254 return ''
255 255 return '\n'.join(self.comments) + '\n\n'
256 256
257 257 def _delmsg(self):
258 258 '''Remove existing message, keeping the rest of the comments fields.
259 259 If comments contains 'subject: ', message will prepend
260 260 the field and a blank line.'''
261 261 if self.message:
262 262 subj = 'subject: ' + self.message[0].lower()
263 263 for i in xrange(len(self.comments)):
264 264 if subj == self.comments[i].lower():
265 265 del self.comments[i]
266 266 self.message = self.message[2:]
267 267 break
268 268 ci = 0
269 269 for mi in self.message:
270 270 while mi != self.comments[ci]:
271 271 ci += 1
272 272 del self.comments[ci]
273 273
274 274 def newcommit(repo, phase, *args, **kwargs):
275 275 """helper dedicated to ensure a commit respect mq.secret setting
276 276
277 277 It should be used instead of repo.commit inside the mq source for operation
278 278 creating new changeset.
279 279 """
280 280 repo = repo.unfiltered()
281 281 if phase is None:
282 282 if repo.ui.configbool('mq', 'secret', False):
283 283 phase = phases.secret
284 284 if phase is not None:
285 285 backup = repo.ui.backupconfig('phases', 'new-commit')
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 if phase is not None:
292 292 repo.ui.restoreconfig(backup)
293 293
294 294 class AbortNoCleanup(error.Abort):
295 295 pass
296 296
297 297 class queue(object):
298 298 def __init__(self, ui, baseui, path, patchdir=None):
299 299 self.basepath = path
300 300 try:
301 301 fh = open(os.path.join(path, 'patches.queue'))
302 302 cur = fh.read().rstrip()
303 303 fh.close()
304 304 if not cur:
305 305 curpath = os.path.join(path, 'patches')
306 306 else:
307 307 curpath = os.path.join(path, 'patches-' + cur)
308 308 except IOError:
309 309 curpath = os.path.join(path, 'patches')
310 310 self.path = patchdir or curpath
311 311 self.opener = scmutil.opener(self.path)
312 312 self.ui = ui
313 313 self.baseui = baseui
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 if all_files and '.hgsubstate' in all_files:
801 801 wctx = repo['.']
802 802 mctx = actx = repo[None]
803 803 overwrite = False
804 804 mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx,
805 805 overwrite)
806 806 files += mergedsubstate.keys()
807 807
808 808 match = scmutil.matchfiles(repo, files or [])
809 809 oldtip = repo['tip']
810 810 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
811 811 force=True)
812 812 if repo['tip'] == oldtip:
813 813 raise util.Abort(_("qpush exactly duplicates child changeset"))
814 814 if n is None:
815 815 raise util.Abort(_("repository commit failed"))
816 816
817 817 if update_status:
818 818 self.applied.append(statusentry(n, patchname))
819 819
820 820 if patcherr:
821 821 self.ui.warn(_("patch failed, rejects left in working dir\n"))
822 822 err = 2
823 823 break
824 824
825 825 if fuzz and strict:
826 826 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
827 827 err = 3
828 828 break
829 829 return (err, n)
830 830
831 831 def _cleanup(self, patches, numrevs, keep=False):
832 832 if not keep:
833 833 r = self.qrepo()
834 834 if r:
835 835 r[None].forget(patches)
836 836 for p in patches:
837 837 try:
838 838 os.unlink(self.join(p))
839 839 except OSError, inst:
840 840 if inst.errno != errno.ENOENT:
841 841 raise
842 842
843 843 qfinished = []
844 844 if numrevs:
845 845 qfinished = self.applied[:numrevs]
846 846 del self.applied[:numrevs]
847 847 self.applieddirty = True
848 848
849 849 unknown = []
850 850
851 851 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
852 852 reverse=True):
853 853 if i is not None:
854 854 del self.fullseries[i]
855 855 else:
856 856 unknown.append(p)
857 857
858 858 if unknown:
859 859 if numrevs:
860 860 rev = dict((entry.name, entry.node) for entry in qfinished)
861 861 for p in unknown:
862 862 msg = _('revision %s refers to unknown patches: %s\n')
863 863 self.ui.warn(msg % (short(rev[p]), p))
864 864 else:
865 865 msg = _('unknown patches: %s\n')
866 866 raise util.Abort(''.join(msg % p for p in unknown))
867 867
868 868 self.parseseries()
869 869 self.seriesdirty = True
870 870 return [entry.node for entry in qfinished]
871 871
872 872 def _revpatches(self, repo, revs):
873 873 firstrev = repo[self.applied[0].node].rev()
874 874 patches = []
875 875 for i, rev in enumerate(revs):
876 876
877 877 if rev < firstrev:
878 878 raise util.Abort(_('revision %d is not managed') % rev)
879 879
880 880 ctx = repo[rev]
881 881 base = self.applied[i].node
882 882 if ctx.node() != base:
883 883 msg = _('cannot delete revision %d above applied patches')
884 884 raise util.Abort(msg % rev)
885 885
886 886 patch = self.applied[i].name
887 887 for fmt in ('[mq]: %s', 'imported patch %s'):
888 888 if ctx.description() == fmt % patch:
889 889 msg = _('patch %s finalized without changeset message\n')
890 890 repo.ui.status(msg % patch)
891 891 break
892 892
893 893 patches.append(patch)
894 894 return patches
895 895
896 896 def finish(self, repo, revs):
897 897 # Manually trigger phase computation to ensure phasedefaults is
898 898 # executed before we remove the patches.
899 899 repo._phasecache
900 900 patches = self._revpatches(repo, sorted(revs))
901 901 qfinished = self._cleanup(patches, len(patches))
902 902 if qfinished and repo.ui.configbool('mq', 'secret', False):
903 903 # only use this logic when the secret option is added
904 904 oldqbase = repo[qfinished[0]]
905 905 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
906 906 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
907 907 phases.advanceboundary(repo, tphase, qfinished)
908 908
909 909 def delete(self, repo, patches, opts):
910 910 if not patches and not opts.get('rev'):
911 911 raise util.Abort(_('qdelete requires at least one revision or '
912 912 'patch name'))
913 913
914 914 realpatches = []
915 915 for patch in patches:
916 916 patch = self.lookup(patch, strict=True)
917 917 info = self.isapplied(patch)
918 918 if info:
919 919 raise util.Abort(_("cannot delete applied patch %s") % patch)
920 920 if patch not in self.series:
921 921 raise util.Abort(_("patch %s not in series file") % patch)
922 922 if patch not in realpatches:
923 923 realpatches.append(patch)
924 924
925 925 numrevs = 0
926 926 if opts.get('rev'):
927 927 if not self.applied:
928 928 raise util.Abort(_('no patches applied'))
929 929 revs = scmutil.revrange(repo, opts.get('rev'))
930 930 if len(revs) > 1 and revs[0] > revs[1]:
931 931 revs.reverse()
932 932 revpatches = self._revpatches(repo, revs)
933 933 realpatches += revpatches
934 934 numrevs = len(revpatches)
935 935
936 936 self._cleanup(realpatches, numrevs, opts.get('keep'))
937 937
938 938 def checktoppatch(self, repo):
939 939 '''check that working directory is at qtip'''
940 940 if self.applied:
941 941 top = self.applied[-1].node
942 942 patch = self.applied[-1].name
943 943 if repo.dirstate.p1() != top:
944 944 raise util.Abort(_("working directory revision is not qtip"))
945 945 return top, patch
946 946 return None, None
947 947
948 948 def putsubstate2changes(self, substatestate, changes):
949 949 for files in changes[:3]:
950 950 if '.hgsubstate' in files:
951 951 return # already listed up
952 952 # not yet listed up
953 953 if substatestate in 'a?':
954 954 changes[1].append('.hgsubstate')
955 955 elif substatestate in 'r':
956 956 changes[2].append('.hgsubstate')
957 957 else: # modified
958 958 changes[0].append('.hgsubstate')
959 959
960 960 def checklocalchanges(self, repo, force=False, refresh=True):
961 961 excsuffix = ''
962 962 if refresh:
963 963 excsuffix = ', refresh first'
964 964 # plain versions for i18n tool to detect them
965 965 _("local changes found, refresh first")
966 966 _("local changed subrepos found, refresh first")
967 967 return checklocalchanges(repo, force, excsuffix)
968 968
969 969 _reserved = ('series', 'status', 'guards', '.', '..')
970 970 def checkreservedname(self, name):
971 971 if name in self._reserved:
972 972 raise util.Abort(_('"%s" cannot be used as the name of a patch')
973 973 % name)
974 974 for prefix in ('.hg', '.mq'):
975 975 if name.startswith(prefix):
976 976 raise util.Abort(_('patch name cannot begin with "%s"')
977 977 % prefix)
978 978 for c in ('#', ':'):
979 979 if c in name:
980 980 raise util.Abort(_('"%s" cannot be used in the name of a patch')
981 981 % c)
982 982
983 983 def checkpatchname(self, name, force=False):
984 984 self.checkreservedname(name)
985 985 if not force and os.path.exists(self.join(name)):
986 986 if os.path.isdir(self.join(name)):
987 987 raise util.Abort(_('"%s" already exists as a directory')
988 988 % name)
989 989 else:
990 990 raise util.Abort(_('patch "%s" already exists') % name)
991 991
992 992 def checkkeepchanges(self, keepchanges, force):
993 993 if force and keepchanges:
994 994 raise util.Abort(_('cannot use both --force and --keep-changes'))
995 995
996 996 def new(self, repo, patchfn, *pats, **opts):
997 997 """options:
998 998 msg: a string or a no-argument function returning a string
999 999 """
1000 1000 msg = opts.get('msg')
1001 1001 user = opts.get('user')
1002 1002 date = opts.get('date')
1003 1003 if date:
1004 1004 date = util.parsedate(date)
1005 1005 diffopts = self.diffopts({'git': opts.get('git')})
1006 1006 if opts.get('checkname', True):
1007 1007 self.checkpatchname(patchfn)
1008 1008 inclsubs = checksubstate(repo)
1009 1009 if inclsubs:
1010 1010 inclsubs.append('.hgsubstate')
1011 1011 substatestate = repo.dirstate['.hgsubstate']
1012 1012 if opts.get('include') or opts.get('exclude') or pats:
1013 1013 if inclsubs:
1014 1014 pats = list(pats or []) + inclsubs
1015 1015 match = scmutil.match(repo[None], pats, opts)
1016 1016 # detect missing files in pats
1017 1017 def badfn(f, msg):
1018 1018 if f != '.hgsubstate': # .hgsubstate is auto-created
1019 1019 raise util.Abort('%s: %s' % (f, msg))
1020 1020 match.bad = badfn
1021 1021 changes = repo.status(match=match)
1022 1022 m, a, r, d = changes[:4]
1023 1023 else:
1024 1024 changes = self.checklocalchanges(repo, force=True)
1025 1025 m, a, r, d = changes
1026 1026 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1027 1027 if len(repo[None].parents()) > 1:
1028 1028 raise util.Abort(_('cannot manage merge changesets'))
1029 1029 commitfiles = m + a + r
1030 1030 self.checktoppatch(repo)
1031 1031 insert = self.fullseriesend()
1032 1032 wlock = repo.wlock()
1033 1033 try:
1034 1034 try:
1035 1035 # if patch file write fails, abort early
1036 1036 p = self.opener(patchfn, "w")
1037 1037 except IOError, e:
1038 1038 raise util.Abort(_('cannot write patch "%s": %s')
1039 1039 % (patchfn, e.strerror))
1040 1040 try:
1041 1041 if self.plainmode:
1042 1042 if user:
1043 1043 p.write("From: " + user + "\n")
1044 1044 if not date:
1045 1045 p.write("\n")
1046 1046 if date:
1047 1047 p.write("Date: %d %d\n\n" % date)
1048 1048 else:
1049 1049 p.write("# HG changeset patch\n")
1050 1050 p.write("# Parent "
1051 1051 + hex(repo[None].p1().node()) + "\n")
1052 1052 if user:
1053 1053 p.write("# User " + user + "\n")
1054 1054 if date:
1055 1055 p.write("# Date %s %s\n\n" % date)
1056 1056 if util.safehasattr(msg, '__call__'):
1057 1057 msg = msg()
1058 1058 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1059 1059 n = newcommit(repo, None, commitmsg, user, date, match=match,
1060 1060 force=True)
1061 1061 if n is None:
1062 1062 raise util.Abort(_("repo commit failed"))
1063 1063 try:
1064 1064 self.fullseries[insert:insert] = [patchfn]
1065 1065 self.applied.append(statusentry(n, patchfn))
1066 1066 self.parseseries()
1067 1067 self.seriesdirty = True
1068 1068 self.applieddirty = True
1069 1069 if msg:
1070 1070 msg = msg + "\n\n"
1071 1071 p.write(msg)
1072 1072 if commitfiles:
1073 1073 parent = self.qparents(repo, n)
1074 1074 if inclsubs:
1075 1075 self.putsubstate2changes(substatestate, changes)
1076 1076 chunks = patchmod.diff(repo, node1=parent, node2=n,
1077 1077 changes=changes, opts=diffopts)
1078 1078 for chunk in chunks:
1079 1079 p.write(chunk)
1080 1080 p.close()
1081 1081 r = self.qrepo()
1082 1082 if r:
1083 1083 r[None].add([patchfn])
1084 1084 except: # re-raises
1085 1085 repo.rollback()
1086 1086 raise
1087 1087 except Exception:
1088 1088 patchpath = self.join(patchfn)
1089 1089 try:
1090 1090 os.unlink(patchpath)
1091 1091 except OSError:
1092 1092 self.ui.warn(_('error unlinking %s\n') % patchpath)
1093 1093 raise
1094 1094 self.removeundo(repo)
1095 1095 finally:
1096 1096 release(wlock)
1097 1097
1098 1098 def strip(self, repo, revs, update=True, backup="all", force=None):
1099 1099 wlock = lock = None
1100 1100 try:
1101 1101 wlock = repo.wlock()
1102 1102 lock = repo.lock()
1103 1103
1104 1104 if update:
1105 self.checklocalchanges(repo, force=force, refresh=False)
1105 checklocalchanges(repo, force=force)
1106 1106 urev = self.qparents(repo, revs[0])
1107 1107 hg.clean(repo, urev)
1108 1108 repo.dirstate.write()
1109 1109
1110 1110 repair.strip(self.ui, repo, revs, backup)
1111 1111 finally:
1112 1112 release(lock, wlock)
1113 1113
1114 1114 def isapplied(self, patch):
1115 1115 """returns (index, rev, patch)"""
1116 1116 for i, a in enumerate(self.applied):
1117 1117 if a.name == patch:
1118 1118 return (i, a.node, a.name)
1119 1119 return None
1120 1120
1121 1121 # if the exact patch name does not exist, we try a few
1122 1122 # variations. If strict is passed, we try only #1
1123 1123 #
1124 1124 # 1) a number (as string) to indicate an offset in the series file
1125 1125 # 2) a unique substring of the patch name was given
1126 1126 # 3) patchname[-+]num to indicate an offset in the series file
1127 1127 def lookup(self, patch, strict=False):
1128 1128 def partialname(s):
1129 1129 if s in self.series:
1130 1130 return s
1131 1131 matches = [x for x in self.series if s in x]
1132 1132 if len(matches) > 1:
1133 1133 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1134 1134 for m in matches:
1135 1135 self.ui.warn(' %s\n' % m)
1136 1136 return None
1137 1137 if matches:
1138 1138 return matches[0]
1139 1139 if self.series and self.applied:
1140 1140 if s == 'qtip':
1141 1141 return self.series[self.seriesend(True) - 1]
1142 1142 if s == 'qbase':
1143 1143 return self.series[0]
1144 1144 return None
1145 1145
1146 1146 if patch in self.series:
1147 1147 return patch
1148 1148
1149 1149 if not os.path.isfile(self.join(patch)):
1150 1150 try:
1151 1151 sno = int(patch)
1152 1152 except (ValueError, OverflowError):
1153 1153 pass
1154 1154 else:
1155 1155 if -len(self.series) <= sno < len(self.series):
1156 1156 return self.series[sno]
1157 1157
1158 1158 if not strict:
1159 1159 res = partialname(patch)
1160 1160 if res:
1161 1161 return res
1162 1162 minus = patch.rfind('-')
1163 1163 if minus >= 0:
1164 1164 res = partialname(patch[:minus])
1165 1165 if res:
1166 1166 i = self.series.index(res)
1167 1167 try:
1168 1168 off = int(patch[minus + 1:] or 1)
1169 1169 except (ValueError, OverflowError):
1170 1170 pass
1171 1171 else:
1172 1172 if i - off >= 0:
1173 1173 return self.series[i - off]
1174 1174 plus = patch.rfind('+')
1175 1175 if plus >= 0:
1176 1176 res = partialname(patch[:plus])
1177 1177 if res:
1178 1178 i = self.series.index(res)
1179 1179 try:
1180 1180 off = int(patch[plus + 1:] or 1)
1181 1181 except (ValueError, OverflowError):
1182 1182 pass
1183 1183 else:
1184 1184 if i + off < len(self.series):
1185 1185 return self.series[i + off]
1186 1186 raise util.Abort(_("patch %s not in series") % patch)
1187 1187
1188 1188 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1189 1189 all=False, move=False, exact=False, nobackup=False,
1190 1190 keepchanges=False):
1191 1191 self.checkkeepchanges(keepchanges, force)
1192 1192 diffopts = self.diffopts()
1193 1193 wlock = repo.wlock()
1194 1194 try:
1195 1195 heads = []
1196 1196 for b, ls in repo.branchmap().iteritems():
1197 1197 heads += ls
1198 1198 if not heads:
1199 1199 heads = [nullid]
1200 1200 if repo.dirstate.p1() not in heads and not exact:
1201 1201 self.ui.status(_("(working directory not at a head)\n"))
1202 1202
1203 1203 if not self.series:
1204 1204 self.ui.warn(_('no patches in series\n'))
1205 1205 return 0
1206 1206
1207 1207 # Suppose our series file is: A B C and the current 'top'
1208 1208 # patch is B. qpush C should be performed (moving forward)
1209 1209 # qpush B is a NOP (no change) qpush A is an error (can't
1210 1210 # go backwards with qpush)
1211 1211 if patch:
1212 1212 patch = self.lookup(patch)
1213 1213 info = self.isapplied(patch)
1214 1214 if info and info[0] >= len(self.applied) - 1:
1215 1215 self.ui.warn(
1216 1216 _('qpush: %s is already at the top\n') % patch)
1217 1217 return 0
1218 1218
1219 1219 pushable, reason = self.pushable(patch)
1220 1220 if pushable:
1221 1221 if self.series.index(patch) < self.seriesend():
1222 1222 raise util.Abort(
1223 1223 _("cannot push to a previous patch: %s") % patch)
1224 1224 else:
1225 1225 if reason:
1226 1226 reason = _('guarded by %s') % reason
1227 1227 else:
1228 1228 reason = _('no matching guards')
1229 1229 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1230 1230 return 1
1231 1231 elif all:
1232 1232 patch = self.series[-1]
1233 1233 if self.isapplied(patch):
1234 1234 self.ui.warn(_('all patches are currently applied\n'))
1235 1235 return 0
1236 1236
1237 1237 # Following the above example, starting at 'top' of B:
1238 1238 # qpush should be performed (pushes C), but a subsequent
1239 1239 # qpush without an argument is an error (nothing to
1240 1240 # apply). This allows a loop of "...while hg qpush..." to
1241 1241 # work as it detects an error when done
1242 1242 start = self.seriesend()
1243 1243 if start == len(self.series):
1244 1244 self.ui.warn(_('patch series already fully applied\n'))
1245 1245 return 1
1246 1246 if not force and not keepchanges:
1247 1247 self.checklocalchanges(repo, refresh=self.applied)
1248 1248
1249 1249 if exact:
1250 1250 if keepchanges:
1251 1251 raise util.Abort(
1252 1252 _("cannot use --exact and --keep-changes together"))
1253 1253 if move:
1254 1254 raise util.Abort(_('cannot use --exact and --move '
1255 1255 'together'))
1256 1256 if self.applied:
1257 1257 raise util.Abort(_('cannot push --exact with applied '
1258 1258 'patches'))
1259 1259 root = self.series[start]
1260 1260 target = patchheader(self.join(root), self.plainmode).parent
1261 1261 if not target:
1262 1262 raise util.Abort(
1263 1263 _("%s does not have a parent recorded") % root)
1264 1264 if not repo[target] == repo['.']:
1265 1265 hg.update(repo, target)
1266 1266
1267 1267 if move:
1268 1268 if not patch:
1269 1269 raise util.Abort(_("please specify the patch to move"))
1270 1270 for fullstart, rpn in enumerate(self.fullseries):
1271 1271 # strip markers for patch guards
1272 1272 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1273 1273 break
1274 1274 for i, rpn in enumerate(self.fullseries[fullstart:]):
1275 1275 # strip markers for patch guards
1276 1276 if self.guard_re.split(rpn, 1)[0] == patch:
1277 1277 break
1278 1278 index = fullstart + i
1279 1279 assert index < len(self.fullseries)
1280 1280 fullpatch = self.fullseries[index]
1281 1281 del self.fullseries[index]
1282 1282 self.fullseries.insert(fullstart, fullpatch)
1283 1283 self.parseseries()
1284 1284 self.seriesdirty = True
1285 1285
1286 1286 self.applieddirty = True
1287 1287 if start > 0:
1288 1288 self.checktoppatch(repo)
1289 1289 if not patch:
1290 1290 patch = self.series[start]
1291 1291 end = start + 1
1292 1292 else:
1293 1293 end = self.series.index(patch, start) + 1
1294 1294
1295 1295 tobackup = set()
1296 1296 if (not nobackup and force) or keepchanges:
1297 1297 m, a, r, d = self.checklocalchanges(repo, force=True)
1298 1298 if keepchanges:
1299 1299 tobackup.update(m + a + r + d)
1300 1300 else:
1301 1301 tobackup.update(m + a)
1302 1302
1303 1303 s = self.series[start:end]
1304 1304 all_files = set()
1305 1305 try:
1306 1306 if mergeq:
1307 1307 ret = self.mergepatch(repo, mergeq, s, diffopts)
1308 1308 else:
1309 1309 ret = self.apply(repo, s, list, all_files=all_files,
1310 1310 tobackup=tobackup, keepchanges=keepchanges)
1311 1311 except: # re-raises
1312 1312 self.ui.warn(_('cleaning up working directory...'))
1313 1313 node = repo.dirstate.p1()
1314 1314 hg.revert(repo, node, None)
1315 1315 # only remove unknown files that we know we touched or
1316 1316 # created while patching
1317 1317 for f in all_files:
1318 1318 if f not in repo.dirstate:
1319 1319 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1320 1320 self.ui.warn(_('done\n'))
1321 1321 raise
1322 1322
1323 1323 if not self.applied:
1324 1324 return ret[0]
1325 1325 top = self.applied[-1].name
1326 1326 if ret[0] and ret[0] > 1:
1327 1327 msg = _("errors during apply, please fix and refresh %s\n")
1328 1328 self.ui.write(msg % top)
1329 1329 else:
1330 1330 self.ui.write(_("now at: %s\n") % top)
1331 1331 return ret[0]
1332 1332
1333 1333 finally:
1334 1334 wlock.release()
1335 1335
1336 1336 def pop(self, repo, patch=None, force=False, update=True, all=False,
1337 1337 nobackup=False, keepchanges=False):
1338 1338 self.checkkeepchanges(keepchanges, force)
1339 1339 wlock = repo.wlock()
1340 1340 try:
1341 1341 if patch:
1342 1342 # index, rev, patch
1343 1343 info = self.isapplied(patch)
1344 1344 if not info:
1345 1345 patch = self.lookup(patch)
1346 1346 info = self.isapplied(patch)
1347 1347 if not info:
1348 1348 raise util.Abort(_("patch %s is not applied") % patch)
1349 1349
1350 1350 if not self.applied:
1351 1351 # Allow qpop -a to work repeatedly,
1352 1352 # but not qpop without an argument
1353 1353 self.ui.warn(_("no patches applied\n"))
1354 1354 return not all
1355 1355
1356 1356 if all:
1357 1357 start = 0
1358 1358 elif patch:
1359 1359 start = info[0] + 1
1360 1360 else:
1361 1361 start = len(self.applied) - 1
1362 1362
1363 1363 if start >= len(self.applied):
1364 1364 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1365 1365 return
1366 1366
1367 1367 if not update:
1368 1368 parents = repo.dirstate.parents()
1369 1369 rr = [x.node for x in self.applied]
1370 1370 for p in parents:
1371 1371 if p in rr:
1372 1372 self.ui.warn(_("qpop: forcing dirstate update\n"))
1373 1373 update = True
1374 1374 else:
1375 1375 parents = [p.node() for p in repo[None].parents()]
1376 1376 needupdate = False
1377 1377 for entry in self.applied[start:]:
1378 1378 if entry.node in parents:
1379 1379 needupdate = True
1380 1380 break
1381 1381 update = needupdate
1382 1382
1383 1383 tobackup = set()
1384 1384 if update:
1385 1385 m, a, r, d = self.checklocalchanges(
1386 1386 repo, force=force or keepchanges)
1387 1387 if force:
1388 1388 if not nobackup:
1389 1389 tobackup.update(m + a)
1390 1390 elif keepchanges:
1391 1391 tobackup.update(m + a + r + d)
1392 1392
1393 1393 self.applieddirty = True
1394 1394 end = len(self.applied)
1395 1395 rev = self.applied[start].node
1396 1396
1397 1397 try:
1398 1398 heads = repo.changelog.heads(rev)
1399 1399 except error.LookupError:
1400 1400 node = short(rev)
1401 1401 raise util.Abort(_('trying to pop unknown node %s') % node)
1402 1402
1403 1403 if heads != [self.applied[-1].node]:
1404 1404 raise util.Abort(_("popping would remove a revision not "
1405 1405 "managed by this patch queue"))
1406 1406 if not repo[self.applied[-1].node].mutable():
1407 1407 raise util.Abort(
1408 1408 _("popping would remove an immutable revision"),
1409 1409 hint=_('see "hg help phases" for details'))
1410 1410
1411 1411 # we know there are no local changes, so we can make a simplified
1412 1412 # form of hg.update.
1413 1413 if update:
1414 1414 qp = self.qparents(repo, rev)
1415 1415 ctx = repo[qp]
1416 1416 m, a, r, d = repo.status(qp, '.')[:4]
1417 1417 if d:
1418 1418 raise util.Abort(_("deletions found between repo revs"))
1419 1419
1420 1420 tobackup = set(a + m + r) & tobackup
1421 1421 if keepchanges and tobackup:
1422 1422 raise util.Abort(_("local changes found, refresh first"))
1423 1423 self.backup(repo, tobackup)
1424 1424
1425 1425 for f in a:
1426 1426 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1427 1427 repo.dirstate.drop(f)
1428 1428 for f in m + r:
1429 1429 fctx = ctx[f]
1430 1430 repo.wwrite(f, fctx.data(), fctx.flags())
1431 1431 repo.dirstate.normal(f)
1432 1432 repo.setparents(qp, nullid)
1433 1433 for patch in reversed(self.applied[start:end]):
1434 1434 self.ui.status(_("popping %s\n") % patch.name)
1435 1435 del self.applied[start:end]
1436 1436 self.strip(repo, [rev], update=False, backup='strip')
1437 1437 for s, state in repo['.'].substate.items():
1438 1438 repo['.'].sub(s).get(state)
1439 1439 if self.applied:
1440 1440 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1441 1441 else:
1442 1442 self.ui.write(_("patch queue now empty\n"))
1443 1443 finally:
1444 1444 wlock.release()
1445 1445
1446 1446 def diff(self, repo, pats, opts):
1447 1447 top, patch = self.checktoppatch(repo)
1448 1448 if not top:
1449 1449 self.ui.write(_("no patches applied\n"))
1450 1450 return
1451 1451 qp = self.qparents(repo, top)
1452 1452 if opts.get('reverse'):
1453 1453 node1, node2 = None, qp
1454 1454 else:
1455 1455 node1, node2 = qp, None
1456 1456 diffopts = self.diffopts(opts, patch)
1457 1457 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1458 1458
1459 1459 def refresh(self, repo, pats=None, **opts):
1460 1460 if not self.applied:
1461 1461 self.ui.write(_("no patches applied\n"))
1462 1462 return 1
1463 1463 msg = opts.get('msg', '').rstrip()
1464 1464 newuser = opts.get('user')
1465 1465 newdate = opts.get('date')
1466 1466 if newdate:
1467 1467 newdate = '%d %d' % util.parsedate(newdate)
1468 1468 wlock = repo.wlock()
1469 1469
1470 1470 try:
1471 1471 self.checktoppatch(repo)
1472 1472 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1473 1473 if repo.changelog.heads(top) != [top]:
1474 1474 raise util.Abort(_("cannot refresh a revision with children"))
1475 1475 if not repo[top].mutable():
1476 1476 raise util.Abort(_("cannot refresh immutable revision"),
1477 1477 hint=_('see "hg help phases" for details'))
1478 1478
1479 1479 cparents = repo.changelog.parents(top)
1480 1480 patchparent = self.qparents(repo, top)
1481 1481
1482 1482 inclsubs = checksubstate(repo, hex(patchparent))
1483 1483 if inclsubs:
1484 1484 inclsubs.append('.hgsubstate')
1485 1485 substatestate = repo.dirstate['.hgsubstate']
1486 1486
1487 1487 ph = patchheader(self.join(patchfn), self.plainmode)
1488 1488 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1489 1489 if msg:
1490 1490 ph.setmessage(msg)
1491 1491 if newuser:
1492 1492 ph.setuser(newuser)
1493 1493 if newdate:
1494 1494 ph.setdate(newdate)
1495 1495 ph.setparent(hex(patchparent))
1496 1496
1497 1497 # only commit new patch when write is complete
1498 1498 patchf = self.opener(patchfn, 'w', atomictemp=True)
1499 1499
1500 1500 comments = str(ph)
1501 1501 if comments:
1502 1502 patchf.write(comments)
1503 1503
1504 1504 # update the dirstate in place, strip off the qtip commit
1505 1505 # and then commit.
1506 1506 #
1507 1507 # this should really read:
1508 1508 # mm, dd, aa = repo.status(top, patchparent)[:3]
1509 1509 # but we do it backwards to take advantage of manifest/changelog
1510 1510 # caching against the next repo.status call
1511 1511 mm, aa, dd = repo.status(patchparent, top)[:3]
1512 1512 changes = repo.changelog.read(top)
1513 1513 man = repo.manifest.read(changes[0])
1514 1514 aaa = aa[:]
1515 1515 matchfn = scmutil.match(repo[None], pats, opts)
1516 1516 # in short mode, we only diff the files included in the
1517 1517 # patch already plus specified files
1518 1518 if opts.get('short'):
1519 1519 # if amending a patch, we start with existing
1520 1520 # files plus specified files - unfiltered
1521 1521 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1522 1522 # filter with include/exclude options
1523 1523 matchfn = scmutil.match(repo[None], opts=opts)
1524 1524 else:
1525 1525 match = scmutil.matchall(repo)
1526 1526 m, a, r, d = repo.status(match=match)[:4]
1527 1527 mm = set(mm)
1528 1528 aa = set(aa)
1529 1529 dd = set(dd)
1530 1530
1531 1531 # we might end up with files that were added between
1532 1532 # qtip and the dirstate parent, but then changed in the
1533 1533 # local dirstate. in this case, we want them to only
1534 1534 # show up in the added section
1535 1535 for x in m:
1536 1536 if x not in aa:
1537 1537 mm.add(x)
1538 1538 # we might end up with files added by the local dirstate that
1539 1539 # were deleted by the patch. In this case, they should only
1540 1540 # show up in the changed section.
1541 1541 for x in a:
1542 1542 if x in dd:
1543 1543 dd.remove(x)
1544 1544 mm.add(x)
1545 1545 else:
1546 1546 aa.add(x)
1547 1547 # make sure any files deleted in the local dirstate
1548 1548 # are not in the add or change column of the patch
1549 1549 forget = []
1550 1550 for x in d + r:
1551 1551 if x in aa:
1552 1552 aa.remove(x)
1553 1553 forget.append(x)
1554 1554 continue
1555 1555 else:
1556 1556 mm.discard(x)
1557 1557 dd.add(x)
1558 1558
1559 1559 m = list(mm)
1560 1560 r = list(dd)
1561 1561 a = list(aa)
1562 1562
1563 1563 # create 'match' that includes the files to be recommitted.
1564 1564 # apply matchfn via repo.status to ensure correct case handling.
1565 1565 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1566 1566 allmatches = set(cm + ca + cr + cd)
1567 1567 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1568 1568
1569 1569 files = set(inclsubs)
1570 1570 for x in refreshchanges:
1571 1571 files.update(x)
1572 1572 match = scmutil.matchfiles(repo, files)
1573 1573
1574 1574 bmlist = repo[top].bookmarks()
1575 1575
1576 1576 try:
1577 1577 if diffopts.git or diffopts.upgrade:
1578 1578 copies = {}
1579 1579 for dst in a:
1580 1580 src = repo.dirstate.copied(dst)
1581 1581 # during qfold, the source file for copies may
1582 1582 # be removed. Treat this as a simple add.
1583 1583 if src is not None and src in repo.dirstate:
1584 1584 copies.setdefault(src, []).append(dst)
1585 1585 repo.dirstate.add(dst)
1586 1586 # remember the copies between patchparent and qtip
1587 1587 for dst in aaa:
1588 1588 f = repo.file(dst)
1589 1589 src = f.renamed(man[dst])
1590 1590 if src:
1591 1591 copies.setdefault(src[0], []).extend(
1592 1592 copies.get(dst, []))
1593 1593 if dst in a:
1594 1594 copies[src[0]].append(dst)
1595 1595 # we can't copy a file created by the patch itself
1596 1596 if dst in copies:
1597 1597 del copies[dst]
1598 1598 for src, dsts in copies.iteritems():
1599 1599 for dst in dsts:
1600 1600 repo.dirstate.copy(src, dst)
1601 1601 else:
1602 1602 for dst in a:
1603 1603 repo.dirstate.add(dst)
1604 1604 # Drop useless copy information
1605 1605 for f in list(repo.dirstate.copies()):
1606 1606 repo.dirstate.copy(None, f)
1607 1607 for f in r:
1608 1608 repo.dirstate.remove(f)
1609 1609 # if the patch excludes a modified file, mark that
1610 1610 # file with mtime=0 so status can see it.
1611 1611 mm = []
1612 1612 for i in xrange(len(m) - 1, -1, -1):
1613 1613 if not matchfn(m[i]):
1614 1614 mm.append(m[i])
1615 1615 del m[i]
1616 1616 for f in m:
1617 1617 repo.dirstate.normal(f)
1618 1618 for f in mm:
1619 1619 repo.dirstate.normallookup(f)
1620 1620 for f in forget:
1621 1621 repo.dirstate.drop(f)
1622 1622
1623 1623 if not msg:
1624 1624 if not ph.message:
1625 1625 message = "[mq]: %s\n" % patchfn
1626 1626 else:
1627 1627 message = "\n".join(ph.message)
1628 1628 else:
1629 1629 message = msg
1630 1630
1631 1631 user = ph.user or changes[1]
1632 1632
1633 1633 oldphase = repo[top].phase()
1634 1634
1635 1635 # assumes strip can roll itself back if interrupted
1636 1636 repo.setparents(*cparents)
1637 1637 self.applied.pop()
1638 1638 self.applieddirty = True
1639 1639 self.strip(repo, [top], update=False,
1640 1640 backup='strip')
1641 1641 except: # re-raises
1642 1642 repo.dirstate.invalidate()
1643 1643 raise
1644 1644
1645 1645 try:
1646 1646 # might be nice to attempt to roll back strip after this
1647 1647
1648 1648 # Ensure we create a new changeset in the same phase than
1649 1649 # the old one.
1650 1650 n = newcommit(repo, oldphase, message, user, ph.date,
1651 1651 match=match, force=True)
1652 1652 # only write patch after a successful commit
1653 1653 c = [list(x) for x in refreshchanges]
1654 1654 if inclsubs:
1655 1655 self.putsubstate2changes(substatestate, c)
1656 1656 chunks = patchmod.diff(repo, patchparent,
1657 1657 changes=c, opts=diffopts)
1658 1658 for chunk in chunks:
1659 1659 patchf.write(chunk)
1660 1660 patchf.close()
1661 1661
1662 1662 marks = repo._bookmarks
1663 1663 for bm in bmlist:
1664 1664 marks[bm] = n
1665 1665 marks.write()
1666 1666
1667 1667 self.applied.append(statusentry(n, patchfn))
1668 1668 except: # re-raises
1669 1669 ctx = repo[cparents[0]]
1670 1670 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1671 1671 self.savedirty()
1672 1672 self.ui.warn(_('refresh interrupted while patch was popped! '
1673 1673 '(revert --all, qpush to recover)\n'))
1674 1674 raise
1675 1675 finally:
1676 1676 wlock.release()
1677 1677 self.removeundo(repo)
1678 1678
1679 1679 def init(self, repo, create=False):
1680 1680 if not create and os.path.isdir(self.path):
1681 1681 raise util.Abort(_("patch queue directory already exists"))
1682 1682 try:
1683 1683 os.mkdir(self.path)
1684 1684 except OSError, inst:
1685 1685 if inst.errno != errno.EEXIST or not create:
1686 1686 raise
1687 1687 if create:
1688 1688 return self.qrepo(create=True)
1689 1689
1690 1690 def unapplied(self, repo, patch=None):
1691 1691 if patch and patch not in self.series:
1692 1692 raise util.Abort(_("patch %s is not in series file") % patch)
1693 1693 if not patch:
1694 1694 start = self.seriesend()
1695 1695 else:
1696 1696 start = self.series.index(patch) + 1
1697 1697 unapplied = []
1698 1698 for i in xrange(start, len(self.series)):
1699 1699 pushable, reason = self.pushable(i)
1700 1700 if pushable:
1701 1701 unapplied.append((i, self.series[i]))
1702 1702 self.explainpushable(i)
1703 1703 return unapplied
1704 1704
1705 1705 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1706 1706 summary=False):
1707 1707 def displayname(pfx, patchname, state):
1708 1708 if pfx:
1709 1709 self.ui.write(pfx)
1710 1710 if summary:
1711 1711 ph = patchheader(self.join(patchname), self.plainmode)
1712 1712 msg = ph.message and ph.message[0] or ''
1713 1713 if self.ui.formatted():
1714 1714 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1715 1715 if width > 0:
1716 1716 msg = util.ellipsis(msg, width)
1717 1717 else:
1718 1718 msg = ''
1719 1719 self.ui.write(patchname, label='qseries.' + state)
1720 1720 self.ui.write(': ')
1721 1721 self.ui.write(msg, label='qseries.message.' + state)
1722 1722 else:
1723 1723 self.ui.write(patchname, label='qseries.' + state)
1724 1724 self.ui.write('\n')
1725 1725
1726 1726 applied = set([p.name for p in self.applied])
1727 1727 if length is None:
1728 1728 length = len(self.series) - start
1729 1729 if not missing:
1730 1730 if self.ui.verbose:
1731 1731 idxwidth = len(str(start + length - 1))
1732 1732 for i in xrange(start, start + length):
1733 1733 patch = self.series[i]
1734 1734 if patch in applied:
1735 1735 char, state = 'A', 'applied'
1736 1736 elif self.pushable(i)[0]:
1737 1737 char, state = 'U', 'unapplied'
1738 1738 else:
1739 1739 char, state = 'G', 'guarded'
1740 1740 pfx = ''
1741 1741 if self.ui.verbose:
1742 1742 pfx = '%*d %s ' % (idxwidth, i, char)
1743 1743 elif status and status != char:
1744 1744 continue
1745 1745 displayname(pfx, patch, state)
1746 1746 else:
1747 1747 msng_list = []
1748 1748 for root, dirs, files in os.walk(self.path):
1749 1749 d = root[len(self.path) + 1:]
1750 1750 for f in files:
1751 1751 fl = os.path.join(d, f)
1752 1752 if (fl not in self.series and
1753 1753 fl not in (self.statuspath, self.seriespath,
1754 1754 self.guardspath)
1755 1755 and not fl.startswith('.')):
1756 1756 msng_list.append(fl)
1757 1757 for x in sorted(msng_list):
1758 1758 pfx = self.ui.verbose and ('D ') or ''
1759 1759 displayname(pfx, x, 'missing')
1760 1760
1761 1761 def issaveline(self, l):
1762 1762 if l.name == '.hg.patches.save.line':
1763 1763 return True
1764 1764
1765 1765 def qrepo(self, create=False):
1766 1766 ui = self.baseui.copy()
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 nextpatch(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 nextpatch(end + 1)
1882 1882 return nextpatch(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 = hg.openpath(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 . -n patch will place the current revision
2137 2137 under mq control). With -g/--git, patches imported with --rev will
2138 2138 use the git diff format. See the diffs help topic for information
2139 2139 on why this is important for preserving rename/copy information
2140 2140 and permission changes. Use :hg:`qfinish` to remove changesets
2141 2141 from mq control.
2142 2142
2143 2143 To import a patch from standard input, pass - as the patch file.
2144 2144 When importing from standard input, a patch name must be specified
2145 2145 using the --name flag.
2146 2146
2147 2147 To import an existing patch while renaming it::
2148 2148
2149 2149 hg qimport -e existing-patch -n new-name
2150 2150
2151 2151 Returns 0 if import succeeded.
2152 2152 """
2153 2153 lock = repo.lock() # cause this may move phase
2154 2154 try:
2155 2155 q = repo.mq
2156 2156 try:
2157 2157 imported = q.qimport(
2158 2158 repo, filename, patchname=opts.get('name'),
2159 2159 existing=opts.get('existing'), force=opts.get('force'),
2160 2160 rev=opts.get('rev'), git=opts.get('git'))
2161 2161 finally:
2162 2162 q.savedirty()
2163 2163 finally:
2164 2164 lock.release()
2165 2165
2166 2166 if imported and opts.get('push') and not opts.get('rev'):
2167 2167 return q.push(repo, imported[-1])
2168 2168 return 0
2169 2169
2170 2170 def qinit(ui, repo, create):
2171 2171 """initialize a new queue repository
2172 2172
2173 2173 This command also creates a series file for ordering patches, and
2174 2174 an mq-specific .hgignore file in the queue repository, to exclude
2175 2175 the status and guards files (these contain mostly transient state).
2176 2176
2177 2177 Returns 0 if initialization succeeded."""
2178 2178 q = repo.mq
2179 2179 r = q.init(repo, create)
2180 2180 q.savedirty()
2181 2181 if r:
2182 2182 if not os.path.exists(r.wjoin('.hgignore')):
2183 2183 fp = r.wopener('.hgignore', 'w')
2184 2184 fp.write('^\\.hg\n')
2185 2185 fp.write('^\\.mq\n')
2186 2186 fp.write('syntax: glob\n')
2187 2187 fp.write('status\n')
2188 2188 fp.write('guards\n')
2189 2189 fp.close()
2190 2190 if not os.path.exists(r.wjoin('series')):
2191 2191 r.wopener('series', 'w').close()
2192 2192 r[None].add(['.hgignore', 'series'])
2193 2193 commands.add(ui, r)
2194 2194 return 0
2195 2195
2196 2196 @command("^qinit",
2197 2197 [('c', 'create-repo', None, _('create queue repository'))],
2198 2198 _('hg qinit [-c]'))
2199 2199 def init(ui, repo, **opts):
2200 2200 """init a new queue repository (DEPRECATED)
2201 2201
2202 2202 The queue repository is unversioned by default. If
2203 2203 -c/--create-repo is specified, qinit will create a separate nested
2204 2204 repository for patches (qinit -c may also be run later to convert
2205 2205 an unversioned patch repository into a versioned one). You can use
2206 2206 qcommit to commit changes to this queue repository.
2207 2207
2208 2208 This command is deprecated. Without -c, it's implied by other relevant
2209 2209 commands. With -c, use :hg:`init --mq` instead."""
2210 2210 return qinit(ui, repo, create=opts.get('create_repo'))
2211 2211
2212 2212 @command("qclone",
2213 2213 [('', 'pull', None, _('use pull protocol to copy metadata')),
2214 2214 ('U', 'noupdate', None,
2215 2215 _('do not update the new working directories')),
2216 2216 ('', 'uncompressed', None,
2217 2217 _('use uncompressed transfer (fast over LAN)')),
2218 2218 ('p', 'patches', '',
2219 2219 _('location of source patch repository'), _('REPO')),
2220 2220 ] + commands.remoteopts,
2221 2221 _('hg qclone [OPTION]... SOURCE [DEST]'))
2222 2222 def clone(ui, source, dest=None, **opts):
2223 2223 '''clone main and patch repository at same time
2224 2224
2225 2225 If source is local, destination will have no patches applied. If
2226 2226 source is remote, this command can not check if patches are
2227 2227 applied in source, so cannot guarantee that patches are not
2228 2228 applied in destination. If you clone remote repository, be sure
2229 2229 before that it has no patches applied.
2230 2230
2231 2231 Source patch repository is looked for in <src>/.hg/patches by
2232 2232 default. Use -p <url> to change.
2233 2233
2234 2234 The patch directory must be a nested Mercurial repository, as
2235 2235 would be created by :hg:`init --mq`.
2236 2236
2237 2237 Return 0 on success.
2238 2238 '''
2239 2239 def patchdir(repo):
2240 2240 """compute a patch repo url from a repo object"""
2241 2241 url = repo.url()
2242 2242 if url.endswith('/'):
2243 2243 url = url[:-1]
2244 2244 return url + '/.hg/patches'
2245 2245
2246 2246 # main repo (destination and sources)
2247 2247 if dest is None:
2248 2248 dest = hg.defaultdest(source)
2249 2249 sr = hg.peer(ui, opts, ui.expandpath(source))
2250 2250
2251 2251 # patches repo (source only)
2252 2252 if opts.get('patches'):
2253 2253 patchespath = ui.expandpath(opts.get('patches'))
2254 2254 else:
2255 2255 patchespath = patchdir(sr)
2256 2256 try:
2257 2257 hg.peer(ui, opts, patchespath)
2258 2258 except error.RepoError:
2259 2259 raise util.Abort(_('versioned patch repository not found'
2260 2260 ' (see init --mq)'))
2261 2261 qbase, destrev = None, None
2262 2262 if sr.local():
2263 2263 repo = sr.local()
2264 2264 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2265 2265 qbase = repo.mq.applied[0].node
2266 2266 if not hg.islocal(dest):
2267 2267 heads = set(repo.heads())
2268 2268 destrev = list(heads.difference(repo.heads(qbase)))
2269 2269 destrev.append(repo.changelog.parents(qbase)[0])
2270 2270 elif sr.capable('lookup'):
2271 2271 try:
2272 2272 qbase = sr.lookup('qbase')
2273 2273 except error.RepoError:
2274 2274 pass
2275 2275
2276 2276 ui.note(_('cloning main repository\n'))
2277 2277 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2278 2278 pull=opts.get('pull'),
2279 2279 rev=destrev,
2280 2280 update=False,
2281 2281 stream=opts.get('uncompressed'))
2282 2282
2283 2283 ui.note(_('cloning patch repository\n'))
2284 2284 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2285 2285 pull=opts.get('pull'), update=not opts.get('noupdate'),
2286 2286 stream=opts.get('uncompressed'))
2287 2287
2288 2288 if dr.local():
2289 2289 repo = dr.local()
2290 2290 if qbase:
2291 2291 ui.note(_('stripping applied patches from destination '
2292 2292 'repository\n'))
2293 2293 repo.mq.strip(repo, [qbase], update=False, backup=None)
2294 2294 if not opts.get('noupdate'):
2295 2295 ui.note(_('updating destination repository\n'))
2296 2296 hg.update(repo, repo.changelog.tip())
2297 2297
2298 2298 @command("qcommit|qci",
2299 2299 commands.table["^commit|ci"][1],
2300 2300 _('hg qcommit [OPTION]... [FILE]...'))
2301 2301 def commit(ui, repo, *pats, **opts):
2302 2302 """commit changes in the queue repository (DEPRECATED)
2303 2303
2304 2304 This command is deprecated; use :hg:`commit --mq` instead."""
2305 2305 q = repo.mq
2306 2306 r = q.qrepo()
2307 2307 if not r:
2308 2308 raise util.Abort('no queue repository')
2309 2309 commands.commit(r.ui, r, *pats, **opts)
2310 2310
2311 2311 @command("qseries",
2312 2312 [('m', 'missing', None, _('print patches not in series')),
2313 2313 ] + seriesopts,
2314 2314 _('hg qseries [-ms]'))
2315 2315 def series(ui, repo, **opts):
2316 2316 """print the entire series file
2317 2317
2318 2318 Returns 0 on success."""
2319 2319 repo.mq.qseries(repo, missing=opts.get('missing'),
2320 2320 summary=opts.get('summary'))
2321 2321 return 0
2322 2322
2323 2323 @command("qtop", seriesopts, _('hg qtop [-s]'))
2324 2324 def top(ui, repo, **opts):
2325 2325 """print the name of the current patch
2326 2326
2327 2327 Returns 0 on success."""
2328 2328 q = repo.mq
2329 2329 t = q.applied and q.seriesend(True) or 0
2330 2330 if t:
2331 2331 q.qseries(repo, start=t - 1, length=1, status='A',
2332 2332 summary=opts.get('summary'))
2333 2333 else:
2334 2334 ui.write(_("no patches applied\n"))
2335 2335 return 1
2336 2336
2337 2337 @command("qnext", seriesopts, _('hg qnext [-s]'))
2338 2338 def next(ui, repo, **opts):
2339 2339 """print the name of the next pushable patch
2340 2340
2341 2341 Returns 0 on success."""
2342 2342 q = repo.mq
2343 2343 end = q.seriesend()
2344 2344 if end == len(q.series):
2345 2345 ui.write(_("all patches applied\n"))
2346 2346 return 1
2347 2347 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2348 2348
2349 2349 @command("qprev", seriesopts, _('hg qprev [-s]'))
2350 2350 def prev(ui, repo, **opts):
2351 2351 """print the name of the preceding applied patch
2352 2352
2353 2353 Returns 0 on success."""
2354 2354 q = repo.mq
2355 2355 l = len(q.applied)
2356 2356 if l == 1:
2357 2357 ui.write(_("only one patch applied\n"))
2358 2358 return 1
2359 2359 if not l:
2360 2360 ui.write(_("no patches applied\n"))
2361 2361 return 1
2362 2362 idx = q.series.index(q.applied[-2].name)
2363 2363 q.qseries(repo, start=idx, length=1, status='A',
2364 2364 summary=opts.get('summary'))
2365 2365
2366 2366 def setupheaderopts(ui, opts):
2367 2367 if not opts.get('user') and opts.get('currentuser'):
2368 2368 opts['user'] = ui.username()
2369 2369 if not opts.get('date') and opts.get('currentdate'):
2370 2370 opts['date'] = "%d %d" % util.makedate()
2371 2371
2372 2372 @command("^qnew",
2373 2373 [('e', 'edit', None, _('edit commit message')),
2374 2374 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2375 2375 ('g', 'git', None, _('use git extended diff format')),
2376 2376 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2377 2377 ('u', 'user', '',
2378 2378 _('add "From: <USER>" to patch'), _('USER')),
2379 2379 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2380 2380 ('d', 'date', '',
2381 2381 _('add "Date: <DATE>" to patch'), _('DATE'))
2382 2382 ] + commands.walkopts + commands.commitopts,
2383 2383 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2384 2384 def new(ui, repo, patch, *args, **opts):
2385 2385 """create a new patch
2386 2386
2387 2387 qnew creates a new patch on top of the currently-applied patch (if
2388 2388 any). The patch will be initialized with any outstanding changes
2389 2389 in the working directory. You may also use -I/--include,
2390 2390 -X/--exclude, and/or a list of files after the patch name to add
2391 2391 only changes to matching files to the new patch, leaving the rest
2392 2392 as uncommitted modifications.
2393 2393
2394 2394 -u/--user and -d/--date can be used to set the (given) user and
2395 2395 date, respectively. -U/--currentuser and -D/--currentdate set user
2396 2396 to current user and date to current date.
2397 2397
2398 2398 -e/--edit, -m/--message or -l/--logfile set the patch header as
2399 2399 well as the commit message. If none is specified, the header is
2400 2400 empty and the commit message is '[mq]: PATCH'.
2401 2401
2402 2402 Use the -g/--git option to keep the patch in the git extended diff
2403 2403 format. Read the diffs help topic for more information on why this
2404 2404 is important for preserving permission changes and copy/rename
2405 2405 information.
2406 2406
2407 2407 Returns 0 on successful creation of a new patch.
2408 2408 """
2409 2409 msg = cmdutil.logmessage(ui, opts)
2410 2410 def getmsg():
2411 2411 return ui.edit(msg, opts.get('user') or ui.username())
2412 2412 q = repo.mq
2413 2413 opts['msg'] = msg
2414 2414 if opts.get('edit'):
2415 2415 opts['msg'] = getmsg
2416 2416 else:
2417 2417 opts['msg'] = msg
2418 2418 setupheaderopts(ui, opts)
2419 2419 q.new(repo, patch, *args, **opts)
2420 2420 q.savedirty()
2421 2421 return 0
2422 2422
2423 2423 @command("^qrefresh",
2424 2424 [('e', 'edit', None, _('edit commit message')),
2425 2425 ('g', 'git', None, _('use git extended diff format')),
2426 2426 ('s', 'short', None,
2427 2427 _('refresh only files already in the patch and specified files')),
2428 2428 ('U', 'currentuser', None,
2429 2429 _('add/update author field in patch with current user')),
2430 2430 ('u', 'user', '',
2431 2431 _('add/update author field in patch with given user'), _('USER')),
2432 2432 ('D', 'currentdate', None,
2433 2433 _('add/update date field in patch with current date')),
2434 2434 ('d', 'date', '',
2435 2435 _('add/update date field in patch with given date'), _('DATE'))
2436 2436 ] + commands.walkopts + commands.commitopts,
2437 2437 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2438 2438 def refresh(ui, repo, *pats, **opts):
2439 2439 """update the current patch
2440 2440
2441 2441 If any file patterns are provided, the refreshed patch will
2442 2442 contain only the modifications that match those patterns; the
2443 2443 remaining modifications will remain in the working directory.
2444 2444
2445 2445 If -s/--short is specified, files currently included in the patch
2446 2446 will be refreshed just like matched files and remain in the patch.
2447 2447
2448 2448 If -e/--edit is specified, Mercurial will start your configured editor for
2449 2449 you to enter a message. In case qrefresh fails, you will find a backup of
2450 2450 your message in ``.hg/last-message.txt``.
2451 2451
2452 2452 hg add/remove/copy/rename work as usual, though you might want to
2453 2453 use git-style patches (-g/--git or [diff] git=1) to track copies
2454 2454 and renames. See the diffs help topic for more information on the
2455 2455 git diff format.
2456 2456
2457 2457 Returns 0 on success.
2458 2458 """
2459 2459 q = repo.mq
2460 2460 message = cmdutil.logmessage(ui, opts)
2461 2461 if opts.get('edit'):
2462 2462 if not q.applied:
2463 2463 ui.write(_("no patches applied\n"))
2464 2464 return 1
2465 2465 if message:
2466 2466 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2467 2467 patch = q.applied[-1].name
2468 2468 ph = patchheader(q.join(patch), q.plainmode)
2469 2469 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2470 2470 # We don't want to lose the patch message if qrefresh fails (issue2062)
2471 2471 repo.savecommitmessage(message)
2472 2472 setupheaderopts(ui, opts)
2473 2473 wlock = repo.wlock()
2474 2474 try:
2475 2475 ret = q.refresh(repo, pats, msg=message, **opts)
2476 2476 q.savedirty()
2477 2477 return ret
2478 2478 finally:
2479 2479 wlock.release()
2480 2480
2481 2481 @command("^qdiff",
2482 2482 commands.diffopts + commands.diffopts2 + commands.walkopts,
2483 2483 _('hg qdiff [OPTION]... [FILE]...'))
2484 2484 def diff(ui, repo, *pats, **opts):
2485 2485 """diff of the current patch and subsequent modifications
2486 2486
2487 2487 Shows a diff which includes the current patch as well as any
2488 2488 changes which have been made in the working directory since the
2489 2489 last refresh (thus showing what the current patch would become
2490 2490 after a qrefresh).
2491 2491
2492 2492 Use :hg:`diff` if you only want to see the changes made since the
2493 2493 last qrefresh, or :hg:`export qtip` if you want to see changes
2494 2494 made by the current patch without including changes made since the
2495 2495 qrefresh.
2496 2496
2497 2497 Returns 0 on success.
2498 2498 """
2499 2499 repo.mq.diff(repo, pats, opts)
2500 2500 return 0
2501 2501
2502 2502 @command('qfold',
2503 2503 [('e', 'edit', None, _('edit patch header')),
2504 2504 ('k', 'keep', None, _('keep folded patch files')),
2505 2505 ] + commands.commitopts,
2506 2506 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2507 2507 def fold(ui, repo, *files, **opts):
2508 2508 """fold the named patches into the current patch
2509 2509
2510 2510 Patches must not yet be applied. Each patch will be successively
2511 2511 applied to the current patch in the order given. If all the
2512 2512 patches apply successfully, the current patch will be refreshed
2513 2513 with the new cumulative patch, and the folded patches will be
2514 2514 deleted. With -k/--keep, the folded patch files will not be
2515 2515 removed afterwards.
2516 2516
2517 2517 The header for each folded patch will be concatenated with the
2518 2518 current patch header, separated by a line of ``* * *``.
2519 2519
2520 2520 Returns 0 on success."""
2521 2521 q = repo.mq
2522 2522 if not files:
2523 2523 raise util.Abort(_('qfold requires at least one patch name'))
2524 2524 if not q.checktoppatch(repo)[0]:
2525 2525 raise util.Abort(_('no patches applied'))
2526 2526 q.checklocalchanges(repo)
2527 2527
2528 2528 message = cmdutil.logmessage(ui, opts)
2529 2529 if opts.get('edit'):
2530 2530 if message:
2531 2531 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2532 2532
2533 2533 parent = q.lookup('qtip')
2534 2534 patches = []
2535 2535 messages = []
2536 2536 for f in files:
2537 2537 p = q.lookup(f)
2538 2538 if p in patches or p == parent:
2539 2539 ui.warn(_('skipping already folded patch %s\n') % p)
2540 2540 if q.isapplied(p):
2541 2541 raise util.Abort(_('qfold cannot fold already applied patch %s')
2542 2542 % p)
2543 2543 patches.append(p)
2544 2544
2545 2545 for p in patches:
2546 2546 if not message:
2547 2547 ph = patchheader(q.join(p), q.plainmode)
2548 2548 if ph.message:
2549 2549 messages.append(ph.message)
2550 2550 pf = q.join(p)
2551 2551 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2552 2552 if not patchsuccess:
2553 2553 raise util.Abort(_('error folding patch %s') % p)
2554 2554
2555 2555 if not message:
2556 2556 ph = patchheader(q.join(parent), q.plainmode)
2557 2557 message, user = ph.message, ph.user
2558 2558 for msg in messages:
2559 2559 message.append('* * *')
2560 2560 message.extend(msg)
2561 2561 message = '\n'.join(message)
2562 2562
2563 2563 if opts.get('edit'):
2564 2564 message = ui.edit(message, user or ui.username())
2565 2565
2566 2566 diffopts = q.patchopts(q.diffopts(), *patches)
2567 2567 wlock = repo.wlock()
2568 2568 try:
2569 2569 q.refresh(repo, msg=message, git=diffopts.git)
2570 2570 q.delete(repo, patches, opts)
2571 2571 q.savedirty()
2572 2572 finally:
2573 2573 wlock.release()
2574 2574
2575 2575 @command("qgoto",
2576 2576 [('', 'keep-changes', None,
2577 2577 _('tolerate non-conflicting local changes')),
2578 2578 ('f', 'force', None, _('overwrite any local changes')),
2579 2579 ('', 'no-backup', None, _('do not save backup copies of files'))],
2580 2580 _('hg qgoto [OPTION]... PATCH'))
2581 2581 def goto(ui, repo, patch, **opts):
2582 2582 '''push or pop patches until named patch is at top of stack
2583 2583
2584 2584 Returns 0 on success.'''
2585 2585 opts = fixkeepchangesopts(ui, opts)
2586 2586 q = repo.mq
2587 2587 patch = q.lookup(patch)
2588 2588 nobackup = opts.get('no_backup')
2589 2589 keepchanges = opts.get('keep_changes')
2590 2590 if q.isapplied(patch):
2591 2591 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2592 2592 keepchanges=keepchanges)
2593 2593 else:
2594 2594 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2595 2595 keepchanges=keepchanges)
2596 2596 q.savedirty()
2597 2597 return ret
2598 2598
2599 2599 @command("qguard",
2600 2600 [('l', 'list', None, _('list all patches and guards')),
2601 2601 ('n', 'none', None, _('drop all guards'))],
2602 2602 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2603 2603 def guard(ui, repo, *args, **opts):
2604 2604 '''set or print guards for a patch
2605 2605
2606 2606 Guards control whether a patch can be pushed. A patch with no
2607 2607 guards is always pushed. A patch with a positive guard ("+foo") is
2608 2608 pushed only if the :hg:`qselect` command has activated it. A patch with
2609 2609 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2610 2610 has activated it.
2611 2611
2612 2612 With no arguments, print the currently active guards.
2613 2613 With arguments, set guards for the named patch.
2614 2614
2615 2615 .. note::
2616 2616 Specifying negative guards now requires '--'.
2617 2617
2618 2618 To set guards on another patch::
2619 2619
2620 2620 hg qguard other.patch -- +2.6.17 -stable
2621 2621
2622 2622 Returns 0 on success.
2623 2623 '''
2624 2624 def status(idx):
2625 2625 guards = q.seriesguards[idx] or ['unguarded']
2626 2626 if q.series[idx] in applied:
2627 2627 state = 'applied'
2628 2628 elif q.pushable(idx)[0]:
2629 2629 state = 'unapplied'
2630 2630 else:
2631 2631 state = 'guarded'
2632 2632 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2633 2633 ui.write('%s: ' % ui.label(q.series[idx], label))
2634 2634
2635 2635 for i, guard in enumerate(guards):
2636 2636 if guard.startswith('+'):
2637 2637 ui.write(guard, label='qguard.positive')
2638 2638 elif guard.startswith('-'):
2639 2639 ui.write(guard, label='qguard.negative')
2640 2640 else:
2641 2641 ui.write(guard, label='qguard.unguarded')
2642 2642 if i != len(guards) - 1:
2643 2643 ui.write(' ')
2644 2644 ui.write('\n')
2645 2645 q = repo.mq
2646 2646 applied = set(p.name for p in q.applied)
2647 2647 patch = None
2648 2648 args = list(args)
2649 2649 if opts.get('list'):
2650 2650 if args or opts.get('none'):
2651 2651 raise util.Abort(_('cannot mix -l/--list with options or '
2652 2652 'arguments'))
2653 2653 for i in xrange(len(q.series)):
2654 2654 status(i)
2655 2655 return
2656 2656 if not args or args[0][0:1] in '-+':
2657 2657 if not q.applied:
2658 2658 raise util.Abort(_('no patches applied'))
2659 2659 patch = q.applied[-1].name
2660 2660 if patch is None and args[0][0:1] not in '-+':
2661 2661 patch = args.pop(0)
2662 2662 if patch is None:
2663 2663 raise util.Abort(_('no patch to work with'))
2664 2664 if args or opts.get('none'):
2665 2665 idx = q.findseries(patch)
2666 2666 if idx is None:
2667 2667 raise util.Abort(_('no patch named %s') % patch)
2668 2668 q.setguards(idx, args)
2669 2669 q.savedirty()
2670 2670 else:
2671 2671 status(q.series.index(q.lookup(patch)))
2672 2672
2673 2673 @command("qheader", [], _('hg qheader [PATCH]'))
2674 2674 def header(ui, repo, patch=None):
2675 2675 """print the header of the topmost or specified patch
2676 2676
2677 2677 Returns 0 on success."""
2678 2678 q = repo.mq
2679 2679
2680 2680 if patch:
2681 2681 patch = q.lookup(patch)
2682 2682 else:
2683 2683 if not q.applied:
2684 2684 ui.write(_('no patches applied\n'))
2685 2685 return 1
2686 2686 patch = q.lookup('qtip')
2687 2687 ph = patchheader(q.join(patch), q.plainmode)
2688 2688
2689 2689 ui.write('\n'.join(ph.message) + '\n')
2690 2690
2691 2691 def lastsavename(path):
2692 2692 (directory, base) = os.path.split(path)
2693 2693 names = os.listdir(directory)
2694 2694 namere = re.compile("%s.([0-9]+)" % base)
2695 2695 maxindex = None
2696 2696 maxname = None
2697 2697 for f in names:
2698 2698 m = namere.match(f)
2699 2699 if m:
2700 2700 index = int(m.group(1))
2701 2701 if maxindex is None or index > maxindex:
2702 2702 maxindex = index
2703 2703 maxname = f
2704 2704 if maxname:
2705 2705 return (os.path.join(directory, maxname), maxindex)
2706 2706 return (None, None)
2707 2707
2708 2708 def savename(path):
2709 2709 (last, index) = lastsavename(path)
2710 2710 if last is None:
2711 2711 index = 0
2712 2712 newpath = path + ".%d" % (index + 1)
2713 2713 return newpath
2714 2714
2715 2715 @command("^qpush",
2716 2716 [('', 'keep-changes', None,
2717 2717 _('tolerate non-conflicting local changes')),
2718 2718 ('f', 'force', None, _('apply on top of local changes')),
2719 2719 ('e', 'exact', None,
2720 2720 _('apply the target patch to its recorded parent')),
2721 2721 ('l', 'list', None, _('list patch name in commit text')),
2722 2722 ('a', 'all', None, _('apply all patches')),
2723 2723 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2724 2724 ('n', 'name', '',
2725 2725 _('merge queue name (DEPRECATED)'), _('NAME')),
2726 2726 ('', 'move', None,
2727 2727 _('reorder patch series and apply only the patch')),
2728 2728 ('', 'no-backup', None, _('do not save backup copies of files'))],
2729 2729 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2730 2730 def push(ui, repo, patch=None, **opts):
2731 2731 """push the next patch onto the stack
2732 2732
2733 2733 By default, abort if the working directory contains uncommitted
2734 2734 changes. With --keep-changes, abort only if the uncommitted files
2735 2735 overlap with patched files. With -f/--force, backup and patch over
2736 2736 uncommitted changes.
2737 2737
2738 2738 Return 0 on success.
2739 2739 """
2740 2740 q = repo.mq
2741 2741 mergeq = None
2742 2742
2743 2743 opts = fixkeepchangesopts(ui, opts)
2744 2744 if opts.get('merge'):
2745 2745 if opts.get('name'):
2746 2746 newpath = repo.join(opts.get('name'))
2747 2747 else:
2748 2748 newpath, i = lastsavename(q.path)
2749 2749 if not newpath:
2750 2750 ui.warn(_("no saved queues found, please use -n\n"))
2751 2751 return 1
2752 2752 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2753 2753 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2754 2754 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2755 2755 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2756 2756 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2757 2757 keepchanges=opts.get('keep_changes'))
2758 2758 return ret
2759 2759
2760 2760 @command("^qpop",
2761 2761 [('a', 'all', None, _('pop all patches')),
2762 2762 ('n', 'name', '',
2763 2763 _('queue name to pop (DEPRECATED)'), _('NAME')),
2764 2764 ('', 'keep-changes', None,
2765 2765 _('tolerate non-conflicting local changes')),
2766 2766 ('f', 'force', None, _('forget any local changes to patched files')),
2767 2767 ('', 'no-backup', None, _('do not save backup copies of files'))],
2768 2768 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2769 2769 def pop(ui, repo, patch=None, **opts):
2770 2770 """pop the current patch off the stack
2771 2771
2772 2772 Without argument, pops off the top of the patch stack. If given a
2773 2773 patch name, keeps popping off patches until the named patch is at
2774 2774 the top of the stack.
2775 2775
2776 2776 By default, abort if the working directory contains uncommitted
2777 2777 changes. With --keep-changes, abort only if the uncommitted files
2778 2778 overlap with patched files. With -f/--force, backup and discard
2779 2779 changes made to such files.
2780 2780
2781 2781 Return 0 on success.
2782 2782 """
2783 2783 opts = fixkeepchangesopts(ui, opts)
2784 2784 localupdate = True
2785 2785 if opts.get('name'):
2786 2786 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2787 2787 ui.warn(_('using patch queue: %s\n') % q.path)
2788 2788 localupdate = False
2789 2789 else:
2790 2790 q = repo.mq
2791 2791 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2792 2792 all=opts.get('all'), nobackup=opts.get('no_backup'),
2793 2793 keepchanges=opts.get('keep_changes'))
2794 2794 q.savedirty()
2795 2795 return ret
2796 2796
2797 2797 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2798 2798 def rename(ui, repo, patch, name=None, **opts):
2799 2799 """rename a patch
2800 2800
2801 2801 With one argument, renames the current patch to PATCH1.
2802 2802 With two arguments, renames PATCH1 to PATCH2.
2803 2803
2804 2804 Returns 0 on success."""
2805 2805 q = repo.mq
2806 2806 if not name:
2807 2807 name = patch
2808 2808 patch = None
2809 2809
2810 2810 if patch:
2811 2811 patch = q.lookup(patch)
2812 2812 else:
2813 2813 if not q.applied:
2814 2814 ui.write(_('no patches applied\n'))
2815 2815 return
2816 2816 patch = q.lookup('qtip')
2817 2817 absdest = q.join(name)
2818 2818 if os.path.isdir(absdest):
2819 2819 name = normname(os.path.join(name, os.path.basename(patch)))
2820 2820 absdest = q.join(name)
2821 2821 q.checkpatchname(name)
2822 2822
2823 2823 ui.note(_('renaming %s to %s\n') % (patch, name))
2824 2824 i = q.findseries(patch)
2825 2825 guards = q.guard_re.findall(q.fullseries[i])
2826 2826 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2827 2827 q.parseseries()
2828 2828 q.seriesdirty = True
2829 2829
2830 2830 info = q.isapplied(patch)
2831 2831 if info:
2832 2832 q.applied[info[0]] = statusentry(info[1], name)
2833 2833 q.applieddirty = True
2834 2834
2835 2835 destdir = os.path.dirname(absdest)
2836 2836 if not os.path.isdir(destdir):
2837 2837 os.makedirs(destdir)
2838 2838 util.rename(q.join(patch), absdest)
2839 2839 r = q.qrepo()
2840 2840 if r and patch in r.dirstate:
2841 2841 wctx = r[None]
2842 2842 wlock = r.wlock()
2843 2843 try:
2844 2844 if r.dirstate[patch] == 'a':
2845 2845 r.dirstate.drop(patch)
2846 2846 r.dirstate.add(name)
2847 2847 else:
2848 2848 wctx.copy(patch, name)
2849 2849 wctx.forget([patch])
2850 2850 finally:
2851 2851 wlock.release()
2852 2852
2853 2853 q.savedirty()
2854 2854
2855 2855 @command("qrestore",
2856 2856 [('d', 'delete', None, _('delete save entry')),
2857 2857 ('u', 'update', None, _('update queue working directory'))],
2858 2858 _('hg qrestore [-d] [-u] REV'))
2859 2859 def restore(ui, repo, rev, **opts):
2860 2860 """restore the queue state saved by a revision (DEPRECATED)
2861 2861
2862 2862 This command is deprecated, use :hg:`rebase` instead."""
2863 2863 rev = repo.lookup(rev)
2864 2864 q = repo.mq
2865 2865 q.restore(repo, rev, delete=opts.get('delete'),
2866 2866 qupdate=opts.get('update'))
2867 2867 q.savedirty()
2868 2868 return 0
2869 2869
2870 2870 @command("qsave",
2871 2871 [('c', 'copy', None, _('copy patch directory')),
2872 2872 ('n', 'name', '',
2873 2873 _('copy directory name'), _('NAME')),
2874 2874 ('e', 'empty', None, _('clear queue status file')),
2875 2875 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2876 2876 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2877 2877 def save(ui, repo, **opts):
2878 2878 """save current queue state (DEPRECATED)
2879 2879
2880 2880 This command is deprecated, use :hg:`rebase` instead."""
2881 2881 q = repo.mq
2882 2882 message = cmdutil.logmessage(ui, opts)
2883 2883 ret = q.save(repo, msg=message)
2884 2884 if ret:
2885 2885 return ret
2886 2886 q.savedirty() # save to .hg/patches before copying
2887 2887 if opts.get('copy'):
2888 2888 path = q.path
2889 2889 if opts.get('name'):
2890 2890 newpath = os.path.join(q.basepath, opts.get('name'))
2891 2891 if os.path.exists(newpath):
2892 2892 if not os.path.isdir(newpath):
2893 2893 raise util.Abort(_('destination %s exists and is not '
2894 2894 'a directory') % newpath)
2895 2895 if not opts.get('force'):
2896 2896 raise util.Abort(_('destination %s exists, '
2897 2897 'use -f to force') % newpath)
2898 2898 else:
2899 2899 newpath = savename(path)
2900 2900 ui.warn(_("copy %s to %s\n") % (path, newpath))
2901 2901 util.copyfiles(path, newpath)
2902 2902 if opts.get('empty'):
2903 2903 del q.applied[:]
2904 2904 q.applieddirty = True
2905 2905 q.savedirty()
2906 2906 return 0
2907 2907
2908 2908 def checksubstate(repo, baserev=None):
2909 2909 '''return list of subrepos at a different revision than substate.
2910 2910 Abort if any subrepos have uncommitted changes.'''
2911 2911 inclsubs = []
2912 2912 wctx = repo[None]
2913 2913 if baserev:
2914 2914 bctx = repo[baserev]
2915 2915 else:
2916 2916 bctx = wctx.parents()[0]
2917 2917 for s in sorted(wctx.substate):
2918 2918 if wctx.sub(s).dirty(True):
2919 2919 raise util.Abort(
2920 2920 _("uncommitted changes in subrepository %s") % s)
2921 2921 elif s not in bctx.substate or bctx.sub(s).dirty():
2922 2922 inclsubs.append(s)
2923 2923 return inclsubs
2924 2924
2925 2925 def checklocalchanges(repo, force=False, excsuffix=''):
2926 2926 cmdutil.checkunfinished(repo)
2927 2927 m, a, r, d = repo.status()[:4]
2928 2928 if not force:
2929 2929 if (m or a or r or d):
2930 2930 _("local changes found") # i18n tool detection
2931 2931 raise util.Abort(_("local changes found" + excsuffix))
2932 2932 if checksubstate(repo):
2933 2933 _("local changed subrepos found") # i18n tool detection
2934 2934 raise util.Abort(_("local changed subrepos found" + excsuffix))
2935 2935 return m, a, r, d
2936 2936
2937 2937
2938 2938 @command("strip",
2939 2939 [
2940 2940 ('r', 'rev', [], _('strip specified revision (optional, '
2941 2941 'can specify revisions without this '
2942 2942 'option)'), _('REV')),
2943 2943 ('f', 'force', None, _('force removal of changesets, discard '
2944 2944 'uncommitted changes (no backup)')),
2945 2945 ('b', 'backup', None, _('bundle only changesets with local revision'
2946 2946 ' number greater than REV which are not'
2947 2947 ' descendants of REV (DEPRECATED)')),
2948 2948 ('', 'no-backup', None, _('no backups')),
2949 2949 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2950 2950 ('n', '', None, _('ignored (DEPRECATED)')),
2951 2951 ('k', 'keep', None, _("do not modify working copy during strip")),
2952 2952 ('B', 'bookmark', '', _("remove revs only reachable from given"
2953 2953 " bookmark"))],
2954 2954 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2955 2955 def strip(ui, repo, *revs, **opts):
2956 2956 """strip changesets and all their descendants from the repository
2957 2957
2958 2958 The strip command removes the specified changesets and all their
2959 2959 descendants. If the working directory has uncommitted changes, the
2960 2960 operation is aborted unless the --force flag is supplied, in which
2961 2961 case changes will be discarded.
2962 2962
2963 2963 If a parent of the working directory is stripped, then the working
2964 2964 directory will automatically be updated to the most recent
2965 2965 available ancestor of the stripped parent after the operation
2966 2966 completes.
2967 2967
2968 2968 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2969 2969 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2970 2970 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2971 2971 where BUNDLE is the bundle file created by the strip. Note that
2972 2972 the local revision numbers will in general be different after the
2973 2973 restore.
2974 2974
2975 2975 Use the --no-backup option to discard the backup bundle once the
2976 2976 operation completes.
2977 2977
2978 2978 Strip is not a history-rewriting operation and can be used on
2979 2979 changesets in the public phase. But if the stripped changesets have
2980 2980 been pushed to a remote repository you will likely pull them again.
2981 2981
2982 2982 Return 0 on success.
2983 2983 """
2984 2984 backup = 'all'
2985 2985 if opts.get('backup'):
2986 2986 backup = 'strip'
2987 2987 elif opts.get('no_backup') or opts.get('nobackup'):
2988 2988 backup = 'none'
2989 2989
2990 2990 cl = repo.changelog
2991 2991 revs = list(revs) + opts.get('rev')
2992 2992 revs = set(scmutil.revrange(repo, revs))
2993 2993
2994 2994 if opts.get('bookmark'):
2995 2995 mark = opts.get('bookmark')
2996 2996 marks = repo._bookmarks
2997 2997 if mark not in marks:
2998 2998 raise util.Abort(_("bookmark '%s' not found") % mark)
2999 2999
3000 3000 # If the requested bookmark is not the only one pointing to a
3001 3001 # a revision we have to only delete the bookmark and not strip
3002 3002 # anything. revsets cannot detect that case.
3003 3003 uniquebm = True
3004 3004 for m, n in marks.iteritems():
3005 3005 if m != mark and n == repo[mark].node():
3006 3006 uniquebm = False
3007 3007 break
3008 3008 if uniquebm:
3009 3009 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
3010 3010 "ancestors(head() and not bookmark(%s)) - "
3011 3011 "ancestors(bookmark() and not bookmark(%s))",
3012 3012 mark, mark, mark)
3013 3013 revs.update(set(rsrevs))
3014 3014 if not revs:
3015 3015 del marks[mark]
3016 3016 marks.write()
3017 3017 ui.write(_("bookmark '%s' deleted\n") % mark)
3018 3018
3019 3019 if not revs:
3020 3020 raise util.Abort(_('empty revision set'))
3021 3021
3022 3022 descendants = set(cl.descendants(revs))
3023 3023 strippedrevs = revs.union(descendants)
3024 3024 roots = revs.difference(descendants)
3025 3025
3026 3026 update = False
3027 3027 # if one of the wdir parent is stripped we'll need
3028 3028 # to update away to an earlier revision
3029 3029 for p in repo.dirstate.parents():
3030 3030 if p != nullid and cl.rev(p) in strippedrevs:
3031 3031 update = True
3032 3032 break
3033 3033
3034 3034 rootnodes = set(cl.node(r) for r in roots)
3035 3035
3036 3036 q = repo.mq
3037 3037 if q.applied:
3038 3038 # refresh queue state if we're about to strip
3039 3039 # applied patches
3040 3040 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3041 3041 q.applieddirty = True
3042 3042 start = 0
3043 3043 end = len(q.applied)
3044 3044 for i, statusentry in enumerate(q.applied):
3045 3045 if statusentry.node in rootnodes:
3046 3046 # if one of the stripped roots is an applied
3047 3047 # patch, only part of the queue is stripped
3048 3048 start = i
3049 3049 break
3050 3050 del q.applied[start:end]
3051 3051 q.savedirty()
3052 3052
3053 3053 revs = sorted(rootnodes)
3054 3054 if update and opts.get('keep'):
3055 3055 wlock = repo.wlock()
3056 3056 try:
3057 3057 urev = repo.mq.qparents(repo, revs[0])
3058 3058 uctx = repo[urev]
3059 3059
3060 3060 # only reset the dirstate for files that would actually change
3061 3061 # between the working context and uctx
3062 3062 descendantrevs = repo.revs("%s::." % uctx.rev())
3063 3063 changedfiles = []
3064 3064 for rev in descendantrevs:
3065 3065 # blindly reset the files, regardless of what actually changed
3066 3066 changedfiles.extend(repo[rev].files())
3067 3067
3068 3068 # reset files that only changed in the dirstate too
3069 3069 dirstate = repo.dirstate
3070 3070 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
3071 3071 changedfiles.extend(dirchanges)
3072 3072
3073 3073 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
3074 3074 repo.dirstate.write()
3075 3075 update = False
3076 3076 finally:
3077 3077 wlock.release()
3078 3078
3079 3079 if opts.get('bookmark'):
3080 3080 if mark == repo._bookmarkcurrent:
3081 3081 bookmarks.setcurrent(repo, None)
3082 3082 del marks[mark]
3083 3083 marks.write()
3084 3084 ui.write(_("bookmark '%s' deleted\n") % mark)
3085 3085
3086 3086 repo.mq.strip(repo, revs, backup=backup, update=update,
3087 3087 force=opts.get('force'))
3088 3088
3089 3089 return 0
3090 3090
3091 3091 @command("qselect",
3092 3092 [('n', 'none', None, _('disable all guards')),
3093 3093 ('s', 'series', None, _('list all guards in series file')),
3094 3094 ('', 'pop', None, _('pop to before first guarded applied patch')),
3095 3095 ('', 'reapply', None, _('pop, then reapply patches'))],
3096 3096 _('hg qselect [OPTION]... [GUARD]...'))
3097 3097 def select(ui, repo, *args, **opts):
3098 3098 '''set or print guarded patches to push
3099 3099
3100 3100 Use the :hg:`qguard` command to set or print guards on patch, then use
3101 3101 qselect to tell mq which guards to use. A patch will be pushed if
3102 3102 it has no guards or any positive guards match the currently
3103 3103 selected guard, but will not be pushed if any negative guards
3104 3104 match the current guard. For example::
3105 3105
3106 3106 qguard foo.patch -- -stable (negative guard)
3107 3107 qguard bar.patch +stable (positive guard)
3108 3108 qselect stable
3109 3109
3110 3110 This activates the "stable" guard. mq will skip foo.patch (because
3111 3111 it has a negative match) but push bar.patch (because it has a
3112 3112 positive match).
3113 3113
3114 3114 With no arguments, prints the currently active guards.
3115 3115 With one argument, sets the active guard.
3116 3116
3117 3117 Use -n/--none to deactivate guards (no other arguments needed).
3118 3118 When no guards are active, patches with positive guards are
3119 3119 skipped and patches with negative guards are pushed.
3120 3120
3121 3121 qselect can change the guards on applied patches. It does not pop
3122 3122 guarded patches by default. Use --pop to pop back to the last
3123 3123 applied patch that is not guarded. Use --reapply (which implies
3124 3124 --pop) to push back to the current patch afterwards, but skip
3125 3125 guarded patches.
3126 3126
3127 3127 Use -s/--series to print a list of all guards in the series file
3128 3128 (no other arguments needed). Use -v for more information.
3129 3129
3130 3130 Returns 0 on success.'''
3131 3131
3132 3132 q = repo.mq
3133 3133 guards = q.active()
3134 3134 if args or opts.get('none'):
3135 3135 old_unapplied = q.unapplied(repo)
3136 3136 old_guarded = [i for i in xrange(len(q.applied)) if
3137 3137 not q.pushable(i)[0]]
3138 3138 q.setactive(args)
3139 3139 q.savedirty()
3140 3140 if not args:
3141 3141 ui.status(_('guards deactivated\n'))
3142 3142 if not opts.get('pop') and not opts.get('reapply'):
3143 3143 unapplied = q.unapplied(repo)
3144 3144 guarded = [i for i in xrange(len(q.applied))
3145 3145 if not q.pushable(i)[0]]
3146 3146 if len(unapplied) != len(old_unapplied):
3147 3147 ui.status(_('number of unguarded, unapplied patches has '
3148 3148 'changed from %d to %d\n') %
3149 3149 (len(old_unapplied), len(unapplied)))
3150 3150 if len(guarded) != len(old_guarded):
3151 3151 ui.status(_('number of guarded, applied patches has changed '
3152 3152 'from %d to %d\n') %
3153 3153 (len(old_guarded), len(guarded)))
3154 3154 elif opts.get('series'):
3155 3155 guards = {}
3156 3156 noguards = 0
3157 3157 for gs in q.seriesguards:
3158 3158 if not gs:
3159 3159 noguards += 1
3160 3160 for g in gs:
3161 3161 guards.setdefault(g, 0)
3162 3162 guards[g] += 1
3163 3163 if ui.verbose:
3164 3164 guards['NONE'] = noguards
3165 3165 guards = guards.items()
3166 3166 guards.sort(key=lambda x: x[0][1:])
3167 3167 if guards:
3168 3168 ui.note(_('guards in series file:\n'))
3169 3169 for guard, count in guards:
3170 3170 ui.note('%2d ' % count)
3171 3171 ui.write(guard, '\n')
3172 3172 else:
3173 3173 ui.note(_('no guards in series file\n'))
3174 3174 else:
3175 3175 if guards:
3176 3176 ui.note(_('active guards:\n'))
3177 3177 for g in guards:
3178 3178 ui.write(g, '\n')
3179 3179 else:
3180 3180 ui.write(_('no active guards\n'))
3181 3181 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3182 3182 popped = False
3183 3183 if opts.get('pop') or opts.get('reapply'):
3184 3184 for i in xrange(len(q.applied)):
3185 3185 pushable, reason = q.pushable(i)
3186 3186 if not pushable:
3187 3187 ui.status(_('popping guarded patches\n'))
3188 3188 popped = True
3189 3189 if i == 0:
3190 3190 q.pop(repo, all=True)
3191 3191 else:
3192 3192 q.pop(repo, str(i - 1))
3193 3193 break
3194 3194 if popped:
3195 3195 try:
3196 3196 if reapply:
3197 3197 ui.status(_('reapplying unguarded patches\n'))
3198 3198 q.push(repo, reapply)
3199 3199 finally:
3200 3200 q.savedirty()
3201 3201
3202 3202 @command("qfinish",
3203 3203 [('a', 'applied', None, _('finish all applied changesets'))],
3204 3204 _('hg qfinish [-a] [REV]...'))
3205 3205 def finish(ui, repo, *revrange, **opts):
3206 3206 """move applied patches into repository history
3207 3207
3208 3208 Finishes the specified revisions (corresponding to applied
3209 3209 patches) by moving them out of mq control into regular repository
3210 3210 history.
3211 3211
3212 3212 Accepts a revision range or the -a/--applied option. If --applied
3213 3213 is specified, all applied mq revisions are removed from mq
3214 3214 control. Otherwise, the given revisions must be at the base of the
3215 3215 stack of applied patches.
3216 3216
3217 3217 This can be especially useful if your changes have been applied to
3218 3218 an upstream repository, or if you are about to push your changes
3219 3219 to upstream.
3220 3220
3221 3221 Returns 0 on success.
3222 3222 """
3223 3223 if not opts.get('applied') and not revrange:
3224 3224 raise util.Abort(_('no revisions specified'))
3225 3225 elif opts.get('applied'):
3226 3226 revrange = ('qbase::qtip',) + revrange
3227 3227
3228 3228 q = repo.mq
3229 3229 if not q.applied:
3230 3230 ui.status(_('no patches applied\n'))
3231 3231 return 0
3232 3232
3233 3233 revs = scmutil.revrange(repo, revrange)
3234 3234 if repo['.'].rev() in revs and repo[None].files():
3235 3235 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3236 3236 # queue.finish may changes phases but leave the responsibility to lock the
3237 3237 # repo to the caller to avoid deadlock with wlock. This command code is
3238 3238 # responsibility for this locking.
3239 3239 lock = repo.lock()
3240 3240 try:
3241 3241 q.finish(repo, revs)
3242 3242 q.savedirty()
3243 3243 finally:
3244 3244 lock.release()
3245 3245 return 0
3246 3246
3247 3247 @command("qqueue",
3248 3248 [('l', 'list', False, _('list all available queues')),
3249 3249 ('', 'active', False, _('print name of active queue')),
3250 3250 ('c', 'create', False, _('create new queue')),
3251 3251 ('', 'rename', False, _('rename active queue')),
3252 3252 ('', 'delete', False, _('delete reference to queue')),
3253 3253 ('', 'purge', False, _('delete queue, and remove patch dir')),
3254 3254 ],
3255 3255 _('[OPTION] [QUEUE]'))
3256 3256 def qqueue(ui, repo, name=None, **opts):
3257 3257 '''manage multiple patch queues
3258 3258
3259 3259 Supports switching between different patch queues, as well as creating
3260 3260 new patch queues and deleting existing ones.
3261 3261
3262 3262 Omitting a queue name or specifying -l/--list will show you the registered
3263 3263 queues - by default the "normal" patches queue is registered. The currently
3264 3264 active queue will be marked with "(active)". Specifying --active will print
3265 3265 only the name of the active queue.
3266 3266
3267 3267 To create a new queue, use -c/--create. The queue is automatically made
3268 3268 active, except in the case where there are applied patches from the
3269 3269 currently active queue in the repository. Then the queue will only be
3270 3270 created and switching will fail.
3271 3271
3272 3272 To delete an existing queue, use --delete. You cannot delete the currently
3273 3273 active queue.
3274 3274
3275 3275 Returns 0 on success.
3276 3276 '''
3277 3277 q = repo.mq
3278 3278 _defaultqueue = 'patches'
3279 3279 _allqueues = 'patches.queues'
3280 3280 _activequeue = 'patches.queue'
3281 3281
3282 3282 def _getcurrent():
3283 3283 cur = os.path.basename(q.path)
3284 3284 if cur.startswith('patches-'):
3285 3285 cur = cur[8:]
3286 3286 return cur
3287 3287
3288 3288 def _noqueues():
3289 3289 try:
3290 3290 fh = repo.opener(_allqueues, 'r')
3291 3291 fh.close()
3292 3292 except IOError:
3293 3293 return True
3294 3294
3295 3295 return False
3296 3296
3297 3297 def _getqueues():
3298 3298 current = _getcurrent()
3299 3299
3300 3300 try:
3301 3301 fh = repo.opener(_allqueues, 'r')
3302 3302 queues = [queue.strip() for queue in fh if queue.strip()]
3303 3303 fh.close()
3304 3304 if current not in queues:
3305 3305 queues.append(current)
3306 3306 except IOError:
3307 3307 queues = [_defaultqueue]
3308 3308
3309 3309 return sorted(queues)
3310 3310
3311 3311 def _setactive(name):
3312 3312 if q.applied:
3313 3313 raise util.Abort(_('new queue created, but cannot make active '
3314 3314 'as patches are applied'))
3315 3315 _setactivenocheck(name)
3316 3316
3317 3317 def _setactivenocheck(name):
3318 3318 fh = repo.opener(_activequeue, 'w')
3319 3319 if name != 'patches':
3320 3320 fh.write(name)
3321 3321 fh.close()
3322 3322
3323 3323 def _addqueue(name):
3324 3324 fh = repo.opener(_allqueues, 'a')
3325 3325 fh.write('%s\n' % (name,))
3326 3326 fh.close()
3327 3327
3328 3328 def _queuedir(name):
3329 3329 if name == 'patches':
3330 3330 return repo.join('patches')
3331 3331 else:
3332 3332 return repo.join('patches-' + name)
3333 3333
3334 3334 def _validname(name):
3335 3335 for n in name:
3336 3336 if n in ':\\/.':
3337 3337 return False
3338 3338 return True
3339 3339
3340 3340 def _delete(name):
3341 3341 if name not in existing:
3342 3342 raise util.Abort(_('cannot delete queue that does not exist'))
3343 3343
3344 3344 current = _getcurrent()
3345 3345
3346 3346 if name == current:
3347 3347 raise util.Abort(_('cannot delete currently active queue'))
3348 3348
3349 3349 fh = repo.opener('patches.queues.new', 'w')
3350 3350 for queue in existing:
3351 3351 if queue == name:
3352 3352 continue
3353 3353 fh.write('%s\n' % (queue,))
3354 3354 fh.close()
3355 3355 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3356 3356
3357 3357 if not name or opts.get('list') or opts.get('active'):
3358 3358 current = _getcurrent()
3359 3359 if opts.get('active'):
3360 3360 ui.write('%s\n' % (current,))
3361 3361 return
3362 3362 for queue in _getqueues():
3363 3363 ui.write('%s' % (queue,))
3364 3364 if queue == current and not ui.quiet:
3365 3365 ui.write(_(' (active)\n'))
3366 3366 else:
3367 3367 ui.write('\n')
3368 3368 return
3369 3369
3370 3370 if not _validname(name):
3371 3371 raise util.Abort(
3372 3372 _('invalid queue name, may not contain the characters ":\\/."'))
3373 3373
3374 3374 existing = _getqueues()
3375 3375
3376 3376 if opts.get('create'):
3377 3377 if name in existing:
3378 3378 raise util.Abort(_('queue "%s" already exists') % name)
3379 3379 if _noqueues():
3380 3380 _addqueue(_defaultqueue)
3381 3381 _addqueue(name)
3382 3382 _setactive(name)
3383 3383 elif opts.get('rename'):
3384 3384 current = _getcurrent()
3385 3385 if name == current:
3386 3386 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3387 3387 if name in existing:
3388 3388 raise util.Abort(_('queue "%s" already exists') % name)
3389 3389
3390 3390 olddir = _queuedir(current)
3391 3391 newdir = _queuedir(name)
3392 3392
3393 3393 if os.path.exists(newdir):
3394 3394 raise util.Abort(_('non-queue directory "%s" already exists') %
3395 3395 newdir)
3396 3396
3397 3397 fh = repo.opener('patches.queues.new', 'w')
3398 3398 for queue in existing:
3399 3399 if queue == current:
3400 3400 fh.write('%s\n' % (name,))
3401 3401 if os.path.exists(olddir):
3402 3402 util.rename(olddir, newdir)
3403 3403 else:
3404 3404 fh.write('%s\n' % (queue,))
3405 3405 fh.close()
3406 3406 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3407 3407 _setactivenocheck(name)
3408 3408 elif opts.get('delete'):
3409 3409 _delete(name)
3410 3410 elif opts.get('purge'):
3411 3411 if name in existing:
3412 3412 _delete(name)
3413 3413 qdir = _queuedir(name)
3414 3414 if os.path.exists(qdir):
3415 3415 shutil.rmtree(qdir)
3416 3416 else:
3417 3417 if name not in existing:
3418 3418 raise util.Abort(_('use --create to create a new queue'))
3419 3419 _setactive(name)
3420 3420
3421 3421 def mqphasedefaults(repo, roots):
3422 3422 """callback used to set mq changeset as secret when no phase data exists"""
3423 3423 if repo.mq.applied:
3424 3424 if repo.ui.configbool('mq', 'secret', False):
3425 3425 mqphase = phases.secret
3426 3426 else:
3427 3427 mqphase = phases.draft
3428 3428 qbase = repo[repo.mq.applied[0].node]
3429 3429 roots[mqphase].add(qbase.node())
3430 3430 return roots
3431 3431
3432 3432 def reposetup(ui, repo):
3433 3433 class mqrepo(repo.__class__):
3434 3434 @localrepo.unfilteredpropertycache
3435 3435 def mq(self):
3436 3436 return queue(self.ui, self.baseui, self.path)
3437 3437
3438 3438 def abortifwdirpatched(self, errmsg, force=False):
3439 3439 if self.mq.applied and not force:
3440 3440 parents = self.dirstate.parents()
3441 3441 patches = [s.node for s in self.mq.applied]
3442 3442 if parents[0] in patches or parents[1] in patches:
3443 3443 raise util.Abort(errmsg)
3444 3444
3445 3445 def commit(self, text="", user=None, date=None, match=None,
3446 3446 force=False, editor=False, extra={}):
3447 3447 self.abortifwdirpatched(
3448 3448 _('cannot commit over an applied mq patch'),
3449 3449 force)
3450 3450
3451 3451 return super(mqrepo, self).commit(text, user, date, match, force,
3452 3452 editor, extra)
3453 3453
3454 3454 def checkpush(self, force, revs):
3455 3455 if self.mq.applied and not force:
3456 3456 outapplied = [e.node for e in self.mq.applied]
3457 3457 if revs:
3458 3458 # Assume applied patches have no non-patch descendants and
3459 3459 # are not on remote already. Filtering any changeset not
3460 3460 # pushed.
3461 3461 heads = set(revs)
3462 3462 for node in reversed(outapplied):
3463 3463 if node in heads:
3464 3464 break
3465 3465 else:
3466 3466 outapplied.pop()
3467 3467 # looking for pushed and shared changeset
3468 3468 for node in outapplied:
3469 3469 if self[node].phase() < phases.secret:
3470 3470 raise util.Abort(_('source has mq patches applied'))
3471 3471 # no non-secret patches pushed
3472 3472 super(mqrepo, self).checkpush(force, revs)
3473 3473
3474 3474 def _findtags(self):
3475 3475 '''augment tags from base class with patch tags'''
3476 3476 result = super(mqrepo, self)._findtags()
3477 3477
3478 3478 q = self.mq
3479 3479 if not q.applied:
3480 3480 return result
3481 3481
3482 3482 mqtags = [(patch.node, patch.name) for patch in q.applied]
3483 3483
3484 3484 try:
3485 3485 # for now ignore filtering business
3486 3486 self.unfiltered().changelog.rev(mqtags[-1][0])
3487 3487 except error.LookupError:
3488 3488 self.ui.warn(_('mq status file refers to unknown node %s\n')
3489 3489 % short(mqtags[-1][0]))
3490 3490 return result
3491 3491
3492 3492 # do not add fake tags for filtered revisions
3493 3493 included = self.changelog.hasnode
3494 3494 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3495 3495 if not mqtags:
3496 3496 return result
3497 3497
3498 3498 mqtags.append((mqtags[-1][0], 'qtip'))
3499 3499 mqtags.append((mqtags[0][0], 'qbase'))
3500 3500 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3501 3501 tags = result[0]
3502 3502 for patch in mqtags:
3503 3503 if patch[1] in tags:
3504 3504 self.ui.warn(_('tag %s overrides mq patch of the same '
3505 3505 'name\n') % patch[1])
3506 3506 else:
3507 3507 tags[patch[1]] = patch[0]
3508 3508
3509 3509 return result
3510 3510
3511 3511 if repo.local():
3512 3512 repo.__class__ = mqrepo
3513 3513
3514 3514 repo._phasedefaults.append(mqphasedefaults)
3515 3515
3516 3516 def mqimport(orig, ui, repo, *args, **kwargs):
3517 3517 if (util.safehasattr(repo, 'abortifwdirpatched')
3518 3518 and not kwargs.get('no_commit', False)):
3519 3519 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3520 3520 kwargs.get('force'))
3521 3521 return orig(ui, repo, *args, **kwargs)
3522 3522
3523 3523 def mqinit(orig, ui, *args, **kwargs):
3524 3524 mq = kwargs.pop('mq', None)
3525 3525
3526 3526 if not mq:
3527 3527 return orig(ui, *args, **kwargs)
3528 3528
3529 3529 if args:
3530 3530 repopath = args[0]
3531 3531 if not hg.islocal(repopath):
3532 3532 raise util.Abort(_('only a local queue repository '
3533 3533 'may be initialized'))
3534 3534 else:
3535 3535 repopath = cmdutil.findrepo(os.getcwd())
3536 3536 if not repopath:
3537 3537 raise util.Abort(_('there is no Mercurial repository here '
3538 3538 '(.hg not found)'))
3539 3539 repo = hg.repository(ui, repopath)
3540 3540 return qinit(ui, repo, True)
3541 3541
3542 3542 def mqcommand(orig, ui, repo, *args, **kwargs):
3543 3543 """Add --mq option to operate on patch repository instead of main"""
3544 3544
3545 3545 # some commands do not like getting unknown options
3546 3546 mq = kwargs.pop('mq', None)
3547 3547
3548 3548 if not mq:
3549 3549 return orig(ui, repo, *args, **kwargs)
3550 3550
3551 3551 q = repo.mq
3552 3552 r = q.qrepo()
3553 3553 if not r:
3554 3554 raise util.Abort(_('no queue repository'))
3555 3555 return orig(r.ui, r, *args, **kwargs)
3556 3556
3557 3557 def summaryhook(ui, repo):
3558 3558 q = repo.mq
3559 3559 m = []
3560 3560 a, u = len(q.applied), len(q.unapplied(repo))
3561 3561 if a:
3562 3562 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3563 3563 if u:
3564 3564 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3565 3565 if m:
3566 3566 # i18n: column positioning for "hg summary"
3567 3567 ui.write(_("mq: %s\n") % ', '.join(m))
3568 3568 else:
3569 3569 # i18n: column positioning for "hg summary"
3570 3570 ui.note(_("mq: (empty queue)\n"))
3571 3571
3572 3572 def revsetmq(repo, subset, x):
3573 3573 """``mq()``
3574 3574 Changesets managed by MQ.
3575 3575 """
3576 3576 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3577 3577 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3578 3578 return [r for r in subset if r in applied]
3579 3579
3580 3580 # tell hggettext to extract docstrings from these functions:
3581 3581 i18nfunctions = [revsetmq]
3582 3582
3583 3583 def extsetup(ui):
3584 3584 # Ensure mq wrappers are called first, regardless of extension load order by
3585 3585 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3586 3586 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3587 3587
3588 3588 extensions.wrapcommand(commands.table, 'import', mqimport)
3589 3589 cmdutil.summaryhooks.add('mq', summaryhook)
3590 3590
3591 3591 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3592 3592 entry[1].extend(mqopt)
3593 3593
3594 3594 nowrap = set(commands.norepo.split(" "))
3595 3595
3596 3596 def dotable(cmdtable):
3597 3597 for cmd in cmdtable.keys():
3598 3598 cmd = cmdutil.parsealiases(cmd)[0]
3599 3599 if cmd in nowrap:
3600 3600 continue
3601 3601 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3602 3602 entry[1].extend(mqopt)
3603 3603
3604 3604 dotable(commands.table)
3605 3605
3606 3606 for extname, extmodule in extensions.extensions():
3607 3607 if extmodule.__file__ != __file__:
3608 3608 dotable(getattr(extmodule, 'cmdtable', {}))
3609 3609
3610 3610 revset.symbols['mq'] = revsetmq
3611 3611
3612 3612 colortable = {'qguard.negative': 'red',
3613 3613 'qguard.positive': 'yellow',
3614 3614 'qguard.unguarded': 'green',
3615 3615 'qseries.applied': 'blue bold underline',
3616 3616 'qseries.guarded': 'black bold',
3617 3617 'qseries.missing': 'red bold',
3618 3618 'qseries.unapplied': 'black bold'}
3619 3619
3620 3620 commands.inferrepo += " qnew qrefresh qdiff qcommit"
General Comments 0
You need to be logged in to leave comments. Login now