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