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