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