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