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