##// END OF EJS Templates
dispatch: start profiling earlier...
Bryan O'Sullivan -
r30934:6d642ecf default
parent child Browse files
Show More
@@ -1,888 +1,889 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 662 uis = set([ui, lui])
663 663
664 664 if req.repo:
665 665 uis.add(req.repo.ui)
666 666
667 667 if '--profile' in args:
668 668 for ui_ in uis:
669 669 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
670 670
671 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
672 # reposetup. Programs like TortoiseHg will call _dispatch several
673 # times so we keep track of configured extensions in _loaded.
674 extensions.loadall(lui)
675 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
676 # Propagate any changes to lui.__class__ by extensions
677 ui.__class__ = lui.__class__
671 with profiling.maybeprofile(lui):
672 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
673 # reposetup. Programs like TortoiseHg will call _dispatch several
674 # times so we keep track of configured extensions in _loaded.
675 extensions.loadall(lui)
676 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
677 # Propagate any changes to lui.__class__ by extensions
678 ui.__class__ = lui.__class__
678 679
679 # (uisetup and extsetup are handled in extensions.loadall)
680 # (uisetup and extsetup are handled in extensions.loadall)
680 681
681 for name, module in exts:
682 for objname, loadermod, loadername in extraloaders:
683 extraobj = getattr(module, objname, None)
684 if extraobj is not None:
685 getattr(loadermod, loadername)(ui, name, extraobj)
686 _loaded.add(name)
682 for name, module in exts:
683 for objname, loadermod, loadername in extraloaders:
684 extraobj = getattr(module, objname, None)
685 if extraobj is not None:
686 getattr(loadermod, loadername)(ui, name, extraobj)
687 _loaded.add(name)
687 688
688 # (reposetup is handled in hg.repository)
689 # (reposetup is handled in hg.repository)
689 690
690 addaliases(lui, commands.table)
691 addaliases(lui, commands.table)
691 692
692 # All aliases and commands are completely defined, now.
693 # Check abbreviation/ambiguity of shell alias.
694 shellaliasfn = _checkshellalias(lui, ui, args)
695 if shellaliasfn:
696 with profiling.maybeprofile(lui):
693 # All aliases and commands are completely defined, now.
694 # Check abbreviation/ambiguity of shell alias.
695 shellaliasfn = _checkshellalias(lui, ui, args)
696 if shellaliasfn:
697 697 return shellaliasfn()
698 698
699 # check for fallback encoding
700 fallback = lui.config('ui', 'fallbackencoding')
701 if fallback:
702 encoding.fallbackencoding = fallback
699 # check for fallback encoding
700 fallback = lui.config('ui', 'fallbackencoding')
701 if fallback:
702 encoding.fallbackencoding = fallback
703 703
704 fullargs = args
705 cmd, func, args, options, cmdoptions = _parse(lui, args)
704 fullargs = args
705 cmd, func, args, options, cmdoptions = _parse(lui, args)
706 706
707 if options["config"]:
708 raise error.Abort(_("option --config may not be abbreviated!"))
709 if options["cwd"]:
710 raise error.Abort(_("option --cwd may not be abbreviated!"))
711 if options["repository"]:
712 raise error.Abort(_(
713 "option -R has to be separated from other options (e.g. not -qR) "
714 "and --repository may only be abbreviated as --repo!"))
707 if options["config"]:
708 raise error.Abort(_("option --config may not be abbreviated!"))
709 if options["cwd"]:
710 raise error.Abort(_("option --cwd may not be abbreviated!"))
711 if options["repository"]:
712 raise error.Abort(_(
713 "option -R has to be separated from other options (e.g. not "
714 "-qR) and --repository may only be abbreviated as --repo!"))
715 715
716 if options["encoding"]:
717 encoding.encoding = options["encoding"]
718 if options["encodingmode"]:
719 encoding.encodingmode = options["encodingmode"]
720 if options["time"]:
721 def get_times():
722 t = os.times()
723 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
724 t = (t[0], t[1], t[2], t[3], time.clock())
725 return t
726 s = get_times()
727 def print_time():
728 t = get_times()
729 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
730 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
731 atexit.register(print_time)
716 if options["encoding"]:
717 encoding.encoding = options["encoding"]
718 if options["encodingmode"]:
719 encoding.encodingmode = options["encodingmode"]
720 if options["time"]:
721 def get_times():
722 t = os.times()
723 if t[4] == 0.0:
724 # Windows leaves this as zero, so use time.clock()
725 t = (t[0], t[1], t[2], t[3], time.clock())
726 return t
727 s = get_times()
728 def print_time():
729 t = get_times()
730 ui.warn(
731 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
732 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
733 atexit.register(print_time)
732 734
733 if options['verbose'] or options['debug'] or options['quiet']:
734 for opt in ('verbose', 'debug', 'quiet'):
735 val = str(bool(options[opt]))
735 if options['verbose'] or options['debug'] or options['quiet']:
736 for opt in ('verbose', 'debug', 'quiet'):
737 val = str(bool(options[opt]))
738 for ui_ in uis:
739 ui_.setconfig('ui', opt, val, '--' + opt)
740
741 if options['traceback']:
736 742 for ui_ in uis:
737 ui_.setconfig('ui', opt, val, '--' + opt)
743 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
738 744
739 if options['traceback']:
740 for ui_ in uis:
741 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
742
743 if options['noninteractive']:
744 for ui_ in uis:
745 ui_.setconfig('ui', 'interactive', 'off', '-y')
745 if options['noninteractive']:
746 for ui_ in uis:
747 ui_.setconfig('ui', 'interactive', 'off', '-y')
746 748
747 if cmdoptions.get('insecure', False):
748 for ui_ in uis:
749 ui_.insecureconnections = True
749 if cmdoptions.get('insecure', False):
750 for ui_ in uis:
751 ui_.insecureconnections = True
750 752
751 if options['version']:
752 return commands.version_(ui)
753 if options['help']:
754 return commands.help_(ui, cmd, command=cmd is not None)
755 elif not cmd:
756 return commands.help_(ui, 'shortlist')
753 if options['version']:
754 return commands.version_(ui)
755 if options['help']:
756 return commands.help_(ui, cmd, command=cmd is not None)
757 elif not cmd:
758 return commands.help_(ui, 'shortlist')
757 759
758 with profiling.maybeprofile(lui):
759 760 repo = None
760 761 cmdpats = args[:]
761 762 if not func.norepo:
762 763 # use the repo from the request only if we don't have -R
763 764 if not rpath and not cwd:
764 765 repo = req.repo
765 766
766 767 if repo:
767 768 # set the descriptors of the repo ui to those of ui
768 769 repo.ui.fin = ui.fin
769 770 repo.ui.fout = ui.fout
770 771 repo.ui.ferr = ui.ferr
771 772 else:
772 773 try:
773 774 repo = hg.repository(ui, path=path)
774 775 if not repo.local():
775 776 raise error.Abort(_("repository '%s' is not local")
776 777 % path)
777 778 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
778 779 'repo')
779 780 except error.RequirementError:
780 781 raise
781 782 except error.RepoError:
782 783 if rpath and rpath[-1]: # invalid -R path
783 784 raise
784 785 if not func.optionalrepo:
785 786 if func.inferrepo and args and not path:
786 787 # try to infer -R from command args
787 788 repos = map(cmdutil.findrepo, args)
788 789 guess = repos[0]
789 790 if guess and repos.count(guess) == len(repos):
790 791 req.args = ['--repository', guess] + fullargs
791 792 return _dispatch(req)
792 793 if not path:
793 794 raise error.RepoError(_("no repository found in"
794 795 " '%s' (.hg not found)")
795 796 % pycompat.getcwd())
796 797 raise
797 798 if repo:
798 799 ui = repo.ui
799 800 if options['hidden']:
800 801 repo = repo.unfiltered()
801 802 args.insert(0, repo)
802 803 elif rpath:
803 804 ui.warn(_("warning: --repository ignored\n"))
804 805
805 806 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
806 807 ui.log("command", '%s\n', msg)
807 808 strcmdopt = pycompat.strkwargs(cmdoptions)
808 809 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
809 810 try:
810 811 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
811 812 cmdpats, cmdoptions)
812 813 finally:
813 814 if repo and repo != req.repo:
814 815 repo.close()
815 816
816 817 def _runcommand(ui, options, cmd, cmdfunc):
817 818 """Run a command function, possibly with profiling enabled."""
818 819 try:
819 820 return cmdfunc()
820 821 except error.SignatureError:
821 822 raise error.CommandError(cmd, _('invalid arguments'))
822 823
823 824 def _exceptionwarning(ui):
824 825 """Produce a warning message for the current active exception"""
825 826
826 827 # For compatibility checking, we discard the portion of the hg
827 828 # version after the + on the assumption that if a "normal
828 829 # user" is running a build with a + in it the packager
829 830 # probably built from fairly close to a tag and anyone with a
830 831 # 'make local' copy of hg (where the version number can be out
831 832 # of date) will be clueful enough to notice the implausible
832 833 # version number and try updating.
833 834 ct = util.versiontuple(n=2)
834 835 worst = None, ct, ''
835 836 if ui.config('ui', 'supportcontact', None) is None:
836 837 for name, mod in extensions.extensions():
837 838 testedwith = getattr(mod, 'testedwith', '')
838 839 report = getattr(mod, 'buglink', _('the extension author.'))
839 840 if not testedwith.strip():
840 841 # We found an untested extension. It's likely the culprit.
841 842 worst = name, 'unknown', report
842 843 break
843 844
844 845 # Never blame on extensions bundled with Mercurial.
845 846 if extensions.ismoduleinternal(mod):
846 847 continue
847 848
848 849 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
849 850 if ct in tested:
850 851 continue
851 852
852 853 lower = [t for t in tested if t < ct]
853 854 nearest = max(lower or tested)
854 855 if worst[0] is None or nearest < worst[1]:
855 856 worst = name, nearest, report
856 857 if worst[0] is not None:
857 858 name, testedwith, report = worst
858 859 if not isinstance(testedwith, str):
859 860 testedwith = '.'.join([str(c) for c in testedwith])
860 861 warning = (_('** Unknown exception encountered with '
861 862 'possibly-broken third-party extension %s\n'
862 863 '** which supports versions %s of Mercurial.\n'
863 864 '** Please disable %s and try your action again.\n'
864 865 '** If that fixes the bug please report it to %s\n')
865 866 % (name, testedwith, name, report))
866 867 else:
867 868 bugtracker = ui.config('ui', 'supportcontact', None)
868 869 if bugtracker is None:
869 870 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
870 871 warning = (_("** unknown exception encountered, "
871 872 "please report by visiting\n** ") + bugtracker + '\n')
872 873 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
873 874 (_("** Mercurial Distributed SCM (version %s)\n") %
874 875 util.version()) +
875 876 (_("** Extensions loaded: %s\n") %
876 877 ", ".join([x[0] for x in extensions.extensions()])))
877 878 return warning
878 879
879 880 def handlecommandexception(ui):
880 881 """Produce a warning message for broken commands
881 882
882 883 Called when handling an exception; the exception is reraised if
883 884 this function returns False, ignored otherwise.
884 885 """
885 886 warning = _exceptionwarning(ui)
886 887 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
887 888 ui.warn(warning)
888 889 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now