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