##// END OF EJS Templates
dispatch: set a blockedtag when running an external alias
Simon Farnsworth -
r31199:3a03264d default
parent child Browse files
Show More
@@ -1,917 +1,918 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 return ui.system(cmd, environ=env)
357 return ui.system(cmd, environ=env,
358 blockedtag='alias_%s' % self.name)
358 359 self.fn = fn
359 360 return
360 361
361 362 try:
362 363 args = pycompat.shlexsplit(self.definition)
363 364 except ValueError as inst:
364 365 self.badalias = (_("error in definition for alias '%s': %s")
365 366 % (self.name, inst))
366 367 return
367 368 self.cmdname = cmd = args.pop(0)
368 369 self.givenargs = args
369 370
370 371 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
371 372 if _earlygetopt([invalidarg], args):
372 373 self.badalias = (_("error in definition for alias '%s': %s may "
373 374 "only be given on the command line")
374 375 % (self.name, invalidarg))
375 376 return
376 377
377 378 try:
378 379 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
379 380 if len(tableentry) > 2:
380 381 self.fn, self.opts, self.help = tableentry
381 382 else:
382 383 self.fn, self.opts = tableentry
383 384
384 385 if self.help.startswith("hg " + cmd):
385 386 # drop prefix in old-style help lines so hg shows the alias
386 387 self.help = self.help[4 + len(cmd):]
387 388 self.__doc__ = self.fn.__doc__
388 389
389 390 except error.UnknownCommand:
390 391 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
391 392 % (self.name, cmd))
392 393 self.unknowncmd = True
393 394 except error.AmbiguousCommand:
394 395 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
395 396 % (self.name, cmd))
396 397
397 398 @property
398 399 def args(self):
399 400 args = map(util.expandpath, self.givenargs)
400 401 return aliasargs(self.fn, args)
401 402
402 403 def __getattr__(self, name):
403 404 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
404 405 if name not in adefaults:
405 406 raise AttributeError(name)
406 407 if self.badalias or util.safehasattr(self, 'shell'):
407 408 return adefaults[name]
408 409 return getattr(self.fn, name)
409 410
410 411 def __call__(self, ui, *args, **opts):
411 412 if self.badalias:
412 413 hint = None
413 414 if self.unknowncmd:
414 415 try:
415 416 # check if the command is in a disabled extension
416 417 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
417 418 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
418 419 except error.UnknownCommand:
419 420 pass
420 421 raise error.Abort(self.badalias, hint=hint)
421 422 if self.shadows:
422 423 ui.debug("alias '%s' shadows command '%s'\n" %
423 424 (self.name, self.cmdname))
424 425
425 426 ui.log('commandalias', "alias '%s' expands to '%s'\n",
426 427 self.name, self.definition)
427 428 if util.safehasattr(self, 'shell'):
428 429 return self.fn(ui, *args, **opts)
429 430 else:
430 431 try:
431 432 return util.checksignature(self.fn)(ui, *args, **opts)
432 433 except error.SignatureError:
433 434 args = ' '.join([self.cmdname] + self.args)
434 435 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
435 436 raise
436 437
437 438 def addaliases(ui, cmdtable):
438 439 # aliases are processed after extensions have been loaded, so they
439 440 # may use extension commands. Aliases can also use other alias definitions,
440 441 # but only if they have been defined prior to the current definition.
441 442 for alias, definition in ui.configitems('alias'):
442 443 source = ui.configsource('alias', alias)
443 444 aliasdef = cmdalias(alias, definition, cmdtable, source)
444 445
445 446 try:
446 447 olddef = cmdtable[aliasdef.cmd][0]
447 448 if olddef.definition == aliasdef.definition:
448 449 continue
449 450 except (KeyError, AttributeError):
450 451 # definition might not exist or it might not be a cmdalias
451 452 pass
452 453
453 454 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
454 455
455 456 def _parse(ui, args):
456 457 options = {}
457 458 cmdoptions = {}
458 459
459 460 try:
460 461 args = fancyopts.fancyopts(args, commands.globalopts, options)
461 462 except getopt.GetoptError as inst:
462 463 raise error.CommandError(None, inst)
463 464
464 465 if args:
465 466 cmd, args = args[0], args[1:]
466 467 aliases, entry = cmdutil.findcmd(cmd, commands.table,
467 468 ui.configbool("ui", "strict"))
468 469 cmd = aliases[0]
469 470 args = aliasargs(entry[0], args)
470 471 defaults = ui.config("defaults", cmd)
471 472 if defaults:
472 473 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
473 474 c = list(entry[1])
474 475 else:
475 476 cmd = None
476 477 c = []
477 478
478 479 # combine global options into local
479 480 for o in commands.globalopts:
480 481 c.append((o[0], o[1], options[o[1]], o[3]))
481 482
482 483 try:
483 484 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
484 485 except getopt.GetoptError as inst:
485 486 raise error.CommandError(cmd, inst)
486 487
487 488 # separate global options back out
488 489 for o in commands.globalopts:
489 490 n = o[1]
490 491 options[n] = cmdoptions[n]
491 492 del cmdoptions[n]
492 493
493 494 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
494 495
495 496 def _parseconfig(ui, config):
496 497 """parse the --config options from the command line"""
497 498 configs = []
498 499
499 500 for cfg in config:
500 501 try:
501 502 name, value = [cfgelem.strip()
502 503 for cfgelem in cfg.split('=', 1)]
503 504 section, name = name.split('.', 1)
504 505 if not section or not name:
505 506 raise IndexError
506 507 ui.setconfig(section, name, value, '--config')
507 508 configs.append((section, name, value))
508 509 except (IndexError, ValueError):
509 510 raise error.Abort(_('malformed --config option: %r '
510 511 '(use --config section.name=value)') % cfg)
511 512
512 513 return configs
513 514
514 515 def _earlygetopt(aliases, args):
515 516 """Return list of values for an option (or aliases).
516 517
517 518 The values are listed in the order they appear in args.
518 519 The options and values are removed from args.
519 520
520 521 >>> args = ['x', '--cwd', 'foo', 'y']
521 522 >>> _earlygetopt(['--cwd'], args), args
522 523 (['foo'], ['x', 'y'])
523 524
524 525 >>> args = ['x', '--cwd=bar', 'y']
525 526 >>> _earlygetopt(['--cwd'], args), args
526 527 (['bar'], ['x', 'y'])
527 528
528 529 >>> args = ['x', '-R', 'foo', 'y']
529 530 >>> _earlygetopt(['-R'], args), args
530 531 (['foo'], ['x', 'y'])
531 532
532 533 >>> args = ['x', '-Rbar', 'y']
533 534 >>> _earlygetopt(['-R'], args), args
534 535 (['bar'], ['x', 'y'])
535 536 """
536 537 try:
537 538 argcount = args.index("--")
538 539 except ValueError:
539 540 argcount = len(args)
540 541 shortopts = [opt for opt in aliases if len(opt) == 2]
541 542 values = []
542 543 pos = 0
543 544 while pos < argcount:
544 545 fullarg = arg = args[pos]
545 546 equals = arg.find('=')
546 547 if equals > -1:
547 548 arg = arg[:equals]
548 549 if arg in aliases:
549 550 del args[pos]
550 551 if equals > -1:
551 552 values.append(fullarg[equals + 1:])
552 553 argcount -= 1
553 554 else:
554 555 if pos + 1 >= argcount:
555 556 # ignore and let getopt report an error if there is no value
556 557 break
557 558 values.append(args.pop(pos))
558 559 argcount -= 2
559 560 elif arg[:2] in shortopts:
560 561 # short option can have no following space, e.g. hg log -Rfoo
561 562 values.append(args.pop(pos)[2:])
562 563 argcount -= 1
563 564 else:
564 565 pos += 1
565 566 return values
566 567
567 568 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
568 569 # run pre-hook, and abort if it fails
569 570 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
570 571 pats=cmdpats, opts=cmdoptions)
571 572 try:
572 573 ret = _runcommand(ui, options, cmd, d)
573 574 # run post-hook, passing command result
574 575 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
575 576 result=ret, pats=cmdpats, opts=cmdoptions)
576 577 except Exception:
577 578 # run failure hook and re-raise
578 579 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
579 580 pats=cmdpats, opts=cmdoptions)
580 581 raise
581 582 return ret
582 583
583 584 def _getlocal(ui, rpath, wd=None):
584 585 """Return (path, local ui object) for the given target path.
585 586
586 587 Takes paths in [cwd]/.hg/hgrc into account."
587 588 """
588 589 if wd is None:
589 590 try:
590 591 wd = pycompat.getcwd()
591 592 except OSError as e:
592 593 raise error.Abort(_("error getting current working directory: %s") %
593 594 e.strerror)
594 595 path = cmdutil.findrepo(wd) or ""
595 596 if not path:
596 597 lui = ui
597 598 else:
598 599 lui = ui.copy()
599 600 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
600 601
601 602 if rpath and rpath[-1]:
602 603 path = lui.expandpath(rpath[-1])
603 604 lui = ui.copy()
604 605 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
605 606
606 607 return path, lui
607 608
608 609 def _checkshellalias(lui, ui, args):
609 610 """Return the function to run the shell alias, if it is required"""
610 611 options = {}
611 612
612 613 try:
613 614 args = fancyopts.fancyopts(args, commands.globalopts, options)
614 615 except getopt.GetoptError:
615 616 return
616 617
617 618 if not args:
618 619 return
619 620
620 621 cmdtable = commands.table
621 622
622 623 cmd = args[0]
623 624 try:
624 625 strict = ui.configbool("ui", "strict")
625 626 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
626 627 except (error.AmbiguousCommand, error.UnknownCommand):
627 628 return
628 629
629 630 cmd = aliases[0]
630 631 fn = entry[0]
631 632
632 633 if cmd and util.safehasattr(fn, 'shell'):
633 634 d = lambda: fn(ui, *args[1:])
634 635 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
635 636 [], {})
636 637
637 638 _loaded = set()
638 639
639 640 # list of (objname, loadermod, loadername) tuple:
640 641 # - objname is the name of an object in extension module, from which
641 642 # extra information is loaded
642 643 # - loadermod is the module where loader is placed
643 644 # - loadername is the name of the function, which takes (ui, extensionname,
644 645 # extraobj) arguments
645 646 extraloaders = [
646 647 ('cmdtable', commands, 'loadcmdtable'),
647 648 ('colortable', color, 'loadcolortable'),
648 649 ('filesetpredicate', fileset, 'loadpredicate'),
649 650 ('revsetpredicate', revset, 'loadpredicate'),
650 651 ('templatefilter', templatefilters, 'loadfilter'),
651 652 ('templatefunc', templater, 'loadfunction'),
652 653 ('templatekeyword', templatekw, 'loadkeyword'),
653 654 ]
654 655
655 656 def _dispatch(req):
656 657 args = req.args
657 658 ui = req.ui
658 659
659 660 # check for cwd
660 661 cwd = _earlygetopt(['--cwd'], args)
661 662 if cwd:
662 663 os.chdir(cwd[-1])
663 664
664 665 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
665 666 path, lui = _getlocal(ui, rpath)
666 667
667 668 # Side-effect of accessing is debugcommands module is guaranteed to be
668 669 # imported and commands.table is populated.
669 670 debugcommands.command
670 671
671 672 uis = set([ui, lui])
672 673
673 674 if req.repo:
674 675 uis.add(req.repo.ui)
675 676
676 677 if '--profile' in args:
677 678 for ui_ in uis:
678 679 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
679 680
680 681 with profiling.maybeprofile(lui):
681 682 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
682 683 # reposetup. Programs like TortoiseHg will call _dispatch several
683 684 # times so we keep track of configured extensions in _loaded.
684 685 extensions.loadall(lui)
685 686 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
686 687 # Propagate any changes to lui.__class__ by extensions
687 688 ui.__class__ = lui.__class__
688 689
689 690 # (uisetup and extsetup are handled in extensions.loadall)
690 691
691 692 for name, module in exts:
692 693 for objname, loadermod, loadername in extraloaders:
693 694 extraobj = getattr(module, objname, None)
694 695 if extraobj is not None:
695 696 getattr(loadermod, loadername)(ui, name, extraobj)
696 697 _loaded.add(name)
697 698
698 699 # (reposetup is handled in hg.repository)
699 700
700 701 addaliases(lui, commands.table)
701 702
702 703 # All aliases and commands are completely defined, now.
703 704 # Check abbreviation/ambiguity of shell alias.
704 705 shellaliasfn = _checkshellalias(lui, ui, args)
705 706 if shellaliasfn:
706 707 return shellaliasfn()
707 708
708 709 # check for fallback encoding
709 710 fallback = lui.config('ui', 'fallbackencoding')
710 711 if fallback:
711 712 encoding.fallbackencoding = fallback
712 713
713 714 fullargs = args
714 715 cmd, func, args, options, cmdoptions = _parse(lui, args)
715 716
716 717 if options["config"]:
717 718 raise error.Abort(_("option --config may not be abbreviated!"))
718 719 if options["cwd"]:
719 720 raise error.Abort(_("option --cwd may not be abbreviated!"))
720 721 if options["repository"]:
721 722 raise error.Abort(_(
722 723 "option -R has to be separated from other options (e.g. not "
723 724 "-qR) and --repository may only be abbreviated as --repo!"))
724 725
725 726 if options["encoding"]:
726 727 encoding.encoding = options["encoding"]
727 728 if options["encodingmode"]:
728 729 encoding.encodingmode = options["encodingmode"]
729 730 if options["time"]:
730 731 def get_times():
731 732 t = os.times()
732 733 if t[4] == 0.0:
733 734 # Windows leaves this as zero, so use time.clock()
734 735 t = (t[0], t[1], t[2], t[3], time.clock())
735 736 return t
736 737 s = get_times()
737 738 def print_time():
738 739 t = get_times()
739 740 ui.warn(
740 741 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
741 742 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
742 743 atexit.register(print_time)
743 744
744 745 if options['verbose'] or options['debug'] or options['quiet']:
745 746 for opt in ('verbose', 'debug', 'quiet'):
746 747 val = str(bool(options[opt]))
747 748 for ui_ in uis:
748 749 ui_.setconfig('ui', opt, val, '--' + opt)
749 750
750 751 if options['traceback']:
751 752 for ui_ in uis:
752 753 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
753 754
754 755 if options['noninteractive']:
755 756 for ui_ in uis:
756 757 ui_.setconfig('ui', 'interactive', 'off', '-y')
757 758
758 759 if util.parsebool(options['pager']):
759 760 ui.pager('internal-always-' + cmd)
760 761 elif options['pager'] != 'auto':
761 762 ui.disablepager()
762 763
763 764 if cmdoptions.get('insecure', False):
764 765 for ui_ in uis:
765 766 ui_.insecureconnections = True
766 767
767 768 # setup color handling
768 769 coloropt = options['color']
769 770 for ui_ in uis:
770 771 if coloropt:
771 772 ui_.setconfig('ui', 'color', coloropt, '--color')
772 773 color.setup(ui_)
773 774
774 775 if options['version']:
775 776 return commands.version_(ui)
776 777 if options['help']:
777 778 return commands.help_(ui, cmd, command=cmd is not None)
778 779 elif not cmd:
779 780 return commands.help_(ui, 'shortlist')
780 781
781 782 repo = None
782 783 cmdpats = args[:]
783 784 if not func.norepo:
784 785 # use the repo from the request only if we don't have -R
785 786 if not rpath and not cwd:
786 787 repo = req.repo
787 788
788 789 if repo:
789 790 # set the descriptors of the repo ui to those of ui
790 791 repo.ui.fin = ui.fin
791 792 repo.ui.fout = ui.fout
792 793 repo.ui.ferr = ui.ferr
793 794 else:
794 795 try:
795 796 repo = hg.repository(ui, path=path)
796 797 if not repo.local():
797 798 raise error.Abort(_("repository '%s' is not local")
798 799 % path)
799 800 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
800 801 'repo')
801 802 except error.RequirementError:
802 803 raise
803 804 except error.RepoError:
804 805 if rpath and rpath[-1]: # invalid -R path
805 806 raise
806 807 if not func.optionalrepo:
807 808 if func.inferrepo and args and not path:
808 809 # try to infer -R from command args
809 810 repos = map(cmdutil.findrepo, args)
810 811 guess = repos[0]
811 812 if guess and repos.count(guess) == len(repos):
812 813 req.args = ['--repository', guess] + fullargs
813 814 return _dispatch(req)
814 815 if not path:
815 816 raise error.RepoError(_("no repository found in"
816 817 " '%s' (.hg not found)")
817 818 % pycompat.getcwd())
818 819 raise
819 820 if repo:
820 821 ui = repo.ui
821 822 if options['hidden']:
822 823 repo = repo.unfiltered()
823 824 args.insert(0, repo)
824 825 elif rpath:
825 826 ui.warn(_("warning: --repository ignored\n"))
826 827
827 828 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
828 829 ui.log("command", '%s\n', msg)
829 830 strcmdopt = pycompat.strkwargs(cmdoptions)
830 831 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
831 832 try:
832 833 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
833 834 cmdpats, cmdoptions)
834 835 finally:
835 836 if repo and repo != req.repo:
836 837 repo.close()
837 838
838 839 def _runcommand(ui, options, cmd, cmdfunc):
839 840 """Run a command function, possibly with profiling enabled."""
840 841 try:
841 842 return cmdfunc()
842 843 except error.SignatureError:
843 844 raise error.CommandError(cmd, _('invalid arguments'))
844 845
845 846 def _exceptionwarning(ui):
846 847 """Produce a warning message for the current active exception"""
847 848
848 849 # For compatibility checking, we discard the portion of the hg
849 850 # version after the + on the assumption that if a "normal
850 851 # user" is running a build with a + in it the packager
851 852 # probably built from fairly close to a tag and anyone with a
852 853 # 'make local' copy of hg (where the version number can be out
853 854 # of date) will be clueful enough to notice the implausible
854 855 # version number and try updating.
855 856 ct = util.versiontuple(n=2)
856 857 worst = None, ct, ''
857 858 if ui.config('ui', 'supportcontact', None) is None:
858 859 for name, mod in extensions.extensions():
859 860 testedwith = getattr(mod, 'testedwith', '')
860 861 if pycompat.ispy3 and isinstance(testedwith, str):
861 862 testedwith = testedwith.encode(u'utf-8')
862 863 report = getattr(mod, 'buglink', _('the extension author.'))
863 864 if not testedwith.strip():
864 865 # We found an untested extension. It's likely the culprit.
865 866 worst = name, 'unknown', report
866 867 break
867 868
868 869 # Never blame on extensions bundled with Mercurial.
869 870 if extensions.ismoduleinternal(mod):
870 871 continue
871 872
872 873 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
873 874 if ct in tested:
874 875 continue
875 876
876 877 lower = [t for t in tested if t < ct]
877 878 nearest = max(lower or tested)
878 879 if worst[0] is None or nearest < worst[1]:
879 880 worst = name, nearest, report
880 881 if worst[0] is not None:
881 882 name, testedwith, report = worst
882 883 if not isinstance(testedwith, (bytes, str)):
883 884 testedwith = '.'.join([str(c) for c in testedwith])
884 885 warning = (_('** Unknown exception encountered with '
885 886 'possibly-broken third-party extension %s\n'
886 887 '** which supports versions %s of Mercurial.\n'
887 888 '** Please disable %s and try your action again.\n'
888 889 '** If that fixes the bug please report it to %s\n')
889 890 % (name, testedwith, name, report))
890 891 else:
891 892 bugtracker = ui.config('ui', 'supportcontact', None)
892 893 if bugtracker is None:
893 894 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
894 895 warning = (_("** unknown exception encountered, "
895 896 "please report by visiting\n** ") + bugtracker + '\n')
896 897 if pycompat.ispy3:
897 898 sysversion = sys.version.encode(u'utf-8')
898 899 else:
899 900 sysversion = sys.version
900 901 sysversion = sysversion.replace('\n', '')
901 902 warning += ((_("** Python %s\n") % sysversion) +
902 903 (_("** Mercurial Distributed SCM (version %s)\n") %
903 904 util.version()) +
904 905 (_("** Extensions loaded: %s\n") %
905 906 ", ".join([x[0] for x in extensions.extensions()])))
906 907 return warning
907 908
908 909 def handlecommandexception(ui):
909 910 """Produce a warning message for broken commands
910 911
911 912 Called when handling an exception; the exception is reraised if
912 913 this function returns False, ignored otherwise.
913 914 """
914 915 warning = _exceptionwarning(ui)
915 916 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
916 917 ui.warn(warning)
917 918 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now