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