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