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