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