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