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