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