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