##// END OF EJS Templates
merge with stable
Matt Mackall -
r19763:ea35caf3 merge default
parent child Browse files
Show More
@@ -1,3618 +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 from mercurial import repair, extensions, error, phases
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 checksubstate(self, repo, baserev=None):
949 949 '''return list of subrepos at a different revision than substate.
950 950 Abort if any subrepos have uncommitted changes.'''
951 951 inclsubs = []
952 952 wctx = repo[None]
953 953 if baserev:
954 954 bctx = repo[baserev]
955 955 else:
956 956 bctx = wctx.parents()[0]
957 957 for s in sorted(wctx.substate):
958 958 if wctx.sub(s).dirty(True):
959 959 raise util.Abort(
960 960 _("uncommitted changes in subrepository %s") % s)
961 961 elif s not in bctx.substate or bctx.sub(s).dirty():
962 962 inclsubs.append(s)
963 963 return inclsubs
964 964
965 965 def putsubstate2changes(self, substatestate, changes):
966 966 for files in changes[:3]:
967 967 if '.hgsubstate' in files:
968 968 return # already listed up
969 969 # not yet listed up
970 970 if substatestate in 'a?':
971 971 changes[1].append('.hgsubstate')
972 972 elif substatestate in 'r':
973 973 changes[2].append('.hgsubstate')
974 974 else: # modified
975 975 changes[0].append('.hgsubstate')
976 976
977 977 def localchangesfound(self, refresh=True):
978 978 if refresh:
979 979 raise util.Abort(_("local changes found, refresh first"))
980 980 else:
981 981 raise util.Abort(_("local changes found"))
982 982
983 983 def localchangedsubreposfound(self, refresh=True):
984 984 if refresh:
985 985 raise util.Abort(_("local changed subrepos found, refresh first"))
986 986 else:
987 987 raise util.Abort(_("local changed subrepos found"))
988 988
989 989 def checklocalchanges(self, repo, force=False, refresh=True):
990 990 cmdutil.checkunfinished(repo)
991 991 m, a, r, d = repo.status()[:4]
992 992 if not force:
993 993 if (m or a or r or d):
994 994 self.localchangesfound(refresh)
995 995 if self.checksubstate(repo):
996 996 self.localchangedsubreposfound(refresh)
997 997 return m, a, r, d
998 998
999 999 _reserved = ('series', 'status', 'guards', '.', '..')
1000 1000 def checkreservedname(self, name):
1001 1001 if name in self._reserved:
1002 1002 raise util.Abort(_('"%s" cannot be used as the name of a patch')
1003 1003 % name)
1004 1004 for prefix in ('.hg', '.mq'):
1005 1005 if name.startswith(prefix):
1006 1006 raise util.Abort(_('patch name cannot begin with "%s"')
1007 1007 % prefix)
1008 1008 for c in ('#', ':'):
1009 1009 if c in name:
1010 1010 raise util.Abort(_('"%s" cannot be used in the name of a patch')
1011 1011 % c)
1012 1012
1013 1013 def checkpatchname(self, name, force=False):
1014 1014 self.checkreservedname(name)
1015 1015 if not force and os.path.exists(self.join(name)):
1016 1016 if os.path.isdir(self.join(name)):
1017 1017 raise util.Abort(_('"%s" already exists as a directory')
1018 1018 % name)
1019 1019 else:
1020 1020 raise util.Abort(_('patch "%s" already exists') % name)
1021 1021
1022 1022 def checkkeepchanges(self, keepchanges, force):
1023 1023 if force and keepchanges:
1024 1024 raise util.Abort(_('cannot use both --force and --keep-changes'))
1025 1025
1026 1026 def new(self, repo, patchfn, *pats, **opts):
1027 1027 """options:
1028 1028 msg: a string or a no-argument function returning a string
1029 1029 """
1030 1030 msg = opts.get('msg')
1031 1031 user = opts.get('user')
1032 1032 date = opts.get('date')
1033 1033 if date:
1034 1034 date = util.parsedate(date)
1035 1035 diffopts = self.diffopts({'git': opts.get('git')})
1036 1036 if opts.get('checkname', True):
1037 1037 self.checkpatchname(patchfn)
1038 1038 inclsubs = self.checksubstate(repo)
1039 1039 if inclsubs:
1040 1040 inclsubs.append('.hgsubstate')
1041 1041 substatestate = repo.dirstate['.hgsubstate']
1042 1042 if opts.get('include') or opts.get('exclude') or pats:
1043 1043 if inclsubs:
1044 1044 pats = list(pats or []) + inclsubs
1045 1045 match = scmutil.match(repo[None], pats, opts)
1046 1046 # detect missing files in pats
1047 1047 def badfn(f, msg):
1048 1048 if f != '.hgsubstate': # .hgsubstate is auto-created
1049 1049 raise util.Abort('%s: %s' % (f, msg))
1050 1050 match.bad = badfn
1051 1051 changes = repo.status(match=match)
1052 1052 m, a, r, d = changes[:4]
1053 1053 else:
1054 1054 changes = self.checklocalchanges(repo, force=True)
1055 1055 m, a, r, d = changes
1056 1056 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1057 1057 if len(repo[None].parents()) > 1:
1058 1058 raise util.Abort(_('cannot manage merge changesets'))
1059 1059 commitfiles = m + a + r
1060 1060 self.checktoppatch(repo)
1061 1061 insert = self.fullseriesend()
1062 1062 wlock = repo.wlock()
1063 1063 try:
1064 1064 try:
1065 1065 # if patch file write fails, abort early
1066 1066 p = self.opener(patchfn, "w")
1067 1067 except IOError, e:
1068 1068 raise util.Abort(_('cannot write patch "%s": %s')
1069 1069 % (patchfn, e.strerror))
1070 1070 try:
1071 1071 if self.plainmode:
1072 1072 if user:
1073 1073 p.write("From: " + user + "\n")
1074 1074 if not date:
1075 1075 p.write("\n")
1076 1076 if date:
1077 1077 p.write("Date: %d %d\n\n" % date)
1078 1078 else:
1079 1079 p.write("# HG changeset patch\n")
1080 1080 p.write("# Parent "
1081 1081 + hex(repo[None].p1().node()) + "\n")
1082 1082 if user:
1083 1083 p.write("# User " + user + "\n")
1084 1084 if date:
1085 1085 p.write("# Date %s %s\n\n" % date)
1086 1086 if util.safehasattr(msg, '__call__'):
1087 1087 msg = msg()
1088 1088 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1089 1089 n = newcommit(repo, None, commitmsg, user, date, match=match,
1090 1090 force=True)
1091 1091 if n is None:
1092 1092 raise util.Abort(_("repo commit failed"))
1093 1093 try:
1094 1094 self.fullseries[insert:insert] = [patchfn]
1095 1095 self.applied.append(statusentry(n, patchfn))
1096 1096 self.parseseries()
1097 1097 self.seriesdirty = True
1098 1098 self.applieddirty = True
1099 1099 if msg:
1100 1100 msg = msg + "\n\n"
1101 1101 p.write(msg)
1102 1102 if commitfiles:
1103 1103 parent = self.qparents(repo, n)
1104 1104 if inclsubs:
1105 1105 self.putsubstate2changes(substatestate, changes)
1106 1106 chunks = patchmod.diff(repo, node1=parent, node2=n,
1107 1107 changes=changes, opts=diffopts)
1108 1108 for chunk in chunks:
1109 1109 p.write(chunk)
1110 1110 p.close()
1111 1111 r = self.qrepo()
1112 1112 if r:
1113 1113 r[None].add([patchfn])
1114 1114 except: # re-raises
1115 1115 repo.rollback()
1116 1116 raise
1117 1117 except Exception:
1118 1118 patchpath = self.join(patchfn)
1119 1119 try:
1120 1120 os.unlink(patchpath)
1121 1121 except OSError:
1122 1122 self.ui.warn(_('error unlinking %s\n') % patchpath)
1123 1123 raise
1124 1124 self.removeundo(repo)
1125 1125 finally:
1126 1126 release(wlock)
1127 1127
1128 1128 def strip(self, repo, revs, update=True, backup="all", force=None):
1129 1129 wlock = lock = None
1130 1130 try:
1131 1131 wlock = repo.wlock()
1132 1132 lock = repo.lock()
1133 1133
1134 1134 if update:
1135 1135 self.checklocalchanges(repo, force=force, refresh=False)
1136 1136 urev = self.qparents(repo, revs[0])
1137 1137 hg.clean(repo, urev)
1138 1138 repo.dirstate.write()
1139 1139
1140 1140 repair.strip(self.ui, repo, revs, backup)
1141 1141 finally:
1142 1142 release(lock, wlock)
1143 1143
1144 1144 def isapplied(self, patch):
1145 1145 """returns (index, rev, patch)"""
1146 1146 for i, a in enumerate(self.applied):
1147 1147 if a.name == patch:
1148 1148 return (i, a.node, a.name)
1149 1149 return None
1150 1150
1151 1151 # if the exact patch name does not exist, we try a few
1152 1152 # variations. If strict is passed, we try only #1
1153 1153 #
1154 1154 # 1) a number (as string) to indicate an offset in the series file
1155 1155 # 2) a unique substring of the patch name was given
1156 1156 # 3) patchname[-+]num to indicate an offset in the series file
1157 1157 def lookup(self, patch, strict=False):
1158 1158 def partialname(s):
1159 1159 if s in self.series:
1160 1160 return s
1161 1161 matches = [x for x in self.series if s in x]
1162 1162 if len(matches) > 1:
1163 1163 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1164 1164 for m in matches:
1165 1165 self.ui.warn(' %s\n' % m)
1166 1166 return None
1167 1167 if matches:
1168 1168 return matches[0]
1169 1169 if self.series and self.applied:
1170 1170 if s == 'qtip':
1171 1171 return self.series[self.seriesend(True) - 1]
1172 1172 if s == 'qbase':
1173 1173 return self.series[0]
1174 1174 return None
1175 1175
1176 1176 if patch in self.series:
1177 1177 return patch
1178 1178
1179 1179 if not os.path.isfile(self.join(patch)):
1180 1180 try:
1181 1181 sno = int(patch)
1182 1182 except (ValueError, OverflowError):
1183 1183 pass
1184 1184 else:
1185 1185 if -len(self.series) <= sno < len(self.series):
1186 1186 return self.series[sno]
1187 1187
1188 1188 if not strict:
1189 1189 res = partialname(patch)
1190 1190 if res:
1191 1191 return res
1192 1192 minus = patch.rfind('-')
1193 1193 if minus >= 0:
1194 1194 res = partialname(patch[:minus])
1195 1195 if res:
1196 1196 i = self.series.index(res)
1197 1197 try:
1198 1198 off = int(patch[minus + 1:] or 1)
1199 1199 except (ValueError, OverflowError):
1200 1200 pass
1201 1201 else:
1202 1202 if i - off >= 0:
1203 1203 return self.series[i - off]
1204 1204 plus = patch.rfind('+')
1205 1205 if plus >= 0:
1206 1206 res = partialname(patch[:plus])
1207 1207 if res:
1208 1208 i = self.series.index(res)
1209 1209 try:
1210 1210 off = int(patch[plus + 1:] or 1)
1211 1211 except (ValueError, OverflowError):
1212 1212 pass
1213 1213 else:
1214 1214 if i + off < len(self.series):
1215 1215 return self.series[i + off]
1216 1216 raise util.Abort(_("patch %s not in series") % patch)
1217 1217
1218 1218 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1219 1219 all=False, move=False, exact=False, nobackup=False,
1220 1220 keepchanges=False):
1221 1221 self.checkkeepchanges(keepchanges, force)
1222 1222 diffopts = self.diffopts()
1223 1223 wlock = repo.wlock()
1224 1224 try:
1225 1225 heads = []
1226 1226 for b, ls in repo.branchmap().iteritems():
1227 1227 heads += ls
1228 1228 if not heads:
1229 1229 heads = [nullid]
1230 1230 if repo.dirstate.p1() not in heads and not exact:
1231 1231 self.ui.status(_("(working directory not at a head)\n"))
1232 1232
1233 1233 if not self.series:
1234 1234 self.ui.warn(_('no patches in series\n'))
1235 1235 return 0
1236 1236
1237 1237 # Suppose our series file is: A B C and the current 'top'
1238 1238 # patch is B. qpush C should be performed (moving forward)
1239 1239 # qpush B is a NOP (no change) qpush A is an error (can't
1240 1240 # go backwards with qpush)
1241 1241 if patch:
1242 1242 patch = self.lookup(patch)
1243 1243 info = self.isapplied(patch)
1244 1244 if info and info[0] >= len(self.applied) - 1:
1245 1245 self.ui.warn(
1246 1246 _('qpush: %s is already at the top\n') % patch)
1247 1247 return 0
1248 1248
1249 1249 pushable, reason = self.pushable(patch)
1250 1250 if pushable:
1251 1251 if self.series.index(patch) < self.seriesend():
1252 1252 raise util.Abort(
1253 1253 _("cannot push to a previous patch: %s") % patch)
1254 1254 else:
1255 1255 if reason:
1256 1256 reason = _('guarded by %s') % reason
1257 1257 else:
1258 1258 reason = _('no matching guards')
1259 1259 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1260 1260 return 1
1261 1261 elif all:
1262 1262 patch = self.series[-1]
1263 1263 if self.isapplied(patch):
1264 1264 self.ui.warn(_('all patches are currently applied\n'))
1265 1265 return 0
1266 1266
1267 1267 # Following the above example, starting at 'top' of B:
1268 1268 # qpush should be performed (pushes C), but a subsequent
1269 1269 # qpush without an argument is an error (nothing to
1270 1270 # apply). This allows a loop of "...while hg qpush..." to
1271 1271 # work as it detects an error when done
1272 1272 start = self.seriesend()
1273 1273 if start == len(self.series):
1274 1274 self.ui.warn(_('patch series already fully applied\n'))
1275 1275 return 1
1276 1276 if not force and not keepchanges:
1277 1277 self.checklocalchanges(repo, refresh=self.applied)
1278 1278
1279 1279 if exact:
1280 1280 if keepchanges:
1281 1281 raise util.Abort(
1282 1282 _("cannot use --exact and --keep-changes together"))
1283 1283 if move:
1284 1284 raise util.Abort(_('cannot use --exact and --move '
1285 1285 'together'))
1286 1286 if self.applied:
1287 1287 raise util.Abort(_('cannot push --exact with applied '
1288 1288 'patches'))
1289 1289 root = self.series[start]
1290 1290 target = patchheader(self.join(root), self.plainmode).parent
1291 1291 if not target:
1292 1292 raise util.Abort(
1293 1293 _("%s does not have a parent recorded") % root)
1294 1294 if not repo[target] == repo['.']:
1295 1295 hg.update(repo, target)
1296 1296
1297 1297 if move:
1298 1298 if not patch:
1299 1299 raise util.Abort(_("please specify the patch to move"))
1300 1300 for fullstart, rpn in enumerate(self.fullseries):
1301 1301 # strip markers for patch guards
1302 1302 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1303 1303 break
1304 1304 for i, rpn in enumerate(self.fullseries[fullstart:]):
1305 1305 # strip markers for patch guards
1306 1306 if self.guard_re.split(rpn, 1)[0] == patch:
1307 1307 break
1308 1308 index = fullstart + i
1309 1309 assert index < len(self.fullseries)
1310 1310 fullpatch = self.fullseries[index]
1311 1311 del self.fullseries[index]
1312 1312 self.fullseries.insert(fullstart, fullpatch)
1313 1313 self.parseseries()
1314 1314 self.seriesdirty = True
1315 1315
1316 1316 self.applieddirty = True
1317 1317 if start > 0:
1318 1318 self.checktoppatch(repo)
1319 1319 if not patch:
1320 1320 patch = self.series[start]
1321 1321 end = start + 1
1322 1322 else:
1323 1323 end = self.series.index(patch, start) + 1
1324 1324
1325 1325 tobackup = set()
1326 1326 if (not nobackup and force) or keepchanges:
1327 1327 m, a, r, d = self.checklocalchanges(repo, force=True)
1328 1328 if keepchanges:
1329 1329 tobackup.update(m + a + r + d)
1330 1330 else:
1331 1331 tobackup.update(m + a)
1332 1332
1333 1333 s = self.series[start:end]
1334 1334 all_files = set()
1335 1335 try:
1336 1336 if mergeq:
1337 1337 ret = self.mergepatch(repo, mergeq, s, diffopts)
1338 1338 else:
1339 1339 ret = self.apply(repo, s, list, all_files=all_files,
1340 1340 tobackup=tobackup, keepchanges=keepchanges)
1341 1341 except: # re-raises
1342 1342 self.ui.warn(_('cleaning up working directory...'))
1343 1343 node = repo.dirstate.p1()
1344 1344 hg.revert(repo, node, None)
1345 1345 # only remove unknown files that we know we touched or
1346 1346 # created while patching
1347 1347 for f in all_files:
1348 1348 if f not in repo.dirstate:
1349 1349 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1350 1350 self.ui.warn(_('done\n'))
1351 1351 raise
1352 1352
1353 1353 if not self.applied:
1354 1354 return ret[0]
1355 1355 top = self.applied[-1].name
1356 1356 if ret[0] and ret[0] > 1:
1357 1357 msg = _("errors during apply, please fix and refresh %s\n")
1358 1358 self.ui.write(msg % top)
1359 1359 else:
1360 1360 self.ui.write(_("now at: %s\n") % top)
1361 1361 return ret[0]
1362 1362
1363 1363 finally:
1364 1364 wlock.release()
1365 1365
1366 1366 def pop(self, repo, patch=None, force=False, update=True, all=False,
1367 1367 nobackup=False, keepchanges=False):
1368 1368 self.checkkeepchanges(keepchanges, force)
1369 1369 wlock = repo.wlock()
1370 1370 try:
1371 1371 if patch:
1372 1372 # index, rev, patch
1373 1373 info = self.isapplied(patch)
1374 1374 if not info:
1375 1375 patch = self.lookup(patch)
1376 1376 info = self.isapplied(patch)
1377 1377 if not info:
1378 1378 raise util.Abort(_("patch %s is not applied") % patch)
1379 1379
1380 1380 if not self.applied:
1381 1381 # Allow qpop -a to work repeatedly,
1382 1382 # but not qpop without an argument
1383 1383 self.ui.warn(_("no patches applied\n"))
1384 1384 return not all
1385 1385
1386 1386 if all:
1387 1387 start = 0
1388 1388 elif patch:
1389 1389 start = info[0] + 1
1390 1390 else:
1391 1391 start = len(self.applied) - 1
1392 1392
1393 1393 if start >= len(self.applied):
1394 1394 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1395 1395 return
1396 1396
1397 1397 if not update:
1398 1398 parents = repo.dirstate.parents()
1399 1399 rr = [x.node for x in self.applied]
1400 1400 for p in parents:
1401 1401 if p in rr:
1402 1402 self.ui.warn(_("qpop: forcing dirstate update\n"))
1403 1403 update = True
1404 1404 else:
1405 1405 parents = [p.node() for p in repo[None].parents()]
1406 1406 needupdate = False
1407 1407 for entry in self.applied[start:]:
1408 1408 if entry.node in parents:
1409 1409 needupdate = True
1410 1410 break
1411 1411 update = needupdate
1412 1412
1413 1413 tobackup = set()
1414 1414 if update:
1415 1415 m, a, r, d = self.checklocalchanges(
1416 1416 repo, force=force or keepchanges)
1417 1417 if force:
1418 1418 if not nobackup:
1419 1419 tobackup.update(m + a)
1420 1420 elif keepchanges:
1421 1421 tobackup.update(m + a + r + d)
1422 1422
1423 1423 self.applieddirty = True
1424 1424 end = len(self.applied)
1425 1425 rev = self.applied[start].node
1426 1426
1427 1427 try:
1428 1428 heads = repo.changelog.heads(rev)
1429 1429 except error.LookupError:
1430 1430 node = short(rev)
1431 1431 raise util.Abort(_('trying to pop unknown node %s') % node)
1432 1432
1433 1433 if heads != [self.applied[-1].node]:
1434 1434 raise util.Abort(_("popping would remove a revision not "
1435 1435 "managed by this patch queue"))
1436 1436 if not repo[self.applied[-1].node].mutable():
1437 1437 raise util.Abort(
1438 1438 _("popping would remove an immutable revision"),
1439 1439 hint=_('see "hg help phases" for details'))
1440 1440
1441 1441 # we know there are no local changes, so we can make a simplified
1442 1442 # form of hg.update.
1443 1443 if update:
1444 1444 qp = self.qparents(repo, rev)
1445 1445 ctx = repo[qp]
1446 1446 m, a, r, d = repo.status(qp, '.')[:4]
1447 1447 if d:
1448 1448 raise util.Abort(_("deletions found between repo revs"))
1449 1449
1450 1450 tobackup = set(a + m + r) & tobackup
1451 1451 if keepchanges and tobackup:
1452 1452 self.localchangesfound()
1453 1453 self.backup(repo, tobackup)
1454 1454
1455 1455 for f in a:
1456 1456 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1457 1457 repo.dirstate.drop(f)
1458 1458 for f in m + r:
1459 1459 fctx = ctx[f]
1460 1460 repo.wwrite(f, fctx.data(), fctx.flags())
1461 1461 repo.dirstate.normal(f)
1462 1462 repo.setparents(qp, nullid)
1463 1463 for patch in reversed(self.applied[start:end]):
1464 1464 self.ui.status(_("popping %s\n") % patch.name)
1465 1465 del self.applied[start:end]
1466 1466 self.strip(repo, [rev], update=False, backup='strip')
1467 1467 for s, state in repo['.'].substate.items():
1468 1468 repo['.'].sub(s).get(state)
1469 1469 if self.applied:
1470 1470 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1471 1471 else:
1472 1472 self.ui.write(_("patch queue now empty\n"))
1473 1473 finally:
1474 1474 wlock.release()
1475 1475
1476 1476 def diff(self, repo, pats, opts):
1477 1477 top, patch = self.checktoppatch(repo)
1478 1478 if not top:
1479 1479 self.ui.write(_("no patches applied\n"))
1480 1480 return
1481 1481 qp = self.qparents(repo, top)
1482 1482 if opts.get('reverse'):
1483 1483 node1, node2 = None, qp
1484 1484 else:
1485 1485 node1, node2 = qp, None
1486 1486 diffopts = self.diffopts(opts, patch)
1487 1487 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1488 1488
1489 1489 def refresh(self, repo, pats=None, **opts):
1490 1490 if not self.applied:
1491 1491 self.ui.write(_("no patches applied\n"))
1492 1492 return 1
1493 1493 msg = opts.get('msg', '').rstrip()
1494 1494 newuser = opts.get('user')
1495 1495 newdate = opts.get('date')
1496 1496 if newdate:
1497 1497 newdate = '%d %d' % util.parsedate(newdate)
1498 1498 wlock = repo.wlock()
1499 1499
1500 1500 try:
1501 1501 self.checktoppatch(repo)
1502 1502 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1503 1503 if repo.changelog.heads(top) != [top]:
1504 1504 raise util.Abort(_("cannot refresh a revision with children"))
1505 1505 if not repo[top].mutable():
1506 1506 raise util.Abort(_("cannot refresh immutable revision"),
1507 1507 hint=_('see "hg help phases" for details'))
1508 1508
1509 1509 cparents = repo.changelog.parents(top)
1510 1510 patchparent = self.qparents(repo, top)
1511 1511
1512 1512 inclsubs = self.checksubstate(repo, hex(patchparent))
1513 1513 if inclsubs:
1514 1514 inclsubs.append('.hgsubstate')
1515 1515 substatestate = repo.dirstate['.hgsubstate']
1516 1516
1517 1517 ph = patchheader(self.join(patchfn), self.plainmode)
1518 1518 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1519 1519 if msg:
1520 1520 ph.setmessage(msg)
1521 1521 if newuser:
1522 1522 ph.setuser(newuser)
1523 1523 if newdate:
1524 1524 ph.setdate(newdate)
1525 1525 ph.setparent(hex(patchparent))
1526 1526
1527 1527 # only commit new patch when write is complete
1528 1528 patchf = self.opener(patchfn, 'w', atomictemp=True)
1529 1529
1530 1530 comments = str(ph)
1531 1531 if comments:
1532 1532 patchf.write(comments)
1533 1533
1534 1534 # update the dirstate in place, strip off the qtip commit
1535 1535 # and then commit.
1536 1536 #
1537 1537 # this should really read:
1538 1538 # mm, dd, aa = repo.status(top, patchparent)[:3]
1539 1539 # but we do it backwards to take advantage of manifest/changelog
1540 1540 # caching against the next repo.status call
1541 1541 mm, aa, dd = repo.status(patchparent, top)[:3]
1542 1542 changes = repo.changelog.read(top)
1543 1543 man = repo.manifest.read(changes[0])
1544 1544 aaa = aa[:]
1545 1545 matchfn = scmutil.match(repo[None], pats, opts)
1546 1546 # in short mode, we only diff the files included in the
1547 1547 # patch already plus specified files
1548 1548 if opts.get('short'):
1549 1549 # if amending a patch, we start with existing
1550 1550 # files plus specified files - unfiltered
1551 1551 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1552 1552 # filter with include/exclude options
1553 1553 matchfn = scmutil.match(repo[None], opts=opts)
1554 1554 else:
1555 1555 match = scmutil.matchall(repo)
1556 1556 m, a, r, d = repo.status(match=match)[:4]
1557 1557 mm = set(mm)
1558 1558 aa = set(aa)
1559 1559 dd = set(dd)
1560 1560
1561 1561 # we might end up with files that were added between
1562 1562 # qtip and the dirstate parent, but then changed in the
1563 1563 # local dirstate. in this case, we want them to only
1564 1564 # show up in the added section
1565 1565 for x in m:
1566 1566 if x not in aa:
1567 1567 mm.add(x)
1568 1568 # we might end up with files added by the local dirstate that
1569 1569 # were deleted by the patch. In this case, they should only
1570 1570 # show up in the changed section.
1571 1571 for x in a:
1572 1572 if x in dd:
1573 1573 dd.remove(x)
1574 1574 mm.add(x)
1575 1575 else:
1576 1576 aa.add(x)
1577 1577 # make sure any files deleted in the local dirstate
1578 1578 # are not in the add or change column of the patch
1579 1579 forget = []
1580 1580 for x in d + r:
1581 1581 if x in aa:
1582 1582 aa.remove(x)
1583 1583 forget.append(x)
1584 1584 continue
1585 1585 else:
1586 1586 mm.discard(x)
1587 1587 dd.add(x)
1588 1588
1589 1589 m = list(mm)
1590 1590 r = list(dd)
1591 1591 a = list(aa)
1592 1592
1593 1593 # create 'match' that includes the files to be recommitted.
1594 1594 # apply matchfn via repo.status to ensure correct case handling.
1595 1595 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1596 1596 allmatches = set(cm + ca + cr + cd)
1597 1597 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1598 1598
1599 1599 files = set(inclsubs)
1600 1600 for x in refreshchanges:
1601 1601 files.update(x)
1602 1602 match = scmutil.matchfiles(repo, files)
1603 1603
1604 1604 bmlist = repo[top].bookmarks()
1605 1605
1606 1606 try:
1607 1607 if diffopts.git or diffopts.upgrade:
1608 1608 copies = {}
1609 1609 for dst in a:
1610 1610 src = repo.dirstate.copied(dst)
1611 1611 # during qfold, the source file for copies may
1612 1612 # be removed. Treat this as a simple add.
1613 1613 if src is not None and src in repo.dirstate:
1614 1614 copies.setdefault(src, []).append(dst)
1615 1615 repo.dirstate.add(dst)
1616 1616 # remember the copies between patchparent and qtip
1617 1617 for dst in aaa:
1618 1618 f = repo.file(dst)
1619 1619 src = f.renamed(man[dst])
1620 1620 if src:
1621 1621 copies.setdefault(src[0], []).extend(
1622 1622 copies.get(dst, []))
1623 1623 if dst in a:
1624 1624 copies[src[0]].append(dst)
1625 1625 # we can't copy a file created by the patch itself
1626 1626 if dst in copies:
1627 1627 del copies[dst]
1628 1628 for src, dsts in copies.iteritems():
1629 1629 for dst in dsts:
1630 1630 repo.dirstate.copy(src, dst)
1631 1631 else:
1632 1632 for dst in a:
1633 1633 repo.dirstate.add(dst)
1634 1634 # Drop useless copy information
1635 1635 for f in list(repo.dirstate.copies()):
1636 1636 repo.dirstate.copy(None, f)
1637 1637 for f in r:
1638 1638 repo.dirstate.remove(f)
1639 1639 # if the patch excludes a modified file, mark that
1640 1640 # file with mtime=0 so status can see it.
1641 1641 mm = []
1642 1642 for i in xrange(len(m) - 1, -1, -1):
1643 1643 if not matchfn(m[i]):
1644 1644 mm.append(m[i])
1645 1645 del m[i]
1646 1646 for f in m:
1647 1647 repo.dirstate.normal(f)
1648 1648 for f in mm:
1649 1649 repo.dirstate.normallookup(f)
1650 1650 for f in forget:
1651 1651 repo.dirstate.drop(f)
1652 1652
1653 1653 if not msg:
1654 1654 if not ph.message:
1655 1655 message = "[mq]: %s\n" % patchfn
1656 1656 else:
1657 1657 message = "\n".join(ph.message)
1658 1658 else:
1659 1659 message = msg
1660 1660
1661 1661 user = ph.user or changes[1]
1662 1662
1663 1663 oldphase = repo[top].phase()
1664 1664
1665 1665 # assumes strip can roll itself back if interrupted
1666 1666 repo.setparents(*cparents)
1667 1667 self.applied.pop()
1668 1668 self.applieddirty = True
1669 1669 self.strip(repo, [top], update=False,
1670 1670 backup='strip')
1671 1671 except: # re-raises
1672 1672 repo.dirstate.invalidate()
1673 1673 raise
1674 1674
1675 1675 try:
1676 1676 # might be nice to attempt to roll back strip after this
1677 1677
1678 1678 # Ensure we create a new changeset in the same phase than
1679 1679 # the old one.
1680 1680 n = newcommit(repo, oldphase, message, user, ph.date,
1681 1681 match=match, force=True)
1682 1682 # only write patch after a successful commit
1683 1683 c = [list(x) for x in refreshchanges]
1684 1684 if inclsubs:
1685 1685 self.putsubstate2changes(substatestate, c)
1686 1686 chunks = patchmod.diff(repo, patchparent,
1687 1687 changes=c, opts=diffopts)
1688 1688 for chunk in chunks:
1689 1689 patchf.write(chunk)
1690 1690 patchf.close()
1691 1691
1692 1692 marks = repo._bookmarks
1693 1693 for bm in bmlist:
1694 1694 marks[bm] = n
1695 1695 marks.write()
1696 1696
1697 1697 self.applied.append(statusentry(n, patchfn))
1698 1698 except: # re-raises
1699 1699 ctx = repo[cparents[0]]
1700 1700 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1701 1701 self.savedirty()
1702 1702 self.ui.warn(_('refresh interrupted while patch was popped! '
1703 1703 '(revert --all, qpush to recover)\n'))
1704 1704 raise
1705 1705 finally:
1706 1706 wlock.release()
1707 1707 self.removeundo(repo)
1708 1708
1709 1709 def init(self, repo, create=False):
1710 1710 if not create and os.path.isdir(self.path):
1711 1711 raise util.Abort(_("patch queue directory already exists"))
1712 1712 try:
1713 1713 os.mkdir(self.path)
1714 1714 except OSError, inst:
1715 1715 if inst.errno != errno.EEXIST or not create:
1716 1716 raise
1717 1717 if create:
1718 1718 return self.qrepo(create=True)
1719 1719
1720 1720 def unapplied(self, repo, patch=None):
1721 1721 if patch and patch not in self.series:
1722 1722 raise util.Abort(_("patch %s is not in series file") % patch)
1723 1723 if not patch:
1724 1724 start = self.seriesend()
1725 1725 else:
1726 1726 start = self.series.index(patch) + 1
1727 1727 unapplied = []
1728 1728 for i in xrange(start, len(self.series)):
1729 1729 pushable, reason = self.pushable(i)
1730 1730 if pushable:
1731 1731 unapplied.append((i, self.series[i]))
1732 1732 self.explainpushable(i)
1733 1733 return unapplied
1734 1734
1735 1735 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1736 1736 summary=False):
1737 1737 def displayname(pfx, patchname, state):
1738 1738 if pfx:
1739 1739 self.ui.write(pfx)
1740 1740 if summary:
1741 1741 ph = patchheader(self.join(patchname), self.plainmode)
1742 1742 msg = ph.message and ph.message[0] or ''
1743 1743 if self.ui.formatted():
1744 1744 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1745 1745 if width > 0:
1746 1746 msg = util.ellipsis(msg, width)
1747 1747 else:
1748 1748 msg = ''
1749 1749 self.ui.write(patchname, label='qseries.' + state)
1750 1750 self.ui.write(': ')
1751 1751 self.ui.write(msg, label='qseries.message.' + state)
1752 1752 else:
1753 1753 self.ui.write(patchname, label='qseries.' + state)
1754 1754 self.ui.write('\n')
1755 1755
1756 1756 applied = set([p.name for p in self.applied])
1757 1757 if length is None:
1758 1758 length = len(self.series) - start
1759 1759 if not missing:
1760 1760 if self.ui.verbose:
1761 1761 idxwidth = len(str(start + length - 1))
1762 1762 for i in xrange(start, start + length):
1763 1763 patch = self.series[i]
1764 1764 if patch in applied:
1765 1765 char, state = 'A', 'applied'
1766 1766 elif self.pushable(i)[0]:
1767 1767 char, state = 'U', 'unapplied'
1768 1768 else:
1769 1769 char, state = 'G', 'guarded'
1770 1770 pfx = ''
1771 1771 if self.ui.verbose:
1772 1772 pfx = '%*d %s ' % (idxwidth, i, char)
1773 1773 elif status and status != char:
1774 1774 continue
1775 1775 displayname(pfx, patch, state)
1776 1776 else:
1777 1777 msng_list = []
1778 1778 for root, dirs, files in os.walk(self.path):
1779 1779 d = root[len(self.path) + 1:]
1780 1780 for f in files:
1781 1781 fl = os.path.join(d, f)
1782 1782 if (fl not in self.series and
1783 1783 fl not in (self.statuspath, self.seriespath,
1784 1784 self.guardspath)
1785 1785 and not fl.startswith('.')):
1786 1786 msng_list.append(fl)
1787 1787 for x in sorted(msng_list):
1788 1788 pfx = self.ui.verbose and ('D ') or ''
1789 1789 displayname(pfx, x, 'missing')
1790 1790
1791 1791 def issaveline(self, l):
1792 1792 if l.name == '.hg.patches.save.line':
1793 1793 return True
1794 1794
1795 1795 def qrepo(self, create=False):
1796 1796 ui = self.baseui.copy()
1797 1797 if create or os.path.isdir(self.join(".hg")):
1798 1798 return hg.repository(ui, path=self.path, create=create)
1799 1799
1800 1800 def restore(self, repo, rev, delete=None, qupdate=None):
1801 1801 desc = repo[rev].description().strip()
1802 1802 lines = desc.splitlines()
1803 1803 i = 0
1804 1804 datastart = None
1805 1805 series = []
1806 1806 applied = []
1807 1807 qpp = None
1808 1808 for i, line in enumerate(lines):
1809 1809 if line == 'Patch Data:':
1810 1810 datastart = i + 1
1811 1811 elif line.startswith('Dirstate:'):
1812 1812 l = line.rstrip()
1813 1813 l = l[10:].split(' ')
1814 1814 qpp = [bin(x) for x in l]
1815 1815 elif datastart is not None:
1816 1816 l = line.rstrip()
1817 1817 n, name = l.split(':', 1)
1818 1818 if n:
1819 1819 applied.append(statusentry(bin(n), name))
1820 1820 else:
1821 1821 series.append(l)
1822 1822 if datastart is None:
1823 1823 self.ui.warn(_("no saved patch data found\n"))
1824 1824 return 1
1825 1825 self.ui.warn(_("restoring status: %s\n") % lines[0])
1826 1826 self.fullseries = series
1827 1827 self.applied = applied
1828 1828 self.parseseries()
1829 1829 self.seriesdirty = True
1830 1830 self.applieddirty = True
1831 1831 heads = repo.changelog.heads()
1832 1832 if delete:
1833 1833 if rev not in heads:
1834 1834 self.ui.warn(_("save entry has children, leaving it alone\n"))
1835 1835 else:
1836 1836 self.ui.warn(_("removing save entry %s\n") % short(rev))
1837 1837 pp = repo.dirstate.parents()
1838 1838 if rev in pp:
1839 1839 update = True
1840 1840 else:
1841 1841 update = False
1842 1842 self.strip(repo, [rev], update=update, backup='strip')
1843 1843 if qpp:
1844 1844 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1845 1845 (short(qpp[0]), short(qpp[1])))
1846 1846 if qupdate:
1847 1847 self.ui.status(_("updating queue directory\n"))
1848 1848 r = self.qrepo()
1849 1849 if not r:
1850 1850 self.ui.warn(_("unable to load queue repository\n"))
1851 1851 return 1
1852 1852 hg.clean(r, qpp[0])
1853 1853
1854 1854 def save(self, repo, msg=None):
1855 1855 if not self.applied:
1856 1856 self.ui.warn(_("save: no patches applied, exiting\n"))
1857 1857 return 1
1858 1858 if self.issaveline(self.applied[-1]):
1859 1859 self.ui.warn(_("status is already saved\n"))
1860 1860 return 1
1861 1861
1862 1862 if not msg:
1863 1863 msg = _("hg patches saved state")
1864 1864 else:
1865 1865 msg = "hg patches: " + msg.rstrip('\r\n')
1866 1866 r = self.qrepo()
1867 1867 if r:
1868 1868 pp = r.dirstate.parents()
1869 1869 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1870 1870 msg += "\n\nPatch Data:\n"
1871 1871 msg += ''.join('%s\n' % x for x in self.applied)
1872 1872 msg += ''.join(':%s\n' % x for x in self.fullseries)
1873 1873 n = repo.commit(msg, force=True)
1874 1874 if not n:
1875 1875 self.ui.warn(_("repo commit failed\n"))
1876 1876 return 1
1877 1877 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1878 1878 self.applieddirty = True
1879 1879 self.removeundo(repo)
1880 1880
1881 1881 def fullseriesend(self):
1882 1882 if self.applied:
1883 1883 p = self.applied[-1].name
1884 1884 end = self.findseries(p)
1885 1885 if end is None:
1886 1886 return len(self.fullseries)
1887 1887 return end + 1
1888 1888 return 0
1889 1889
1890 1890 def seriesend(self, all_patches=False):
1891 1891 """If all_patches is False, return the index of the next pushable patch
1892 1892 in the series, or the series length. If all_patches is True, return the
1893 1893 index of the first patch past the last applied one.
1894 1894 """
1895 1895 end = 0
1896 1896 def nextpatch(start):
1897 1897 if all_patches or start >= len(self.series):
1898 1898 return start
1899 1899 for i in xrange(start, len(self.series)):
1900 1900 p, reason = self.pushable(i)
1901 1901 if p:
1902 1902 return i
1903 1903 self.explainpushable(i)
1904 1904 return len(self.series)
1905 1905 if self.applied:
1906 1906 p = self.applied[-1].name
1907 1907 try:
1908 1908 end = self.series.index(p)
1909 1909 except ValueError:
1910 1910 return 0
1911 1911 return nextpatch(end + 1)
1912 1912 return nextpatch(end)
1913 1913
1914 1914 def appliedname(self, index):
1915 1915 pname = self.applied[index].name
1916 1916 if not self.ui.verbose:
1917 1917 p = pname
1918 1918 else:
1919 1919 p = str(self.series.index(pname)) + " " + pname
1920 1920 return p
1921 1921
1922 1922 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1923 1923 force=None, git=False):
1924 1924 def checkseries(patchname):
1925 1925 if patchname in self.series:
1926 1926 raise util.Abort(_('patch %s is already in the series file')
1927 1927 % patchname)
1928 1928
1929 1929 if rev:
1930 1930 if files:
1931 1931 raise util.Abort(_('option "-r" not valid when importing '
1932 1932 'files'))
1933 1933 rev = scmutil.revrange(repo, rev)
1934 1934 rev.sort(reverse=True)
1935 1935 elif not files:
1936 1936 raise util.Abort(_('no files or revisions specified'))
1937 1937 if (len(files) > 1 or len(rev) > 1) and patchname:
1938 1938 raise util.Abort(_('option "-n" not valid when importing multiple '
1939 1939 'patches'))
1940 1940 imported = []
1941 1941 if rev:
1942 1942 # If mq patches are applied, we can only import revisions
1943 1943 # that form a linear path to qbase.
1944 1944 # Otherwise, they should form a linear path to a head.
1945 1945 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1946 1946 if len(heads) > 1:
1947 1947 raise util.Abort(_('revision %d is the root of more than one '
1948 1948 'branch') % rev[-1])
1949 1949 if self.applied:
1950 1950 base = repo.changelog.node(rev[0])
1951 1951 if base in [n.node for n in self.applied]:
1952 1952 raise util.Abort(_('revision %d is already managed')
1953 1953 % rev[0])
1954 1954 if heads != [self.applied[-1].node]:
1955 1955 raise util.Abort(_('revision %d is not the parent of '
1956 1956 'the queue') % rev[0])
1957 1957 base = repo.changelog.rev(self.applied[0].node)
1958 1958 lastparent = repo.changelog.parentrevs(base)[0]
1959 1959 else:
1960 1960 if heads != [repo.changelog.node(rev[0])]:
1961 1961 raise util.Abort(_('revision %d has unmanaged children')
1962 1962 % rev[0])
1963 1963 lastparent = None
1964 1964
1965 1965 diffopts = self.diffopts({'git': git})
1966 1966 for r in rev:
1967 1967 if not repo[r].mutable():
1968 1968 raise util.Abort(_('revision %d is not mutable') % r,
1969 1969 hint=_('see "hg help phases" for details'))
1970 1970 p1, p2 = repo.changelog.parentrevs(r)
1971 1971 n = repo.changelog.node(r)
1972 1972 if p2 != nullrev:
1973 1973 raise util.Abort(_('cannot import merge revision %d') % r)
1974 1974 if lastparent and lastparent != r:
1975 1975 raise util.Abort(_('revision %d is not the parent of %d')
1976 1976 % (r, lastparent))
1977 1977 lastparent = p1
1978 1978
1979 1979 if not patchname:
1980 1980 patchname = normname('%d.diff' % r)
1981 1981 checkseries(patchname)
1982 1982 self.checkpatchname(patchname, force)
1983 1983 self.fullseries.insert(0, patchname)
1984 1984
1985 1985 patchf = self.opener(patchname, "w")
1986 1986 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1987 1987 patchf.close()
1988 1988
1989 1989 se = statusentry(n, patchname)
1990 1990 self.applied.insert(0, se)
1991 1991
1992 1992 self.added.append(patchname)
1993 1993 imported.append(patchname)
1994 1994 patchname = None
1995 1995 if rev and repo.ui.configbool('mq', 'secret', False):
1996 1996 # if we added anything with --rev, we must move the secret root
1997 1997 phases.retractboundary(repo, phases.secret, [n])
1998 1998 self.parseseries()
1999 1999 self.applieddirty = True
2000 2000 self.seriesdirty = True
2001 2001
2002 2002 for i, filename in enumerate(files):
2003 2003 if existing:
2004 2004 if filename == '-':
2005 2005 raise util.Abort(_('-e is incompatible with import from -'))
2006 2006 filename = normname(filename)
2007 2007 self.checkreservedname(filename)
2008 2008 originpath = self.join(filename)
2009 2009 if not os.path.isfile(originpath):
2010 2010 raise util.Abort(_("patch %s does not exist") % filename)
2011 2011
2012 2012 if patchname:
2013 2013 self.checkpatchname(patchname, force)
2014 2014
2015 2015 self.ui.write(_('renaming %s to %s\n')
2016 2016 % (filename, patchname))
2017 2017 util.rename(originpath, self.join(patchname))
2018 2018 else:
2019 2019 patchname = filename
2020 2020
2021 2021 else:
2022 2022 if filename == '-' and not patchname:
2023 2023 raise util.Abort(_('need --name to import a patch from -'))
2024 2024 elif not patchname:
2025 2025 patchname = normname(os.path.basename(filename.rstrip('/')))
2026 2026 self.checkpatchname(patchname, force)
2027 2027 try:
2028 2028 if filename == '-':
2029 2029 text = self.ui.fin.read()
2030 2030 else:
2031 2031 fp = hg.openpath(self.ui, filename)
2032 2032 text = fp.read()
2033 2033 fp.close()
2034 2034 except (OSError, IOError):
2035 2035 raise util.Abort(_("unable to read file %s") % filename)
2036 2036 patchf = self.opener(patchname, "w")
2037 2037 patchf.write(text)
2038 2038 patchf.close()
2039 2039 if not force:
2040 2040 checkseries(patchname)
2041 2041 if patchname not in self.series:
2042 2042 index = self.fullseriesend() + i
2043 2043 self.fullseries[index:index] = [patchname]
2044 2044 self.parseseries()
2045 2045 self.seriesdirty = True
2046 2046 self.ui.warn(_("adding %s to series file\n") % patchname)
2047 2047 self.added.append(patchname)
2048 2048 imported.append(patchname)
2049 2049 patchname = None
2050 2050
2051 2051 self.removeundo(repo)
2052 2052 return imported
2053 2053
2054 2054 def fixkeepchangesopts(ui, opts):
2055 2055 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2056 2056 or opts.get('exact')):
2057 2057 return opts
2058 2058 opts = dict(opts)
2059 2059 opts['keep_changes'] = True
2060 2060 return opts
2061 2061
2062 2062 @command("qdelete|qremove|qrm",
2063 2063 [('k', 'keep', None, _('keep patch file')),
2064 2064 ('r', 'rev', [],
2065 2065 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2066 2066 _('hg qdelete [-k] [PATCH]...'))
2067 2067 def delete(ui, repo, *patches, **opts):
2068 2068 """remove patches from queue
2069 2069
2070 2070 The patches must not be applied, and at least one patch is required. Exact
2071 2071 patch identifiers must be given. With -k/--keep, the patch files are
2072 2072 preserved in the patch directory.
2073 2073
2074 2074 To stop managing a patch and move it into permanent history,
2075 2075 use the :hg:`qfinish` command."""
2076 2076 q = repo.mq
2077 2077 q.delete(repo, patches, opts)
2078 2078 q.savedirty()
2079 2079 return 0
2080 2080
2081 2081 @command("qapplied",
2082 2082 [('1', 'last', None, _('show only the preceding applied patch'))
2083 2083 ] + seriesopts,
2084 2084 _('hg qapplied [-1] [-s] [PATCH]'))
2085 2085 def applied(ui, repo, patch=None, **opts):
2086 2086 """print the patches already applied
2087 2087
2088 2088 Returns 0 on success."""
2089 2089
2090 2090 q = repo.mq
2091 2091
2092 2092 if patch:
2093 2093 if patch not in q.series:
2094 2094 raise util.Abort(_("patch %s is not in series file") % patch)
2095 2095 end = q.series.index(patch) + 1
2096 2096 else:
2097 2097 end = q.seriesend(True)
2098 2098
2099 2099 if opts.get('last') and not end:
2100 2100 ui.write(_("no patches applied\n"))
2101 2101 return 1
2102 2102 elif opts.get('last') and end == 1:
2103 2103 ui.write(_("only one patch applied\n"))
2104 2104 return 1
2105 2105 elif opts.get('last'):
2106 2106 start = end - 2
2107 2107 end = 1
2108 2108 else:
2109 2109 start = 0
2110 2110
2111 2111 q.qseries(repo, length=end, start=start, status='A',
2112 2112 summary=opts.get('summary'))
2113 2113
2114 2114
2115 2115 @command("qunapplied",
2116 2116 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2117 2117 _('hg qunapplied [-1] [-s] [PATCH]'))
2118 2118 def unapplied(ui, repo, patch=None, **opts):
2119 2119 """print the patches not yet applied
2120 2120
2121 2121 Returns 0 on success."""
2122 2122
2123 2123 q = repo.mq
2124 2124 if patch:
2125 2125 if patch not in q.series:
2126 2126 raise util.Abort(_("patch %s is not in series file") % patch)
2127 2127 start = q.series.index(patch) + 1
2128 2128 else:
2129 2129 start = q.seriesend(True)
2130 2130
2131 2131 if start == len(q.series) and opts.get('first'):
2132 2132 ui.write(_("all patches applied\n"))
2133 2133 return 1
2134 2134
2135 2135 length = opts.get('first') and 1 or None
2136 2136 q.qseries(repo, start=start, length=length, status='U',
2137 2137 summary=opts.get('summary'))
2138 2138
2139 2139 @command("qimport",
2140 2140 [('e', 'existing', None, _('import file in patch directory')),
2141 2141 ('n', 'name', '',
2142 2142 _('name of patch file'), _('NAME')),
2143 2143 ('f', 'force', None, _('overwrite existing files')),
2144 2144 ('r', 'rev', [],
2145 2145 _('place existing revisions under mq control'), _('REV')),
2146 2146 ('g', 'git', None, _('use git extended diff format')),
2147 2147 ('P', 'push', None, _('qpush after importing'))],
2148 2148 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2149 2149 def qimport(ui, repo, *filename, **opts):
2150 2150 """import a patch or existing changeset
2151 2151
2152 2152 The patch is inserted into the series after the last applied
2153 2153 patch. If no patches have been applied, qimport prepends the patch
2154 2154 to the series.
2155 2155
2156 2156 The patch will have the same name as its source file unless you
2157 2157 give it a new one with -n/--name.
2158 2158
2159 2159 You can register an existing patch inside the patch directory with
2160 2160 the -e/--existing flag.
2161 2161
2162 2162 With -f/--force, an existing patch of the same name will be
2163 2163 overwritten.
2164 2164
2165 2165 An existing changeset may be placed under mq control with -r/--rev
2166 2166 (e.g. qimport --rev . -n patch will place the current revision
2167 2167 under mq control). With -g/--git, patches imported with --rev will
2168 2168 use the git diff format. See the diffs help topic for information
2169 2169 on why this is important for preserving rename/copy information
2170 2170 and permission changes. Use :hg:`qfinish` to remove changesets
2171 2171 from mq control.
2172 2172
2173 2173 To import a patch from standard input, pass - as the patch file.
2174 2174 When importing from standard input, a patch name must be specified
2175 2175 using the --name flag.
2176 2176
2177 2177 To import an existing patch while renaming it::
2178 2178
2179 2179 hg qimport -e existing-patch -n new-name
2180 2180
2181 2181 Returns 0 if import succeeded.
2182 2182 """
2183 2183 lock = repo.lock() # cause this may move phase
2184 2184 try:
2185 2185 q = repo.mq
2186 2186 try:
2187 2187 imported = q.qimport(
2188 2188 repo, filename, patchname=opts.get('name'),
2189 2189 existing=opts.get('existing'), force=opts.get('force'),
2190 2190 rev=opts.get('rev'), git=opts.get('git'))
2191 2191 finally:
2192 2192 q.savedirty()
2193 2193 finally:
2194 2194 lock.release()
2195 2195
2196 2196 if imported and opts.get('push') and not opts.get('rev'):
2197 2197 return q.push(repo, imported[-1])
2198 2198 return 0
2199 2199
2200 2200 def qinit(ui, repo, create):
2201 2201 """initialize a new queue repository
2202 2202
2203 2203 This command also creates a series file for ordering patches, and
2204 2204 an mq-specific .hgignore file in the queue repository, to exclude
2205 2205 the status and guards files (these contain mostly transient state).
2206 2206
2207 2207 Returns 0 if initialization succeeded."""
2208 2208 q = repo.mq
2209 2209 r = q.init(repo, create)
2210 2210 q.savedirty()
2211 2211 if r:
2212 2212 if not os.path.exists(r.wjoin('.hgignore')):
2213 2213 fp = r.wopener('.hgignore', 'w')
2214 2214 fp.write('^\\.hg\n')
2215 2215 fp.write('^\\.mq\n')
2216 2216 fp.write('syntax: glob\n')
2217 2217 fp.write('status\n')
2218 2218 fp.write('guards\n')
2219 2219 fp.close()
2220 2220 if not os.path.exists(r.wjoin('series')):
2221 2221 r.wopener('series', 'w').close()
2222 2222 r[None].add(['.hgignore', 'series'])
2223 2223 commands.add(ui, r)
2224 2224 return 0
2225 2225
2226 2226 @command("^qinit",
2227 2227 [('c', 'create-repo', None, _('create queue repository'))],
2228 2228 _('hg qinit [-c]'))
2229 2229 def init(ui, repo, **opts):
2230 2230 """init a new queue repository (DEPRECATED)
2231 2231
2232 2232 The queue repository is unversioned by default. If
2233 2233 -c/--create-repo is specified, qinit will create a separate nested
2234 2234 repository for patches (qinit -c may also be run later to convert
2235 2235 an unversioned patch repository into a versioned one). You can use
2236 2236 qcommit to commit changes to this queue repository.
2237 2237
2238 2238 This command is deprecated. Without -c, it's implied by other relevant
2239 2239 commands. With -c, use :hg:`init --mq` instead."""
2240 2240 return qinit(ui, repo, create=opts.get('create_repo'))
2241 2241
2242 2242 @command("qclone",
2243 2243 [('', 'pull', None, _('use pull protocol to copy metadata')),
2244 2244 ('U', 'noupdate', None,
2245 2245 _('do not update the new working directories')),
2246 2246 ('', 'uncompressed', None,
2247 2247 _('use uncompressed transfer (fast over LAN)')),
2248 2248 ('p', 'patches', '',
2249 2249 _('location of source patch repository'), _('REPO')),
2250 2250 ] + commands.remoteopts,
2251 2251 _('hg qclone [OPTION]... SOURCE [DEST]'))
2252 2252 def clone(ui, source, dest=None, **opts):
2253 2253 '''clone main and patch repository at same time
2254 2254
2255 2255 If source is local, destination will have no patches applied. If
2256 2256 source is remote, this command can not check if patches are
2257 2257 applied in source, so cannot guarantee that patches are not
2258 2258 applied in destination. If you clone remote repository, be sure
2259 2259 before that it has no patches applied.
2260 2260
2261 2261 Source patch repository is looked for in <src>/.hg/patches by
2262 2262 default. Use -p <url> to change.
2263 2263
2264 2264 The patch directory must be a nested Mercurial repository, as
2265 2265 would be created by :hg:`init --mq`.
2266 2266
2267 2267 Return 0 on success.
2268 2268 '''
2269 2269 def patchdir(repo):
2270 2270 """compute a patch repo url from a repo object"""
2271 2271 url = repo.url()
2272 2272 if url.endswith('/'):
2273 2273 url = url[:-1]
2274 2274 return url + '/.hg/patches'
2275 2275
2276 2276 # main repo (destination and sources)
2277 2277 if dest is None:
2278 2278 dest = hg.defaultdest(source)
2279 2279 sr = hg.peer(ui, opts, ui.expandpath(source))
2280 2280
2281 2281 # patches repo (source only)
2282 2282 if opts.get('patches'):
2283 2283 patchespath = ui.expandpath(opts.get('patches'))
2284 2284 else:
2285 2285 patchespath = patchdir(sr)
2286 2286 try:
2287 2287 hg.peer(ui, opts, patchespath)
2288 2288 except error.RepoError:
2289 2289 raise util.Abort(_('versioned patch repository not found'
2290 2290 ' (see init --mq)'))
2291 2291 qbase, destrev = None, None
2292 2292 if sr.local():
2293 2293 repo = sr.local()
2294 2294 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2295 2295 qbase = repo.mq.applied[0].node
2296 2296 if not hg.islocal(dest):
2297 2297 heads = set(repo.heads())
2298 2298 destrev = list(heads.difference(repo.heads(qbase)))
2299 2299 destrev.append(repo.changelog.parents(qbase)[0])
2300 2300 elif sr.capable('lookup'):
2301 2301 try:
2302 2302 qbase = sr.lookup('qbase')
2303 2303 except error.RepoError:
2304 2304 pass
2305 2305
2306 2306 ui.note(_('cloning main repository\n'))
2307 2307 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2308 2308 pull=opts.get('pull'),
2309 2309 rev=destrev,
2310 2310 update=False,
2311 2311 stream=opts.get('uncompressed'))
2312 2312
2313 2313 ui.note(_('cloning patch repository\n'))
2314 2314 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2315 2315 pull=opts.get('pull'), update=not opts.get('noupdate'),
2316 2316 stream=opts.get('uncompressed'))
2317 2317
2318 2318 if dr.local():
2319 2319 repo = dr.local()
2320 2320 if qbase:
2321 2321 ui.note(_('stripping applied patches from destination '
2322 2322 'repository\n'))
2323 2323 repo.mq.strip(repo, [qbase], update=False, backup=None)
2324 2324 if not opts.get('noupdate'):
2325 2325 ui.note(_('updating destination repository\n'))
2326 2326 hg.update(repo, repo.changelog.tip())
2327 2327
2328 2328 @command("qcommit|qci",
2329 2329 commands.table["^commit|ci"][1],
2330 2330 _('hg qcommit [OPTION]... [FILE]...'))
2331 2331 def commit(ui, repo, *pats, **opts):
2332 2332 """commit changes in the queue repository (DEPRECATED)
2333 2333
2334 2334 This command is deprecated; use :hg:`commit --mq` instead."""
2335 2335 q = repo.mq
2336 2336 r = q.qrepo()
2337 2337 if not r:
2338 2338 raise util.Abort('no queue repository')
2339 2339 commands.commit(r.ui, r, *pats, **opts)
2340 2340
2341 2341 @command("qseries",
2342 2342 [('m', 'missing', None, _('print patches not in series')),
2343 2343 ] + seriesopts,
2344 2344 _('hg qseries [-ms]'))
2345 2345 def series(ui, repo, **opts):
2346 2346 """print the entire series file
2347 2347
2348 2348 Returns 0 on success."""
2349 2349 repo.mq.qseries(repo, missing=opts.get('missing'),
2350 2350 summary=opts.get('summary'))
2351 2351 return 0
2352 2352
2353 2353 @command("qtop", seriesopts, _('hg qtop [-s]'))
2354 2354 def top(ui, repo, **opts):
2355 2355 """print the name of the current patch
2356 2356
2357 2357 Returns 0 on success."""
2358 2358 q = repo.mq
2359 2359 t = q.applied and q.seriesend(True) or 0
2360 2360 if t:
2361 2361 q.qseries(repo, start=t - 1, length=1, status='A',
2362 2362 summary=opts.get('summary'))
2363 2363 else:
2364 2364 ui.write(_("no patches applied\n"))
2365 2365 return 1
2366 2366
2367 2367 @command("qnext", seriesopts, _('hg qnext [-s]'))
2368 2368 def next(ui, repo, **opts):
2369 2369 """print the name of the next pushable patch
2370 2370
2371 2371 Returns 0 on success."""
2372 2372 q = repo.mq
2373 2373 end = q.seriesend()
2374 2374 if end == len(q.series):
2375 2375 ui.write(_("all patches applied\n"))
2376 2376 return 1
2377 2377 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2378 2378
2379 2379 @command("qprev", seriesopts, _('hg qprev [-s]'))
2380 2380 def prev(ui, repo, **opts):
2381 2381 """print the name of the preceding applied patch
2382 2382
2383 2383 Returns 0 on success."""
2384 2384 q = repo.mq
2385 2385 l = len(q.applied)
2386 2386 if l == 1:
2387 2387 ui.write(_("only one patch applied\n"))
2388 2388 return 1
2389 2389 if not l:
2390 2390 ui.write(_("no patches applied\n"))
2391 2391 return 1
2392 2392 idx = q.series.index(q.applied[-2].name)
2393 2393 q.qseries(repo, start=idx, length=1, status='A',
2394 2394 summary=opts.get('summary'))
2395 2395
2396 2396 def setupheaderopts(ui, opts):
2397 2397 if not opts.get('user') and opts.get('currentuser'):
2398 2398 opts['user'] = ui.username()
2399 2399 if not opts.get('date') and opts.get('currentdate'):
2400 2400 opts['date'] = "%d %d" % util.makedate()
2401 2401
2402 2402 @command("^qnew",
2403 2403 [('e', 'edit', None, _('edit commit message')),
2404 2404 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2405 2405 ('g', 'git', None, _('use git extended diff format')),
2406 2406 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2407 2407 ('u', 'user', '',
2408 2408 _('add "From: <USER>" to patch'), _('USER')),
2409 2409 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2410 2410 ('d', 'date', '',
2411 2411 _('add "Date: <DATE>" to patch'), _('DATE'))
2412 2412 ] + commands.walkopts + commands.commitopts,
2413 2413 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2414 2414 def new(ui, repo, patch, *args, **opts):
2415 2415 """create a new patch
2416 2416
2417 2417 qnew creates a new patch on top of the currently-applied patch (if
2418 2418 any). The patch will be initialized with any outstanding changes
2419 2419 in the working directory. You may also use -I/--include,
2420 2420 -X/--exclude, and/or a list of files after the patch name to add
2421 2421 only changes to matching files to the new patch, leaving the rest
2422 2422 as uncommitted modifications.
2423 2423
2424 2424 -u/--user and -d/--date can be used to set the (given) user and
2425 2425 date, respectively. -U/--currentuser and -D/--currentdate set user
2426 2426 to current user and date to current date.
2427 2427
2428 2428 -e/--edit, -m/--message or -l/--logfile set the patch header as
2429 2429 well as the commit message. If none is specified, the header is
2430 2430 empty and the commit message is '[mq]: PATCH'.
2431 2431
2432 2432 Use the -g/--git option to keep the patch in the git extended diff
2433 2433 format. Read the diffs help topic for more information on why this
2434 2434 is important for preserving permission changes and copy/rename
2435 2435 information.
2436 2436
2437 2437 Returns 0 on successful creation of a new patch.
2438 2438 """
2439 2439 msg = cmdutil.logmessage(ui, opts)
2440 2440 def getmsg():
2441 2441 return ui.edit(msg, opts.get('user') or ui.username())
2442 2442 q = repo.mq
2443 2443 opts['msg'] = msg
2444 2444 if opts.get('edit'):
2445 2445 opts['msg'] = getmsg
2446 2446 else:
2447 2447 opts['msg'] = msg
2448 2448 setupheaderopts(ui, opts)
2449 2449 q.new(repo, patch, *args, **opts)
2450 2450 q.savedirty()
2451 2451 return 0
2452 2452
2453 2453 @command("^qrefresh",
2454 2454 [('e', 'edit', None, _('edit commit message')),
2455 2455 ('g', 'git', None, _('use git extended diff format')),
2456 2456 ('s', 'short', None,
2457 2457 _('refresh only files already in the patch and specified files')),
2458 2458 ('U', 'currentuser', None,
2459 2459 _('add/update author field in patch with current user')),
2460 2460 ('u', 'user', '',
2461 2461 _('add/update author field in patch with given user'), _('USER')),
2462 2462 ('D', 'currentdate', None,
2463 2463 _('add/update date field in patch with current date')),
2464 2464 ('d', 'date', '',
2465 2465 _('add/update date field in patch with given date'), _('DATE'))
2466 2466 ] + commands.walkopts + commands.commitopts,
2467 2467 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2468 2468 def refresh(ui, repo, *pats, **opts):
2469 2469 """update the current patch
2470 2470
2471 2471 If any file patterns are provided, the refreshed patch will
2472 2472 contain only the modifications that match those patterns; the
2473 2473 remaining modifications will remain in the working directory.
2474 2474
2475 2475 If -s/--short is specified, files currently included in the patch
2476 2476 will be refreshed just like matched files and remain in the patch.
2477 2477
2478 2478 If -e/--edit is specified, Mercurial will start your configured editor for
2479 2479 you to enter a message. In case qrefresh fails, you will find a backup of
2480 2480 your message in ``.hg/last-message.txt``.
2481 2481
2482 2482 hg add/remove/copy/rename work as usual, though you might want to
2483 2483 use git-style patches (-g/--git or [diff] git=1) to track copies
2484 2484 and renames. See the diffs help topic for more information on the
2485 2485 git diff format.
2486 2486
2487 2487 Returns 0 on success.
2488 2488 """
2489 2489 q = repo.mq
2490 2490 message = cmdutil.logmessage(ui, opts)
2491 2491 if opts.get('edit'):
2492 2492 if not q.applied:
2493 2493 ui.write(_("no patches applied\n"))
2494 2494 return 1
2495 2495 if message:
2496 2496 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2497 2497 patch = q.applied[-1].name
2498 2498 ph = patchheader(q.join(patch), q.plainmode)
2499 2499 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2500 2500 # We don't want to lose the patch message if qrefresh fails (issue2062)
2501 2501 repo.savecommitmessage(message)
2502 2502 setupheaderopts(ui, opts)
2503 2503 wlock = repo.wlock()
2504 2504 try:
2505 2505 ret = q.refresh(repo, pats, msg=message, **opts)
2506 2506 q.savedirty()
2507 2507 return ret
2508 2508 finally:
2509 2509 wlock.release()
2510 2510
2511 2511 @command("^qdiff",
2512 2512 commands.diffopts + commands.diffopts2 + commands.walkopts,
2513 2513 _('hg qdiff [OPTION]... [FILE]...'))
2514 2514 def diff(ui, repo, *pats, **opts):
2515 2515 """diff of the current patch and subsequent modifications
2516 2516
2517 2517 Shows a diff which includes the current patch as well as any
2518 2518 changes which have been made in the working directory since the
2519 2519 last refresh (thus showing what the current patch would become
2520 2520 after a qrefresh).
2521 2521
2522 2522 Use :hg:`diff` if you only want to see the changes made since the
2523 2523 last qrefresh, or :hg:`export qtip` if you want to see changes
2524 2524 made by the current patch without including changes made since the
2525 2525 qrefresh.
2526 2526
2527 2527 Returns 0 on success.
2528 2528 """
2529 2529 repo.mq.diff(repo, pats, opts)
2530 2530 return 0
2531 2531
2532 2532 @command('qfold',
2533 2533 [('e', 'edit', None, _('edit patch header')),
2534 2534 ('k', 'keep', None, _('keep folded patch files')),
2535 2535 ] + commands.commitopts,
2536 2536 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2537 2537 def fold(ui, repo, *files, **opts):
2538 2538 """fold the named patches into the current patch
2539 2539
2540 2540 Patches must not yet be applied. Each patch will be successively
2541 2541 applied to the current patch in the order given. If all the
2542 2542 patches apply successfully, the current patch will be refreshed
2543 2543 with the new cumulative patch, and the folded patches will be
2544 2544 deleted. With -k/--keep, the folded patch files will not be
2545 2545 removed afterwards.
2546 2546
2547 2547 The header for each folded patch will be concatenated with the
2548 2548 current patch header, separated by a line of ``* * *``.
2549 2549
2550 2550 Returns 0 on success."""
2551 2551 q = repo.mq
2552 2552 if not files:
2553 2553 raise util.Abort(_('qfold requires at least one patch name'))
2554 2554 if not q.checktoppatch(repo)[0]:
2555 2555 raise util.Abort(_('no patches applied'))
2556 2556 q.checklocalchanges(repo)
2557 2557
2558 2558 message = cmdutil.logmessage(ui, opts)
2559 2559 if opts.get('edit'):
2560 2560 if message:
2561 2561 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2562 2562
2563 2563 parent = q.lookup('qtip')
2564 2564 patches = []
2565 2565 messages = []
2566 2566 for f in files:
2567 2567 p = q.lookup(f)
2568 2568 if p in patches or p == parent:
2569 2569 ui.warn(_('skipping already folded patch %s\n') % p)
2570 2570 if q.isapplied(p):
2571 2571 raise util.Abort(_('qfold cannot fold already applied patch %s')
2572 2572 % p)
2573 2573 patches.append(p)
2574 2574
2575 2575 for p in patches:
2576 2576 if not message:
2577 2577 ph = patchheader(q.join(p), q.plainmode)
2578 2578 if ph.message:
2579 2579 messages.append(ph.message)
2580 2580 pf = q.join(p)
2581 2581 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2582 2582 if not patchsuccess:
2583 2583 raise util.Abort(_('error folding patch %s') % p)
2584 2584
2585 2585 if not message:
2586 2586 ph = patchheader(q.join(parent), q.plainmode)
2587 2587 message, user = ph.message, ph.user
2588 2588 for msg in messages:
2589 2589 message.append('* * *')
2590 2590 message.extend(msg)
2591 2591 message = '\n'.join(message)
2592 2592
2593 2593 if opts.get('edit'):
2594 2594 message = ui.edit(message, user or ui.username())
2595 2595
2596 2596 diffopts = q.patchopts(q.diffopts(), *patches)
2597 2597 wlock = repo.wlock()
2598 2598 try:
2599 2599 q.refresh(repo, msg=message, git=diffopts.git)
2600 2600 q.delete(repo, patches, opts)
2601 2601 q.savedirty()
2602 2602 finally:
2603 2603 wlock.release()
2604 2604
2605 2605 @command("qgoto",
2606 2606 [('', 'keep-changes', None,
2607 2607 _('tolerate non-conflicting local changes')),
2608 2608 ('f', 'force', None, _('overwrite any local changes')),
2609 2609 ('', 'no-backup', None, _('do not save backup copies of files'))],
2610 2610 _('hg qgoto [OPTION]... PATCH'))
2611 2611 def goto(ui, repo, patch, **opts):
2612 2612 '''push or pop patches until named patch is at top of stack
2613 2613
2614 2614 Returns 0 on success.'''
2615 2615 opts = fixkeepchangesopts(ui, opts)
2616 2616 q = repo.mq
2617 2617 patch = q.lookup(patch)
2618 2618 nobackup = opts.get('no_backup')
2619 2619 keepchanges = opts.get('keep_changes')
2620 2620 if q.isapplied(patch):
2621 2621 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2622 2622 keepchanges=keepchanges)
2623 2623 else:
2624 2624 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2625 2625 keepchanges=keepchanges)
2626 2626 q.savedirty()
2627 2627 return ret
2628 2628
2629 2629 @command("qguard",
2630 2630 [('l', 'list', None, _('list all patches and guards')),
2631 2631 ('n', 'none', None, _('drop all guards'))],
2632 2632 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2633 2633 def guard(ui, repo, *args, **opts):
2634 2634 '''set or print guards for a patch
2635 2635
2636 2636 Guards control whether a patch can be pushed. A patch with no
2637 2637 guards is always pushed. A patch with a positive guard ("+foo") is
2638 2638 pushed only if the :hg:`qselect` command has activated it. A patch with
2639 2639 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2640 2640 has activated it.
2641 2641
2642 2642 With no arguments, print the currently active guards.
2643 2643 With arguments, set guards for the named patch.
2644 2644
2645 2645 .. note::
2646 2646 Specifying negative guards now requires '--'.
2647 2647
2648 2648 To set guards on another patch::
2649 2649
2650 2650 hg qguard other.patch -- +2.6.17 -stable
2651 2651
2652 2652 Returns 0 on success.
2653 2653 '''
2654 2654 def status(idx):
2655 2655 guards = q.seriesguards[idx] or ['unguarded']
2656 2656 if q.series[idx] in applied:
2657 2657 state = 'applied'
2658 2658 elif q.pushable(idx)[0]:
2659 2659 state = 'unapplied'
2660 2660 else:
2661 2661 state = 'guarded'
2662 2662 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2663 2663 ui.write('%s: ' % ui.label(q.series[idx], label))
2664 2664
2665 2665 for i, guard in enumerate(guards):
2666 2666 if guard.startswith('+'):
2667 2667 ui.write(guard, label='qguard.positive')
2668 2668 elif guard.startswith('-'):
2669 2669 ui.write(guard, label='qguard.negative')
2670 2670 else:
2671 2671 ui.write(guard, label='qguard.unguarded')
2672 2672 if i != len(guards) - 1:
2673 2673 ui.write(' ')
2674 2674 ui.write('\n')
2675 2675 q = repo.mq
2676 2676 applied = set(p.name for p in q.applied)
2677 2677 patch = None
2678 2678 args = list(args)
2679 2679 if opts.get('list'):
2680 2680 if args or opts.get('none'):
2681 2681 raise util.Abort(_('cannot mix -l/--list with options or '
2682 2682 'arguments'))
2683 2683 for i in xrange(len(q.series)):
2684 2684 status(i)
2685 2685 return
2686 2686 if not args or args[0][0:1] in '-+':
2687 2687 if not q.applied:
2688 2688 raise util.Abort(_('no patches applied'))
2689 2689 patch = q.applied[-1].name
2690 2690 if patch is None and args[0][0:1] not in '-+':
2691 2691 patch = args.pop(0)
2692 2692 if patch is None:
2693 2693 raise util.Abort(_('no patch to work with'))
2694 2694 if args or opts.get('none'):
2695 2695 idx = q.findseries(patch)
2696 2696 if idx is None:
2697 2697 raise util.Abort(_('no patch named %s') % patch)
2698 2698 q.setguards(idx, args)
2699 2699 q.savedirty()
2700 2700 else:
2701 2701 status(q.series.index(q.lookup(patch)))
2702 2702
2703 2703 @command("qheader", [], _('hg qheader [PATCH]'))
2704 2704 def header(ui, repo, patch=None):
2705 2705 """print the header of the topmost or specified patch
2706 2706
2707 2707 Returns 0 on success."""
2708 2708 q = repo.mq
2709 2709
2710 2710 if patch:
2711 2711 patch = q.lookup(patch)
2712 2712 else:
2713 2713 if not q.applied:
2714 2714 ui.write(_('no patches applied\n'))
2715 2715 return 1
2716 2716 patch = q.lookup('qtip')
2717 2717 ph = patchheader(q.join(patch), q.plainmode)
2718 2718
2719 2719 ui.write('\n'.join(ph.message) + '\n')
2720 2720
2721 2721 def lastsavename(path):
2722 2722 (directory, base) = os.path.split(path)
2723 2723 names = os.listdir(directory)
2724 2724 namere = re.compile("%s.([0-9]+)" % base)
2725 2725 maxindex = None
2726 2726 maxname = None
2727 2727 for f in names:
2728 2728 m = namere.match(f)
2729 2729 if m:
2730 2730 index = int(m.group(1))
2731 2731 if maxindex is None or index > maxindex:
2732 2732 maxindex = index
2733 2733 maxname = f
2734 2734 if maxname:
2735 2735 return (os.path.join(directory, maxname), maxindex)
2736 2736 return (None, None)
2737 2737
2738 2738 def savename(path):
2739 2739 (last, index) = lastsavename(path)
2740 2740 if last is None:
2741 2741 index = 0
2742 2742 newpath = path + ".%d" % (index + 1)
2743 2743 return newpath
2744 2744
2745 2745 @command("^qpush",
2746 2746 [('', 'keep-changes', None,
2747 2747 _('tolerate non-conflicting local changes')),
2748 2748 ('f', 'force', None, _('apply on top of local changes')),
2749 2749 ('e', 'exact', None,
2750 2750 _('apply the target patch to its recorded parent')),
2751 2751 ('l', 'list', None, _('list patch name in commit text')),
2752 2752 ('a', 'all', None, _('apply all patches')),
2753 2753 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2754 2754 ('n', 'name', '',
2755 2755 _('merge queue name (DEPRECATED)'), _('NAME')),
2756 2756 ('', 'move', None,
2757 2757 _('reorder patch series and apply only the patch')),
2758 2758 ('', 'no-backup', None, _('do not save backup copies of files'))],
2759 2759 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2760 2760 def push(ui, repo, patch=None, **opts):
2761 2761 """push the next patch onto the stack
2762 2762
2763 2763 By default, abort if the working directory contains uncommitted
2764 2764 changes. With --keep-changes, abort only if the uncommitted files
2765 2765 overlap with patched files. With -f/--force, backup and patch over
2766 2766 uncommitted changes.
2767 2767
2768 2768 Return 0 on success.
2769 2769 """
2770 2770 q = repo.mq
2771 2771 mergeq = None
2772 2772
2773 2773 opts = fixkeepchangesopts(ui, opts)
2774 2774 if opts.get('merge'):
2775 2775 if opts.get('name'):
2776 2776 newpath = repo.join(opts.get('name'))
2777 2777 else:
2778 2778 newpath, i = lastsavename(q.path)
2779 2779 if not newpath:
2780 2780 ui.warn(_("no saved queues found, please use -n\n"))
2781 2781 return 1
2782 2782 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2783 2783 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2784 2784 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2785 2785 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2786 2786 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2787 2787 keepchanges=opts.get('keep_changes'))
2788 2788 return ret
2789 2789
2790 2790 @command("^qpop",
2791 2791 [('a', 'all', None, _('pop all patches')),
2792 2792 ('n', 'name', '',
2793 2793 _('queue name to pop (DEPRECATED)'), _('NAME')),
2794 2794 ('', 'keep-changes', None,
2795 2795 _('tolerate non-conflicting local changes')),
2796 2796 ('f', 'force', None, _('forget any local changes to patched files')),
2797 2797 ('', 'no-backup', None, _('do not save backup copies of files'))],
2798 2798 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2799 2799 def pop(ui, repo, patch=None, **opts):
2800 2800 """pop the current patch off the stack
2801 2801
2802 2802 Without argument, pops off the top of the patch stack. If given a
2803 2803 patch name, keeps popping off patches until the named patch is at
2804 2804 the top of the stack.
2805 2805
2806 2806 By default, abort if the working directory contains uncommitted
2807 2807 changes. With --keep-changes, abort only if the uncommitted files
2808 2808 overlap with patched files. With -f/--force, backup and discard
2809 2809 changes made to such files.
2810 2810
2811 2811 Return 0 on success.
2812 2812 """
2813 2813 opts = fixkeepchangesopts(ui, opts)
2814 2814 localupdate = True
2815 2815 if opts.get('name'):
2816 2816 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2817 2817 ui.warn(_('using patch queue: %s\n') % q.path)
2818 2818 localupdate = False
2819 2819 else:
2820 2820 q = repo.mq
2821 2821 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2822 2822 all=opts.get('all'), nobackup=opts.get('no_backup'),
2823 2823 keepchanges=opts.get('keep_changes'))
2824 2824 q.savedirty()
2825 2825 return ret
2826 2826
2827 2827 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2828 2828 def rename(ui, repo, patch, name=None, **opts):
2829 2829 """rename a patch
2830 2830
2831 2831 With one argument, renames the current patch to PATCH1.
2832 2832 With two arguments, renames PATCH1 to PATCH2.
2833 2833
2834 2834 Returns 0 on success."""
2835 2835 q = repo.mq
2836 2836 if not name:
2837 2837 name = patch
2838 2838 patch = None
2839 2839
2840 2840 if patch:
2841 2841 patch = q.lookup(patch)
2842 2842 else:
2843 2843 if not q.applied:
2844 2844 ui.write(_('no patches applied\n'))
2845 2845 return
2846 2846 patch = q.lookup('qtip')
2847 2847 absdest = q.join(name)
2848 2848 if os.path.isdir(absdest):
2849 2849 name = normname(os.path.join(name, os.path.basename(patch)))
2850 2850 absdest = q.join(name)
2851 2851 q.checkpatchname(name)
2852 2852
2853 2853 ui.note(_('renaming %s to %s\n') % (patch, name))
2854 2854 i = q.findseries(patch)
2855 2855 guards = q.guard_re.findall(q.fullseries[i])
2856 2856 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2857 2857 q.parseseries()
2858 2858 q.seriesdirty = True
2859 2859
2860 2860 info = q.isapplied(patch)
2861 2861 if info:
2862 2862 q.applied[info[0]] = statusentry(info[1], name)
2863 2863 q.applieddirty = True
2864 2864
2865 2865 destdir = os.path.dirname(absdest)
2866 2866 if not os.path.isdir(destdir):
2867 2867 os.makedirs(destdir)
2868 2868 util.rename(q.join(patch), absdest)
2869 2869 r = q.qrepo()
2870 2870 if r and patch in r.dirstate:
2871 2871 wctx = r[None]
2872 2872 wlock = r.wlock()
2873 2873 try:
2874 2874 if r.dirstate[patch] == 'a':
2875 2875 r.dirstate.drop(patch)
2876 2876 r.dirstate.add(name)
2877 2877 else:
2878 2878 wctx.copy(patch, name)
2879 2879 wctx.forget([patch])
2880 2880 finally:
2881 2881 wlock.release()
2882 2882
2883 2883 q.savedirty()
2884 2884
2885 2885 @command("qrestore",
2886 2886 [('d', 'delete', None, _('delete save entry')),
2887 2887 ('u', 'update', None, _('update queue working directory'))],
2888 2888 _('hg qrestore [-d] [-u] REV'))
2889 2889 def restore(ui, repo, rev, **opts):
2890 2890 """restore the queue state saved by a revision (DEPRECATED)
2891 2891
2892 2892 This command is deprecated, use :hg:`rebase` instead."""
2893 2893 rev = repo.lookup(rev)
2894 2894 q = repo.mq
2895 2895 q.restore(repo, rev, delete=opts.get('delete'),
2896 2896 qupdate=opts.get('update'))
2897 2897 q.savedirty()
2898 2898 return 0
2899 2899
2900 2900 @command("qsave",
2901 2901 [('c', 'copy', None, _('copy patch directory')),
2902 2902 ('n', 'name', '',
2903 2903 _('copy directory name'), _('NAME')),
2904 2904 ('e', 'empty', None, _('clear queue status file')),
2905 2905 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2906 2906 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2907 2907 def save(ui, repo, **opts):
2908 2908 """save current queue state (DEPRECATED)
2909 2909
2910 2910 This command is deprecated, use :hg:`rebase` instead."""
2911 2911 q = repo.mq
2912 2912 message = cmdutil.logmessage(ui, opts)
2913 2913 ret = q.save(repo, msg=message)
2914 2914 if ret:
2915 2915 return ret
2916 2916 q.savedirty() # save to .hg/patches before copying
2917 2917 if opts.get('copy'):
2918 2918 path = q.path
2919 2919 if opts.get('name'):
2920 2920 newpath = os.path.join(q.basepath, opts.get('name'))
2921 2921 if os.path.exists(newpath):
2922 2922 if not os.path.isdir(newpath):
2923 2923 raise util.Abort(_('destination %s exists and is not '
2924 2924 'a directory') % newpath)
2925 2925 if not opts.get('force'):
2926 2926 raise util.Abort(_('destination %s exists, '
2927 2927 'use -f to force') % newpath)
2928 2928 else:
2929 2929 newpath = savename(path)
2930 2930 ui.warn(_("copy %s to %s\n") % (path, newpath))
2931 2931 util.copyfiles(path, newpath)
2932 2932 if opts.get('empty'):
2933 2933 del q.applied[:]
2934 2934 q.applieddirty = True
2935 2935 q.savedirty()
2936 2936 return 0
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 if mark == repo._bookmarkcurrent:
3081 bookmarks.setcurrent(repo, None)
3080 3082 del marks[mark]
3081 3083 marks.write()
3082 3084 ui.write(_("bookmark '%s' deleted\n") % mark)
3083 3085
3084 3086 repo.mq.strip(repo, revs, backup=backup, update=update,
3085 3087 force=opts.get('force'))
3086 3088
3087 3089 return 0
3088 3090
3089 3091 @command("qselect",
3090 3092 [('n', 'none', None, _('disable all guards')),
3091 3093 ('s', 'series', None, _('list all guards in series file')),
3092 3094 ('', 'pop', None, _('pop to before first guarded applied patch')),
3093 3095 ('', 'reapply', None, _('pop, then reapply patches'))],
3094 3096 _('hg qselect [OPTION]... [GUARD]...'))
3095 3097 def select(ui, repo, *args, **opts):
3096 3098 '''set or print guarded patches to push
3097 3099
3098 3100 Use the :hg:`qguard` command to set or print guards on patch, then use
3099 3101 qselect to tell mq which guards to use. A patch will be pushed if
3100 3102 it has no guards or any positive guards match the currently
3101 3103 selected guard, but will not be pushed if any negative guards
3102 3104 match the current guard. For example::
3103 3105
3104 3106 qguard foo.patch -- -stable (negative guard)
3105 3107 qguard bar.patch +stable (positive guard)
3106 3108 qselect stable
3107 3109
3108 3110 This activates the "stable" guard. mq will skip foo.patch (because
3109 3111 it has a negative match) but push bar.patch (because it has a
3110 3112 positive match).
3111 3113
3112 3114 With no arguments, prints the currently active guards.
3113 3115 With one argument, sets the active guard.
3114 3116
3115 3117 Use -n/--none to deactivate guards (no other arguments needed).
3116 3118 When no guards are active, patches with positive guards are
3117 3119 skipped and patches with negative guards are pushed.
3118 3120
3119 3121 qselect can change the guards on applied patches. It does not pop
3120 3122 guarded patches by default. Use --pop to pop back to the last
3121 3123 applied patch that is not guarded. Use --reapply (which implies
3122 3124 --pop) to push back to the current patch afterwards, but skip
3123 3125 guarded patches.
3124 3126
3125 3127 Use -s/--series to print a list of all guards in the series file
3126 3128 (no other arguments needed). Use -v for more information.
3127 3129
3128 3130 Returns 0 on success.'''
3129 3131
3130 3132 q = repo.mq
3131 3133 guards = q.active()
3132 3134 if args or opts.get('none'):
3133 3135 old_unapplied = q.unapplied(repo)
3134 3136 old_guarded = [i for i in xrange(len(q.applied)) if
3135 3137 not q.pushable(i)[0]]
3136 3138 q.setactive(args)
3137 3139 q.savedirty()
3138 3140 if not args:
3139 3141 ui.status(_('guards deactivated\n'))
3140 3142 if not opts.get('pop') and not opts.get('reapply'):
3141 3143 unapplied = q.unapplied(repo)
3142 3144 guarded = [i for i in xrange(len(q.applied))
3143 3145 if not q.pushable(i)[0]]
3144 3146 if len(unapplied) != len(old_unapplied):
3145 3147 ui.status(_('number of unguarded, unapplied patches has '
3146 3148 'changed from %d to %d\n') %
3147 3149 (len(old_unapplied), len(unapplied)))
3148 3150 if len(guarded) != len(old_guarded):
3149 3151 ui.status(_('number of guarded, applied patches has changed '
3150 3152 'from %d to %d\n') %
3151 3153 (len(old_guarded), len(guarded)))
3152 3154 elif opts.get('series'):
3153 3155 guards = {}
3154 3156 noguards = 0
3155 3157 for gs in q.seriesguards:
3156 3158 if not gs:
3157 3159 noguards += 1
3158 3160 for g in gs:
3159 3161 guards.setdefault(g, 0)
3160 3162 guards[g] += 1
3161 3163 if ui.verbose:
3162 3164 guards['NONE'] = noguards
3163 3165 guards = guards.items()
3164 3166 guards.sort(key=lambda x: x[0][1:])
3165 3167 if guards:
3166 3168 ui.note(_('guards in series file:\n'))
3167 3169 for guard, count in guards:
3168 3170 ui.note('%2d ' % count)
3169 3171 ui.write(guard, '\n')
3170 3172 else:
3171 3173 ui.note(_('no guards in series file\n'))
3172 3174 else:
3173 3175 if guards:
3174 3176 ui.note(_('active guards:\n'))
3175 3177 for g in guards:
3176 3178 ui.write(g, '\n')
3177 3179 else:
3178 3180 ui.write(_('no active guards\n'))
3179 3181 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3180 3182 popped = False
3181 3183 if opts.get('pop') or opts.get('reapply'):
3182 3184 for i in xrange(len(q.applied)):
3183 3185 pushable, reason = q.pushable(i)
3184 3186 if not pushable:
3185 3187 ui.status(_('popping guarded patches\n'))
3186 3188 popped = True
3187 3189 if i == 0:
3188 3190 q.pop(repo, all=True)
3189 3191 else:
3190 3192 q.pop(repo, str(i - 1))
3191 3193 break
3192 3194 if popped:
3193 3195 try:
3194 3196 if reapply:
3195 3197 ui.status(_('reapplying unguarded patches\n'))
3196 3198 q.push(repo, reapply)
3197 3199 finally:
3198 3200 q.savedirty()
3199 3201
3200 3202 @command("qfinish",
3201 3203 [('a', 'applied', None, _('finish all applied changesets'))],
3202 3204 _('hg qfinish [-a] [REV]...'))
3203 3205 def finish(ui, repo, *revrange, **opts):
3204 3206 """move applied patches into repository history
3205 3207
3206 3208 Finishes the specified revisions (corresponding to applied
3207 3209 patches) by moving them out of mq control into regular repository
3208 3210 history.
3209 3211
3210 3212 Accepts a revision range or the -a/--applied option. If --applied
3211 3213 is specified, all applied mq revisions are removed from mq
3212 3214 control. Otherwise, the given revisions must be at the base of the
3213 3215 stack of applied patches.
3214 3216
3215 3217 This can be especially useful if your changes have been applied to
3216 3218 an upstream repository, or if you are about to push your changes
3217 3219 to upstream.
3218 3220
3219 3221 Returns 0 on success.
3220 3222 """
3221 3223 if not opts.get('applied') and not revrange:
3222 3224 raise util.Abort(_('no revisions specified'))
3223 3225 elif opts.get('applied'):
3224 3226 revrange = ('qbase::qtip',) + revrange
3225 3227
3226 3228 q = repo.mq
3227 3229 if not q.applied:
3228 3230 ui.status(_('no patches applied\n'))
3229 3231 return 0
3230 3232
3231 3233 revs = scmutil.revrange(repo, revrange)
3232 3234 if repo['.'].rev() in revs and repo[None].files():
3233 3235 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3234 3236 # queue.finish may changes phases but leave the responsibility to lock the
3235 3237 # repo to the caller to avoid deadlock with wlock. This command code is
3236 3238 # responsibility for this locking.
3237 3239 lock = repo.lock()
3238 3240 try:
3239 3241 q.finish(repo, revs)
3240 3242 q.savedirty()
3241 3243 finally:
3242 3244 lock.release()
3243 3245 return 0
3244 3246
3245 3247 @command("qqueue",
3246 3248 [('l', 'list', False, _('list all available queues')),
3247 3249 ('', 'active', False, _('print name of active queue')),
3248 3250 ('c', 'create', False, _('create new queue')),
3249 3251 ('', 'rename', False, _('rename active queue')),
3250 3252 ('', 'delete', False, _('delete reference to queue')),
3251 3253 ('', 'purge', False, _('delete queue, and remove patch dir')),
3252 3254 ],
3253 3255 _('[OPTION] [QUEUE]'))
3254 3256 def qqueue(ui, repo, name=None, **opts):
3255 3257 '''manage multiple patch queues
3256 3258
3257 3259 Supports switching between different patch queues, as well as creating
3258 3260 new patch queues and deleting existing ones.
3259 3261
3260 3262 Omitting a queue name or specifying -l/--list will show you the registered
3261 3263 queues - by default the "normal" patches queue is registered. The currently
3262 3264 active queue will be marked with "(active)". Specifying --active will print
3263 3265 only the name of the active queue.
3264 3266
3265 3267 To create a new queue, use -c/--create. The queue is automatically made
3266 3268 active, except in the case where there are applied patches from the
3267 3269 currently active queue in the repository. Then the queue will only be
3268 3270 created and switching will fail.
3269 3271
3270 3272 To delete an existing queue, use --delete. You cannot delete the currently
3271 3273 active queue.
3272 3274
3273 3275 Returns 0 on success.
3274 3276 '''
3275 3277 q = repo.mq
3276 3278 _defaultqueue = 'patches'
3277 3279 _allqueues = 'patches.queues'
3278 3280 _activequeue = 'patches.queue'
3279 3281
3280 3282 def _getcurrent():
3281 3283 cur = os.path.basename(q.path)
3282 3284 if cur.startswith('patches-'):
3283 3285 cur = cur[8:]
3284 3286 return cur
3285 3287
3286 3288 def _noqueues():
3287 3289 try:
3288 3290 fh = repo.opener(_allqueues, 'r')
3289 3291 fh.close()
3290 3292 except IOError:
3291 3293 return True
3292 3294
3293 3295 return False
3294 3296
3295 3297 def _getqueues():
3296 3298 current = _getcurrent()
3297 3299
3298 3300 try:
3299 3301 fh = repo.opener(_allqueues, 'r')
3300 3302 queues = [queue.strip() for queue in fh if queue.strip()]
3301 3303 fh.close()
3302 3304 if current not in queues:
3303 3305 queues.append(current)
3304 3306 except IOError:
3305 3307 queues = [_defaultqueue]
3306 3308
3307 3309 return sorted(queues)
3308 3310
3309 3311 def _setactive(name):
3310 3312 if q.applied:
3311 3313 raise util.Abort(_('new queue created, but cannot make active '
3312 3314 'as patches are applied'))
3313 3315 _setactivenocheck(name)
3314 3316
3315 3317 def _setactivenocheck(name):
3316 3318 fh = repo.opener(_activequeue, 'w')
3317 3319 if name != 'patches':
3318 3320 fh.write(name)
3319 3321 fh.close()
3320 3322
3321 3323 def _addqueue(name):
3322 3324 fh = repo.opener(_allqueues, 'a')
3323 3325 fh.write('%s\n' % (name,))
3324 3326 fh.close()
3325 3327
3326 3328 def _queuedir(name):
3327 3329 if name == 'patches':
3328 3330 return repo.join('patches')
3329 3331 else:
3330 3332 return repo.join('patches-' + name)
3331 3333
3332 3334 def _validname(name):
3333 3335 for n in name:
3334 3336 if n in ':\\/.':
3335 3337 return False
3336 3338 return True
3337 3339
3338 3340 def _delete(name):
3339 3341 if name not in existing:
3340 3342 raise util.Abort(_('cannot delete queue that does not exist'))
3341 3343
3342 3344 current = _getcurrent()
3343 3345
3344 3346 if name == current:
3345 3347 raise util.Abort(_('cannot delete currently active queue'))
3346 3348
3347 3349 fh = repo.opener('patches.queues.new', 'w')
3348 3350 for queue in existing:
3349 3351 if queue == name:
3350 3352 continue
3351 3353 fh.write('%s\n' % (queue,))
3352 3354 fh.close()
3353 3355 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3354 3356
3355 3357 if not name or opts.get('list') or opts.get('active'):
3356 3358 current = _getcurrent()
3357 3359 if opts.get('active'):
3358 3360 ui.write('%s\n' % (current,))
3359 3361 return
3360 3362 for queue in _getqueues():
3361 3363 ui.write('%s' % (queue,))
3362 3364 if queue == current and not ui.quiet:
3363 3365 ui.write(_(' (active)\n'))
3364 3366 else:
3365 3367 ui.write('\n')
3366 3368 return
3367 3369
3368 3370 if not _validname(name):
3369 3371 raise util.Abort(
3370 3372 _('invalid queue name, may not contain the characters ":\\/."'))
3371 3373
3372 3374 existing = _getqueues()
3373 3375
3374 3376 if opts.get('create'):
3375 3377 if name in existing:
3376 3378 raise util.Abort(_('queue "%s" already exists') % name)
3377 3379 if _noqueues():
3378 3380 _addqueue(_defaultqueue)
3379 3381 _addqueue(name)
3380 3382 _setactive(name)
3381 3383 elif opts.get('rename'):
3382 3384 current = _getcurrent()
3383 3385 if name == current:
3384 3386 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3385 3387 if name in existing:
3386 3388 raise util.Abort(_('queue "%s" already exists') % name)
3387 3389
3388 3390 olddir = _queuedir(current)
3389 3391 newdir = _queuedir(name)
3390 3392
3391 3393 if os.path.exists(newdir):
3392 3394 raise util.Abort(_('non-queue directory "%s" already exists') %
3393 3395 newdir)
3394 3396
3395 3397 fh = repo.opener('patches.queues.new', 'w')
3396 3398 for queue in existing:
3397 3399 if queue == current:
3398 3400 fh.write('%s\n' % (name,))
3399 3401 if os.path.exists(olddir):
3400 3402 util.rename(olddir, newdir)
3401 3403 else:
3402 3404 fh.write('%s\n' % (queue,))
3403 3405 fh.close()
3404 3406 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3405 3407 _setactivenocheck(name)
3406 3408 elif opts.get('delete'):
3407 3409 _delete(name)
3408 3410 elif opts.get('purge'):
3409 3411 if name in existing:
3410 3412 _delete(name)
3411 3413 qdir = _queuedir(name)
3412 3414 if os.path.exists(qdir):
3413 3415 shutil.rmtree(qdir)
3414 3416 else:
3415 3417 if name not in existing:
3416 3418 raise util.Abort(_('use --create to create a new queue'))
3417 3419 _setactive(name)
3418 3420
3419 3421 def mqphasedefaults(repo, roots):
3420 3422 """callback used to set mq changeset as secret when no phase data exists"""
3421 3423 if repo.mq.applied:
3422 3424 if repo.ui.configbool('mq', 'secret', False):
3423 3425 mqphase = phases.secret
3424 3426 else:
3425 3427 mqphase = phases.draft
3426 3428 qbase = repo[repo.mq.applied[0].node]
3427 3429 roots[mqphase].add(qbase.node())
3428 3430 return roots
3429 3431
3430 3432 def reposetup(ui, repo):
3431 3433 class mqrepo(repo.__class__):
3432 3434 @localrepo.unfilteredpropertycache
3433 3435 def mq(self):
3434 3436 return queue(self.ui, self.baseui, self.path)
3435 3437
3436 3438 def abortifwdirpatched(self, errmsg, force=False):
3437 3439 if self.mq.applied and not force:
3438 3440 parents = self.dirstate.parents()
3439 3441 patches = [s.node for s in self.mq.applied]
3440 3442 if parents[0] in patches or parents[1] in patches:
3441 3443 raise util.Abort(errmsg)
3442 3444
3443 3445 def commit(self, text="", user=None, date=None, match=None,
3444 3446 force=False, editor=False, extra={}):
3445 3447 self.abortifwdirpatched(
3446 3448 _('cannot commit over an applied mq patch'),
3447 3449 force)
3448 3450
3449 3451 return super(mqrepo, self).commit(text, user, date, match, force,
3450 3452 editor, extra)
3451 3453
3452 3454 def checkpush(self, force, revs):
3453 3455 if self.mq.applied and not force:
3454 3456 outapplied = [e.node for e in self.mq.applied]
3455 3457 if revs:
3456 3458 # Assume applied patches have no non-patch descendants and
3457 3459 # are not on remote already. Filtering any changeset not
3458 3460 # pushed.
3459 3461 heads = set(revs)
3460 3462 for node in reversed(outapplied):
3461 3463 if node in heads:
3462 3464 break
3463 3465 else:
3464 3466 outapplied.pop()
3465 3467 # looking for pushed and shared changeset
3466 3468 for node in outapplied:
3467 3469 if self[node].phase() < phases.secret:
3468 3470 raise util.Abort(_('source has mq patches applied'))
3469 3471 # no non-secret patches pushed
3470 3472 super(mqrepo, self).checkpush(force, revs)
3471 3473
3472 3474 def _findtags(self):
3473 3475 '''augment tags from base class with patch tags'''
3474 3476 result = super(mqrepo, self)._findtags()
3475 3477
3476 3478 q = self.mq
3477 3479 if not q.applied:
3478 3480 return result
3479 3481
3480 3482 mqtags = [(patch.node, patch.name) for patch in q.applied]
3481 3483
3482 3484 try:
3483 3485 # for now ignore filtering business
3484 3486 self.unfiltered().changelog.rev(mqtags[-1][0])
3485 3487 except error.LookupError:
3486 3488 self.ui.warn(_('mq status file refers to unknown node %s\n')
3487 3489 % short(mqtags[-1][0]))
3488 3490 return result
3489 3491
3490 3492 # do not add fake tags for filtered revisions
3491 3493 included = self.changelog.hasnode
3492 3494 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3493 3495 if not mqtags:
3494 3496 return result
3495 3497
3496 3498 mqtags.append((mqtags[-1][0], 'qtip'))
3497 3499 mqtags.append((mqtags[0][0], 'qbase'))
3498 3500 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3499 3501 tags = result[0]
3500 3502 for patch in mqtags:
3501 3503 if patch[1] in tags:
3502 3504 self.ui.warn(_('tag %s overrides mq patch of the same '
3503 3505 'name\n') % patch[1])
3504 3506 else:
3505 3507 tags[patch[1]] = patch[0]
3506 3508
3507 3509 return result
3508 3510
3509 3511 if repo.local():
3510 3512 repo.__class__ = mqrepo
3511 3513
3512 3514 repo._phasedefaults.append(mqphasedefaults)
3513 3515
3514 3516 def mqimport(orig, ui, repo, *args, **kwargs):
3515 3517 if (util.safehasattr(repo, 'abortifwdirpatched')
3516 3518 and not kwargs.get('no_commit', False)):
3517 3519 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3518 3520 kwargs.get('force'))
3519 3521 return orig(ui, repo, *args, **kwargs)
3520 3522
3521 3523 def mqinit(orig, ui, *args, **kwargs):
3522 3524 mq = kwargs.pop('mq', None)
3523 3525
3524 3526 if not mq:
3525 3527 return orig(ui, *args, **kwargs)
3526 3528
3527 3529 if args:
3528 3530 repopath = args[0]
3529 3531 if not hg.islocal(repopath):
3530 3532 raise util.Abort(_('only a local queue repository '
3531 3533 'may be initialized'))
3532 3534 else:
3533 3535 repopath = cmdutil.findrepo(os.getcwd())
3534 3536 if not repopath:
3535 3537 raise util.Abort(_('there is no Mercurial repository here '
3536 3538 '(.hg not found)'))
3537 3539 repo = hg.repository(ui, repopath)
3538 3540 return qinit(ui, repo, True)
3539 3541
3540 3542 def mqcommand(orig, ui, repo, *args, **kwargs):
3541 3543 """Add --mq option to operate on patch repository instead of main"""
3542 3544
3543 3545 # some commands do not like getting unknown options
3544 3546 mq = kwargs.pop('mq', None)
3545 3547
3546 3548 if not mq:
3547 3549 return orig(ui, repo, *args, **kwargs)
3548 3550
3549 3551 q = repo.mq
3550 3552 r = q.qrepo()
3551 3553 if not r:
3552 3554 raise util.Abort(_('no queue repository'))
3553 3555 return orig(r.ui, r, *args, **kwargs)
3554 3556
3555 3557 def summaryhook(ui, repo):
3556 3558 q = repo.mq
3557 3559 m = []
3558 3560 a, u = len(q.applied), len(q.unapplied(repo))
3559 3561 if a:
3560 3562 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3561 3563 if u:
3562 3564 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3563 3565 if m:
3564 3566 # i18n: column positioning for "hg summary"
3565 3567 ui.write(_("mq: %s\n") % ', '.join(m))
3566 3568 else:
3567 3569 # i18n: column positioning for "hg summary"
3568 3570 ui.note(_("mq: (empty queue)\n"))
3569 3571
3570 3572 def revsetmq(repo, subset, x):
3571 3573 """``mq()``
3572 3574 Changesets managed by MQ.
3573 3575 """
3574 3576 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3575 3577 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3576 3578 return [r for r in subset if r in applied]
3577 3579
3578 3580 # tell hggettext to extract docstrings from these functions:
3579 3581 i18nfunctions = [revsetmq]
3580 3582
3581 3583 def extsetup(ui):
3582 3584 # Ensure mq wrappers are called first, regardless of extension load order by
3583 3585 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3584 3586 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3585 3587
3586 3588 extensions.wrapcommand(commands.table, 'import', mqimport)
3587 3589 cmdutil.summaryhooks.add('mq', summaryhook)
3588 3590
3589 3591 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3590 3592 entry[1].extend(mqopt)
3591 3593
3592 3594 nowrap = set(commands.norepo.split(" "))
3593 3595
3594 3596 def dotable(cmdtable):
3595 3597 for cmd in cmdtable.keys():
3596 3598 cmd = cmdutil.parsealiases(cmd)[0]
3597 3599 if cmd in nowrap:
3598 3600 continue
3599 3601 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3600 3602 entry[1].extend(mqopt)
3601 3603
3602 3604 dotable(commands.table)
3603 3605
3604 3606 for extname, extmodule in extensions.extensions():
3605 3607 if extmodule.__file__ != __file__:
3606 3608 dotable(getattr(extmodule, 'cmdtable', {}))
3607 3609
3608 3610 revset.symbols['mq'] = revsetmq
3609 3611
3610 3612 colortable = {'qguard.negative': 'red',
3611 3613 'qguard.positive': 'yellow',
3612 3614 'qguard.unguarded': 'green',
3613 3615 'qseries.applied': 'blue bold underline',
3614 3616 'qseries.guarded': 'black bold',
3615 3617 'qseries.missing': 'red bold',
3616 3618 'qseries.unapplied': 'black bold'}
3617 3619
3618 3620 commands.inferrepo += " qnew qrefresh qdiff qcommit"
General Comments 0
You need to be logged in to leave comments. Login now