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