##// END OF EJS Templates
py3: use pycompat.ziplist instead of zip...
Pulkit Goyal -
r35960:f81df691 default
parent child Browse files
Show More
@@ -1,996 +1,997 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 % (self.name, '/'.join(zip(*earlyopts)[0])))
480 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
481 [0])))
481 482 return
482 483 self.cmdname = cmd = args.pop(0)
483 484 self.givenargs = args
484 485
485 486 try:
486 487 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
487 488 if len(tableentry) > 2:
488 489 self.fn, self.opts, self.help = tableentry
489 490 else:
490 491 self.fn, self.opts = tableentry
491 492
492 493 if self.help.startswith("hg " + cmd):
493 494 # drop prefix in old-style help lines so hg shows the alias
494 495 self.help = self.help[4 + len(cmd):]
495 496 self.__doc__ = self.fn.__doc__
496 497
497 498 except error.UnknownCommand:
498 499 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
499 500 % (self.name, cmd))
500 501 self.unknowncmd = True
501 502 except error.AmbiguousCommand:
502 503 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
503 504 % (self.name, cmd))
504 505
505 506 @property
506 507 def args(self):
507 508 args = pycompat.maplist(util.expandpath, self.givenargs)
508 509 return aliasargs(self.fn, args)
509 510
510 511 def __getattr__(self, name):
511 512 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
512 513 r'optionalrepo': False, r'inferrepo': False}
513 514 if name not in adefaults:
514 515 raise AttributeError(name)
515 516 if self.badalias or util.safehasattr(self, 'shell'):
516 517 return adefaults[name]
517 518 return getattr(self.fn, name)
518 519
519 520 def __call__(self, ui, *args, **opts):
520 521 if self.badalias:
521 522 hint = None
522 523 if self.unknowncmd:
523 524 try:
524 525 # check if the command is in a disabled extension
525 526 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
526 527 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
527 528 except error.UnknownCommand:
528 529 pass
529 530 raise error.Abort(self.badalias, hint=hint)
530 531 if self.shadows:
531 532 ui.debug("alias '%s' shadows command '%s'\n" %
532 533 (self.name, self.cmdname))
533 534
534 535 ui.log('commandalias', "alias '%s' expands to '%s'\n",
535 536 self.name, self.definition)
536 537 if util.safehasattr(self, 'shell'):
537 538 return self.fn(ui, *args, **opts)
538 539 else:
539 540 try:
540 541 return util.checksignature(self.fn)(ui, *args, **opts)
541 542 except error.SignatureError:
542 543 args = ' '.join([self.cmdname] + self.args)
543 544 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
544 545 raise
545 546
546 547 class lazyaliasentry(object):
547 548 """like a typical command entry (func, opts, help), but is lazy"""
548 549
549 550 def __init__(self, name, definition, cmdtable, source):
550 551 self.name = name
551 552 self.definition = definition
552 553 self.cmdtable = cmdtable.copy()
553 554 self.source = source
554 555
555 556 @util.propertycache
556 557 def _aliasdef(self):
557 558 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
558 559
559 560 def __getitem__(self, n):
560 561 aliasdef = self._aliasdef
561 562 if n == 0:
562 563 return aliasdef
563 564 elif n == 1:
564 565 return aliasdef.opts
565 566 elif n == 2:
566 567 return aliasdef.help
567 568 else:
568 569 raise IndexError
569 570
570 571 def __iter__(self):
571 572 for i in range(3):
572 573 yield self[i]
573 574
574 575 def __len__(self):
575 576 return 3
576 577
577 578 def addaliases(ui, cmdtable):
578 579 # aliases are processed after extensions have been loaded, so they
579 580 # may use extension commands. Aliases can also use other alias definitions,
580 581 # but only if they have been defined prior to the current definition.
581 582 for alias, definition in ui.configitems('alias'):
582 583 try:
583 584 if cmdtable[alias].definition == definition:
584 585 continue
585 586 except (KeyError, AttributeError):
586 587 # definition might not exist or it might not be a cmdalias
587 588 pass
588 589
589 590 source = ui.configsource('alias', alias)
590 591 entry = lazyaliasentry(alias, definition, cmdtable, source)
591 592 cmdtable[alias] = entry
592 593
593 594 def _parse(ui, args):
594 595 options = {}
595 596 cmdoptions = {}
596 597
597 598 try:
598 599 args = fancyopts.fancyopts(args, commands.globalopts, options)
599 600 except getopt.GetoptError as inst:
600 601 raise error.CommandError(None, inst)
601 602
602 603 if args:
603 604 cmd, args = args[0], args[1:]
604 605 aliases, entry = cmdutil.findcmd(cmd, commands.table,
605 606 ui.configbool("ui", "strict"))
606 607 cmd = aliases[0]
607 608 args = aliasargs(entry[0], args)
608 609 defaults = ui.config("defaults", cmd)
609 610 if defaults:
610 611 args = pycompat.maplist(
611 612 util.expandpath, pycompat.shlexsplit(defaults)) + args
612 613 c = list(entry[1])
613 614 else:
614 615 cmd = None
615 616 c = []
616 617
617 618 # combine global options into local
618 619 for o in commands.globalopts:
619 620 c.append((o[0], o[1], options[o[1]], o[3]))
620 621
621 622 try:
622 623 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
623 624 except getopt.GetoptError as inst:
624 625 raise error.CommandError(cmd, inst)
625 626
626 627 # separate global options back out
627 628 for o in commands.globalopts:
628 629 n = o[1]
629 630 options[n] = cmdoptions[n]
630 631 del cmdoptions[n]
631 632
632 633 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
633 634
634 635 def _parseconfig(ui, config):
635 636 """parse the --config options from the command line"""
636 637 configs = []
637 638
638 639 for cfg in config:
639 640 try:
640 641 name, value = [cfgelem.strip()
641 642 for cfgelem in cfg.split('=', 1)]
642 643 section, name = name.split('.', 1)
643 644 if not section or not name:
644 645 raise IndexError
645 646 ui.setconfig(section, name, value, '--config')
646 647 configs.append((section, name, value))
647 648 except (IndexError, ValueError):
648 649 raise error.Abort(_('malformed --config option: %r '
649 650 '(use --config section.name=value)') % cfg)
650 651
651 652 return configs
652 653
653 654 def _earlyparseopts(ui, args):
654 655 options = {}
655 656 fancyopts.fancyopts(args, commands.globalopts, options,
656 657 gnu=not ui.plain('strictflags'), early=True,
657 658 optaliases={'repository': ['repo']})
658 659 return options
659 660
660 661 def _earlysplitopts(args):
661 662 """Split args into a list of possible early options and remainder args"""
662 663 shortoptions = 'R:'
663 664 # TODO: perhaps 'debugger' should be included
664 665 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
665 666 return fancyopts.earlygetopt(args, shortoptions, longoptions,
666 667 gnu=True, keepsep=True)
667 668
668 669 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
669 670 # run pre-hook, and abort if it fails
670 671 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
671 672 pats=cmdpats, opts=cmdoptions)
672 673 try:
673 674 ret = _runcommand(ui, options, cmd, d)
674 675 # run post-hook, passing command result
675 676 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
676 677 result=ret, pats=cmdpats, opts=cmdoptions)
677 678 except Exception:
678 679 # run failure hook and re-raise
679 680 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
680 681 pats=cmdpats, opts=cmdoptions)
681 682 raise
682 683 return ret
683 684
684 685 def _getlocal(ui, rpath, wd=None):
685 686 """Return (path, local ui object) for the given target path.
686 687
687 688 Takes paths in [cwd]/.hg/hgrc into account."
688 689 """
689 690 if wd is None:
690 691 try:
691 692 wd = pycompat.getcwd()
692 693 except OSError as e:
693 694 raise error.Abort(_("error getting current working directory: %s") %
694 695 encoding.strtolocal(e.strerror))
695 696 path = cmdutil.findrepo(wd) or ""
696 697 if not path:
697 698 lui = ui
698 699 else:
699 700 lui = ui.copy()
700 701 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
701 702
702 703 if rpath:
703 704 path = lui.expandpath(rpath)
704 705 lui = ui.copy()
705 706 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
706 707
707 708 return path, lui
708 709
709 710 def _checkshellalias(lui, ui, args):
710 711 """Return the function to run the shell alias, if it is required"""
711 712 options = {}
712 713
713 714 try:
714 715 args = fancyopts.fancyopts(args, commands.globalopts, options)
715 716 except getopt.GetoptError:
716 717 return
717 718
718 719 if not args:
719 720 return
720 721
721 722 cmdtable = commands.table
722 723
723 724 cmd = args[0]
724 725 try:
725 726 strict = ui.configbool("ui", "strict")
726 727 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
727 728 except (error.AmbiguousCommand, error.UnknownCommand):
728 729 return
729 730
730 731 cmd = aliases[0]
731 732 fn = entry[0]
732 733
733 734 if cmd and util.safehasattr(fn, 'shell'):
734 735 # shell alias shouldn't receive early options which are consumed by hg
735 736 _earlyopts, args = _earlysplitopts(args)
736 737 d = lambda: fn(ui, *args[1:])
737 738 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
738 739 [], {})
739 740
740 741 def _dispatch(req):
741 742 args = req.args
742 743 ui = req.ui
743 744
744 745 # check for cwd
745 746 cwd = req.earlyoptions['cwd']
746 747 if cwd:
747 748 os.chdir(cwd)
748 749
749 750 rpath = req.earlyoptions['repository']
750 751 path, lui = _getlocal(ui, rpath)
751 752
752 753 uis = {ui, lui}
753 754
754 755 if req.repo:
755 756 uis.add(req.repo.ui)
756 757
757 758 if req.earlyoptions['profile']:
758 759 for ui_ in uis:
759 760 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
760 761
761 762 profile = lui.configbool('profiling', 'enabled')
762 763 with profiling.profile(lui, enabled=profile) as profiler:
763 764 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
764 765 # reposetup
765 766 extensions.loadall(lui)
766 767 # Propagate any changes to lui.__class__ by extensions
767 768 ui.__class__ = lui.__class__
768 769
769 770 # (uisetup and extsetup are handled in extensions.loadall)
770 771
771 772 # (reposetup is handled in hg.repository)
772 773
773 774 addaliases(lui, commands.table)
774 775
775 776 # All aliases and commands are completely defined, now.
776 777 # Check abbreviation/ambiguity of shell alias.
777 778 shellaliasfn = _checkshellalias(lui, ui, args)
778 779 if shellaliasfn:
779 780 return shellaliasfn()
780 781
781 782 # check for fallback encoding
782 783 fallback = lui.config('ui', 'fallbackencoding')
783 784 if fallback:
784 785 encoding.fallbackencoding = fallback
785 786
786 787 fullargs = args
787 788 cmd, func, args, options, cmdoptions = _parse(lui, args)
788 789
789 790 if options["config"] != req.earlyoptions["config"]:
790 791 raise error.Abort(_("option --config may not be abbreviated!"))
791 792 if options["cwd"] != req.earlyoptions["cwd"]:
792 793 raise error.Abort(_("option --cwd may not be abbreviated!"))
793 794 if options["repository"] != req.earlyoptions["repository"]:
794 795 raise error.Abort(_(
795 796 "option -R has to be separated from other options (e.g. not "
796 797 "-qR) and --repository may only be abbreviated as --repo!"))
797 798 if options["debugger"] != req.earlyoptions["debugger"]:
798 799 raise error.Abort(_("option --debugger may not be abbreviated!"))
799 800 # don't validate --profile/--traceback, which can be enabled from now
800 801
801 802 if options["encoding"]:
802 803 encoding.encoding = options["encoding"]
803 804 if options["encodingmode"]:
804 805 encoding.encodingmode = options["encodingmode"]
805 806 if options["time"]:
806 807 def get_times():
807 808 t = os.times()
808 809 if t[4] == 0.0:
809 810 # Windows leaves this as zero, so use time.clock()
810 811 t = (t[0], t[1], t[2], t[3], time.clock())
811 812 return t
812 813 s = get_times()
813 814 def print_time():
814 815 t = get_times()
815 816 ui.warn(
816 817 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
817 818 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
818 819 ui.atexit(print_time)
819 820 if options["profile"]:
820 821 profiler.start()
821 822
822 823 if options['verbose'] or options['debug'] or options['quiet']:
823 824 for opt in ('verbose', 'debug', 'quiet'):
824 825 val = pycompat.bytestr(bool(options[opt]))
825 826 for ui_ in uis:
826 827 ui_.setconfig('ui', opt, val, '--' + opt)
827 828
828 829 if options['traceback']:
829 830 for ui_ in uis:
830 831 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
831 832
832 833 if options['noninteractive']:
833 834 for ui_ in uis:
834 835 ui_.setconfig('ui', 'interactive', 'off', '-y')
835 836
836 837 if cmdoptions.get('insecure', False):
837 838 for ui_ in uis:
838 839 ui_.insecureconnections = True
839 840
840 841 # setup color handling before pager, because setting up pager
841 842 # might cause incorrect console information
842 843 coloropt = options['color']
843 844 for ui_ in uis:
844 845 if coloropt:
845 846 ui_.setconfig('ui', 'color', coloropt, '--color')
846 847 color.setup(ui_)
847 848
848 849 if util.parsebool(options['pager']):
849 850 # ui.pager() expects 'internal-always-' prefix in this case
850 851 ui.pager('internal-always-' + cmd)
851 852 elif options['pager'] != 'auto':
852 853 for ui_ in uis:
853 854 ui_.disablepager()
854 855
855 856 if options['version']:
856 857 return commands.version_(ui)
857 858 if options['help']:
858 859 return commands.help_(ui, cmd, command=cmd is not None)
859 860 elif not cmd:
860 861 return commands.help_(ui, 'shortlist')
861 862
862 863 repo = None
863 864 cmdpats = args[:]
864 865 if not func.norepo:
865 866 # use the repo from the request only if we don't have -R
866 867 if not rpath and not cwd:
867 868 repo = req.repo
868 869
869 870 if repo:
870 871 # set the descriptors of the repo ui to those of ui
871 872 repo.ui.fin = ui.fin
872 873 repo.ui.fout = ui.fout
873 874 repo.ui.ferr = ui.ferr
874 875 else:
875 876 try:
876 877 repo = hg.repository(ui, path=path,
877 878 presetupfuncs=req.prereposetups)
878 879 if not repo.local():
879 880 raise error.Abort(_("repository '%s' is not local")
880 881 % path)
881 882 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
882 883 'repo')
883 884 except error.RequirementError:
884 885 raise
885 886 except error.RepoError:
886 887 if rpath: # invalid -R path
887 888 raise
888 889 if not func.optionalrepo:
889 890 if func.inferrepo and args and not path:
890 891 # try to infer -R from command args
891 892 repos = pycompat.maplist(cmdutil.findrepo, args)
892 893 guess = repos[0]
893 894 if guess and repos.count(guess) == len(repos):
894 895 req.args = ['--repository', guess] + fullargs
895 896 req.earlyoptions['repository'] = guess
896 897 return _dispatch(req)
897 898 if not path:
898 899 raise error.RepoError(_("no repository found in"
899 900 " '%s' (.hg not found)")
900 901 % pycompat.getcwd())
901 902 raise
902 903 if repo:
903 904 ui = repo.ui
904 905 if options['hidden']:
905 906 repo = repo.unfiltered()
906 907 args.insert(0, repo)
907 908 elif rpath:
908 909 ui.warn(_("warning: --repository ignored\n"))
909 910
910 911 msg = _formatargs(fullargs)
911 912 ui.log("command", '%s\n', msg)
912 913 strcmdopt = pycompat.strkwargs(cmdoptions)
913 914 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
914 915 try:
915 916 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
916 917 cmdpats, cmdoptions)
917 918 finally:
918 919 if repo and repo != req.repo:
919 920 repo.close()
920 921
921 922 def _runcommand(ui, options, cmd, cmdfunc):
922 923 """Run a command function, possibly with profiling enabled."""
923 924 try:
924 925 return cmdfunc()
925 926 except error.SignatureError:
926 927 raise error.CommandError(cmd, _('invalid arguments'))
927 928
928 929 def _exceptionwarning(ui):
929 930 """Produce a warning message for the current active exception"""
930 931
931 932 # For compatibility checking, we discard the portion of the hg
932 933 # version after the + on the assumption that if a "normal
933 934 # user" is running a build with a + in it the packager
934 935 # probably built from fairly close to a tag and anyone with a
935 936 # 'make local' copy of hg (where the version number can be out
936 937 # of date) will be clueful enough to notice the implausible
937 938 # version number and try updating.
938 939 ct = util.versiontuple(n=2)
939 940 worst = None, ct, ''
940 941 if ui.config('ui', 'supportcontact') is None:
941 942 for name, mod in extensions.extensions():
942 943 # 'testedwith' should be bytes, but not all extensions are ported
943 944 # to py3 and we don't want UnicodeException because of that.
944 945 testedwith = util.forcebytestr(getattr(mod, 'testedwith', ''))
945 946 report = getattr(mod, 'buglink', _('the extension author.'))
946 947 if not testedwith.strip():
947 948 # We found an untested extension. It's likely the culprit.
948 949 worst = name, 'unknown', report
949 950 break
950 951
951 952 # Never blame on extensions bundled with Mercurial.
952 953 if extensions.ismoduleinternal(mod):
953 954 continue
954 955
955 956 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
956 957 if ct in tested:
957 958 continue
958 959
959 960 lower = [t for t in tested if t < ct]
960 961 nearest = max(lower or tested)
961 962 if worst[0] is None or nearest < worst[1]:
962 963 worst = name, nearest, report
963 964 if worst[0] is not None:
964 965 name, testedwith, report = worst
965 966 if not isinstance(testedwith, (bytes, str)):
966 967 testedwith = '.'.join([str(c) for c in testedwith])
967 968 warning = (_('** Unknown exception encountered with '
968 969 'possibly-broken third-party extension %s\n'
969 970 '** which supports versions %s of Mercurial.\n'
970 971 '** Please disable %s and try your action again.\n'
971 972 '** If that fixes the bug please report it to %s\n')
972 973 % (name, testedwith, name, report))
973 974 else:
974 975 bugtracker = ui.config('ui', 'supportcontact')
975 976 if bugtracker is None:
976 977 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
977 978 warning = (_("** unknown exception encountered, "
978 979 "please report by visiting\n** ") + bugtracker + '\n')
979 980 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
980 981 warning += ((_("** Python %s\n") % sysversion) +
981 982 (_("** Mercurial Distributed SCM (version %s)\n") %
982 983 util.version()) +
983 984 (_("** Extensions loaded: %s\n") %
984 985 ", ".join([x[0] for x in extensions.extensions()])))
985 986 return warning
986 987
987 988 def handlecommandexception(ui):
988 989 """Produce a warning message for broken commands
989 990
990 991 Called when handling an exception; the exception is reraised if
991 992 this function returns False, ignored otherwise.
992 993 """
993 994 warning = _exceptionwarning(ui)
994 995 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
995 996 ui.warn(warning)
996 997 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now