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