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