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