##// END OF EJS Templates
dispatch: print the version of each extension in the bug report, if available...
Matt Harbison -
r46566:a120d1c9 default
parent child Browse files
Show More
@@ -1,1335 +1,1345
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 errno
11 11 import getopt
12 12 import io
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import traceback
19 19
20 20
21 21 from .i18n import _
22 22 from .pycompat import getattr
23 23
24 24 from hgdemandimport import tracing
25 25
26 26 from . import (
27 27 cmdutil,
28 28 color,
29 29 commands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 help,
36 36 hg,
37 37 hook,
38 38 localrepo,
39 39 profiling,
40 40 pycompat,
41 41 rcutil,
42 42 registrar,
43 43 requirements as requirementsmod,
44 44 scmutil,
45 45 ui as uimod,
46 46 util,
47 47 vfs,
48 48 )
49 49
50 50 from .utils import (
51 51 procutil,
52 52 stringutil,
53 53 )
54 54
55 55
56 56 class request(object):
57 57 def __init__(
58 58 self,
59 59 args,
60 60 ui=None,
61 61 repo=None,
62 62 fin=None,
63 63 fout=None,
64 64 ferr=None,
65 65 fmsg=None,
66 66 prereposetups=None,
67 67 ):
68 68 self.args = args
69 69 self.ui = ui
70 70 self.repo = repo
71 71
72 72 # input/output/error streams
73 73 self.fin = fin
74 74 self.fout = fout
75 75 self.ferr = ferr
76 76 # separate stream for status/error messages
77 77 self.fmsg = fmsg
78 78
79 79 # remember options pre-parsed by _earlyparseopts()
80 80 self.earlyoptions = {}
81 81
82 82 # reposetups which run before extensions, useful for chg to pre-fill
83 83 # low-level repo state (for example, changelog) before extensions.
84 84 self.prereposetups = prereposetups or []
85 85
86 86 # store the parsed and canonical command
87 87 self.canonical_command = None
88 88
89 89 def _runexithandlers(self):
90 90 exc = None
91 91 handlers = self.ui._exithandlers
92 92 try:
93 93 while handlers:
94 94 func, args, kwargs = handlers.pop()
95 95 try:
96 96 func(*args, **kwargs)
97 97 except: # re-raises below
98 98 if exc is None:
99 99 exc = sys.exc_info()[1]
100 100 self.ui.warnnoi18n(b'error in exit handlers:\n')
101 101 self.ui.traceback(force=True)
102 102 finally:
103 103 if exc is not None:
104 104 raise exc
105 105
106 106
107 107 def run():
108 108 """run the command in sys.argv"""
109 109 try:
110 110 initstdio()
111 111 with tracing.log('parse args into request'):
112 112 req = request(pycompat.sysargv[1:])
113 113 err = None
114 114 try:
115 115 status = dispatch(req)
116 116 except error.StdioError as e:
117 117 err = e
118 118 status = -1
119 119
120 120 # In all cases we try to flush stdio streams.
121 121 if util.safehasattr(req.ui, b'fout'):
122 122 assert req.ui is not None # help pytype
123 123 assert req.ui.fout is not None # help pytype
124 124 try:
125 125 req.ui.fout.flush()
126 126 except IOError as e:
127 127 err = e
128 128 status = -1
129 129
130 130 if util.safehasattr(req.ui, b'ferr'):
131 131 assert req.ui is not None # help pytype
132 132 assert req.ui.ferr is not None # help pytype
133 133 try:
134 134 if err is not None and err.errno != errno.EPIPE:
135 135 req.ui.ferr.write(
136 136 b'abort: %s\n' % encoding.strtolocal(err.strerror)
137 137 )
138 138 req.ui.ferr.flush()
139 139 # There's not much we can do about an I/O error here. So (possibly)
140 140 # change the status code and move on.
141 141 except IOError:
142 142 status = -1
143 143
144 144 _silencestdio()
145 145 except KeyboardInterrupt:
146 146 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
147 147 # be printed to console to avoid another IOError/KeyboardInterrupt.
148 148 status = -1
149 149 sys.exit(status & 255)
150 150
151 151
152 152 if pycompat.ispy3:
153 153
154 154 def initstdio():
155 155 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
156 156 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
157 157 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
158 158 # instances, which write to the underlying stdio file descriptor in binary
159 159 # mode. ui.write() uses \n for line endings and no line ending normalization
160 160 # is attempted through this interface. This "just works," even if the system
161 161 # preferred line ending is not \n.
162 162 #
163 163 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
164 164 # and sys.stderr. They will inherit the line ending normalization settings,
165 165 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
166 166 # "just work," here we change the sys.* streams to disable line ending
167 167 # normalization, ensuring compatibility with our ui type.
168 168
169 169 # write_through is new in Python 3.7.
170 170 kwargs = {
171 171 "newline": "\n",
172 172 "line_buffering": sys.stdout.line_buffering,
173 173 }
174 174 if util.safehasattr(sys.stdout, "write_through"):
175 175 kwargs["write_through"] = sys.stdout.write_through
176 176 sys.stdout = io.TextIOWrapper(
177 177 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
178 178 )
179 179
180 180 kwargs = {
181 181 "newline": "\n",
182 182 "line_buffering": sys.stderr.line_buffering,
183 183 }
184 184 if util.safehasattr(sys.stderr, "write_through"):
185 185 kwargs["write_through"] = sys.stderr.write_through
186 186 sys.stderr = io.TextIOWrapper(
187 187 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
188 188 )
189 189
190 190 # No write_through on read-only stream.
191 191 sys.stdin = io.TextIOWrapper(
192 192 sys.stdin.buffer,
193 193 sys.stdin.encoding,
194 194 sys.stdin.errors,
195 195 # None is universal newlines mode.
196 196 newline=None,
197 197 line_buffering=sys.stdin.line_buffering,
198 198 )
199 199
200 200 def _silencestdio():
201 201 for fp in (sys.stdout, sys.stderr):
202 202 # Check if the file is okay
203 203 try:
204 204 fp.flush()
205 205 continue
206 206 except IOError:
207 207 pass
208 208 # Otherwise mark it as closed to silence "Exception ignored in"
209 209 # message emitted by the interpreter finalizer. Be careful to
210 210 # not close procutil.stdout, which may be a fdopen-ed file object
211 211 # and its close() actually closes the underlying file descriptor.
212 212 try:
213 213 fp.close()
214 214 except IOError:
215 215 pass
216 216
217 217
218 218 else:
219 219
220 220 def initstdio():
221 221 for fp in (sys.stdin, sys.stdout, sys.stderr):
222 222 procutil.setbinary(fp)
223 223
224 224 def _silencestdio():
225 225 pass
226 226
227 227
228 228 def _formatargs(args):
229 229 return b' '.join(procutil.shellquote(a) for a in args)
230 230
231 231
232 232 def dispatch(req):
233 233 """run the command specified in req.args; returns an integer status code"""
234 234 with tracing.log('dispatch.dispatch'):
235 235 if req.ferr:
236 236 ferr = req.ferr
237 237 elif req.ui:
238 238 ferr = req.ui.ferr
239 239 else:
240 240 ferr = procutil.stderr
241 241
242 242 try:
243 243 if not req.ui:
244 244 req.ui = uimod.ui.load()
245 245 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
246 246 if req.earlyoptions[b'traceback']:
247 247 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
248 248
249 249 # set ui streams from the request
250 250 if req.fin:
251 251 req.ui.fin = req.fin
252 252 if req.fout:
253 253 req.ui.fout = req.fout
254 254 if req.ferr:
255 255 req.ui.ferr = req.ferr
256 256 if req.fmsg:
257 257 req.ui.fmsg = req.fmsg
258 258 except error.Abort as inst:
259 259 ferr.write(inst.format())
260 260 return -1
261 261
262 262 msg = _formatargs(req.args)
263 263 starttime = util.timer()
264 264 ret = 1 # default of Python exit code on unhandled exception
265 265 try:
266 266 ret = _runcatch(req) or 0
267 267 except error.ProgrammingError as inst:
268 268 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
269 269 if inst.hint:
270 270 req.ui.error(_(b'** (%s)\n') % inst.hint)
271 271 raise
272 272 except KeyboardInterrupt as inst:
273 273 try:
274 274 if isinstance(inst, error.SignalInterrupt):
275 275 msg = _(b"killed!\n")
276 276 else:
277 277 msg = _(b"interrupted!\n")
278 278 req.ui.error(msg)
279 279 except error.SignalInterrupt:
280 280 # maybe pager would quit without consuming all the output, and
281 281 # SIGPIPE was raised. we cannot print anything in this case.
282 282 pass
283 283 except IOError as inst:
284 284 if inst.errno != errno.EPIPE:
285 285 raise
286 286 ret = -1
287 287 finally:
288 288 duration = util.timer() - starttime
289 289 req.ui.flush() # record blocked times
290 290 if req.ui.logblockedtimes:
291 291 req.ui._blockedtimes[b'command_duration'] = duration * 1000
292 292 req.ui.log(
293 293 b'uiblocked',
294 294 b'ui blocked ms\n',
295 295 **pycompat.strkwargs(req.ui._blockedtimes)
296 296 )
297 297 return_code = ret & 255
298 298 req.ui.log(
299 299 b"commandfinish",
300 300 b"%s exited %d after %0.2f seconds\n",
301 301 msg,
302 302 return_code,
303 303 duration,
304 304 return_code=return_code,
305 305 duration=duration,
306 306 canonical_command=req.canonical_command,
307 307 )
308 308 try:
309 309 req._runexithandlers()
310 310 except: # exiting, so no re-raises
311 311 ret = ret or -1
312 312 # do flush again since ui.log() and exit handlers may write to ui
313 313 req.ui.flush()
314 314 return ret
315 315
316 316
317 317 def _runcatch(req):
318 318 with tracing.log('dispatch._runcatch'):
319 319
320 320 def catchterm(*args):
321 321 raise error.SignalInterrupt
322 322
323 323 ui = req.ui
324 324 try:
325 325 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
326 326 num = getattr(signal, name, None)
327 327 if num:
328 328 signal.signal(num, catchterm)
329 329 except ValueError:
330 330 pass # happens if called in a thread
331 331
332 332 def _runcatchfunc():
333 333 realcmd = None
334 334 try:
335 335 cmdargs = fancyopts.fancyopts(
336 336 req.args[:], commands.globalopts, {}
337 337 )
338 338 cmd = cmdargs[0]
339 339 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
340 340 realcmd = aliases[0]
341 341 except (
342 342 error.UnknownCommand,
343 343 error.AmbiguousCommand,
344 344 IndexError,
345 345 getopt.GetoptError,
346 346 ):
347 347 # Don't handle this here. We know the command is
348 348 # invalid, but all we're worried about for now is that
349 349 # it's not a command that server operators expect to
350 350 # be safe to offer to users in a sandbox.
351 351 pass
352 352 if realcmd == b'serve' and b'--stdio' in cmdargs:
353 353 # We want to constrain 'hg serve --stdio' instances pretty
354 354 # closely, as many shared-ssh access tools want to grant
355 355 # access to run *only* 'hg -R $repo serve --stdio'. We
356 356 # restrict to exactly that set of arguments, and prohibit
357 357 # any repo name that starts with '--' to prevent
358 358 # shenanigans wherein a user does something like pass
359 359 # --debugger or --config=ui.debugger=1 as a repo
360 360 # name. This used to actually run the debugger.
361 361 if (
362 362 len(req.args) != 4
363 363 or req.args[0] != b'-R'
364 364 or req.args[1].startswith(b'--')
365 365 or req.args[2] != b'serve'
366 366 or req.args[3] != b'--stdio'
367 367 ):
368 368 raise error.Abort(
369 369 _(b'potentially unsafe serve --stdio invocation: %s')
370 370 % (stringutil.pprint(req.args),)
371 371 )
372 372
373 373 try:
374 374 debugger = b'pdb'
375 375 debugtrace = {b'pdb': pdb.set_trace}
376 376 debugmortem = {b'pdb': pdb.post_mortem}
377 377
378 378 # read --config before doing anything else
379 379 # (e.g. to change trust settings for reading .hg/hgrc)
380 380 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
381 381
382 382 if req.repo:
383 383 # copy configs that were passed on the cmdline (--config) to
384 384 # the repo ui
385 385 for sec, name, val in cfgs:
386 386 req.repo.ui.setconfig(
387 387 sec, name, val, source=b'--config'
388 388 )
389 389
390 390 # developer config: ui.debugger
391 391 debugger = ui.config(b"ui", b"debugger")
392 392 debugmod = pdb
393 393 if not debugger or ui.plain():
394 394 # if we are in HGPLAIN mode, then disable custom debugging
395 395 debugger = b'pdb'
396 396 elif req.earlyoptions[b'debugger']:
397 397 # This import can be slow for fancy debuggers, so only
398 398 # do it when absolutely necessary, i.e. when actual
399 399 # debugging has been requested
400 400 with demandimport.deactivated():
401 401 try:
402 402 debugmod = __import__(debugger)
403 403 except ImportError:
404 404 pass # Leave debugmod = pdb
405 405
406 406 debugtrace[debugger] = debugmod.set_trace
407 407 debugmortem[debugger] = debugmod.post_mortem
408 408
409 409 # enter the debugger before command execution
410 410 if req.earlyoptions[b'debugger']:
411 411 ui.warn(
412 412 _(
413 413 b"entering debugger - "
414 414 b"type c to continue starting hg or h for help\n"
415 415 )
416 416 )
417 417
418 418 if (
419 419 debugger != b'pdb'
420 420 and debugtrace[debugger] == debugtrace[b'pdb']
421 421 ):
422 422 ui.warn(
423 423 _(
424 424 b"%s debugger specified "
425 425 b"but its module was not found\n"
426 426 )
427 427 % debugger
428 428 )
429 429 with demandimport.deactivated():
430 430 debugtrace[debugger]()
431 431 try:
432 432 return _dispatch(req)
433 433 finally:
434 434 ui.flush()
435 435 except: # re-raises
436 436 # enter the debugger when we hit an exception
437 437 if req.earlyoptions[b'debugger']:
438 438 traceback.print_exc()
439 439 debugmortem[debugger](sys.exc_info()[2])
440 440 raise
441 441
442 442 return _callcatch(ui, _runcatchfunc)
443 443
444 444
445 445 def _callcatch(ui, func):
446 446 """like scmutil.callcatch but handles more high-level exceptions about
447 447 config parsing and commands. besides, use handlecommandexception to handle
448 448 uncaught exceptions.
449 449 """
450 450 try:
451 451 return scmutil.callcatch(ui, func)
452 452 except error.AmbiguousCommand as inst:
453 453 ui.warn(
454 454 _(b"hg: command '%s' is ambiguous:\n %s\n")
455 455 % (inst.prefix, b" ".join(inst.matches))
456 456 )
457 457 except error.CommandError as inst:
458 458 if inst.command:
459 459 ui.pager(b'help')
460 460 msgbytes = pycompat.bytestr(inst.message)
461 461 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
462 462 commands.help_(ui, inst.command, full=False, command=True)
463 463 else:
464 464 ui.warn(_(b"hg: %s\n") % inst.message)
465 465 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
466 466 except error.UnknownCommand as inst:
467 467 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
468 468 try:
469 469 # check if the command is in a disabled extension
470 470 # (but don't check for extensions themselves)
471 471 formatted = help.formattedhelp(
472 472 ui, commands, inst.command, unknowncmd=True
473 473 )
474 474 ui.warn(nocmdmsg)
475 475 ui.write(formatted)
476 476 except (error.UnknownCommand, error.Abort):
477 477 suggested = False
478 478 if inst.all_commands:
479 479 sim = error.getsimilar(inst.all_commands, inst.command)
480 480 if sim:
481 481 ui.warn(nocmdmsg)
482 482 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
483 483 suggested = True
484 484 if not suggested:
485 485 ui.warn(nocmdmsg)
486 486 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
487 487 except IOError:
488 488 raise
489 489 except KeyboardInterrupt:
490 490 raise
491 491 except: # probably re-raises
492 492 if not handlecommandexception(ui):
493 493 raise
494 494
495 495 return -1
496 496
497 497
498 498 def aliasargs(fn, givenargs):
499 499 args = []
500 500 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
501 501 if not util.safehasattr(fn, b'_origfunc'):
502 502 args = getattr(fn, 'args', args)
503 503 if args:
504 504 cmd = b' '.join(map(procutil.shellquote, args))
505 505
506 506 nums = []
507 507
508 508 def replacer(m):
509 509 num = int(m.group(1)) - 1
510 510 nums.append(num)
511 511 if num < len(givenargs):
512 512 return givenargs[num]
513 513 raise error.InputError(_(b'too few arguments for command alias'))
514 514
515 515 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
516 516 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
517 517 args = pycompat.shlexsplit(cmd)
518 518 return args + givenargs
519 519
520 520
521 521 def aliasinterpolate(name, args, cmd):
522 522 """interpolate args into cmd for shell aliases
523 523
524 524 This also handles $0, $@ and "$@".
525 525 """
526 526 # util.interpolate can't deal with "$@" (with quotes) because it's only
527 527 # built to match prefix + patterns.
528 528 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
529 529 replacemap[b'$0'] = name
530 530 replacemap[b'$$'] = b'$'
531 531 replacemap[b'$@'] = b' '.join(args)
532 532 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
533 533 # parameters, separated out into words. Emulate the same behavior here by
534 534 # quoting the arguments individually. POSIX shells will then typically
535 535 # tokenize each argument into exactly one word.
536 536 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
537 537 # escape '\$' for regex
538 538 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
539 539 r = re.compile(regex)
540 540 return r.sub(lambda x: replacemap[x.group()], cmd)
541 541
542 542
543 543 class cmdalias(object):
544 544 def __init__(self, ui, name, definition, cmdtable, source):
545 545 self.name = self.cmd = name
546 546 self.cmdname = b''
547 547 self.definition = definition
548 548 self.fn = None
549 549 self.givenargs = []
550 550 self.opts = []
551 551 self.help = b''
552 552 self.badalias = None
553 553 self.unknowncmd = False
554 554 self.source = source
555 555
556 556 try:
557 557 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
558 558 for alias, e in pycompat.iteritems(cmdtable):
559 559 if e is entry:
560 560 self.cmd = alias
561 561 break
562 562 self.shadows = True
563 563 except error.UnknownCommand:
564 564 self.shadows = False
565 565
566 566 if not self.definition:
567 567 self.badalias = _(b"no definition for alias '%s'") % self.name
568 568 return
569 569
570 570 if self.definition.startswith(b'!'):
571 571 shdef = self.definition[1:]
572 572 self.shell = True
573 573
574 574 def fn(ui, *args):
575 575 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
576 576
577 577 def _checkvar(m):
578 578 if m.groups()[0] == b'$':
579 579 return m.group()
580 580 elif int(m.groups()[0]) <= len(args):
581 581 return m.group()
582 582 else:
583 583 ui.debug(
584 584 b"No argument found for substitution "
585 585 b"of %i variable in alias '%s' definition.\n"
586 586 % (int(m.groups()[0]), self.name)
587 587 )
588 588 return b''
589 589
590 590 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
591 591 cmd = aliasinterpolate(self.name, args, cmd)
592 592 return ui.system(
593 593 cmd, environ=env, blockedtag=b'alias_%s' % self.name
594 594 )
595 595
596 596 self.fn = fn
597 597 self.alias = True
598 598 self._populatehelp(ui, name, shdef, self.fn)
599 599 return
600 600
601 601 try:
602 602 args = pycompat.shlexsplit(self.definition)
603 603 except ValueError as inst:
604 604 self.badalias = _(b"error in definition for alias '%s': %s") % (
605 605 self.name,
606 606 stringutil.forcebytestr(inst),
607 607 )
608 608 return
609 609 earlyopts, args = _earlysplitopts(args)
610 610 if earlyopts:
611 611 self.badalias = _(
612 612 b"error in definition for alias '%s': %s may "
613 613 b"only be given on the command line"
614 614 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
615 615 return
616 616 self.cmdname = cmd = args.pop(0)
617 617 self.givenargs = args
618 618
619 619 try:
620 620 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
621 621 if len(tableentry) > 2:
622 622 self.fn, self.opts, cmdhelp = tableentry
623 623 else:
624 624 self.fn, self.opts = tableentry
625 625 cmdhelp = None
626 626
627 627 self.alias = True
628 628 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
629 629
630 630 except error.UnknownCommand:
631 631 self.badalias = _(
632 632 b"alias '%s' resolves to unknown command '%s'"
633 633 ) % (
634 634 self.name,
635 635 cmd,
636 636 )
637 637 self.unknowncmd = True
638 638 except error.AmbiguousCommand:
639 639 self.badalias = _(
640 640 b"alias '%s' resolves to ambiguous command '%s'"
641 641 ) % (
642 642 self.name,
643 643 cmd,
644 644 )
645 645
646 646 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
647 647 # confine strings to be passed to i18n.gettext()
648 648 cfg = {}
649 649 for k in (b'doc', b'help', b'category'):
650 650 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
651 651 if v is None:
652 652 continue
653 653 if not encoding.isasciistr(v):
654 654 self.badalias = _(
655 655 b"non-ASCII character in alias definition '%s:%s'"
656 656 ) % (name, k)
657 657 return
658 658 cfg[k] = v
659 659
660 660 self.help = cfg.get(b'help', defaulthelp or b'')
661 661 if self.help and self.help.startswith(b"hg " + cmd):
662 662 # drop prefix in old-style help lines so hg shows the alias
663 663 self.help = self.help[4 + len(cmd) :]
664 664
665 665 self.owndoc = b'doc' in cfg
666 666 doc = cfg.get(b'doc', pycompat.getdoc(fn))
667 667 if doc is not None:
668 668 doc = pycompat.sysstr(doc)
669 669 self.__doc__ = doc
670 670
671 671 self.helpcategory = cfg.get(
672 672 b'category', registrar.command.CATEGORY_NONE
673 673 )
674 674
675 675 @property
676 676 def args(self):
677 677 args = pycompat.maplist(util.expandpath, self.givenargs)
678 678 return aliasargs(self.fn, args)
679 679
680 680 def __getattr__(self, name):
681 681 adefaults = {
682 682 'norepo': True,
683 683 'intents': set(),
684 684 'optionalrepo': False,
685 685 'inferrepo': False,
686 686 }
687 687 if name not in adefaults:
688 688 raise AttributeError(name)
689 689 if self.badalias or util.safehasattr(self, b'shell'):
690 690 return adefaults[name]
691 691 return getattr(self.fn, name)
692 692
693 693 def __call__(self, ui, *args, **opts):
694 694 if self.badalias:
695 695 hint = None
696 696 if self.unknowncmd:
697 697 try:
698 698 # check if the command is in a disabled extension
699 699 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
700 700 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
701 701 except error.UnknownCommand:
702 702 pass
703 703 raise error.ConfigError(self.badalias, hint=hint)
704 704 if self.shadows:
705 705 ui.debug(
706 706 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
707 707 )
708 708
709 709 ui.log(
710 710 b'commandalias',
711 711 b"alias '%s' expands to '%s'\n",
712 712 self.name,
713 713 self.definition,
714 714 )
715 715 if util.safehasattr(self, b'shell'):
716 716 return self.fn(ui, *args, **opts)
717 717 else:
718 718 try:
719 719 return util.checksignature(self.fn)(ui, *args, **opts)
720 720 except error.SignatureError:
721 721 args = b' '.join([self.cmdname] + self.args)
722 722 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
723 723 raise
724 724
725 725
726 726 class lazyaliasentry(object):
727 727 """like a typical command entry (func, opts, help), but is lazy"""
728 728
729 729 def __init__(self, ui, name, definition, cmdtable, source):
730 730 self.ui = ui
731 731 self.name = name
732 732 self.definition = definition
733 733 self.cmdtable = cmdtable.copy()
734 734 self.source = source
735 735 self.alias = True
736 736
737 737 @util.propertycache
738 738 def _aliasdef(self):
739 739 return cmdalias(
740 740 self.ui, self.name, self.definition, self.cmdtable, self.source
741 741 )
742 742
743 743 def __getitem__(self, n):
744 744 aliasdef = self._aliasdef
745 745 if n == 0:
746 746 return aliasdef
747 747 elif n == 1:
748 748 return aliasdef.opts
749 749 elif n == 2:
750 750 return aliasdef.help
751 751 else:
752 752 raise IndexError
753 753
754 754 def __iter__(self):
755 755 for i in range(3):
756 756 yield self[i]
757 757
758 758 def __len__(self):
759 759 return 3
760 760
761 761
762 762 def addaliases(ui, cmdtable):
763 763 # aliases are processed after extensions have been loaded, so they
764 764 # may use extension commands. Aliases can also use other alias definitions,
765 765 # but only if they have been defined prior to the current definition.
766 766 for alias, definition in ui.configitems(b'alias', ignoresub=True):
767 767 try:
768 768 if cmdtable[alias].definition == definition:
769 769 continue
770 770 except (KeyError, AttributeError):
771 771 # definition might not exist or it might not be a cmdalias
772 772 pass
773 773
774 774 source = ui.configsource(b'alias', alias)
775 775 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
776 776 cmdtable[alias] = entry
777 777
778 778
779 779 def _parse(ui, args):
780 780 options = {}
781 781 cmdoptions = {}
782 782
783 783 try:
784 784 args = fancyopts.fancyopts(args, commands.globalopts, options)
785 785 except getopt.GetoptError as inst:
786 786 raise error.CommandError(None, stringutil.forcebytestr(inst))
787 787
788 788 if args:
789 789 cmd, args = args[0], args[1:]
790 790 aliases, entry = cmdutil.findcmd(
791 791 cmd, commands.table, ui.configbool(b"ui", b"strict")
792 792 )
793 793 cmd = aliases[0]
794 794 args = aliasargs(entry[0], args)
795 795 defaults = ui.config(b"defaults", cmd)
796 796 if defaults:
797 797 args = (
798 798 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
799 799 + args
800 800 )
801 801 c = list(entry[1])
802 802 else:
803 803 cmd = None
804 804 c = []
805 805
806 806 # combine global options into local
807 807 for o in commands.globalopts:
808 808 c.append((o[0], o[1], options[o[1]], o[3]))
809 809
810 810 try:
811 811 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
812 812 except getopt.GetoptError as inst:
813 813 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
814 814
815 815 # separate global options back out
816 816 for o in commands.globalopts:
817 817 n = o[1]
818 818 options[n] = cmdoptions[n]
819 819 del cmdoptions[n]
820 820
821 821 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
822 822
823 823
824 824 def _parseconfig(ui, config):
825 825 """parse the --config options from the command line"""
826 826 configs = []
827 827
828 828 for cfg in config:
829 829 try:
830 830 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
831 831 section, name = name.split(b'.', 1)
832 832 if not section or not name:
833 833 raise IndexError
834 834 ui.setconfig(section, name, value, b'--config')
835 835 configs.append((section, name, value))
836 836 except (IndexError, ValueError):
837 837 raise error.Abort(
838 838 _(
839 839 b'malformed --config option: %r '
840 840 b'(use --config section.name=value)'
841 841 )
842 842 % pycompat.bytestr(cfg)
843 843 )
844 844
845 845 return configs
846 846
847 847
848 848 def _earlyparseopts(ui, args):
849 849 options = {}
850 850 fancyopts.fancyopts(
851 851 args,
852 852 commands.globalopts,
853 853 options,
854 854 gnu=not ui.plain(b'strictflags'),
855 855 early=True,
856 856 optaliases={b'repository': [b'repo']},
857 857 )
858 858 return options
859 859
860 860
861 861 def _earlysplitopts(args):
862 862 """Split args into a list of possible early options and remainder args"""
863 863 shortoptions = b'R:'
864 864 # TODO: perhaps 'debugger' should be included
865 865 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
866 866 return fancyopts.earlygetopt(
867 867 args, shortoptions, longoptions, gnu=True, keepsep=True
868 868 )
869 869
870 870
871 871 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
872 872 # run pre-hook, and abort if it fails
873 873 hook.hook(
874 874 lui,
875 875 repo,
876 876 b"pre-%s" % cmd,
877 877 True,
878 878 args=b" ".join(fullargs),
879 879 pats=cmdpats,
880 880 opts=cmdoptions,
881 881 )
882 882 try:
883 883 ret = _runcommand(ui, options, cmd, d)
884 884 # run post-hook, passing command result
885 885 hook.hook(
886 886 lui,
887 887 repo,
888 888 b"post-%s" % cmd,
889 889 False,
890 890 args=b" ".join(fullargs),
891 891 result=ret,
892 892 pats=cmdpats,
893 893 opts=cmdoptions,
894 894 )
895 895 except Exception:
896 896 # run failure hook and re-raise
897 897 hook.hook(
898 898 lui,
899 899 repo,
900 900 b"fail-%s" % cmd,
901 901 False,
902 902 args=b" ".join(fullargs),
903 903 pats=cmdpats,
904 904 opts=cmdoptions,
905 905 )
906 906 raise
907 907 return ret
908 908
909 909
910 910 def _readsharedsourceconfig(ui, path):
911 911 """if the current repository is shared one, this tries to read
912 912 .hg/hgrc of shared source if we are in share-safe mode
913 913
914 914 Config read is loaded into the ui object passed
915 915
916 916 This should be called before reading .hg/hgrc or the main repo
917 917 as that overrides config set in shared source"""
918 918 try:
919 919 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
920 920 requirements = set(fp.read().splitlines())
921 921 if not (
922 922 requirementsmod.SHARESAFE_REQUIREMENT in requirements
923 923 and requirementsmod.SHARED_REQUIREMENT in requirements
924 924 ):
925 925 return
926 926 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
927 927 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
928 928 root = sharedvfs.base
929 929 ui.readconfig(sharedvfs.join(b"hgrc"), root)
930 930 except IOError:
931 931 pass
932 932
933 933
934 934 def _getlocal(ui, rpath, wd=None):
935 935 """Return (path, local ui object) for the given target path.
936 936
937 937 Takes paths in [cwd]/.hg/hgrc into account."
938 938 """
939 939 if wd is None:
940 940 try:
941 941 wd = encoding.getcwd()
942 942 except OSError as e:
943 943 raise error.Abort(
944 944 _(b"error getting current working directory: %s")
945 945 % encoding.strtolocal(e.strerror)
946 946 )
947 947
948 948 path = cmdutil.findrepo(wd) or b""
949 949 if not path:
950 950 lui = ui
951 951 else:
952 952 lui = ui.copy()
953 953 if rcutil.use_repo_hgrc():
954 954 _readsharedsourceconfig(lui, path)
955 955 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
956 956 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
957 957
958 958 if rpath:
959 959 path = lui.expandpath(rpath)
960 960 lui = ui.copy()
961 961 if rcutil.use_repo_hgrc():
962 962 _readsharedsourceconfig(lui, path)
963 963 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
964 964 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
965 965
966 966 return path, lui
967 967
968 968
969 969 def _checkshellalias(lui, ui, args):
970 970 """Return the function to run the shell alias, if it is required"""
971 971 options = {}
972 972
973 973 try:
974 974 args = fancyopts.fancyopts(args, commands.globalopts, options)
975 975 except getopt.GetoptError:
976 976 return
977 977
978 978 if not args:
979 979 return
980 980
981 981 cmdtable = commands.table
982 982
983 983 cmd = args[0]
984 984 try:
985 985 strict = ui.configbool(b"ui", b"strict")
986 986 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
987 987 except (error.AmbiguousCommand, error.UnknownCommand):
988 988 return
989 989
990 990 cmd = aliases[0]
991 991 fn = entry[0]
992 992
993 993 if cmd and util.safehasattr(fn, b'shell'):
994 994 # shell alias shouldn't receive early options which are consumed by hg
995 995 _earlyopts, args = _earlysplitopts(args)
996 996 d = lambda: fn(ui, *args[1:])
997 997 return lambda: runcommand(
998 998 lui, None, cmd, args[:1], ui, options, d, [], {}
999 999 )
1000 1000
1001 1001
1002 1002 def _dispatch(req):
1003 1003 args = req.args
1004 1004 ui = req.ui
1005 1005
1006 1006 # check for cwd
1007 1007 cwd = req.earlyoptions[b'cwd']
1008 1008 if cwd:
1009 1009 os.chdir(cwd)
1010 1010
1011 1011 rpath = req.earlyoptions[b'repository']
1012 1012 path, lui = _getlocal(ui, rpath)
1013 1013
1014 1014 uis = {ui, lui}
1015 1015
1016 1016 if req.repo:
1017 1017 uis.add(req.repo.ui)
1018 1018
1019 1019 if (
1020 1020 req.earlyoptions[b'verbose']
1021 1021 or req.earlyoptions[b'debug']
1022 1022 or req.earlyoptions[b'quiet']
1023 1023 ):
1024 1024 for opt in (b'verbose', b'debug', b'quiet'):
1025 1025 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1026 1026 for ui_ in uis:
1027 1027 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1028 1028
1029 1029 if req.earlyoptions[b'profile']:
1030 1030 for ui_ in uis:
1031 1031 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1032 1032
1033 1033 profile = lui.configbool(b'profiling', b'enabled')
1034 1034 with profiling.profile(lui, enabled=profile) as profiler:
1035 1035 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1036 1036 # reposetup
1037 1037 extensions.loadall(lui)
1038 1038 # Propagate any changes to lui.__class__ by extensions
1039 1039 ui.__class__ = lui.__class__
1040 1040
1041 1041 # (uisetup and extsetup are handled in extensions.loadall)
1042 1042
1043 1043 # (reposetup is handled in hg.repository)
1044 1044
1045 1045 addaliases(lui, commands.table)
1046 1046
1047 1047 # All aliases and commands are completely defined, now.
1048 1048 # Check abbreviation/ambiguity of shell alias.
1049 1049 shellaliasfn = _checkshellalias(lui, ui, args)
1050 1050 if shellaliasfn:
1051 1051 # no additional configs will be set, set up the ui instances
1052 1052 for ui_ in uis:
1053 1053 extensions.populateui(ui_)
1054 1054 return shellaliasfn()
1055 1055
1056 1056 # check for fallback encoding
1057 1057 fallback = lui.config(b'ui', b'fallbackencoding')
1058 1058 if fallback:
1059 1059 encoding.fallbackencoding = fallback
1060 1060
1061 1061 fullargs = args
1062 1062 cmd, func, args, options, cmdoptions = _parse(lui, args)
1063 1063
1064 1064 # store the canonical command name in request object for later access
1065 1065 req.canonical_command = cmd
1066 1066
1067 1067 if options[b"config"] != req.earlyoptions[b"config"]:
1068 1068 raise error.InputError(_(b"option --config may not be abbreviated"))
1069 1069 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1070 1070 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1071 1071 if options[b"repository"] != req.earlyoptions[b"repository"]:
1072 1072 raise error.InputError(
1073 1073 _(
1074 1074 b"option -R has to be separated from other options (e.g. not "
1075 1075 b"-qR) and --repository may only be abbreviated as --repo"
1076 1076 )
1077 1077 )
1078 1078 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1079 1079 raise error.InputError(
1080 1080 _(b"option --debugger may not be abbreviated")
1081 1081 )
1082 1082 # don't validate --profile/--traceback, which can be enabled from now
1083 1083
1084 1084 if options[b"encoding"]:
1085 1085 encoding.encoding = options[b"encoding"]
1086 1086 if options[b"encodingmode"]:
1087 1087 encoding.encodingmode = options[b"encodingmode"]
1088 1088 if options[b"time"]:
1089 1089
1090 1090 def get_times():
1091 1091 t = os.times()
1092 1092 if t[4] == 0.0:
1093 1093 # Windows leaves this as zero, so use time.perf_counter()
1094 1094 t = (t[0], t[1], t[2], t[3], util.timer())
1095 1095 return t
1096 1096
1097 1097 s = get_times()
1098 1098
1099 1099 def print_time():
1100 1100 t = get_times()
1101 1101 ui.warn(
1102 1102 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1103 1103 % (
1104 1104 t[4] - s[4],
1105 1105 t[0] - s[0],
1106 1106 t[2] - s[2],
1107 1107 t[1] - s[1],
1108 1108 t[3] - s[3],
1109 1109 )
1110 1110 )
1111 1111
1112 1112 ui.atexit(print_time)
1113 1113 if options[b"profile"]:
1114 1114 profiler.start()
1115 1115
1116 1116 # if abbreviated version of this were used, take them in account, now
1117 1117 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1118 1118 for opt in (b'verbose', b'debug', b'quiet'):
1119 1119 if options[opt] == req.earlyoptions[opt]:
1120 1120 continue
1121 1121 val = pycompat.bytestr(bool(options[opt]))
1122 1122 for ui_ in uis:
1123 1123 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1124 1124
1125 1125 if options[b'traceback']:
1126 1126 for ui_ in uis:
1127 1127 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1128 1128
1129 1129 if options[b'noninteractive']:
1130 1130 for ui_ in uis:
1131 1131 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1132 1132
1133 1133 if cmdoptions.get(b'insecure', False):
1134 1134 for ui_ in uis:
1135 1135 ui_.insecureconnections = True
1136 1136
1137 1137 # setup color handling before pager, because setting up pager
1138 1138 # might cause incorrect console information
1139 1139 coloropt = options[b'color']
1140 1140 for ui_ in uis:
1141 1141 if coloropt:
1142 1142 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1143 1143 color.setup(ui_)
1144 1144
1145 1145 if stringutil.parsebool(options[b'pager']):
1146 1146 # ui.pager() expects 'internal-always-' prefix in this case
1147 1147 ui.pager(b'internal-always-' + cmd)
1148 1148 elif options[b'pager'] != b'auto':
1149 1149 for ui_ in uis:
1150 1150 ui_.disablepager()
1151 1151
1152 1152 # configs are fully loaded, set up the ui instances
1153 1153 for ui_ in uis:
1154 1154 extensions.populateui(ui_)
1155 1155
1156 1156 if options[b'version']:
1157 1157 return commands.version_(ui)
1158 1158 if options[b'help']:
1159 1159 return commands.help_(ui, cmd, command=cmd is not None)
1160 1160 elif not cmd:
1161 1161 return commands.help_(ui, b'shortlist')
1162 1162
1163 1163 repo = None
1164 1164 cmdpats = args[:]
1165 1165 assert func is not None # help out pytype
1166 1166 if not func.norepo:
1167 1167 # use the repo from the request only if we don't have -R
1168 1168 if not rpath and not cwd:
1169 1169 repo = req.repo
1170 1170
1171 1171 if repo:
1172 1172 # set the descriptors of the repo ui to those of ui
1173 1173 repo.ui.fin = ui.fin
1174 1174 repo.ui.fout = ui.fout
1175 1175 repo.ui.ferr = ui.ferr
1176 1176 repo.ui.fmsg = ui.fmsg
1177 1177 else:
1178 1178 try:
1179 1179 repo = hg.repository(
1180 1180 ui,
1181 1181 path=path,
1182 1182 presetupfuncs=req.prereposetups,
1183 1183 intents=func.intents,
1184 1184 )
1185 1185 if not repo.local():
1186 1186 raise error.InputError(
1187 1187 _(b"repository '%s' is not local") % path
1188 1188 )
1189 1189 repo.ui.setconfig(
1190 1190 b"bundle", b"mainreporoot", repo.root, b'repo'
1191 1191 )
1192 1192 except error.RequirementError:
1193 1193 raise
1194 1194 except error.RepoError:
1195 1195 if rpath: # invalid -R path
1196 1196 raise
1197 1197 if not func.optionalrepo:
1198 1198 if func.inferrepo and args and not path:
1199 1199 # try to infer -R from command args
1200 1200 repos = pycompat.maplist(cmdutil.findrepo, args)
1201 1201 guess = repos[0]
1202 1202 if guess and repos.count(guess) == len(repos):
1203 1203 req.args = [b'--repository', guess] + fullargs
1204 1204 req.earlyoptions[b'repository'] = guess
1205 1205 return _dispatch(req)
1206 1206 if not path:
1207 1207 raise error.InputError(
1208 1208 _(
1209 1209 b"no repository found in"
1210 1210 b" '%s' (.hg not found)"
1211 1211 )
1212 1212 % encoding.getcwd()
1213 1213 )
1214 1214 raise
1215 1215 if repo:
1216 1216 ui = repo.ui
1217 1217 if options[b'hidden']:
1218 1218 repo = repo.unfiltered()
1219 1219 args.insert(0, repo)
1220 1220 elif rpath:
1221 1221 ui.warn(_(b"warning: --repository ignored\n"))
1222 1222
1223 1223 msg = _formatargs(fullargs)
1224 1224 ui.log(b"command", b'%s\n', msg)
1225 1225 strcmdopt = pycompat.strkwargs(cmdoptions)
1226 1226 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1227 1227 try:
1228 1228 return runcommand(
1229 1229 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1230 1230 )
1231 1231 finally:
1232 1232 if repo and repo != req.repo:
1233 1233 repo.close()
1234 1234
1235 1235
1236 1236 def _runcommand(ui, options, cmd, cmdfunc):
1237 1237 """Run a command function, possibly with profiling enabled."""
1238 1238 try:
1239 1239 with tracing.log("Running %s command" % cmd):
1240 1240 return cmdfunc()
1241 1241 except error.SignatureError:
1242 1242 raise error.CommandError(cmd, _(b'invalid arguments'))
1243 1243
1244 1244
1245 1245 def _exceptionwarning(ui):
1246 1246 """Produce a warning message for the current active exception"""
1247 1247
1248 1248 # For compatibility checking, we discard the portion of the hg
1249 1249 # version after the + on the assumption that if a "normal
1250 1250 # user" is running a build with a + in it the packager
1251 1251 # probably built from fairly close to a tag and anyone with a
1252 1252 # 'make local' copy of hg (where the version number can be out
1253 1253 # of date) will be clueful enough to notice the implausible
1254 1254 # version number and try updating.
1255 1255 ct = util.versiontuple(n=2)
1256 1256 worst = None, ct, b'', b''
1257 1257 if ui.config(b'ui', b'supportcontact') is None:
1258 1258 for name, mod in extensions.extensions():
1259 1259 # 'testedwith' should be bytes, but not all extensions are ported
1260 1260 # to py3 and we don't want UnicodeException because of that.
1261 1261 testedwith = stringutil.forcebytestr(
1262 1262 getattr(mod, 'testedwith', b'')
1263 1263 )
1264 1264 version = extensions.moduleversion(mod)
1265 1265 report = getattr(mod, 'buglink', _(b'the extension author.'))
1266 1266 if not testedwith.strip():
1267 1267 # We found an untested extension. It's likely the culprit.
1268 1268 worst = name, b'unknown', report, version
1269 1269 break
1270 1270
1271 1271 # Never blame on extensions bundled with Mercurial.
1272 1272 if extensions.ismoduleinternal(mod):
1273 1273 continue
1274 1274
1275 1275 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1276 1276 if ct in tested:
1277 1277 continue
1278 1278
1279 1279 lower = [t for t in tested if t < ct]
1280 1280 nearest = max(lower or tested)
1281 1281 if worst[0] is None or nearest < worst[1]:
1282 1282 worst = name, nearest, report, version
1283 1283 if worst[0] is not None:
1284 1284 name, testedwith, report, version = worst
1285 1285 if not isinstance(testedwith, (bytes, str)):
1286 1286 testedwith = b'.'.join(
1287 1287 [stringutil.forcebytestr(c) for c in testedwith]
1288 1288 )
1289 1289 extver = version or _(b"(version N/A)")
1290 1290 warning = _(
1291 1291 b'** Unknown exception encountered with '
1292 1292 b'possibly-broken third-party extension "%s" %s\n'
1293 1293 b'** which supports versions %s of Mercurial.\n'
1294 1294 b'** Please disable "%s" and try your action again.\n'
1295 1295 b'** If that fixes the bug please report it to %s\n'
1296 1296 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1297 1297 else:
1298 1298 bugtracker = ui.config(b'ui', b'supportcontact')
1299 1299 if bugtracker is None:
1300 1300 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1301 1301 warning = (
1302 1302 _(
1303 1303 b"** unknown exception encountered, "
1304 1304 b"please report by visiting\n** "
1305 1305 )
1306 1306 + bugtracker
1307 1307 + b'\n'
1308 1308 )
1309 1309 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1310
1311 def ext_with_ver(x):
1312 ext = x[0]
1313 ver = extensions.moduleversion(x[1])
1314 if ver:
1315 ext += b' ' + ver
1316 return ext
1317
1310 1318 warning += (
1311 1319 (_(b"** Python %s\n") % sysversion)
1312 1320 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1313 1321 + (
1314 1322 _(b"** Extensions loaded: %s\n")
1315 % b", ".join([x[0] for x in sorted(extensions.extensions())])
1323 % b", ".join(
1324 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1325 )
1316 1326 )
1317 1327 )
1318 1328 return warning
1319 1329
1320 1330
1321 1331 def handlecommandexception(ui):
1322 1332 """Produce a warning message for broken commands
1323 1333
1324 1334 Called when handling an exception; the exception is reraised if
1325 1335 this function returns False, ignored otherwise.
1326 1336 """
1327 1337 warning = _exceptionwarning(ui)
1328 1338 ui.log(
1329 1339 b"commandexception",
1330 1340 b"%s\n%s\n",
1331 1341 warning,
1332 1342 pycompat.sysbytes(traceback.format_exc()),
1333 1343 )
1334 1344 ui.warn(warning)
1335 1345 return False # re-raise the exception
@@ -1,1891 +1,1891
1 1 Test basic extension support
2 2 $ cat > unflush.py <<EOF
3 3 > import sys
4 4 > from mercurial import pycompat
5 5 > if pycompat.ispy3:
6 6 > # no changes required
7 7 > sys.exit(0)
8 8 > with open(sys.argv[1], 'rb') as f:
9 9 > data = f.read()
10 10 > with open(sys.argv[1], 'wb') as f:
11 11 > f.write(data.replace(b', flush=True', b''))
12 12 > EOF
13 13
14 14 $ cat > foobar.py <<EOF
15 15 > import os
16 16 > from mercurial import commands, exthelper, registrar
17 17 >
18 18 > eh = exthelper.exthelper()
19 19 > eh.configitem(b'tests', b'foo', default=b"Foo")
20 20 >
21 21 > uisetup = eh.finaluisetup
22 22 > uipopulate = eh.finaluipopulate
23 23 > reposetup = eh.finalreposetup
24 24 > cmdtable = eh.cmdtable
25 25 > configtable = eh.configtable
26 26 >
27 27 > @eh.uisetup
28 28 > def _uisetup(ui):
29 29 > ui.debug(b"uisetup called [debug]\\n")
30 30 > ui.write(b"uisetup called\\n")
31 31 > ui.status(b"uisetup called [status]\\n")
32 32 > ui.flush()
33 33 > @eh.uipopulate
34 34 > def _uipopulate(ui):
35 35 > ui._populatecnt = getattr(ui, "_populatecnt", 0) + 1
36 36 > ui.write(b"uipopulate called (%d times)\n" % ui._populatecnt)
37 37 > @eh.reposetup
38 38 > def _reposetup(ui, repo):
39 39 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
40 40 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
41 41 > ui.flush()
42 42 > @eh.command(b'foo', [], b'hg foo')
43 43 > def foo(ui, *args, **kwargs):
44 44 > foo = ui.config(b'tests', b'foo')
45 45 > ui.write(foo)
46 46 > ui.write(b"\\n")
47 47 > @eh.command(b'bar', [], b'hg bar', norepo=True)
48 48 > def bar(ui, *args, **kwargs):
49 49 > ui.write(b"Bar\\n")
50 50 > EOF
51 51 $ abspath=`pwd`/foobar.py
52 52
53 53 $ mkdir barfoo
54 54 $ cp foobar.py barfoo/__init__.py
55 55 $ barfoopath=`pwd`/barfoo
56 56
57 57 $ hg init a
58 58 $ cd a
59 59 $ echo foo > file
60 60 $ hg add file
61 61 $ hg commit -m 'add file'
62 62
63 63 $ echo '[extensions]' >> $HGRCPATH
64 64 $ echo "foobar = $abspath" >> $HGRCPATH
65 65 $ hg foo
66 66 uisetup called
67 67 uisetup called [status]
68 68 uipopulate called (1 times)
69 69 uipopulate called (1 times)
70 70 uipopulate called (1 times)
71 71 reposetup called for a
72 72 ui == repo.ui
73 73 uipopulate called (1 times) (chg !)
74 74 uipopulate called (1 times) (chg !)
75 75 uipopulate called (1 times) (chg !)
76 76 uipopulate called (1 times) (chg !)
77 77 uipopulate called (1 times) (chg !)
78 78 reposetup called for a (chg !)
79 79 ui == repo.ui (chg !)
80 80 Foo
81 81 $ hg foo --quiet
82 82 uisetup called (no-chg !)
83 83 uipopulate called (1 times)
84 84 uipopulate called (1 times)
85 85 uipopulate called (1 times) (chg !)
86 86 uipopulate called (1 times) (chg !)
87 87 uipopulate called (1 times)
88 88 reposetup called for a
89 89 ui == repo.ui
90 90 Foo
91 91 $ hg foo --debug
92 92 uisetup called [debug] (no-chg !)
93 93 uisetup called (no-chg !)
94 94 uisetup called [status] (no-chg !)
95 95 uipopulate called (1 times)
96 96 uipopulate called (1 times)
97 97 uipopulate called (1 times) (chg !)
98 98 uipopulate called (1 times) (chg !)
99 99 uipopulate called (1 times)
100 100 reposetup called for a
101 101 ui == repo.ui
102 102 Foo
103 103
104 104 $ cd ..
105 105 $ hg clone a b
106 106 uisetup called (no-chg !)
107 107 uisetup called [status] (no-chg !)
108 108 uipopulate called (1 times)
109 109 uipopulate called (1 times) (chg !)
110 110 uipopulate called (1 times)
111 111 reposetup called for a
112 112 ui == repo.ui
113 113 uipopulate called (1 times)
114 114 reposetup called for b
115 115 ui == repo.ui
116 116 updating to branch default
117 117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118
119 119 $ hg bar
120 120 uisetup called (no-chg !)
121 121 uisetup called [status] (no-chg !)
122 122 uipopulate called (1 times)
123 123 uipopulate called (1 times) (chg !)
124 124 Bar
125 125 $ echo 'foobar = !' >> $HGRCPATH
126 126
127 127 module/__init__.py-style
128 128
129 129 $ echo "barfoo = $barfoopath" >> $HGRCPATH
130 130 $ cd a
131 131 $ hg foo
132 132 uisetup called
133 133 uisetup called [status]
134 134 uipopulate called (1 times)
135 135 uipopulate called (1 times)
136 136 uipopulate called (1 times)
137 137 reposetup called for a
138 138 ui == repo.ui
139 139 uipopulate called (1 times) (chg !)
140 140 uipopulate called (1 times) (chg !)
141 141 uipopulate called (1 times) (chg !)
142 142 uipopulate called (1 times) (chg !)
143 143 uipopulate called (1 times) (chg !)
144 144 reposetup called for a (chg !)
145 145 ui == repo.ui (chg !)
146 146 Foo
147 147 $ echo 'barfoo = !' >> $HGRCPATH
148 148
149 149 Check that extensions are loaded in phases:
150 150
151 151 $ cat > foo.py <<EOF
152 152 > from __future__ import print_function
153 153 > import os
154 154 > from mercurial import exthelper
155 155 > from mercurial.utils import procutil
156 156 >
157 157 > write = procutil.stdout.write
158 158 > name = os.path.basename(__file__).rsplit('.', 1)[0]
159 159 > bytesname = name.encode('utf-8')
160 160 > write(b"1) %s imported\n" % bytesname)
161 161 > eh = exthelper.exthelper()
162 162 > @eh.uisetup
163 163 > def _uisetup(ui):
164 164 > write(b"2) %s uisetup\n" % bytesname)
165 165 > @eh.extsetup
166 166 > def _extsetup(ui):
167 167 > write(b"3) %s extsetup\n" % bytesname)
168 168 > @eh.uipopulate
169 169 > def _uipopulate(ui):
170 170 > write(b"4) %s uipopulate\n" % bytesname)
171 171 > @eh.reposetup
172 172 > def _reposetup(ui, repo):
173 173 > write(b"5) %s reposetup\n" % bytesname)
174 174 >
175 175 > extsetup = eh.finalextsetup
176 176 > reposetup = eh.finalreposetup
177 177 > uipopulate = eh.finaluipopulate
178 178 > uisetup = eh.finaluisetup
179 179 > revsetpredicate = eh.revsetpredicate
180 180 >
181 181 > # custom predicate to check registration of functions at loading
182 182 > from mercurial import (
183 183 > smartset,
184 184 > )
185 185 > @eh.revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
186 186 > def custompredicate(repo, subset, x):
187 187 > return smartset.baseset([r for r in subset if r in {0}])
188 188 > EOF
189 189 $ "$PYTHON" $TESTTMP/unflush.py foo.py
190 190
191 191 $ cp foo.py bar.py
192 192 $ echo 'foo = foo.py' >> $HGRCPATH
193 193 $ echo 'bar = bar.py' >> $HGRCPATH
194 194
195 195 Check normal command's load order of extensions and registration of functions
196 196
197 197 $ hg log -r "foo() and bar()" -q
198 198 1) foo imported
199 199 1) bar imported
200 200 2) foo uisetup
201 201 2) bar uisetup
202 202 3) foo extsetup
203 203 3) bar extsetup
204 204 4) foo uipopulate
205 205 4) bar uipopulate
206 206 4) foo uipopulate
207 207 4) bar uipopulate
208 208 4) foo uipopulate
209 209 4) bar uipopulate
210 210 5) foo reposetup
211 211 5) bar reposetup
212 212 0:c24b9ac61126
213 213
214 214 Check hgweb's load order of extensions and registration of functions
215 215
216 216 $ cat > hgweb.cgi <<EOF
217 217 > #!$PYTHON
218 218 > from mercurial import demandimport; demandimport.enable()
219 219 > from mercurial.hgweb import hgweb
220 220 > from mercurial.hgweb import wsgicgi
221 221 > application = hgweb(b'.', b'test repo')
222 222 > wsgicgi.launch(application)
223 223 > EOF
224 224 $ . "$TESTDIR/cgienv"
225 225
226 226 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
227 227 > | grep '^[0-9]) ' # ignores HTML output
228 228 1) foo imported
229 229 1) bar imported
230 230 2) foo uisetup
231 231 2) bar uisetup
232 232 3) foo extsetup
233 233 3) bar extsetup
234 234 4) foo uipopulate
235 235 4) bar uipopulate
236 236 4) foo uipopulate
237 237 4) bar uipopulate
238 238 5) foo reposetup
239 239 5) bar reposetup
240 240
241 241 (check that revset predicate foo() and bar() are available)
242 242
243 243 #if msys
244 244 $ PATH_INFO='//shortlog'
245 245 #else
246 246 $ PATH_INFO='/shortlog'
247 247 #endif
248 248 $ export PATH_INFO
249 249 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
250 250 > | grep '<a href="/rev/[0-9a-z]*">'
251 251 <a href="/rev/c24b9ac61126">add file</a>
252 252
253 253 $ echo 'foo = !' >> $HGRCPATH
254 254 $ echo 'bar = !' >> $HGRCPATH
255 255
256 256 Check "from __future__ import absolute_import" support for external libraries
257 257
258 258 (import-checker.py reports issues for some of heredoc python code
259 259 fragments below, because import-checker.py does not know test specific
260 260 package hierarchy. NO_CHECK_* should be used as a limit mark of
261 261 heredoc, in order to make import-checker.py ignore them. For
262 262 simplicity, all python code fragments below are generated with such
263 263 limit mark, regardless of importing module or not.)
264 264
265 265 #if windows
266 266 $ PATHSEP=";"
267 267 #else
268 268 $ PATHSEP=":"
269 269 #endif
270 270 $ export PATHSEP
271 271
272 272 $ mkdir $TESTTMP/libroot
273 273 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
274 274 $ mkdir $TESTTMP/libroot/mod
275 275 $ touch $TESTTMP/libroot/mod/__init__.py
276 276 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
277 277
278 278 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
279 279 > from __future__ import absolute_import, print_function
280 280 > import ambig # should load "libroot/ambig.py"
281 281 > s = ambig.s
282 282 > NO_CHECK_EOF
283 283 $ cat > loadabs.py <<NO_CHECK_EOF
284 284 > import mod.ambigabs as ambigabs
285 285 > def extsetup(ui):
286 286 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
287 287 > NO_CHECK_EOF
288 288 $ "$PYTHON" $TESTTMP/unflush.py loadabs.py
289 289 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
290 290 ambigabs.s=libroot/ambig.py
291 291 $TESTTMP/a
292 292
293 293 #if no-py3
294 294 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
295 295 > from __future__ import print_function
296 296 > import ambig # should load "libroot/mod/ambig.py"
297 297 > s = ambig.s
298 298 > NO_CHECK_EOF
299 299 $ cat > loadrel.py <<NO_CHECK_EOF
300 300 > import mod.ambigrel as ambigrel
301 301 > def extsetup(ui):
302 302 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
303 303 > NO_CHECK_EOF
304 304 $ "$PYTHON" $TESTTMP/unflush.py loadrel.py
305 305 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
306 306 ambigrel.s=libroot/mod/ambig.py
307 307 $TESTTMP/a
308 308 #endif
309 309
310 310 Check absolute/relative import of extension specific modules
311 311
312 312 $ mkdir $TESTTMP/extroot
313 313 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
314 314 > s = b'this is extroot.bar'
315 315 > NO_CHECK_EOF
316 316 $ mkdir $TESTTMP/extroot/sub1
317 317 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
318 318 > s = b'this is extroot.sub1.__init__'
319 319 > NO_CHECK_EOF
320 320 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
321 321 > s = b'this is extroot.sub1.baz'
322 322 > NO_CHECK_EOF
323 323 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
324 324 > from __future__ import absolute_import
325 325 > s = b'this is extroot.__init__'
326 326 > from . import foo
327 327 > def extsetup(ui):
328 328 > ui.write(b'(extroot) ', foo.func(), b'\n')
329 329 > ui.flush()
330 330 > NO_CHECK_EOF
331 331
332 332 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
333 333 > # test absolute import
334 334 > buf = []
335 335 > def func():
336 336 > # "not locals" case
337 337 > import extroot.bar
338 338 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
339 339 > return b'\n(extroot) '.join(buf)
340 340 > # b"fromlist == ('*',)" case
341 341 > from extroot.bar import *
342 342 > buf.append(b'from extroot.bar import *: %s' % s)
343 343 > # "not fromlist" and "if '.' in name" case
344 344 > import extroot.sub1.baz
345 345 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
346 346 > # "not fromlist" and NOT "if '.' in name" case
347 347 > import extroot
348 348 > buf.append(b'import extroot: %s' % extroot.s)
349 349 > # NOT "not fromlist" and NOT "level != -1" case
350 350 > from extroot.bar import s
351 351 > buf.append(b'from extroot.bar import s: %s' % s)
352 352 > NO_CHECK_EOF
353 353 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
354 354 (extroot) from extroot.bar import *: this is extroot.bar
355 355 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
356 356 (extroot) import extroot: this is extroot.__init__
357 357 (extroot) from extroot.bar import s: this is extroot.bar
358 358 (extroot) import extroot.bar in func(): this is extroot.bar
359 359 $TESTTMP/a
360 360
361 361 #if no-py3
362 362 $ rm "$TESTTMP"/extroot/foo.*
363 363 $ rm -Rf "$TESTTMP/extroot/__pycache__"
364 364 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
365 365 > # test relative import
366 366 > buf = []
367 367 > def func():
368 368 > # "not locals" case
369 369 > import bar
370 370 > buf.append('import bar in func(): %s' % bar.s)
371 371 > return '\n(extroot) '.join(buf)
372 372 > # "fromlist == ('*',)" case
373 373 > from bar import *
374 374 > buf.append('from bar import *: %s' % s)
375 375 > # "not fromlist" and "if '.' in name" case
376 376 > import sub1.baz
377 377 > buf.append('import sub1.baz: %s' % sub1.baz.s)
378 378 > # "not fromlist" and NOT "if '.' in name" case
379 379 > import sub1
380 380 > buf.append('import sub1: %s' % sub1.s)
381 381 > # NOT "not fromlist" and NOT "level != -1" case
382 382 > from bar import s
383 383 > buf.append('from bar import s: %s' % s)
384 384 > NO_CHECK_EOF
385 385 $ hg --config extensions.extroot=$TESTTMP/extroot root
386 386 (extroot) from bar import *: this is extroot.bar
387 387 (extroot) import sub1.baz: this is extroot.sub1.baz
388 388 (extroot) import sub1: this is extroot.sub1.__init__
389 389 (extroot) from bar import s: this is extroot.bar
390 390 (extroot) import bar in func(): this is extroot.bar
391 391 $TESTTMP/a
392 392 #endif
393 393
394 394 #if demandimport
395 395
396 396 Examine whether module loading is delayed until actual referring, even
397 397 though module is imported with "absolute_import" feature.
398 398
399 399 Files below in each packages are used for described purpose:
400 400
401 401 - "called": examine whether "from MODULE import ATTR" works correctly
402 402 - "unused": examine whether loading is delayed correctly
403 403 - "used": examine whether "from PACKAGE import MODULE" works correctly
404 404
405 405 Package hierarchy is needed to examine whether demand importing works
406 406 as expected for "from SUB.PACK.AGE import MODULE".
407 407
408 408 Setup "external library" to be imported with "absolute_import"
409 409 feature.
410 410
411 411 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
412 412 $ touch $TESTTMP/extlibroot/__init__.py
413 413 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
414 414 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
415 415
416 416 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
417 417 > def func():
418 418 > return b"this is extlibroot.lsub1.lsub2.called.func()"
419 419 > NO_CHECK_EOF
420 420 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
421 421 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
422 422 > NO_CHECK_EOF
423 423 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
424 424 > detail = b"this is extlibroot.lsub1.lsub2.used"
425 425 > NO_CHECK_EOF
426 426
427 427 Setup sub-package of "external library", which causes instantiation of
428 428 demandmod in "recurse down the module chain" code path. Relative
429 429 importing with "absolute_import" feature isn't tested, because "level
430 430 >=1 " doesn't cause instantiation of demandmod.
431 431
432 432 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
433 433 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
434 434 > detail = b"this is extlibroot.recursedown.abs.used"
435 435 > NO_CHECK_EOF
436 436 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
437 437 > from __future__ import absolute_import
438 438 > from extlibroot.recursedown.abs.used import detail
439 439 > NO_CHECK_EOF
440 440
441 441 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
442 442 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
443 443 > detail = b"this is extlibroot.recursedown.legacy.used"
444 444 > NO_CHECK_EOF
445 445 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
446 446 > # legacy style (level == -1) import
447 447 > from extlibroot.recursedown.legacy.used import detail
448 448 > NO_CHECK_EOF
449 449
450 450 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
451 451 > from __future__ import absolute_import
452 452 > from extlibroot.recursedown.abs import detail as absdetail
453 453 > from .legacy import detail as legacydetail
454 454 > NO_CHECK_EOF
455 455
456 456 Setup package that re-exports an attribute of its submodule as the same
457 457 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
458 458 the submodule 'used' should be somehow accessible. (issue5617)
459 459
460 460 $ mkdir -p $TESTTMP/extlibroot/shadowing
461 461 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
462 462 > detail = b"this is extlibroot.shadowing.used"
463 463 > NO_CHECK_EOF
464 464 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
465 465 > from __future__ import absolute_import
466 466 > from extlibroot.shadowing.used import detail
467 467 > NO_CHECK_EOF
468 468 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
469 469 > from __future__ import absolute_import
470 470 > from .used import detail as used
471 471 > NO_CHECK_EOF
472 472
473 473 Setup extension local modules to be imported with "absolute_import"
474 474 feature.
475 475
476 476 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
477 477 $ touch $TESTTMP/absextroot/xsub1/__init__.py
478 478 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
479 479
480 480 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
481 481 > def func():
482 482 > return b"this is absextroot.xsub1.xsub2.called.func()"
483 483 > NO_CHECK_EOF
484 484 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
485 485 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
486 486 > NO_CHECK_EOF
487 487 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
488 488 > detail = b"this is absextroot.xsub1.xsub2.used"
489 489 > NO_CHECK_EOF
490 490
491 491 Setup extension local modules to examine whether demand importing
492 492 works as expected in "level > 1" case.
493 493
494 494 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
495 495 > detail = b"this is absextroot.relimportee"
496 496 > NO_CHECK_EOF
497 497 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
498 498 > from __future__ import absolute_import
499 499 > from mercurial import pycompat
500 500 > from ... import relimportee
501 501 > detail = b"this relimporter imports %r" % (
502 502 > pycompat.bytestr(relimportee.detail))
503 503 > NO_CHECK_EOF
504 504
505 505 Setup modules, which actually import extension local modules at
506 506 runtime.
507 507
508 508 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
509 509 > from __future__ import absolute_import
510 510 >
511 511 > # import extension local modules absolutely (level = 0)
512 512 > from absextroot.xsub1.xsub2 import used, unused
513 513 > from absextroot.xsub1.xsub2.called import func
514 514 >
515 515 > def getresult():
516 516 > result = []
517 517 > result.append(used.detail)
518 518 > result.append(func())
519 519 > return result
520 520 > NO_CHECK_EOF
521 521
522 522 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
523 523 > from __future__ import absolute_import
524 524 >
525 525 > # import extension local modules relatively (level == 1)
526 526 > from .xsub1.xsub2 import used, unused
527 527 > from .xsub1.xsub2.called import func
528 528 >
529 529 > # import a module, which implies "importing with level > 1"
530 530 > from .xsub1.xsub2 import relimporter
531 531 >
532 532 > def getresult():
533 533 > result = []
534 534 > result.append(used.detail)
535 535 > result.append(func())
536 536 > result.append(relimporter.detail)
537 537 > return result
538 538 > NO_CHECK_EOF
539 539
540 540 Setup main procedure of extension.
541 541
542 542 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
543 543 > from __future__ import absolute_import
544 544 > from mercurial import registrar
545 545 > cmdtable = {}
546 546 > command = registrar.command(cmdtable)
547 547 >
548 548 > # "absolute" and "relative" shouldn't be imported before actual
549 549 > # command execution, because (1) they import same modules, and (2)
550 550 > # preceding import (= instantiate "demandmod" object instead of
551 551 > # real "module" object) might hide problem of succeeding import.
552 552 >
553 553 > @command(b'showabsolute', [], norepo=True)
554 554 > def showabsolute(ui, *args, **opts):
555 555 > from absextroot import absolute
556 556 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
557 557 >
558 558 > @command(b'showrelative', [], norepo=True)
559 559 > def showrelative(ui, *args, **opts):
560 560 > from . import relative
561 561 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
562 562 >
563 563 > # import modules from external library
564 564 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
565 565 > from extlibroot.lsub1.lsub2.called import func as lfunc
566 566 > from extlibroot.recursedown import absdetail, legacydetail
567 567 > from extlibroot.shadowing import proxied
568 568 >
569 569 > def uisetup(ui):
570 570 > result = []
571 571 > result.append(lused.detail)
572 572 > result.append(lfunc())
573 573 > result.append(absdetail)
574 574 > result.append(legacydetail)
575 575 > result.append(proxied.detail)
576 576 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
577 577 > NO_CHECK_EOF
578 578
579 579 Examine module importing.
580 580
581 581 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
582 582 LIB: this is extlibroot.lsub1.lsub2.used
583 583 LIB: this is extlibroot.lsub1.lsub2.called.func()
584 584 LIB: this is extlibroot.recursedown.abs.used
585 585 LIB: this is extlibroot.recursedown.legacy.used
586 586 LIB: this is extlibroot.shadowing.used
587 587 ABS: this is absextroot.xsub1.xsub2.used
588 588 ABS: this is absextroot.xsub1.xsub2.called.func()
589 589
590 590 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
591 591 LIB: this is extlibroot.lsub1.lsub2.used
592 592 LIB: this is extlibroot.lsub1.lsub2.called.func()
593 593 LIB: this is extlibroot.recursedown.abs.used
594 594 LIB: this is extlibroot.recursedown.legacy.used
595 595 LIB: this is extlibroot.shadowing.used
596 596 REL: this is absextroot.xsub1.xsub2.used
597 597 REL: this is absextroot.xsub1.xsub2.called.func()
598 598 REL: this relimporter imports 'this is absextroot.relimportee'
599 599
600 600 Examine whether sub-module is imported relatively as expected.
601 601
602 602 See also issue5208 for detail about example case on Python 3.x.
603 603
604 604 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
605 605 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
606 606
607 607 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
608 608 > text = 'notexist.py at root is loaded unintentionally\n'
609 609 > NO_CHECK_EOF
610 610
611 611 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
612 612 > from mercurial import registrar
613 613 > cmdtable = {}
614 614 > command = registrar.command(cmdtable)
615 615 >
616 616 > # demand import avoids failure of importing notexist here, but only on
617 617 > # Python 2.
618 618 > import extlibroot.lsub1.lsub2.notexist
619 619 >
620 620 > @command(b'checkrelativity', [], norepo=True)
621 621 > def checkrelativity(ui, *args, **opts):
622 622 > try:
623 623 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
624 624 > return 1 # unintentional success
625 625 > except ImportError:
626 626 > pass # intentional failure
627 627 > NO_CHECK_EOF
628 628
629 629 Python 3's lazy importer verifies modules exist before returning the lazy
630 630 module stub. Our custom lazy importer for Python 2 always returns a stub.
631 631
632 632 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity) || true
633 633 *** failed to import extension checkrelativity from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !)
634 634 hg: unknown command 'checkrelativity' (py3 !)
635 635 (use 'hg help' for a list of commands) (py3 !)
636 636
637 637 #endif
638 638
639 639 (Here, module importing tests are finished. Therefore, use other than
640 640 NO_CHECK_* limit mark for heredoc python files, in order to apply
641 641 import-checker.py or so on their contents)
642 642
643 643 Make sure a broken uisetup doesn't globally break hg:
644 644 $ cat > $TESTTMP/baduisetup.py <<EOF
645 645 > def uisetup(ui):
646 646 > 1 / 0
647 647 > EOF
648 648
649 649 Even though the extension fails during uisetup, hg is still basically usable:
650 650 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
651 651 Traceback (most recent call last):
652 652 File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
653 653 uisetup(ui)
654 654 File "$TESTTMP/baduisetup.py", line 2, in uisetup
655 655 1 / 0
656 656 ZeroDivisionError: * by zero (glob)
657 657 *** failed to set up extension baduisetup: * by zero (glob)
658 658 Mercurial Distributed SCM (version *) (glob)
659 659 (see https://mercurial-scm.org for more information)
660 660
661 661 Copyright (C) 2005-* Matt Mackall and others (glob)
662 662 This is free software; see the source for copying conditions. There is NO
663 663 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
664 664
665 665 $ cd ..
666 666
667 667 hide outer repo
668 668 $ hg init
669 669
670 670 $ cat > empty.py <<EOF
671 671 > '''empty cmdtable
672 672 > '''
673 673 > cmdtable = {}
674 674 > EOF
675 675 $ emptypath=`pwd`/empty.py
676 676 $ echo "empty = $emptypath" >> $HGRCPATH
677 677 $ hg help empty
678 678 empty extension - empty cmdtable
679 679
680 680 no commands defined
681 681
682 682
683 683 $ echo 'empty = !' >> $HGRCPATH
684 684
685 685 $ cat > debugextension.py <<EOF
686 686 > '''only debugcommands
687 687 > '''
688 688 > from mercurial import registrar
689 689 > cmdtable = {}
690 690 > command = registrar.command(cmdtable)
691 691 > @command(b'debugfoobar', [], b'hg debugfoobar')
692 692 > def debugfoobar(ui, repo, *args, **opts):
693 693 > "yet another debug command"
694 694 > @command(b'foo', [], b'hg foo')
695 695 > def foo(ui, repo, *args, **opts):
696 696 > """yet another foo command
697 697 > This command has been DEPRECATED since forever.
698 698 > """
699 699 > EOF
700 700 $ debugpath=`pwd`/debugextension.py
701 701 $ echo "debugextension = $debugpath" >> $HGRCPATH
702 702
703 703 $ hg help debugextension
704 704 hg debugextensions
705 705
706 706 show information about active extensions
707 707
708 708 options:
709 709
710 710 -T --template TEMPLATE display with template
711 711
712 712 (some details hidden, use --verbose to show complete help)
713 713
714 714
715 715 $ hg --verbose help debugextension
716 716 hg debugextensions
717 717
718 718 show information about active extensions
719 719
720 720 options:
721 721
722 722 -T --template TEMPLATE display with template
723 723
724 724 global options ([+] can be repeated):
725 725
726 726 -R --repository REPO repository root directory or name of overlay bundle
727 727 file
728 728 --cwd DIR change working directory
729 729 -y --noninteractive do not prompt, automatically pick the first choice for
730 730 all prompts
731 731 -q --quiet suppress output
732 732 -v --verbose enable additional output
733 733 --color TYPE when to colorize (boolean, always, auto, never, or
734 734 debug)
735 735 --config CONFIG [+] set/override config option (use 'section.name=value')
736 736 --debug enable debugging output
737 737 --debugger start debugger
738 738 --encoding ENCODE set the charset encoding (default: ascii)
739 739 --encodingmode MODE set the charset encoding mode (default: strict)
740 740 --traceback always print a traceback on exception
741 741 --time time how long the command takes
742 742 --profile print command execution profile
743 743 --version output version information and exit
744 744 -h --help display help and exit
745 745 --hidden consider hidden changesets
746 746 --pager TYPE when to paginate (boolean, always, auto, or never)
747 747 (default: auto)
748 748
749 749
750 750
751 751
752 752
753 753
754 754 $ hg --debug help debugextension
755 755 hg debugextensions
756 756
757 757 show information about active extensions
758 758
759 759 options:
760 760
761 761 -T --template TEMPLATE display with template
762 762
763 763 global options ([+] can be repeated):
764 764
765 765 -R --repository REPO repository root directory or name of overlay bundle
766 766 file
767 767 --cwd DIR change working directory
768 768 -y --noninteractive do not prompt, automatically pick the first choice for
769 769 all prompts
770 770 -q --quiet suppress output
771 771 -v --verbose enable additional output
772 772 --color TYPE when to colorize (boolean, always, auto, never, or
773 773 debug)
774 774 --config CONFIG [+] set/override config option (use 'section.name=value')
775 775 --debug enable debugging output
776 776 --debugger start debugger
777 777 --encoding ENCODE set the charset encoding (default: ascii)
778 778 --encodingmode MODE set the charset encoding mode (default: strict)
779 779 --traceback always print a traceback on exception
780 780 --time time how long the command takes
781 781 --profile print command execution profile
782 782 --version output version information and exit
783 783 -h --help display help and exit
784 784 --hidden consider hidden changesets
785 785 --pager TYPE when to paginate (boolean, always, auto, or never)
786 786 (default: auto)
787 787
788 788
789 789
790 790
791 791
792 792 $ echo 'debugextension = !' >> $HGRCPATH
793 793
794 794 Asking for help about a deprecated extension should do something useful:
795 795
796 796 $ hg help glog
797 797 'glog' is provided by the following extension:
798 798
799 799 graphlog command to view revision graphs from a shell (DEPRECATED)
800 800
801 801 (use 'hg help extensions' for information on enabling extensions)
802 802
803 803 Extension module help vs command help:
804 804
805 805 $ echo 'extdiff =' >> $HGRCPATH
806 806 $ hg help extdiff
807 807 hg extdiff [OPT]... [FILE]...
808 808
809 809 use external program to diff repository (or selected files)
810 810
811 811 Show differences between revisions for the specified files, using an
812 812 external program. The default program used is diff, with default options
813 813 "-Npru".
814 814
815 815 To select a different program, use the -p/--program option. The program
816 816 will be passed the names of two directories to compare, unless the --per-
817 817 file option is specified (see below). To pass additional options to the
818 818 program, use -o/--option. These will be passed before the names of the
819 819 directories or files to compare.
820 820
821 821 When two revision arguments are given, then changes are shown between
822 822 those revisions. If only one revision is specified then that revision is
823 823 compared to the working directory, and, when no revisions are specified,
824 824 the working directory files are compared to its parent.
825 825
826 826 The --per-file option runs the external program repeatedly on each file to
827 827 diff, instead of once on two directories. By default, this happens one by
828 828 one, where the next file diff is open in the external program only once
829 829 the previous external program (for the previous file diff) has exited. If
830 830 the external program has a graphical interface, it can open all the file
831 831 diffs at once instead of one by one. See 'hg help -e extdiff' for
832 832 information about how to tell Mercurial that a given program has a
833 833 graphical interface.
834 834
835 835 The --confirm option will prompt the user before each invocation of the
836 836 external program. It is ignored if --per-file isn't specified.
837 837
838 838 (use 'hg help -e extdiff' to show help for the extdiff extension)
839 839
840 840 options ([+] can be repeated):
841 841
842 842 -p --program CMD comparison program to run
843 843 -o --option OPT [+] pass option to comparison program
844 844 -r --rev REV [+] revision
845 845 -c --change REV change made by revision
846 846 --per-file compare each file instead of revision snapshots
847 847 --confirm prompt user before each external program invocation
848 848 --patch compare patches for two revisions
849 849 -I --include PATTERN [+] include names matching the given patterns
850 850 -X --exclude PATTERN [+] exclude names matching the given patterns
851 851 -S --subrepos recurse into subrepositories
852 852
853 853 (some details hidden, use --verbose to show complete help)
854 854
855 855
856 856
857 857
858 858
859 859
860 860
861 861
862 862
863 863
864 864 $ hg help --extension extdiff
865 865 extdiff extension - command to allow external programs to compare revisions
866 866
867 867 The extdiff Mercurial extension allows you to use external programs to compare
868 868 revisions, or revision with working directory. The external diff programs are
869 869 called with a configurable set of options and two non-option arguments: paths
870 870 to directories containing snapshots of files to compare.
871 871
872 872 If there is more than one file being compared and the "child" revision is the
873 873 working directory, any modifications made in the external diff program will be
874 874 copied back to the working directory from the temporary directory.
875 875
876 876 The extdiff extension also allows you to configure new diff commands, so you
877 877 do not need to type 'hg extdiff -p kdiff3' always.
878 878
879 879 [extdiff]
880 880 # add new command that runs GNU diff(1) in 'context diff' mode
881 881 cdiff = gdiff -Nprc5
882 882 ## or the old way:
883 883 #cmd.cdiff = gdiff
884 884 #opts.cdiff = -Nprc5
885 885
886 886 # add new command called meld, runs meld (no need to name twice). If
887 887 # the meld executable is not available, the meld tool in [merge-tools]
888 888 # will be used, if available
889 889 meld =
890 890
891 891 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
892 892 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
893 893 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
894 894 # your .vimrc
895 895 vimdiff = gvim -f "+next" \
896 896 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
897 897
898 898 Tool arguments can include variables that are expanded at runtime:
899 899
900 900 $parent1, $plabel1 - filename, descriptive label of first parent
901 901 $child, $clabel - filename, descriptive label of child revision
902 902 $parent2, $plabel2 - filename, descriptive label of second parent
903 903 $root - repository root
904 904 $parent is an alias for $parent1.
905 905
906 906 The extdiff extension will look in your [diff-tools] and [merge-tools]
907 907 sections for diff tool arguments, when none are specified in [extdiff].
908 908
909 909 [extdiff]
910 910 kdiff3 =
911 911
912 912 [diff-tools]
913 913 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
914 914
915 915 If a program has a graphical interface, it might be interesting to tell
916 916 Mercurial about it. It will prevent the program from being mistakenly used in
917 917 a terminal-only environment (such as an SSH terminal session), and will make
918 918 'hg extdiff --per-file' open multiple file diffs at once instead of one by one
919 919 (if you still want to open file diffs one by one, you can use the --confirm
920 920 option).
921 921
922 922 Declaring that a tool has a graphical interface can be done with the "gui"
923 923 flag next to where "diffargs" are specified:
924 924
925 925 [diff-tools]
926 926 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
927 927 kdiff3.gui = true
928 928
929 929 You can use -I/-X and list of file or directory names like normal 'hg diff'
930 930 command. The extdiff extension makes snapshots of only needed files, so
931 931 running the external diff program will actually be pretty fast (at least
932 932 faster than having to compare the entire tree).
933 933
934 934 list of commands:
935 935
936 936 extdiff use external program to diff repository (or selected files)
937 937
938 938 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
939 939
940 940
941 941
942 942
943 943
944 944
945 945
946 946
947 947
948 948
949 949
950 950
951 951
952 952
953 953
954 954
955 955 $ echo 'extdiff = !' >> $HGRCPATH
956 956
957 957 Test help topic with same name as extension
958 958
959 959 $ cat > multirevs.py <<EOF
960 960 > from mercurial import commands, registrar
961 961 > cmdtable = {}
962 962 > command = registrar.command(cmdtable)
963 963 > """multirevs extension
964 964 > Big multi-line module docstring."""
965 965 > @command(b'multirevs', [], b'ARG', norepo=True)
966 966 > def multirevs(ui, repo, arg, *args, **opts):
967 967 > """multirevs command"""
968 968 > EOF
969 969 $ echo "multirevs = multirevs.py" >> $HGRCPATH
970 970
971 971 $ hg help multirevs | tail
972 972 used):
973 973
974 974 hg update :@
975 975
976 976 - Show diff between tags 1.3 and 1.5 (this works because the first and the
977 977 last revisions of the revset are used):
978 978
979 979 hg diff -r 1.3::1.5
980 980
981 981 use 'hg help -c multirevs' to see help for the multirevs command
982 982
983 983
984 984
985 985
986 986
987 987
988 988 $ hg help -c multirevs
989 989 hg multirevs ARG
990 990
991 991 multirevs command
992 992
993 993 (some details hidden, use --verbose to show complete help)
994 994
995 995
996 996
997 997 $ hg multirevs
998 998 hg multirevs: invalid arguments
999 999 hg multirevs ARG
1000 1000
1001 1001 multirevs command
1002 1002
1003 1003 (use 'hg multirevs -h' to show more help)
1004 1004 [255]
1005 1005
1006 1006
1007 1007
1008 1008 $ echo "multirevs = !" >> $HGRCPATH
1009 1009
1010 1010 Issue811: Problem loading extensions twice (by site and by user)
1011 1011
1012 1012 $ cat <<EOF >> $HGRCPATH
1013 1013 > mq =
1014 1014 > strip =
1015 1015 > hgext.mq =
1016 1016 > hgext/mq =
1017 1017 > EOF
1018 1018
1019 1019 Show extensions:
1020 1020 (note that mq force load strip, also checking it's not loaded twice)
1021 1021
1022 1022 #if no-extraextensions
1023 1023 $ hg debugextensions
1024 1024 mq
1025 1025 strip
1026 1026 #endif
1027 1027
1028 1028 For extensions, which name matches one of its commands, help
1029 1029 message should ask '-v -e' to get list of built-in aliases
1030 1030 along with extension help itself
1031 1031
1032 1032 $ mkdir $TESTTMP/d
1033 1033 $ cat > $TESTTMP/d/dodo.py <<EOF
1034 1034 > """
1035 1035 > This is an awesome 'dodo' extension. It does nothing and
1036 1036 > writes 'Foo foo'
1037 1037 > """
1038 1038 > from mercurial import commands, registrar
1039 1039 > cmdtable = {}
1040 1040 > command = registrar.command(cmdtable)
1041 1041 > @command(b'dodo', [], b'hg dodo')
1042 1042 > def dodo(ui, *args, **kwargs):
1043 1043 > """Does nothing"""
1044 1044 > ui.write(b"I do nothing. Yay\\n")
1045 1045 > @command(b'foofoo', [], b'hg foofoo')
1046 1046 > def foofoo(ui, *args, **kwargs):
1047 1047 > """Writes 'Foo foo'"""
1048 1048 > ui.write(b"Foo foo\\n")
1049 1049 > EOF
1050 1050 $ dodopath=$TESTTMP/d/dodo.py
1051 1051
1052 1052 $ echo "dodo = $dodopath" >> $HGRCPATH
1053 1053
1054 1054 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
1055 1055 $ hg help -e dodo
1056 1056 dodo extension -
1057 1057
1058 1058 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1059 1059
1060 1060 list of commands:
1061 1061
1062 1062 dodo Does nothing
1063 1063 foofoo Writes 'Foo foo'
1064 1064
1065 1065 (use 'hg help -v -e dodo' to show built-in aliases and global options)
1066 1066
1067 1067 Make sure that '-v -e' prints list of built-in aliases along with
1068 1068 extension help itself
1069 1069 $ hg help -v -e dodo
1070 1070 dodo extension -
1071 1071
1072 1072 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1073 1073
1074 1074 list of commands:
1075 1075
1076 1076 dodo Does nothing
1077 1077 foofoo Writes 'Foo foo'
1078 1078
1079 1079 global options ([+] can be repeated):
1080 1080
1081 1081 -R --repository REPO repository root directory or name of overlay bundle
1082 1082 file
1083 1083 --cwd DIR change working directory
1084 1084 -y --noninteractive do not prompt, automatically pick the first choice for
1085 1085 all prompts
1086 1086 -q --quiet suppress output
1087 1087 -v --verbose enable additional output
1088 1088 --color TYPE when to colorize (boolean, always, auto, never, or
1089 1089 debug)
1090 1090 --config CONFIG [+] set/override config option (use 'section.name=value')
1091 1091 --debug enable debugging output
1092 1092 --debugger start debugger
1093 1093 --encoding ENCODE set the charset encoding (default: ascii)
1094 1094 --encodingmode MODE set the charset encoding mode (default: strict)
1095 1095 --traceback always print a traceback on exception
1096 1096 --time time how long the command takes
1097 1097 --profile print command execution profile
1098 1098 --version output version information and exit
1099 1099 -h --help display help and exit
1100 1100 --hidden consider hidden changesets
1101 1101 --pager TYPE when to paginate (boolean, always, auto, or never)
1102 1102 (default: auto)
1103 1103
1104 1104 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1105 1105 $ hg help -v dodo
1106 1106 hg dodo
1107 1107
1108 1108 Does nothing
1109 1109
1110 1110 (use 'hg help -e dodo' to show help for the dodo extension)
1111 1111
1112 1112 options:
1113 1113
1114 1114 --mq operate on patch repository
1115 1115
1116 1116 global options ([+] can be repeated):
1117 1117
1118 1118 -R --repository REPO repository root directory or name of overlay bundle
1119 1119 file
1120 1120 --cwd DIR change working directory
1121 1121 -y --noninteractive do not prompt, automatically pick the first choice for
1122 1122 all prompts
1123 1123 -q --quiet suppress output
1124 1124 -v --verbose enable additional output
1125 1125 --color TYPE when to colorize (boolean, always, auto, never, or
1126 1126 debug)
1127 1127 --config CONFIG [+] set/override config option (use 'section.name=value')
1128 1128 --debug enable debugging output
1129 1129 --debugger start debugger
1130 1130 --encoding ENCODE set the charset encoding (default: ascii)
1131 1131 --encodingmode MODE set the charset encoding mode (default: strict)
1132 1132 --traceback always print a traceback on exception
1133 1133 --time time how long the command takes
1134 1134 --profile print command execution profile
1135 1135 --version output version information and exit
1136 1136 -h --help display help and exit
1137 1137 --hidden consider hidden changesets
1138 1138 --pager TYPE when to paginate (boolean, always, auto, or never)
1139 1139 (default: auto)
1140 1140
1141 1141 In case when extension name doesn't match any of its commands,
1142 1142 help message should ask for '-v' to get list of built-in aliases
1143 1143 along with extension help
1144 1144 $ cat > $TESTTMP/d/dudu.py <<EOF
1145 1145 > """
1146 1146 > This is an awesome 'dudu' extension. It does something and
1147 1147 > also writes 'Beep beep'
1148 1148 > """
1149 1149 > from mercurial import commands, registrar
1150 1150 > cmdtable = {}
1151 1151 > command = registrar.command(cmdtable)
1152 1152 > @command(b'something', [], b'hg something')
1153 1153 > def something(ui, *args, **kwargs):
1154 1154 > """Does something"""
1155 1155 > ui.write(b"I do something. Yaaay\\n")
1156 1156 > @command(b'beep', [], b'hg beep')
1157 1157 > def beep(ui, *args, **kwargs):
1158 1158 > """Writes 'Beep beep'"""
1159 1159 > ui.write(b"Beep beep\\n")
1160 1160 > EOF
1161 1161 $ dudupath=$TESTTMP/d/dudu.py
1162 1162
1163 1163 $ echo "dudu = $dudupath" >> $HGRCPATH
1164 1164
1165 1165 $ hg help -e dudu
1166 1166 dudu extension -
1167 1167
1168 1168 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1169 1169 beep'
1170 1170
1171 1171 list of commands:
1172 1172
1173 1173 beep Writes 'Beep beep'
1174 1174 something Does something
1175 1175
1176 1176 (use 'hg help -v dudu' to show built-in aliases and global options)
1177 1177
1178 1178 In case when extension name doesn't match any of its commands,
1179 1179 help options '-v' and '-v -e' should be equivalent
1180 1180 $ hg help -v dudu
1181 1181 dudu extension -
1182 1182
1183 1183 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1184 1184 beep'
1185 1185
1186 1186 list of commands:
1187 1187
1188 1188 beep Writes 'Beep beep'
1189 1189 something Does something
1190 1190
1191 1191 global options ([+] can be repeated):
1192 1192
1193 1193 -R --repository REPO repository root directory or name of overlay bundle
1194 1194 file
1195 1195 --cwd DIR change working directory
1196 1196 -y --noninteractive do not prompt, automatically pick the first choice for
1197 1197 all prompts
1198 1198 -q --quiet suppress output
1199 1199 -v --verbose enable additional output
1200 1200 --color TYPE when to colorize (boolean, always, auto, never, or
1201 1201 debug)
1202 1202 --config CONFIG [+] set/override config option (use 'section.name=value')
1203 1203 --debug enable debugging output
1204 1204 --debugger start debugger
1205 1205 --encoding ENCODE set the charset encoding (default: ascii)
1206 1206 --encodingmode MODE set the charset encoding mode (default: strict)
1207 1207 --traceback always print a traceback on exception
1208 1208 --time time how long the command takes
1209 1209 --profile print command execution profile
1210 1210 --version output version information and exit
1211 1211 -h --help display help and exit
1212 1212 --hidden consider hidden changesets
1213 1213 --pager TYPE when to paginate (boolean, always, auto, or never)
1214 1214 (default: auto)
1215 1215
1216 1216 $ hg help -v -e dudu
1217 1217 dudu extension -
1218 1218
1219 1219 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1220 1220 beep'
1221 1221
1222 1222 list of commands:
1223 1223
1224 1224 beep Writes 'Beep beep'
1225 1225 something Does something
1226 1226
1227 1227 global options ([+] can be repeated):
1228 1228
1229 1229 -R --repository REPO repository root directory or name of overlay bundle
1230 1230 file
1231 1231 --cwd DIR change working directory
1232 1232 -y --noninteractive do not prompt, automatically pick the first choice for
1233 1233 all prompts
1234 1234 -q --quiet suppress output
1235 1235 -v --verbose enable additional output
1236 1236 --color TYPE when to colorize (boolean, always, auto, never, or
1237 1237 debug)
1238 1238 --config CONFIG [+] set/override config option (use 'section.name=value')
1239 1239 --debug enable debugging output
1240 1240 --debugger start debugger
1241 1241 --encoding ENCODE set the charset encoding (default: ascii)
1242 1242 --encodingmode MODE set the charset encoding mode (default: strict)
1243 1243 --traceback always print a traceback on exception
1244 1244 --time time how long the command takes
1245 1245 --profile print command execution profile
1246 1246 --version output version information and exit
1247 1247 -h --help display help and exit
1248 1248 --hidden consider hidden changesets
1249 1249 --pager TYPE when to paginate (boolean, always, auto, or never)
1250 1250 (default: auto)
1251 1251
1252 1252 Disabled extension commands:
1253 1253
1254 1254 $ ORGHGRCPATH=$HGRCPATH
1255 1255 $ HGRCPATH=
1256 1256 $ export HGRCPATH
1257 1257 $ hg help email
1258 1258 'email' is provided by the following extension:
1259 1259
1260 1260 patchbomb command to send changesets as (a series of) patch emails
1261 1261
1262 1262 (use 'hg help extensions' for information on enabling extensions)
1263 1263
1264 1264
1265 1265 $ hg qdel
1266 1266 hg: unknown command 'qdel'
1267 1267 'qdelete' is provided by the following extension:
1268 1268
1269 1269 mq manage a stack of patches
1270 1270
1271 1271 (use 'hg help extensions' for information on enabling extensions)
1272 1272 [255]
1273 1273
1274 1274
1275 1275 $ hg churn
1276 1276 hg: unknown command 'churn'
1277 1277 'churn' is provided by the following extension:
1278 1278
1279 1279 churn command to display statistics about repository history
1280 1280
1281 1281 (use 'hg help extensions' for information on enabling extensions)
1282 1282 [255]
1283 1283
1284 1284
1285 1285
1286 1286 Disabled extensions:
1287 1287
1288 1288 $ hg help churn
1289 1289 churn extension - command to display statistics about repository history
1290 1290
1291 1291 (use 'hg help extensions' for information on enabling extensions)
1292 1292
1293 1293 $ hg help patchbomb
1294 1294 patchbomb extension - command to send changesets as (a series of) patch emails
1295 1295
1296 1296 The series is started off with a "[PATCH 0 of N]" introduction, which
1297 1297 describes the series as a whole.
1298 1298
1299 1299 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1300 1300 line of the changeset description as the subject text. The message contains
1301 1301 two or three body parts:
1302 1302
1303 1303 - The changeset description.
1304 1304 - [Optional] The result of running diffstat on the patch.
1305 1305 - The patch itself, as generated by 'hg export'.
1306 1306
1307 1307 Each message refers to the first in the series using the In-Reply-To and
1308 1308 References headers, so they will show up as a sequence in threaded mail and
1309 1309 news readers, and in mail archives.
1310 1310
1311 1311 To configure other defaults, add a section like this to your configuration
1312 1312 file:
1313 1313
1314 1314 [email]
1315 1315 from = My Name <my@email>
1316 1316 to = recipient1, recipient2, ...
1317 1317 cc = cc1, cc2, ...
1318 1318 bcc = bcc1, bcc2, ...
1319 1319 reply-to = address1, address2, ...
1320 1320
1321 1321 Use "[patchbomb]" as configuration section name if you need to override global
1322 1322 "[email]" address settings.
1323 1323
1324 1324 Then you can use the 'hg email' command to mail a series of changesets as a
1325 1325 patchbomb.
1326 1326
1327 1327 You can also either configure the method option in the email section to be a
1328 1328 sendmail compatible mailer or fill out the [smtp] section so that the
1329 1329 patchbomb extension can automatically send patchbombs directly from the
1330 1330 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1331 1331
1332 1332 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1333 1333 supply one via configuration or the command line. You can override this to
1334 1334 never prompt by configuring an empty value:
1335 1335
1336 1336 [email]
1337 1337 cc =
1338 1338
1339 1339 You can control the default inclusion of an introduction message with the
1340 1340 "patchbomb.intro" configuration option. The configuration is always
1341 1341 overwritten by command line flags like --intro and --desc:
1342 1342
1343 1343 [patchbomb]
1344 1344 intro=auto # include introduction message if more than 1 patch (default)
1345 1345 intro=never # never include an introduction message
1346 1346 intro=always # always include an introduction message
1347 1347
1348 1348 You can specify a template for flags to be added in subject prefixes. Flags
1349 1349 specified by --flag option are exported as "{flags}" keyword:
1350 1350
1351 1351 [patchbomb]
1352 1352 flagtemplate = "{separate(' ',
1353 1353 ifeq(branch, 'default', '', branch|upper),
1354 1354 flags)}"
1355 1355
1356 1356 You can set patchbomb to always ask for confirmation by setting
1357 1357 "patchbomb.confirm" to true.
1358 1358
1359 1359 (use 'hg help extensions' for information on enabling extensions)
1360 1360
1361 1361
1362 1362 Broken disabled extension and command:
1363 1363
1364 1364 $ mkdir hgext
1365 1365 $ echo > hgext/__init__.py
1366 1366 $ cat > hgext/broken.py <<NO_CHECK_EOF
1367 1367 > "broken extension'
1368 1368 > NO_CHECK_EOF
1369 1369 $ cat > path.py <<EOF
1370 1370 > import os
1371 1371 > import sys
1372 1372 > sys.path.insert(0, os.environ['HGEXTPATH'])
1373 1373 > EOF
1374 1374 $ HGEXTPATH=`pwd`
1375 1375 $ export HGEXTPATH
1376 1376
1377 1377 $ hg --config extensions.path=./path.py help broken
1378 1378 broken extension - (no help text available)
1379 1379
1380 1380 (use 'hg help extensions' for information on enabling extensions)
1381 1381
1382 1382
1383 1383 $ cat > hgext/forest.py <<EOF
1384 1384 > cmdtable = None
1385 1385 > @command()
1386 1386 > def f():
1387 1387 > pass
1388 1388 > @command(123)
1389 1389 > def g():
1390 1390 > pass
1391 1391 > EOF
1392 1392 $ hg --config extensions.path=./path.py help foo
1393 1393 abort: no such help topic: foo
1394 1394 (try 'hg help --keyword foo')
1395 1395 [255]
1396 1396
1397 1397 $ cat > throw.py <<EOF
1398 1398 > from mercurial import commands, registrar, util
1399 1399 > cmdtable = {}
1400 1400 > command = registrar.command(cmdtable)
1401 1401 > class Bogon(Exception): pass
1402 1402 > # NB: version should be bytes; simulating extension not ported to py3
1403 1403 > __version__ = '1.0.0'
1404 1404 > @command(b'throw', [], b'hg throw', norepo=True)
1405 1405 > def throw(ui, **opts):
1406 1406 > """throws an exception"""
1407 1407 > raise Bogon()
1408 1408 > EOF
1409 1409
1410 1410 Test extension without proper byteification of key attributes doesn't crash when
1411 1411 accessed.
1412 1412
1413 1413 $ hg version -v --config extensions.throw=throw.py | grep '^ '
1414 1414 throw external 1.0.0
1415 1415
1416 1416 No declared supported version, extension complains:
1417 1417 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1418 1418 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1419 1419 ** which supports versions unknown of Mercurial.
1420 1420 ** Please disable "throw" and try your action again.
1421 1421 ** If that fixes the bug please report it to the extension author.
1422 1422 ** Python * (glob)
1423 1423 ** Mercurial Distributed SCM * (glob)
1424 ** Extensions loaded: throw
1424 ** Extensions loaded: throw 1.0.0
1425 1425
1426 1426 empty declaration of supported version, extension complains (but doesn't choke if
1427 1427 the value is improperly a str instead of bytes):
1428 1428 $ echo "testedwith = ''" >> throw.py
1429 1429 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1430 1430 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1431 1431 ** which supports versions unknown of Mercurial.
1432 1432 ** Please disable "throw" and try your action again.
1433 1433 ** If that fixes the bug please report it to the extension author.
1434 1434 ** Python * (glob)
1435 1435 ** Mercurial Distributed SCM (*) (glob)
1436 ** Extensions loaded: throw
1436 ** Extensions loaded: throw 1.0.0
1437 1437
1438 1438 If the extension specifies a buglink, show that (but don't choke if the value is
1439 1439 improperly a str instead of bytes):
1440 1440 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1441 1441 $ rm -f throw.pyc throw.pyo
1442 1442 $ rm -Rf __pycache__
1443 1443 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1444 1444 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1445 1445 ** which supports versions unknown of Mercurial.
1446 1446 ** Please disable "throw" and try your action again.
1447 1447 ** If that fixes the bug please report it to http://example.com/bts
1448 1448 ** Python * (glob)
1449 1449 ** Mercurial Distributed SCM (*) (glob)
1450 ** Extensions loaded: throw
1450 ** Extensions loaded: throw 1.0.0
1451 1451
1452 1452 If the extensions declare outdated versions, accuse the older extension first:
1453 1453 $ echo "from mercurial import util" >> older.py
1454 1454 $ echo "util.version = lambda:b'2.2'" >> older.py
1455 1455 $ echo "testedwith = b'1.9.3'" >> older.py
1456 1456 $ echo "testedwith = b'2.1.1'" >> throw.py
1457 1457 $ rm -f throw.pyc throw.pyo
1458 1458 $ rm -Rf __pycache__
1459 1459 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1460 1460 > throw 2>&1 | egrep '^\*\*'
1461 1461 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1462 1462 ** which supports versions 1.9 of Mercurial.
1463 1463 ** Please disable "older" and try your action again.
1464 1464 ** If that fixes the bug please report it to the extension author.
1465 1465 ** Python * (glob)
1466 1466 ** Mercurial Distributed SCM (version 2.2)
1467 ** Extensions loaded: older, throw
1467 ** Extensions loaded: older, throw 1.0.0
1468 1468
1469 1469 One extension only tested with older, one only with newer versions:
1470 1470 $ echo "util.version = lambda:b'2.1'" >> older.py
1471 1471 $ rm -f older.pyc older.pyo
1472 1472 $ rm -Rf __pycache__
1473 1473 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1474 1474 > throw 2>&1 | egrep '^\*\*'
1475 1475 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1476 1476 ** which supports versions 1.9 of Mercurial.
1477 1477 ** Please disable "older" and try your action again.
1478 1478 ** If that fixes the bug please report it to the extension author.
1479 1479 ** Python * (glob)
1480 1480 ** Mercurial Distributed SCM (version 2.1)
1481 ** Extensions loaded: older, throw
1481 ** Extensions loaded: older, throw 1.0.0
1482 1482
1483 1483 Older extension is tested with current version, the other only with newer:
1484 1484 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1485 1485 $ rm -f older.pyc older.pyo
1486 1486 $ rm -Rf __pycache__
1487 1487 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1488 1488 > throw 2>&1 | egrep '^\*\*'
1489 1489 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1490 1490 ** which supports versions 2.1 of Mercurial.
1491 1491 ** Please disable "throw" and try your action again.
1492 1492 ** If that fixes the bug please report it to http://example.com/bts
1493 1493 ** Python * (glob)
1494 1494 ** Mercurial Distributed SCM (version 1.9.3)
1495 ** Extensions loaded: older, throw
1495 ** Extensions loaded: older, throw 1.0.0
1496 1496
1497 1497 Ability to point to a different point
1498 1498 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1499 1499 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1500 1500 ** unknown exception encountered, please report by visiting
1501 1501 ** Your Local Goat Lenders
1502 1502 ** Python * (glob)
1503 1503 ** Mercurial Distributed SCM (*) (glob)
1504 ** Extensions loaded: older, throw
1504 ** Extensions loaded: older, throw 1.0.0
1505 1505
1506 1506 Declare the version as supporting this hg version, show regular bts link:
1507 1507 $ hgver=`hg debuginstall -T '{hgver}'`
1508 1508 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1509 1509 $ if [ -z "$hgver" ]; then
1510 1510 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1511 1511 > fi
1512 1512 $ rm -f throw.pyc throw.pyo
1513 1513 $ rm -Rf __pycache__
1514 1514 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1515 1515 ** unknown exception encountered, please report by visiting
1516 1516 ** https://mercurial-scm.org/wiki/BugTracker
1517 1517 ** Python * (glob)
1518 1518 ** Mercurial Distributed SCM (*) (glob)
1519 ** Extensions loaded: throw
1519 ** Extensions loaded: throw 1.0.0
1520 1520
1521 1521 Patch version is ignored during compatibility check
1522 1522 $ echo "testedwith = b'3.2'" >> throw.py
1523 1523 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1524 1524 $ rm -f throw.pyc throw.pyo
1525 1525 $ rm -Rf __pycache__
1526 1526 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1527 1527 ** unknown exception encountered, please report by visiting
1528 1528 ** https://mercurial-scm.org/wiki/BugTracker
1529 1529 ** Python * (glob)
1530 1530 ** Mercurial Distributed SCM (*) (glob)
1531 ** Extensions loaded: throw
1531 ** Extensions loaded: throw 1.0.0
1532 1532
1533 1533 Test version number support in 'hg version':
1534 1534 $ echo '__version__ = (1, 2, 3)' >> throw.py
1535 1535 $ rm -f throw.pyc throw.pyo
1536 1536 $ rm -Rf __pycache__
1537 1537 $ hg version -v
1538 1538 Mercurial Distributed SCM (version *) (glob)
1539 1539 (see https://mercurial-scm.org for more information)
1540 1540
1541 1541 Copyright (C) 2005-* Matt Mackall and others (glob)
1542 1542 This is free software; see the source for copying conditions. There is NO
1543 1543 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1544 1544
1545 1545 Enabled extensions:
1546 1546
1547 1547
1548 1548 $ hg version -v --config extensions.throw=throw.py
1549 1549 Mercurial Distributed SCM (version *) (glob)
1550 1550 (see https://mercurial-scm.org for more information)
1551 1551
1552 1552 Copyright (C) 2005-* Matt Mackall and others (glob)
1553 1553 This is free software; see the source for copying conditions. There is NO
1554 1554 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1555 1555
1556 1556 Enabled extensions:
1557 1557
1558 1558 throw external 1.2.3
1559 1559 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1560 1560 $ rm -f throw.pyc throw.pyo
1561 1561 $ rm -Rf __pycache__
1562 1562 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1563 1563 Mercurial Distributed SCM (version *) (glob)
1564 1564 (see https://mercurial-scm.org for more information)
1565 1565
1566 1566 Copyright (C) 2005-* Matt Mackall and others (glob)
1567 1567 This is free software; see the source for copying conditions. There is NO
1568 1568 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1569 1569
1570 1570 Enabled extensions:
1571 1571
1572 1572 strip internal
1573 1573 throw external 1.twentythree
1574 1574
1575 1575 $ hg version -q --config extensions.throw=throw.py
1576 1576 Mercurial Distributed SCM (version *) (glob)
1577 1577
1578 1578 Test template output:
1579 1579
1580 1580 $ hg version --config extensions.strip= -T'{extensions}'
1581 1581 strip
1582 1582
1583 1583 Test JSON output of version:
1584 1584
1585 1585 $ hg version -Tjson
1586 1586 [
1587 1587 {
1588 1588 "extensions": [],
1589 1589 "ver": "*" (glob)
1590 1590 }
1591 1591 ]
1592 1592
1593 1593 $ hg version --config extensions.throw=throw.py -Tjson
1594 1594 [
1595 1595 {
1596 1596 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1597 1597 "ver": "3.2.2"
1598 1598 }
1599 1599 ]
1600 1600
1601 1601 $ hg version --config extensions.strip= -Tjson
1602 1602 [
1603 1603 {
1604 1604 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1605 1605 "ver": "*" (glob)
1606 1606 }
1607 1607 ]
1608 1608
1609 1609 Test template output of version:
1610 1610
1611 1611 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1612 1612 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1613 1613 strip (internal)
1614 1614 throw 1.twentythree (external)
1615 1615
1616 1616 Refuse to load extensions with minimum version requirements
1617 1617
1618 1618 $ cat > minversion1.py << EOF
1619 1619 > from mercurial import util
1620 1620 > util.version = lambda: b'3.5.2'
1621 1621 > minimumhgversion = b'3.6'
1622 1622 > EOF
1623 1623 $ hg --config extensions.minversion=minversion1.py version
1624 1624 (third party extension minversion requires version 3.6 or newer of Mercurial (current: 3.5.2); disabling)
1625 1625 Mercurial Distributed SCM (version 3.5.2)
1626 1626 (see https://mercurial-scm.org for more information)
1627 1627
1628 1628 Copyright (C) 2005-* Matt Mackall and others (glob)
1629 1629 This is free software; see the source for copying conditions. There is NO
1630 1630 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1631 1631
1632 1632 $ cat > minversion2.py << EOF
1633 1633 > from mercurial import util
1634 1634 > util.version = lambda: b'3.6'
1635 1635 > minimumhgversion = b'3.7'
1636 1636 > EOF
1637 1637 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1638 1638 (third party extension minversion requires version 3.7 or newer of Mercurial (current: 3.6); disabling)
1639 1639
1640 1640 Can load version that is only off by point release
1641 1641
1642 1642 $ cat > minversion2.py << EOF
1643 1643 > from mercurial import util
1644 1644 > util.version = lambda: b'3.6.1'
1645 1645 > minimumhgversion = b'3.6'
1646 1646 > EOF
1647 1647 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1648 1648 [1]
1649 1649
1650 1650 Can load minimum version identical to current
1651 1651
1652 1652 $ cat > minversion3.py << EOF
1653 1653 > from mercurial import util
1654 1654 > util.version = lambda: b'3.5'
1655 1655 > minimumhgversion = b'3.5'
1656 1656 > EOF
1657 1657 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1658 1658 [1]
1659 1659
1660 1660 Restore HGRCPATH
1661 1661
1662 1662 $ HGRCPATH=$ORGHGRCPATH
1663 1663 $ export HGRCPATH
1664 1664
1665 1665 Commands handling multiple repositories at a time should invoke only
1666 1666 "reposetup()" of extensions enabling in the target repository.
1667 1667
1668 1668 $ mkdir reposetup-test
1669 1669 $ cd reposetup-test
1670 1670
1671 1671 $ cat > $TESTTMP/reposetuptest.py <<EOF
1672 1672 > from mercurial import extensions
1673 1673 > def reposetup(ui, repo):
1674 1674 > ui.write(b'reposetup() for %s\n' % (repo.root))
1675 1675 > ui.flush()
1676 1676 > EOF
1677 1677 $ hg init src
1678 1678 $ echo a > src/a
1679 1679 $ hg -R src commit -Am '#0 at src/a'
1680 1680 adding a
1681 1681 $ echo '[extensions]' >> src/.hg/hgrc
1682 1682 $ echo '# enable extension locally' >> src/.hg/hgrc
1683 1683 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1684 1684 $ hg -R src status
1685 1685 reposetup() for $TESTTMP/reposetup-test/src
1686 1686 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1687 1687
1688 1688 #if no-extraextensions
1689 1689 $ hg --cwd src debugextensions
1690 1690 reposetup() for $TESTTMP/reposetup-test/src
1691 1691 dodo (untested!)
1692 1692 dudu (untested!)
1693 1693 mq
1694 1694 reposetuptest (untested!)
1695 1695 strip
1696 1696 #endif
1697 1697
1698 1698 $ hg clone -U src clone-dst1
1699 1699 reposetup() for $TESTTMP/reposetup-test/src
1700 1700 $ hg init push-dst1
1701 1701 $ hg -q -R src push push-dst1
1702 1702 reposetup() for $TESTTMP/reposetup-test/src
1703 1703 $ hg init pull-src1
1704 1704 $ hg -q -R pull-src1 pull src
1705 1705 reposetup() for $TESTTMP/reposetup-test/src
1706 1706
1707 1707 $ cat <<EOF >> $HGRCPATH
1708 1708 > [extensions]
1709 1709 > # disable extension globally and explicitly
1710 1710 > reposetuptest = !
1711 1711 > EOF
1712 1712 $ hg clone -U src clone-dst2
1713 1713 reposetup() for $TESTTMP/reposetup-test/src
1714 1714 $ hg init push-dst2
1715 1715 $ hg -q -R src push push-dst2
1716 1716 reposetup() for $TESTTMP/reposetup-test/src
1717 1717 $ hg init pull-src2
1718 1718 $ hg -q -R pull-src2 pull src
1719 1719 reposetup() for $TESTTMP/reposetup-test/src
1720 1720
1721 1721 $ cat <<EOF >> $HGRCPATH
1722 1722 > [extensions]
1723 1723 > # enable extension globally
1724 1724 > reposetuptest = $TESTTMP/reposetuptest.py
1725 1725 > EOF
1726 1726 $ hg clone -U src clone-dst3
1727 1727 reposetup() for $TESTTMP/reposetup-test/src
1728 1728 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1729 1729 $ hg init push-dst3
1730 1730 reposetup() for $TESTTMP/reposetup-test/push-dst3
1731 1731 $ hg -q -R src push push-dst3
1732 1732 reposetup() for $TESTTMP/reposetup-test/src
1733 1733 reposetup() for $TESTTMP/reposetup-test/push-dst3
1734 1734 $ hg init pull-src3
1735 1735 reposetup() for $TESTTMP/reposetup-test/pull-src3
1736 1736 $ hg -q -R pull-src3 pull src
1737 1737 reposetup() for $TESTTMP/reposetup-test/pull-src3
1738 1738 reposetup() for $TESTTMP/reposetup-test/src
1739 1739
1740 1740 $ echo '[extensions]' >> src/.hg/hgrc
1741 1741 $ echo '# disable extension locally' >> src/.hg/hgrc
1742 1742 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1743 1743 $ hg clone -U src clone-dst4
1744 1744 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1745 1745 $ hg init push-dst4
1746 1746 reposetup() for $TESTTMP/reposetup-test/push-dst4
1747 1747 $ hg -q -R src push push-dst4
1748 1748 reposetup() for $TESTTMP/reposetup-test/push-dst4
1749 1749 $ hg init pull-src4
1750 1750 reposetup() for $TESTTMP/reposetup-test/pull-src4
1751 1751 $ hg -q -R pull-src4 pull src
1752 1752 reposetup() for $TESTTMP/reposetup-test/pull-src4
1753 1753
1754 1754 disabling in command line overlays with all configuration
1755 1755 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1756 1756 $ hg --config extensions.reposetuptest=! init push-dst5
1757 1757 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1758 1758 $ hg --config extensions.reposetuptest=! init pull-src5
1759 1759 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1760 1760
1761 1761 $ cat <<EOF >> $HGRCPATH
1762 1762 > [extensions]
1763 1763 > # disable extension globally and explicitly
1764 1764 > reposetuptest = !
1765 1765 > EOF
1766 1766 $ hg init parent
1767 1767 $ hg init parent/sub1
1768 1768 $ echo 1 > parent/sub1/1
1769 1769 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1770 1770 adding 1
1771 1771 $ hg init parent/sub2
1772 1772 $ hg init parent/sub2/sub21
1773 1773 $ echo 21 > parent/sub2/sub21/21
1774 1774 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1775 1775 adding 21
1776 1776 $ cat > parent/sub2/.hgsub <<EOF
1777 1777 > sub21 = sub21
1778 1778 > EOF
1779 1779 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1780 1780 adding .hgsub
1781 1781 $ hg init parent/sub3
1782 1782 $ echo 3 > parent/sub3/3
1783 1783 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1784 1784 adding 3
1785 1785 $ cat > parent/.hgsub <<EOF
1786 1786 > sub1 = sub1
1787 1787 > sub2 = sub2
1788 1788 > sub3 = sub3
1789 1789 > EOF
1790 1790 $ hg -R parent commit -Am '#0 at parent'
1791 1791 adding .hgsub
1792 1792 $ echo '[extensions]' >> parent/.hg/hgrc
1793 1793 $ echo '# enable extension locally' >> parent/.hg/hgrc
1794 1794 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1795 1795 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1796 1796 $ hg -R parent status -S -A
1797 1797 reposetup() for $TESTTMP/reposetup-test/parent
1798 1798 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1799 1799 C .hgsub
1800 1800 C .hgsubstate
1801 1801 C sub1/1
1802 1802 C sub2/.hgsub
1803 1803 C sub2/.hgsubstate
1804 1804 C sub2/sub21/21
1805 1805 C sub3/3
1806 1806
1807 1807 $ cd ..
1808 1808
1809 1809 Prohibit registration of commands that don't use @command (issue5137)
1810 1810
1811 1811 $ hg init deprecated
1812 1812 $ cd deprecated
1813 1813
1814 1814 $ cat <<EOF > deprecatedcmd.py
1815 1815 > def deprecatedcmd(repo, ui):
1816 1816 > pass
1817 1817 > cmdtable = {
1818 1818 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1819 1819 > }
1820 1820 > EOF
1821 1821 $ cat <<EOF > .hg/hgrc
1822 1822 > [extensions]
1823 1823 > deprecatedcmd = `pwd`/deprecatedcmd.py
1824 1824 > mq = !
1825 1825 > hgext.mq = !
1826 1826 > hgext/mq = !
1827 1827 > EOF
1828 1828
1829 1829 $ hg deprecatedcmd > /dev/null
1830 1830 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1831 1831 *** (use @command decorator to register 'deprecatedcmd')
1832 1832 hg: unknown command 'deprecatedcmd'
1833 1833 (use 'hg help' for a list of commands)
1834 1834 [255]
1835 1835
1836 1836 the extension shouldn't be loaded at all so the mq works:
1837 1837
1838 1838 $ hg qseries --config extensions.mq= > /dev/null
1839 1839 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1840 1840 *** (use @command decorator to register 'deprecatedcmd')
1841 1841
1842 1842 $ cd ..
1843 1843
1844 1844 Test synopsis and docstring extending
1845 1845
1846 1846 $ hg init exthelp
1847 1847 $ cat > exthelp.py <<EOF
1848 1848 > from mercurial import commands, extensions
1849 1849 > def exbookmarks(orig, *args, **opts):
1850 1850 > return orig(*args, **opts)
1851 1851 > def uisetup(ui):
1852 1852 > synopsis = b' GREPME [--foo] [-x]'
1853 1853 > docstring = '''
1854 1854 > GREPME make sure that this is in the help!
1855 1855 > '''
1856 1856 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1857 1857 > synopsis, docstring)
1858 1858 > EOF
1859 1859 $ abspath=`pwd`/exthelp.py
1860 1860 $ echo '[extensions]' >> $HGRCPATH
1861 1861 $ echo "exthelp = $abspath" >> $HGRCPATH
1862 1862 $ cd exthelp
1863 1863 $ hg help bookmarks | grep GREPME
1864 1864 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1865 1865 GREPME make sure that this is in the help!
1866 1866 $ cd ..
1867 1867
1868 1868 Prohibit the use of unicode strings as the default value of options
1869 1869
1870 1870 $ hg init $TESTTMP/opt-unicode-default
1871 1871
1872 1872 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1873 1873 > from __future__ import print_function
1874 1874 > from mercurial import registrar
1875 1875 > cmdtable = {}
1876 1876 > command = registrar.command(cmdtable)
1877 1877 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1878 1878 > def ext(*args, **opts):
1879 1879 > print(opts[b'opt'], flush=True)
1880 1880 > EOF
1881 1881 $ "$PYTHON" $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1882 1882 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1883 1883 > [extensions]
1884 1884 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1885 1885 > EOF
1886 1886 $ hg -R $TESTTMP/opt-unicode-default dummy
1887 1887 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1888 1888 *** (use b'' to make it byte string)
1889 1889 hg: unknown command 'dummy'
1890 1890 (did you mean summary?)
1891 1891 [255]
General Comments 0
You need to be logged in to leave comments. Login now