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