##// END OF EJS Templates
dispatch: allow testedwith to be bytes or str
Augie Fackler -
r31179:49ad6bf6 default
parent child Browse files
Show More
@@ -1,910 +1,912 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import atexit
11 11 import difflib
12 12 import errno
13 13 import getopt
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import time
20 20 import traceback
21 21
22 22
23 23 from .i18n import _
24 24
25 25 from . import (
26 26 cmdutil,
27 27 color,
28 28 commands,
29 29 debugcommands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 fileset,
36 36 help,
37 37 hg,
38 38 hook,
39 39 profiling,
40 40 pycompat,
41 41 revset,
42 42 scmutil,
43 43 templatefilters,
44 44 templatekw,
45 45 templater,
46 46 ui as uimod,
47 47 util,
48 48 )
49 49
50 50 class request(object):
51 51 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
52 52 ferr=None):
53 53 self.args = args
54 54 self.ui = ui
55 55 self.repo = repo
56 56
57 57 # input/output/error streams
58 58 self.fin = fin
59 59 self.fout = fout
60 60 self.ferr = ferr
61 61
62 62 def run():
63 63 "run the command in sys.argv"
64 64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
65 65
66 66 def _getsimilar(symbols, value):
67 67 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
68 68 # The cutoff for similarity here is pretty arbitrary. It should
69 69 # probably be investigated and tweaked.
70 70 return [s for s in symbols if sim(s) > 0.6]
71 71
72 72 def _reportsimilar(write, similar):
73 73 if len(similar) == 1:
74 74 write(_("(did you mean %s?)\n") % similar[0])
75 75 elif similar:
76 76 ss = ", ".join(sorted(similar))
77 77 write(_("(did you mean one of %s?)\n") % ss)
78 78
79 79 def _formatparse(write, inst):
80 80 similar = []
81 81 if isinstance(inst, error.UnknownIdentifier):
82 82 # make sure to check fileset first, as revset can invoke fileset
83 83 similar = _getsimilar(inst.symbols, inst.function)
84 84 if len(inst.args) > 1:
85 85 write(_("hg: parse error at %s: %s\n") %
86 86 (inst.args[1], inst.args[0]))
87 87 if (inst.args[0][0] == ' '):
88 88 write(_("unexpected leading whitespace\n"))
89 89 else:
90 90 write(_("hg: parse error: %s\n") % inst.args[0])
91 91 _reportsimilar(write, similar)
92 92 if inst.hint:
93 93 write(_("(%s)\n") % inst.hint)
94 94
95 95 def dispatch(req):
96 96 "run the command specified in req.args"
97 97 if req.ferr:
98 98 ferr = req.ferr
99 99 elif req.ui:
100 100 ferr = req.ui.ferr
101 101 else:
102 102 ferr = util.stderr
103 103
104 104 try:
105 105 if not req.ui:
106 106 req.ui = uimod.ui.load()
107 107 if '--traceback' in req.args:
108 108 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
109 109
110 110 # set ui streams from the request
111 111 if req.fin:
112 112 req.ui.fin = req.fin
113 113 if req.fout:
114 114 req.ui.fout = req.fout
115 115 if req.ferr:
116 116 req.ui.ferr = req.ferr
117 117 except error.Abort as inst:
118 118 ferr.write(_("abort: %s\n") % inst)
119 119 if inst.hint:
120 120 ferr.write(_("(%s)\n") % inst.hint)
121 121 return -1
122 122 except error.ParseError as inst:
123 123 _formatparse(ferr.write, inst)
124 124 return -1
125 125
126 126 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
127 127 starttime = util.timer()
128 128 ret = None
129 129 try:
130 130 ret = _runcatch(req)
131 131 except KeyboardInterrupt:
132 132 try:
133 133 req.ui.warn(_("interrupted!\n"))
134 134 except IOError as inst:
135 135 if inst.errno != errno.EPIPE:
136 136 raise
137 137 ret = -1
138 138 finally:
139 139 duration = util.timer() - starttime
140 140 req.ui.flush()
141 141 if req.ui.logblockedtimes:
142 142 req.ui._blockedtimes['command_duration'] = duration * 1000
143 143 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
144 144 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
145 145 msg, ret or 0, duration)
146 146 return ret
147 147
148 148 def _runcatch(req):
149 149 def catchterm(*args):
150 150 raise error.SignalInterrupt
151 151
152 152 ui = req.ui
153 153 try:
154 154 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
155 155 num = getattr(signal, name, None)
156 156 if num:
157 157 signal.signal(num, catchterm)
158 158 except ValueError:
159 159 pass # happens if called in a thread
160 160
161 161 def _runcatchfunc():
162 162 try:
163 163 debugger = 'pdb'
164 164 debugtrace = {
165 165 'pdb' : pdb.set_trace
166 166 }
167 167 debugmortem = {
168 168 'pdb' : pdb.post_mortem
169 169 }
170 170
171 171 # read --config before doing anything else
172 172 # (e.g. to change trust settings for reading .hg/hgrc)
173 173 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
174 174
175 175 if req.repo:
176 176 # copy configs that were passed on the cmdline (--config) to
177 177 # the repo ui
178 178 for sec, name, val in cfgs:
179 179 req.repo.ui.setconfig(sec, name, val, source='--config')
180 180
181 181 # developer config: ui.debugger
182 182 debugger = ui.config("ui", "debugger")
183 183 debugmod = pdb
184 184 if not debugger or ui.plain():
185 185 # if we are in HGPLAIN mode, then disable custom debugging
186 186 debugger = 'pdb'
187 187 elif '--debugger' in req.args:
188 188 # This import can be slow for fancy debuggers, so only
189 189 # do it when absolutely necessary, i.e. when actual
190 190 # debugging has been requested
191 191 with demandimport.deactivated():
192 192 try:
193 193 debugmod = __import__(debugger)
194 194 except ImportError:
195 195 pass # Leave debugmod = pdb
196 196
197 197 debugtrace[debugger] = debugmod.set_trace
198 198 debugmortem[debugger] = debugmod.post_mortem
199 199
200 200 # enter the debugger before command execution
201 201 if '--debugger' in req.args:
202 202 ui.warn(_("entering debugger - "
203 203 "type c to continue starting hg or h for help\n"))
204 204
205 205 if (debugger != 'pdb' and
206 206 debugtrace[debugger] == debugtrace['pdb']):
207 207 ui.warn(_("%s debugger specified "
208 208 "but its module was not found\n") % debugger)
209 209 with demandimport.deactivated():
210 210 debugtrace[debugger]()
211 211 try:
212 212 return _dispatch(req)
213 213 finally:
214 214 ui.flush()
215 215 except: # re-raises
216 216 # enter the debugger when we hit an exception
217 217 if '--debugger' in req.args:
218 218 traceback.print_exc()
219 219 debugmortem[debugger](sys.exc_info()[2])
220 220 ui.traceback()
221 221 raise
222 222
223 223 return callcatch(ui, _runcatchfunc)
224 224
225 225 def callcatch(ui, func):
226 226 """like scmutil.callcatch but handles more high-level exceptions about
227 227 config parsing and commands. besides, use handlecommandexception to handle
228 228 uncaught exceptions.
229 229 """
230 230 try:
231 231 return scmutil.callcatch(ui, func)
232 232 except error.AmbiguousCommand as inst:
233 233 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
234 234 (inst.args[0], " ".join(inst.args[1])))
235 235 except error.CommandError as inst:
236 236 if inst.args[0]:
237 237 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
238 238 commands.help_(ui, inst.args[0], full=False, command=True)
239 239 else:
240 240 ui.warn(_("hg: %s\n") % inst.args[1])
241 241 commands.help_(ui, 'shortlist')
242 242 except error.ParseError as inst:
243 243 _formatparse(ui.warn, inst)
244 244 return -1
245 245 except error.UnknownCommand as inst:
246 246 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
247 247 try:
248 248 # check if the command is in a disabled extension
249 249 # (but don't check for extensions themselves)
250 250 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
251 251 ui.warn(nocmdmsg)
252 252 ui.write(formatted)
253 253 except (error.UnknownCommand, error.Abort):
254 254 suggested = False
255 255 if len(inst.args) == 2:
256 256 sim = _getsimilar(inst.args[1], inst.args[0])
257 257 if sim:
258 258 ui.warn(nocmdmsg)
259 259 _reportsimilar(ui.warn, sim)
260 260 suggested = True
261 261 if not suggested:
262 262 ui.pager('help')
263 263 ui.warn(nocmdmsg)
264 264 commands.help_(ui, 'shortlist')
265 265 except IOError:
266 266 raise
267 267 except KeyboardInterrupt:
268 268 raise
269 269 except: # probably re-raises
270 270 if not handlecommandexception(ui):
271 271 raise
272 272
273 273 return -1
274 274
275 275 def aliasargs(fn, givenargs):
276 276 args = getattr(fn, 'args', [])
277 277 if args:
278 278 cmd = ' '.join(map(util.shellquote, args))
279 279
280 280 nums = []
281 281 def replacer(m):
282 282 num = int(m.group(1)) - 1
283 283 nums.append(num)
284 284 if num < len(givenargs):
285 285 return givenargs[num]
286 286 raise error.Abort(_('too few arguments for command alias'))
287 287 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
288 288 givenargs = [x for i, x in enumerate(givenargs)
289 289 if i not in nums]
290 290 args = pycompat.shlexsplit(cmd)
291 291 return args + givenargs
292 292
293 293 def aliasinterpolate(name, args, cmd):
294 294 '''interpolate args into cmd for shell aliases
295 295
296 296 This also handles $0, $@ and "$@".
297 297 '''
298 298 # util.interpolate can't deal with "$@" (with quotes) because it's only
299 299 # built to match prefix + patterns.
300 300 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
301 301 replacemap['$0'] = name
302 302 replacemap['$$'] = '$'
303 303 replacemap['$@'] = ' '.join(args)
304 304 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
305 305 # parameters, separated out into words. Emulate the same behavior here by
306 306 # quoting the arguments individually. POSIX shells will then typically
307 307 # tokenize each argument into exactly one word.
308 308 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
309 309 # escape '\$' for regex
310 310 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
311 311 r = re.compile(regex)
312 312 return r.sub(lambda x: replacemap[x.group()], cmd)
313 313
314 314 class cmdalias(object):
315 315 def __init__(self, name, definition, cmdtable, source):
316 316 self.name = self.cmd = name
317 317 self.cmdname = ''
318 318 self.definition = definition
319 319 self.fn = None
320 320 self.givenargs = []
321 321 self.opts = []
322 322 self.help = ''
323 323 self.badalias = None
324 324 self.unknowncmd = False
325 325 self.source = source
326 326
327 327 try:
328 328 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
329 329 for alias, e in cmdtable.iteritems():
330 330 if e is entry:
331 331 self.cmd = alias
332 332 break
333 333 self.shadows = True
334 334 except error.UnknownCommand:
335 335 self.shadows = False
336 336
337 337 if not self.definition:
338 338 self.badalias = _("no definition for alias '%s'") % self.name
339 339 return
340 340
341 341 if self.definition.startswith('!'):
342 342 self.shell = True
343 343 def fn(ui, *args):
344 344 env = {'HG_ARGS': ' '.join((self.name,) + args)}
345 345 def _checkvar(m):
346 346 if m.groups()[0] == '$':
347 347 return m.group()
348 348 elif int(m.groups()[0]) <= len(args):
349 349 return m.group()
350 350 else:
351 351 ui.debug("No argument found for substitution "
352 352 "of %i variable in alias '%s' definition."
353 353 % (int(m.groups()[0]), self.name))
354 354 return ''
355 355 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
356 356 cmd = aliasinterpolate(self.name, args, cmd)
357 357 return ui.system(cmd, environ=env)
358 358 self.fn = fn
359 359 return
360 360
361 361 try:
362 362 args = pycompat.shlexsplit(self.definition)
363 363 except ValueError as inst:
364 364 self.badalias = (_("error in definition for alias '%s': %s")
365 365 % (self.name, inst))
366 366 return
367 367 self.cmdname = cmd = args.pop(0)
368 368 self.givenargs = args
369 369
370 370 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
371 371 if _earlygetopt([invalidarg], args):
372 372 self.badalias = (_("error in definition for alias '%s': %s may "
373 373 "only be given on the command line")
374 374 % (self.name, invalidarg))
375 375 return
376 376
377 377 try:
378 378 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
379 379 if len(tableentry) > 2:
380 380 self.fn, self.opts, self.help = tableentry
381 381 else:
382 382 self.fn, self.opts = tableentry
383 383
384 384 if self.help.startswith("hg " + cmd):
385 385 # drop prefix in old-style help lines so hg shows the alias
386 386 self.help = self.help[4 + len(cmd):]
387 387 self.__doc__ = self.fn.__doc__
388 388
389 389 except error.UnknownCommand:
390 390 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
391 391 % (self.name, cmd))
392 392 self.unknowncmd = True
393 393 except error.AmbiguousCommand:
394 394 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
395 395 % (self.name, cmd))
396 396
397 397 @property
398 398 def args(self):
399 399 args = map(util.expandpath, self.givenargs)
400 400 return aliasargs(self.fn, args)
401 401
402 402 def __getattr__(self, name):
403 403 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
404 404 if name not in adefaults:
405 405 raise AttributeError(name)
406 406 if self.badalias or util.safehasattr(self, 'shell'):
407 407 return adefaults[name]
408 408 return getattr(self.fn, name)
409 409
410 410 def __call__(self, ui, *args, **opts):
411 411 if self.badalias:
412 412 hint = None
413 413 if self.unknowncmd:
414 414 try:
415 415 # check if the command is in a disabled extension
416 416 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
417 417 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
418 418 except error.UnknownCommand:
419 419 pass
420 420 raise error.Abort(self.badalias, hint=hint)
421 421 if self.shadows:
422 422 ui.debug("alias '%s' shadows command '%s'\n" %
423 423 (self.name, self.cmdname))
424 424
425 425 ui.log('commandalias', "alias '%s' expands to '%s'\n",
426 426 self.name, self.definition)
427 427 if util.safehasattr(self, 'shell'):
428 428 return self.fn(ui, *args, **opts)
429 429 else:
430 430 try:
431 431 return util.checksignature(self.fn)(ui, *args, **opts)
432 432 except error.SignatureError:
433 433 args = ' '.join([self.cmdname] + self.args)
434 434 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
435 435 raise
436 436
437 437 def addaliases(ui, cmdtable):
438 438 # aliases are processed after extensions have been loaded, so they
439 439 # may use extension commands. Aliases can also use other alias definitions,
440 440 # but only if they have been defined prior to the current definition.
441 441 for alias, definition in ui.configitems('alias'):
442 442 source = ui.configsource('alias', alias)
443 443 aliasdef = cmdalias(alias, definition, cmdtable, source)
444 444
445 445 try:
446 446 olddef = cmdtable[aliasdef.cmd][0]
447 447 if olddef.definition == aliasdef.definition:
448 448 continue
449 449 except (KeyError, AttributeError):
450 450 # definition might not exist or it might not be a cmdalias
451 451 pass
452 452
453 453 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
454 454
455 455 def _parse(ui, args):
456 456 options = {}
457 457 cmdoptions = {}
458 458
459 459 try:
460 460 args = fancyopts.fancyopts(args, commands.globalopts, options)
461 461 except getopt.GetoptError as inst:
462 462 raise error.CommandError(None, inst)
463 463
464 464 if args:
465 465 cmd, args = args[0], args[1:]
466 466 aliases, entry = cmdutil.findcmd(cmd, commands.table,
467 467 ui.configbool("ui", "strict"))
468 468 cmd = aliases[0]
469 469 args = aliasargs(entry[0], args)
470 470 defaults = ui.config("defaults", cmd)
471 471 if defaults:
472 472 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
473 473 c = list(entry[1])
474 474 else:
475 475 cmd = None
476 476 c = []
477 477
478 478 # combine global options into local
479 479 for o in commands.globalopts:
480 480 c.append((o[0], o[1], options[o[1]], o[3]))
481 481
482 482 try:
483 483 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
484 484 except getopt.GetoptError as inst:
485 485 raise error.CommandError(cmd, inst)
486 486
487 487 # separate global options back out
488 488 for o in commands.globalopts:
489 489 n = o[1]
490 490 options[n] = cmdoptions[n]
491 491 del cmdoptions[n]
492 492
493 493 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
494 494
495 495 def _parseconfig(ui, config):
496 496 """parse the --config options from the command line"""
497 497 configs = []
498 498
499 499 for cfg in config:
500 500 try:
501 501 name, value = [cfgelem.strip()
502 502 for cfgelem in cfg.split('=', 1)]
503 503 section, name = name.split('.', 1)
504 504 if not section or not name:
505 505 raise IndexError
506 506 ui.setconfig(section, name, value, '--config')
507 507 configs.append((section, name, value))
508 508 except (IndexError, ValueError):
509 509 raise error.Abort(_('malformed --config option: %r '
510 510 '(use --config section.name=value)') % cfg)
511 511
512 512 return configs
513 513
514 514 def _earlygetopt(aliases, args):
515 515 """Return list of values for an option (or aliases).
516 516
517 517 The values are listed in the order they appear in args.
518 518 The options and values are removed from args.
519 519
520 520 >>> args = ['x', '--cwd', 'foo', 'y']
521 521 >>> _earlygetopt(['--cwd'], args), args
522 522 (['foo'], ['x', 'y'])
523 523
524 524 >>> args = ['x', '--cwd=bar', 'y']
525 525 >>> _earlygetopt(['--cwd'], args), args
526 526 (['bar'], ['x', 'y'])
527 527
528 528 >>> args = ['x', '-R', 'foo', 'y']
529 529 >>> _earlygetopt(['-R'], args), args
530 530 (['foo'], ['x', 'y'])
531 531
532 532 >>> args = ['x', '-Rbar', 'y']
533 533 >>> _earlygetopt(['-R'], args), args
534 534 (['bar'], ['x', 'y'])
535 535 """
536 536 try:
537 537 argcount = args.index("--")
538 538 except ValueError:
539 539 argcount = len(args)
540 540 shortopts = [opt for opt in aliases if len(opt) == 2]
541 541 values = []
542 542 pos = 0
543 543 while pos < argcount:
544 544 fullarg = arg = args[pos]
545 545 equals = arg.find('=')
546 546 if equals > -1:
547 547 arg = arg[:equals]
548 548 if arg in aliases:
549 549 del args[pos]
550 550 if equals > -1:
551 551 values.append(fullarg[equals + 1:])
552 552 argcount -= 1
553 553 else:
554 554 if pos + 1 >= argcount:
555 555 # ignore and let getopt report an error if there is no value
556 556 break
557 557 values.append(args.pop(pos))
558 558 argcount -= 2
559 559 elif arg[:2] in shortopts:
560 560 # short option can have no following space, e.g. hg log -Rfoo
561 561 values.append(args.pop(pos)[2:])
562 562 argcount -= 1
563 563 else:
564 564 pos += 1
565 565 return values
566 566
567 567 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
568 568 # run pre-hook, and abort if it fails
569 569 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
570 570 pats=cmdpats, opts=cmdoptions)
571 571 try:
572 572 ret = _runcommand(ui, options, cmd, d)
573 573 # run post-hook, passing command result
574 574 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
575 575 result=ret, pats=cmdpats, opts=cmdoptions)
576 576 except Exception:
577 577 # run failure hook and re-raise
578 578 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
579 579 pats=cmdpats, opts=cmdoptions)
580 580 raise
581 581 return ret
582 582
583 583 def _getlocal(ui, rpath, wd=None):
584 584 """Return (path, local ui object) for the given target path.
585 585
586 586 Takes paths in [cwd]/.hg/hgrc into account."
587 587 """
588 588 if wd is None:
589 589 try:
590 590 wd = pycompat.getcwd()
591 591 except OSError as e:
592 592 raise error.Abort(_("error getting current working directory: %s") %
593 593 e.strerror)
594 594 path = cmdutil.findrepo(wd) or ""
595 595 if not path:
596 596 lui = ui
597 597 else:
598 598 lui = ui.copy()
599 599 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
600 600
601 601 if rpath and rpath[-1]:
602 602 path = lui.expandpath(rpath[-1])
603 603 lui = ui.copy()
604 604 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
605 605
606 606 return path, lui
607 607
608 608 def _checkshellalias(lui, ui, args):
609 609 """Return the function to run the shell alias, if it is required"""
610 610 options = {}
611 611
612 612 try:
613 613 args = fancyopts.fancyopts(args, commands.globalopts, options)
614 614 except getopt.GetoptError:
615 615 return
616 616
617 617 if not args:
618 618 return
619 619
620 620 cmdtable = commands.table
621 621
622 622 cmd = args[0]
623 623 try:
624 624 strict = ui.configbool("ui", "strict")
625 625 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
626 626 except (error.AmbiguousCommand, error.UnknownCommand):
627 627 return
628 628
629 629 cmd = aliases[0]
630 630 fn = entry[0]
631 631
632 632 if cmd and util.safehasattr(fn, 'shell'):
633 633 d = lambda: fn(ui, *args[1:])
634 634 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
635 635 [], {})
636 636
637 637 _loaded = set()
638 638
639 639 # list of (objname, loadermod, loadername) tuple:
640 640 # - objname is the name of an object in extension module, from which
641 641 # extra information is loaded
642 642 # - loadermod is the module where loader is placed
643 643 # - loadername is the name of the function, which takes (ui, extensionname,
644 644 # extraobj) arguments
645 645 extraloaders = [
646 646 ('cmdtable', commands, 'loadcmdtable'),
647 647 ('colortable', color, 'loadcolortable'),
648 648 ('filesetpredicate', fileset, 'loadpredicate'),
649 649 ('revsetpredicate', revset, 'loadpredicate'),
650 650 ('templatefilter', templatefilters, 'loadfilter'),
651 651 ('templatefunc', templater, 'loadfunction'),
652 652 ('templatekeyword', templatekw, 'loadkeyword'),
653 653 ]
654 654
655 655 def _dispatch(req):
656 656 args = req.args
657 657 ui = req.ui
658 658
659 659 # check for cwd
660 660 cwd = _earlygetopt(['--cwd'], args)
661 661 if cwd:
662 662 os.chdir(cwd[-1])
663 663
664 664 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
665 665 path, lui = _getlocal(ui, rpath)
666 666
667 667 # Side-effect of accessing is debugcommands module is guaranteed to be
668 668 # imported and commands.table is populated.
669 669 debugcommands.command
670 670
671 671 uis = set([ui, lui])
672 672
673 673 if req.repo:
674 674 uis.add(req.repo.ui)
675 675
676 676 if '--profile' in args:
677 677 for ui_ in uis:
678 678 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
679 679
680 680 with profiling.maybeprofile(lui):
681 681 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
682 682 # reposetup. Programs like TortoiseHg will call _dispatch several
683 683 # times so we keep track of configured extensions in _loaded.
684 684 extensions.loadall(lui)
685 685 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
686 686 # Propagate any changes to lui.__class__ by extensions
687 687 ui.__class__ = lui.__class__
688 688
689 689 # (uisetup and extsetup are handled in extensions.loadall)
690 690
691 691 for name, module in exts:
692 692 for objname, loadermod, loadername in extraloaders:
693 693 extraobj = getattr(module, objname, None)
694 694 if extraobj is not None:
695 695 getattr(loadermod, loadername)(ui, name, extraobj)
696 696 _loaded.add(name)
697 697
698 698 # (reposetup is handled in hg.repository)
699 699
700 700 addaliases(lui, commands.table)
701 701
702 702 # All aliases and commands are completely defined, now.
703 703 # Check abbreviation/ambiguity of shell alias.
704 704 shellaliasfn = _checkshellalias(lui, ui, args)
705 705 if shellaliasfn:
706 706 return shellaliasfn()
707 707
708 708 # check for fallback encoding
709 709 fallback = lui.config('ui', 'fallbackencoding')
710 710 if fallback:
711 711 encoding.fallbackencoding = fallback
712 712
713 713 fullargs = args
714 714 cmd, func, args, options, cmdoptions = _parse(lui, args)
715 715
716 716 if options["config"]:
717 717 raise error.Abort(_("option --config may not be abbreviated!"))
718 718 if options["cwd"]:
719 719 raise error.Abort(_("option --cwd may not be abbreviated!"))
720 720 if options["repository"]:
721 721 raise error.Abort(_(
722 722 "option -R has to be separated from other options (e.g. not "
723 723 "-qR) and --repository may only be abbreviated as --repo!"))
724 724
725 725 if options["encoding"]:
726 726 encoding.encoding = options["encoding"]
727 727 if options["encodingmode"]:
728 728 encoding.encodingmode = options["encodingmode"]
729 729 if options["time"]:
730 730 def get_times():
731 731 t = os.times()
732 732 if t[4] == 0.0:
733 733 # Windows leaves this as zero, so use time.clock()
734 734 t = (t[0], t[1], t[2], t[3], time.clock())
735 735 return t
736 736 s = get_times()
737 737 def print_time():
738 738 t = get_times()
739 739 ui.warn(
740 740 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
741 741 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
742 742 atexit.register(print_time)
743 743
744 744 if options['verbose'] or options['debug'] or options['quiet']:
745 745 for opt in ('verbose', 'debug', 'quiet'):
746 746 val = str(bool(options[opt]))
747 747 for ui_ in uis:
748 748 ui_.setconfig('ui', opt, val, '--' + opt)
749 749
750 750 if options['traceback']:
751 751 for ui_ in uis:
752 752 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
753 753
754 754 if options['noninteractive']:
755 755 for ui_ in uis:
756 756 ui_.setconfig('ui', 'interactive', 'off', '-y')
757 757
758 758 if util.parsebool(options['pager']):
759 759 ui.pager('internal-always-' + cmd)
760 760 elif options['pager'] != 'auto':
761 761 ui.disablepager()
762 762
763 763 if cmdoptions.get('insecure', False):
764 764 for ui_ in uis:
765 765 ui_.insecureconnections = True
766 766
767 767 # setup color handling
768 768 coloropt = options['color']
769 769 for ui_ in uis:
770 770 if coloropt:
771 771 ui_.setconfig('ui', 'color', coloropt, '--color')
772 772 color.setup(ui_)
773 773
774 774 if options['version']:
775 775 return commands.version_(ui)
776 776 if options['help']:
777 777 return commands.help_(ui, cmd, command=cmd is not None)
778 778 elif not cmd:
779 779 return commands.help_(ui, 'shortlist')
780 780
781 781 repo = None
782 782 cmdpats = args[:]
783 783 if not func.norepo:
784 784 # use the repo from the request only if we don't have -R
785 785 if not rpath and not cwd:
786 786 repo = req.repo
787 787
788 788 if repo:
789 789 # set the descriptors of the repo ui to those of ui
790 790 repo.ui.fin = ui.fin
791 791 repo.ui.fout = ui.fout
792 792 repo.ui.ferr = ui.ferr
793 793 else:
794 794 try:
795 795 repo = hg.repository(ui, path=path)
796 796 if not repo.local():
797 797 raise error.Abort(_("repository '%s' is not local")
798 798 % path)
799 799 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
800 800 'repo')
801 801 except error.RequirementError:
802 802 raise
803 803 except error.RepoError:
804 804 if rpath and rpath[-1]: # invalid -R path
805 805 raise
806 806 if not func.optionalrepo:
807 807 if func.inferrepo and args and not path:
808 808 # try to infer -R from command args
809 809 repos = map(cmdutil.findrepo, args)
810 810 guess = repos[0]
811 811 if guess and repos.count(guess) == len(repos):
812 812 req.args = ['--repository', guess] + fullargs
813 813 return _dispatch(req)
814 814 if not path:
815 815 raise error.RepoError(_("no repository found in"
816 816 " '%s' (.hg not found)")
817 817 % pycompat.getcwd())
818 818 raise
819 819 if repo:
820 820 ui = repo.ui
821 821 if options['hidden']:
822 822 repo = repo.unfiltered()
823 823 args.insert(0, repo)
824 824 elif rpath:
825 825 ui.warn(_("warning: --repository ignored\n"))
826 826
827 827 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
828 828 ui.log("command", '%s\n', msg)
829 829 strcmdopt = pycompat.strkwargs(cmdoptions)
830 830 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
831 831 try:
832 832 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
833 833 cmdpats, cmdoptions)
834 834 finally:
835 835 if repo and repo != req.repo:
836 836 repo.close()
837 837
838 838 def _runcommand(ui, options, cmd, cmdfunc):
839 839 """Run a command function, possibly with profiling enabled."""
840 840 try:
841 841 return cmdfunc()
842 842 except error.SignatureError:
843 843 raise error.CommandError(cmd, _('invalid arguments'))
844 844
845 845 def _exceptionwarning(ui):
846 846 """Produce a warning message for the current active exception"""
847 847
848 848 # For compatibility checking, we discard the portion of the hg
849 849 # version after the + on the assumption that if a "normal
850 850 # user" is running a build with a + in it the packager
851 851 # probably built from fairly close to a tag and anyone with a
852 852 # 'make local' copy of hg (where the version number can be out
853 853 # of date) will be clueful enough to notice the implausible
854 854 # version number and try updating.
855 855 ct = util.versiontuple(n=2)
856 856 worst = None, ct, ''
857 857 if ui.config('ui', 'supportcontact', None) is None:
858 858 for name, mod in extensions.extensions():
859 859 testedwith = getattr(mod, 'testedwith', '')
860 if pycompat.ispy3 and isinstance(testedwith, str):
861 testedwith = testedwith.encode(u'utf-8')
860 862 report = getattr(mod, 'buglink', _('the extension author.'))
861 863 if not testedwith.strip():
862 864 # We found an untested extension. It's likely the culprit.
863 865 worst = name, 'unknown', report
864 866 break
865 867
866 868 # Never blame on extensions bundled with Mercurial.
867 869 if extensions.ismoduleinternal(mod):
868 870 continue
869 871
870 872 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
871 873 if ct in tested:
872 874 continue
873 875
874 876 lower = [t for t in tested if t < ct]
875 877 nearest = max(lower or tested)
876 878 if worst[0] is None or nearest < worst[1]:
877 879 worst = name, nearest, report
878 880 if worst[0] is not None:
879 881 name, testedwith, report = worst
880 if not isinstance(testedwith, str):
882 if not isinstance(testedwith, (bytes, str)):
881 883 testedwith = '.'.join([str(c) for c in testedwith])
882 884 warning = (_('** Unknown exception encountered with '
883 885 'possibly-broken third-party extension %s\n'
884 886 '** which supports versions %s of Mercurial.\n'
885 887 '** Please disable %s and try your action again.\n'
886 888 '** If that fixes the bug please report it to %s\n')
887 889 % (name, testedwith, name, report))
888 890 else:
889 891 bugtracker = ui.config('ui', 'supportcontact', None)
890 892 if bugtracker is None:
891 893 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
892 894 warning = (_("** unknown exception encountered, "
893 895 "please report by visiting\n** ") + bugtracker + '\n')
894 896 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
895 897 (_("** Mercurial Distributed SCM (version %s)\n") %
896 898 util.version()) +
897 899 (_("** Extensions loaded: %s\n") %
898 900 ", ".join([x[0] for x in extensions.extensions()])))
899 901 return warning
900 902
901 903 def handlecommandexception(ui):
902 904 """Produce a warning message for broken commands
903 905
904 906 Called when handling an exception; the exception is reraised if
905 907 this function returns False, ignored otherwise.
906 908 """
907 909 warning = _exceptionwarning(ui)
908 910 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
909 911 ui.warn(warning)
910 912 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now