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