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