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