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