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