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