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