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