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