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