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