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