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