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