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