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