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