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