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