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