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