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