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