##// END OF EJS Templates
dispatch: use the right context manager to deactivate demandimport...
Jordi Gutiérrez Hermoso -
r26236:2e425171 default
parent child Browse files
Show More
@@ -1,1052 +1,1052 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
9 9
10 10 import atexit
11 11 import difflib
12 12 import errno
13 13 import os
14 14 import pdb
15 15 import re
16 16 import shlex
17 17 import signal
18 18 import socket
19 19 import sys
20 20 import time
21 21 import traceback
22 22
23 23
24 24 from .i18n import _
25 25
26 26 from . import (
27 27 cmdutil,
28 28 commands,
29 29 demandimport,
30 30 encoding,
31 31 error,
32 32 extensions,
33 33 fancyopts,
34 34 hg,
35 35 hook,
36 36 ui as uimod,
37 37 util,
38 38 )
39 39
40 40 class request(object):
41 41 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
42 42 ferr=None):
43 43 self.args = args
44 44 self.ui = ui
45 45 self.repo = repo
46 46
47 47 # input/output/error streams
48 48 self.fin = fin
49 49 self.fout = fout
50 50 self.ferr = ferr
51 51
52 52 def run():
53 53 "run the command in sys.argv"
54 54 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
55 55
56 56 def _getsimilar(symbols, value):
57 57 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
58 58 # The cutoff for similarity here is pretty arbitrary. It should
59 59 # probably be investigated and tweaked.
60 60 return [s for s in symbols if sim(s) > 0.6]
61 61
62 62 def _formatparse(write, inst):
63 63 similar = []
64 64 if isinstance(inst, error.UnknownIdentifier):
65 65 # make sure to check fileset first, as revset can invoke fileset
66 66 similar = _getsimilar(inst.symbols, inst.function)
67 67 if len(inst.args) > 1:
68 68 write(_("hg: parse error at %s: %s\n") %
69 69 (inst.args[1], inst.args[0]))
70 70 if (inst.args[0][0] == ' '):
71 71 write(_("unexpected leading whitespace\n"))
72 72 else:
73 73 write(_("hg: parse error: %s\n") % inst.args[0])
74 74 if similar:
75 75 if len(similar) == 1:
76 76 write(_("(did you mean %r?)\n") % similar[0])
77 77 else:
78 78 ss = ", ".join(sorted(similar))
79 79 write(_("(did you mean one of %s?)\n") % ss)
80 80
81 81 def dispatch(req):
82 82 "run the command specified in req.args"
83 83 if req.ferr:
84 84 ferr = req.ferr
85 85 elif req.ui:
86 86 ferr = req.ui.ferr
87 87 else:
88 88 ferr = sys.stderr
89 89
90 90 try:
91 91 if not req.ui:
92 92 req.ui = uimod.ui()
93 93 if '--traceback' in req.args:
94 94 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
95 95
96 96 # set ui streams from the request
97 97 if req.fin:
98 98 req.ui.fin = req.fin
99 99 if req.fout:
100 100 req.ui.fout = req.fout
101 101 if req.ferr:
102 102 req.ui.ferr = req.ferr
103 103 except util.Abort as inst:
104 104 ferr.write(_("abort: %s\n") % inst)
105 105 if inst.hint:
106 106 ferr.write(_("(%s)\n") % inst.hint)
107 107 return -1
108 108 except error.ParseError as inst:
109 109 _formatparse(ferr.write, inst)
110 110 return -1
111 111
112 112 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
113 113 starttime = time.time()
114 114 ret = None
115 115 try:
116 116 ret = _runcatch(req)
117 117 return ret
118 118 finally:
119 119 duration = time.time() - starttime
120 120 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
121 121 msg, ret or 0, duration)
122 122
123 123 def _runcatch(req):
124 124 def catchterm(*args):
125 125 raise error.SignalInterrupt
126 126
127 127 ui = req.ui
128 128 try:
129 129 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
130 130 num = getattr(signal, name, None)
131 131 if num:
132 132 signal.signal(num, catchterm)
133 133 except ValueError:
134 134 pass # happens if called in a thread
135 135
136 136 try:
137 137 try:
138 138 debugger = 'pdb'
139 139 debugtrace = {
140 140 'pdb' : pdb.set_trace
141 141 }
142 142 debugmortem = {
143 143 'pdb' : pdb.post_mortem
144 144 }
145 145
146 146 # read --config before doing anything else
147 147 # (e.g. to change trust settings for reading .hg/hgrc)
148 148 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
149 149
150 150 if req.repo:
151 151 # copy configs that were passed on the cmdline (--config) to
152 152 # the repo ui
153 153 for sec, name, val in cfgs:
154 154 req.repo.ui.setconfig(sec, name, val, source='--config')
155 155
156 156 # developer config: ui.debugger
157 157 debugger = ui.config("ui", "debugger")
158 158 debugmod = pdb
159 159 if not debugger or ui.plain():
160 160 # if we are in HGPLAIN mode, then disable custom debugging
161 161 debugger = 'pdb'
162 162 elif '--debugger' in req.args:
163 163 # This import can be slow for fancy debuggers, so only
164 164 # do it when absolutely necessary, i.e. when actual
165 165 # debugging has been requested
166 166 with demandimport.deactivated():
167 167 try:
168 168 debugmod = __import__(debugger)
169 169 except ImportError:
170 170 pass # Leave debugmod = pdb
171 171
172 172 debugtrace[debugger] = debugmod.set_trace
173 173 debugmortem[debugger] = debugmod.post_mortem
174 174
175 175 # enter the debugger before command execution
176 176 if '--debugger' in req.args:
177 177 ui.warn(_("entering debugger - "
178 178 "type c to continue starting hg or h for help\n"))
179 179
180 180 if (debugger != 'pdb' and
181 181 debugtrace[debugger] == debugtrace['pdb']):
182 182 ui.warn(_("%s debugger specified "
183 183 "but its module was not found\n") % debugger)
184 with demandimport.disabled():
184 with demandimport.deactivated():
185 185 debugtrace[debugger]()
186 186 try:
187 187 return _dispatch(req)
188 188 finally:
189 189 ui.flush()
190 190 except: # re-raises
191 191 # enter the debugger when we hit an exception
192 192 if '--debugger' in req.args:
193 193 traceback.print_exc()
194 194 debugmortem[debugger](sys.exc_info()[2])
195 195 ui.traceback()
196 196 raise
197 197
198 198 # Global exception handling, alphabetically
199 199 # Mercurial-specific first, followed by built-in and library exceptions
200 200 except error.AmbiguousCommand as inst:
201 201 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
202 202 (inst.args[0], " ".join(inst.args[1])))
203 203 except error.ParseError as inst:
204 204 _formatparse(ui.warn, inst)
205 205 return -1
206 206 except error.LockHeld as inst:
207 207 if inst.errno == errno.ETIMEDOUT:
208 208 reason = _('timed out waiting for lock held by %s') % inst.locker
209 209 else:
210 210 reason = _('lock held by %s') % inst.locker
211 211 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
212 212 except error.LockUnavailable as inst:
213 213 ui.warn(_("abort: could not lock %s: %s\n") %
214 214 (inst.desc or inst.filename, inst.strerror))
215 215 except error.CommandError as inst:
216 216 if inst.args[0]:
217 217 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
218 218 commands.help_(ui, inst.args[0], full=False, command=True)
219 219 else:
220 220 ui.warn(_("hg: %s\n") % inst.args[1])
221 221 commands.help_(ui, 'shortlist')
222 222 except error.OutOfBandError as inst:
223 223 if inst.args:
224 224 msg = _("abort: remote error:\n")
225 225 else:
226 226 msg = _("abort: remote error\n")
227 227 ui.warn(msg)
228 228 if inst.args:
229 229 ui.warn(''.join(inst.args))
230 230 if inst.hint:
231 231 ui.warn('(%s)\n' % inst.hint)
232 232 except error.RepoError as inst:
233 233 ui.warn(_("abort: %s!\n") % inst)
234 234 if inst.hint:
235 235 ui.warn(_("(%s)\n") % inst.hint)
236 236 except error.ResponseError as inst:
237 237 ui.warn(_("abort: %s") % inst.args[0])
238 238 if not isinstance(inst.args[1], basestring):
239 239 ui.warn(" %r\n" % (inst.args[1],))
240 240 elif not inst.args[1]:
241 241 ui.warn(_(" empty string\n"))
242 242 else:
243 243 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
244 244 except error.CensoredNodeError as inst:
245 245 ui.warn(_("abort: file censored %s!\n") % inst)
246 246 except error.RevlogError as inst:
247 247 ui.warn(_("abort: %s!\n") % inst)
248 248 except error.SignalInterrupt:
249 249 ui.warn(_("killed!\n"))
250 250 except error.UnknownCommand as inst:
251 251 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
252 252 try:
253 253 # check if the command is in a disabled extension
254 254 # (but don't check for extensions themselves)
255 255 commands.help_(ui, inst.args[0], unknowncmd=True)
256 256 except error.UnknownCommand:
257 257 suggested = False
258 258 if len(inst.args) == 2:
259 259 sim = _getsimilar(inst.args[1], inst.args[0])
260 260 if sim:
261 261 ui.warn(_('(did you mean one of %s?)\n') %
262 262 ', '.join(sorted(sim)))
263 263 suggested = True
264 264 if not suggested:
265 265 commands.help_(ui, 'shortlist')
266 266 except error.InterventionRequired as inst:
267 267 ui.warn("%s\n" % inst)
268 268 return 1
269 269 except util.Abort as inst:
270 270 ui.warn(_("abort: %s\n") % inst)
271 271 if inst.hint:
272 272 ui.warn(_("(%s)\n") % inst.hint)
273 273 except ImportError as inst:
274 274 ui.warn(_("abort: %s!\n") % inst)
275 275 m = str(inst).split()[-1]
276 276 if m in "mpatch bdiff".split():
277 277 ui.warn(_("(did you forget to compile extensions?)\n"))
278 278 elif m in "zlib".split():
279 279 ui.warn(_("(is your Python install correct?)\n"))
280 280 except IOError as inst:
281 281 if util.safehasattr(inst, "code"):
282 282 ui.warn(_("abort: %s\n") % inst)
283 283 elif util.safehasattr(inst, "reason"):
284 284 try: # usually it is in the form (errno, strerror)
285 285 reason = inst.reason.args[1]
286 286 except (AttributeError, IndexError):
287 287 # it might be anything, for example a string
288 288 reason = inst.reason
289 289 if isinstance(reason, unicode):
290 290 # SSLError of Python 2.7.9 contains a unicode
291 291 reason = reason.encode(encoding.encoding, 'replace')
292 292 ui.warn(_("abort: error: %s\n") % reason)
293 293 elif (util.safehasattr(inst, "args")
294 294 and inst.args and inst.args[0] == errno.EPIPE):
295 295 if ui.debugflag:
296 296 ui.warn(_("broken pipe\n"))
297 297 elif getattr(inst, "strerror", None):
298 298 if getattr(inst, "filename", None):
299 299 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
300 300 else:
301 301 ui.warn(_("abort: %s\n") % inst.strerror)
302 302 else:
303 303 raise
304 304 except OSError as inst:
305 305 if getattr(inst, "filename", None) is not None:
306 306 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
307 307 else:
308 308 ui.warn(_("abort: %s\n") % inst.strerror)
309 309 except KeyboardInterrupt:
310 310 try:
311 311 ui.warn(_("interrupted!\n"))
312 312 except IOError as inst:
313 313 if inst.errno == errno.EPIPE:
314 314 if ui.debugflag:
315 315 ui.warn(_("\nbroken pipe\n"))
316 316 else:
317 317 raise
318 318 except MemoryError:
319 319 ui.warn(_("abort: out of memory\n"))
320 320 except SystemExit as inst:
321 321 # Commands shouldn't sys.exit directly, but give a return code.
322 322 # Just in case catch this and and pass exit code to caller.
323 323 return inst.code
324 324 except socket.error as inst:
325 325 ui.warn(_("abort: %s\n") % inst.args[-1])
326 326 except: # re-raises
327 327 myver = util.version()
328 328 # For compatibility checking, we discard the portion of the hg
329 329 # version after the + on the assumption that if a "normal
330 330 # user" is running a build with a + in it the packager
331 331 # probably built from fairly close to a tag and anyone with a
332 332 # 'make local' copy of hg (where the version number can be out
333 333 # of date) will be clueful enough to notice the implausible
334 334 # version number and try updating.
335 335 compare = myver.split('+')[0]
336 336 ct = tuplever(compare)
337 337 worst = None, ct, ''
338 338 for name, mod in extensions.extensions():
339 339 testedwith = getattr(mod, 'testedwith', '')
340 340 report = getattr(mod, 'buglink', _('the extension author.'))
341 341 if not testedwith.strip():
342 342 # We found an untested extension. It's likely the culprit.
343 343 worst = name, 'unknown', report
344 344 break
345 345
346 346 # Never blame on extensions bundled with Mercurial.
347 347 if testedwith == 'internal':
348 348 continue
349 349
350 350 tested = [tuplever(t) for t in testedwith.split()]
351 351 if ct in tested:
352 352 continue
353 353
354 354 lower = [t for t in tested if t < ct]
355 355 nearest = max(lower or tested)
356 356 if worst[0] is None or nearest < worst[1]:
357 357 worst = name, nearest, report
358 358 if worst[0] is not None:
359 359 name, testedwith, report = worst
360 360 if not isinstance(testedwith, str):
361 361 testedwith = '.'.join([str(c) for c in testedwith])
362 362 warning = (_('** Unknown exception encountered with '
363 363 'possibly-broken third-party extension %s\n'
364 364 '** which supports versions %s of Mercurial.\n'
365 365 '** Please disable %s and try your action again.\n'
366 366 '** If that fixes the bug please report it to %s\n')
367 367 % (name, testedwith, name, report))
368 368 else:
369 369 warning = (_("** unknown exception encountered, "
370 370 "please report by visiting\n") +
371 371 _("** http://mercurial.selenic.com/wiki/BugTracker\n"))
372 372 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
373 373 (_("** Mercurial Distributed SCM (version %s)\n") % myver) +
374 374 (_("** Extensions loaded: %s\n") %
375 375 ", ".join([x[0] for x in extensions.extensions()])))
376 376 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
377 377 ui.warn(warning)
378 378 raise
379 379
380 380 return -1
381 381
382 382 def tuplever(v):
383 383 try:
384 384 # Assertion: tuplever is only used for extension compatibility
385 385 # checking. Otherwise, the discarding of extra version fields is
386 386 # incorrect.
387 387 return tuple([int(i) for i in v.split('.')[0:2]])
388 388 except ValueError:
389 389 return tuple()
390 390
391 391 def aliasargs(fn, givenargs):
392 392 args = getattr(fn, 'args', [])
393 393 if args:
394 394 cmd = ' '.join(map(util.shellquote, args))
395 395
396 396 nums = []
397 397 def replacer(m):
398 398 num = int(m.group(1)) - 1
399 399 nums.append(num)
400 400 if num < len(givenargs):
401 401 return givenargs[num]
402 402 raise util.Abort(_('too few arguments for command alias'))
403 403 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
404 404 givenargs = [x for i, x in enumerate(givenargs)
405 405 if i not in nums]
406 406 args = shlex.split(cmd)
407 407 return args + givenargs
408 408
409 409 def aliasinterpolate(name, args, cmd):
410 410 '''interpolate args into cmd for shell aliases
411 411
412 412 This also handles $0, $@ and "$@".
413 413 '''
414 414 # util.interpolate can't deal with "$@" (with quotes) because it's only
415 415 # built to match prefix + patterns.
416 416 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
417 417 replacemap['$0'] = name
418 418 replacemap['$$'] = '$'
419 419 replacemap['$@'] = ' '.join(args)
420 420 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
421 421 # parameters, separated out into words. Emulate the same behavior here by
422 422 # quoting the arguments individually. POSIX shells will then typically
423 423 # tokenize each argument into exactly one word.
424 424 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
425 425 # escape '\$' for regex
426 426 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
427 427 r = re.compile(regex)
428 428 return r.sub(lambda x: replacemap[x.group()], cmd)
429 429
430 430 class cmdalias(object):
431 431 def __init__(self, name, definition, cmdtable):
432 432 self.name = self.cmd = name
433 433 self.cmdname = ''
434 434 self.definition = definition
435 435 self.fn = None
436 436 self.args = []
437 437 self.opts = []
438 438 self.help = ''
439 439 self.norepo = True
440 440 self.optionalrepo = False
441 441 self.badalias = None
442 442 self.unknowncmd = False
443 443
444 444 try:
445 445 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
446 446 for alias, e in cmdtable.iteritems():
447 447 if e is entry:
448 448 self.cmd = alias
449 449 break
450 450 self.shadows = True
451 451 except error.UnknownCommand:
452 452 self.shadows = False
453 453
454 454 if not self.definition:
455 455 self.badalias = _("no definition for alias '%s'") % self.name
456 456 return
457 457
458 458 if self.definition.startswith('!'):
459 459 self.shell = True
460 460 def fn(ui, *args):
461 461 env = {'HG_ARGS': ' '.join((self.name,) + args)}
462 462 def _checkvar(m):
463 463 if m.groups()[0] == '$':
464 464 return m.group()
465 465 elif int(m.groups()[0]) <= len(args):
466 466 return m.group()
467 467 else:
468 468 ui.debug("No argument found for substitution "
469 469 "of %i variable in alias '%s' definition."
470 470 % (int(m.groups()[0]), self.name))
471 471 return ''
472 472 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
473 473 cmd = aliasinterpolate(self.name, args, cmd)
474 474 return ui.system(cmd, environ=env)
475 475 self.fn = fn
476 476 return
477 477
478 478 try:
479 479 args = shlex.split(self.definition)
480 480 except ValueError as inst:
481 481 self.badalias = (_("error in definition for alias '%s': %s")
482 482 % (self.name, inst))
483 483 return
484 484 self.cmdname = cmd = args.pop(0)
485 485 args = map(util.expandpath, args)
486 486
487 487 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
488 488 if _earlygetopt([invalidarg], args):
489 489 self.badalias = (_("error in definition for alias '%s': %s may "
490 490 "only be given on the command line")
491 491 % (self.name, invalidarg))
492 492 return
493 493
494 494 try:
495 495 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
496 496 if len(tableentry) > 2:
497 497 self.fn, self.opts, self.help = tableentry
498 498 else:
499 499 self.fn, self.opts = tableentry
500 500
501 501 self.args = aliasargs(self.fn, args)
502 502 if cmd not in commands.norepo.split(' '):
503 503 self.norepo = False
504 504 if cmd in commands.optionalrepo.split(' '):
505 505 self.optionalrepo = True
506 506 if self.help.startswith("hg " + cmd):
507 507 # drop prefix in old-style help lines so hg shows the alias
508 508 self.help = self.help[4 + len(cmd):]
509 509 self.__doc__ = self.fn.__doc__
510 510
511 511 except error.UnknownCommand:
512 512 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
513 513 % (self.name, cmd))
514 514 self.unknowncmd = True
515 515 except error.AmbiguousCommand:
516 516 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
517 517 % (self.name, cmd))
518 518
519 519 def __call__(self, ui, *args, **opts):
520 520 if self.badalias:
521 521 hint = None
522 522 if self.unknowncmd:
523 523 try:
524 524 # check if the command is in a disabled extension
525 525 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
526 526 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
527 527 except error.UnknownCommand:
528 528 pass
529 529 raise util.Abort(self.badalias, hint=hint)
530 530 if self.shadows:
531 531 ui.debug("alias '%s' shadows command '%s'\n" %
532 532 (self.name, self.cmdname))
533 533
534 534 if util.safehasattr(self, 'shell'):
535 535 return self.fn(ui, *args, **opts)
536 536 else:
537 537 try:
538 538 return util.checksignature(self.fn)(ui, *args, **opts)
539 539 except error.SignatureError:
540 540 args = ' '.join([self.cmdname] + self.args)
541 541 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
542 542 raise
543 543
544 544 def addaliases(ui, cmdtable):
545 545 # aliases are processed after extensions have been loaded, so they
546 546 # may use extension commands. Aliases can also use other alias definitions,
547 547 # but only if they have been defined prior to the current definition.
548 548 for alias, definition in ui.configitems('alias'):
549 549 aliasdef = cmdalias(alias, definition, cmdtable)
550 550
551 551 try:
552 552 olddef = cmdtable[aliasdef.cmd][0]
553 553 if olddef.definition == aliasdef.definition:
554 554 continue
555 555 except (KeyError, AttributeError):
556 556 # definition might not exist or it might not be a cmdalias
557 557 pass
558 558
559 559 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
560 560 if aliasdef.norepo:
561 561 commands.norepo += ' %s' % alias
562 562 if aliasdef.optionalrepo:
563 563 commands.optionalrepo += ' %s' % alias
564 564
565 565 def _parse(ui, args):
566 566 options = {}
567 567 cmdoptions = {}
568 568
569 569 try:
570 570 args = fancyopts.fancyopts(args, commands.globalopts, options)
571 571 except fancyopts.getopt.GetoptError as inst:
572 572 raise error.CommandError(None, inst)
573 573
574 574 if args:
575 575 cmd, args = args[0], args[1:]
576 576 aliases, entry = cmdutil.findcmd(cmd, commands.table,
577 577 ui.configbool("ui", "strict"))
578 578 cmd = aliases[0]
579 579 args = aliasargs(entry[0], args)
580 580 defaults = ui.config("defaults", cmd)
581 581 if defaults:
582 582 args = map(util.expandpath, shlex.split(defaults)) + args
583 583 c = list(entry[1])
584 584 else:
585 585 cmd = None
586 586 c = []
587 587
588 588 # combine global options into local
589 589 for o in commands.globalopts:
590 590 c.append((o[0], o[1], options[o[1]], o[3]))
591 591
592 592 try:
593 593 args = fancyopts.fancyopts(args, c, cmdoptions, True)
594 594 except fancyopts.getopt.GetoptError as inst:
595 595 raise error.CommandError(cmd, inst)
596 596
597 597 # separate global options back out
598 598 for o in commands.globalopts:
599 599 n = o[1]
600 600 options[n] = cmdoptions[n]
601 601 del cmdoptions[n]
602 602
603 603 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
604 604
605 605 def _parseconfig(ui, config):
606 606 """parse the --config options from the command line"""
607 607 configs = []
608 608
609 609 for cfg in config:
610 610 try:
611 611 name, value = cfg.split('=', 1)
612 612 section, name = name.split('.', 1)
613 613 if not section or not name:
614 614 raise IndexError
615 615 ui.setconfig(section, name, value, '--config')
616 616 configs.append((section, name, value))
617 617 except (IndexError, ValueError):
618 618 raise util.Abort(_('malformed --config option: %r '
619 619 '(use --config section.name=value)') % cfg)
620 620
621 621 return configs
622 622
623 623 def _earlygetopt(aliases, args):
624 624 """Return list of values for an option (or aliases).
625 625
626 626 The values are listed in the order they appear in args.
627 627 The options and values are removed from args.
628 628
629 629 >>> args = ['x', '--cwd', 'foo', 'y']
630 630 >>> _earlygetopt(['--cwd'], args), args
631 631 (['foo'], ['x', 'y'])
632 632
633 633 >>> args = ['x', '--cwd=bar', 'y']
634 634 >>> _earlygetopt(['--cwd'], args), args
635 635 (['bar'], ['x', 'y'])
636 636
637 637 >>> args = ['x', '-R', 'foo', 'y']
638 638 >>> _earlygetopt(['-R'], args), args
639 639 (['foo'], ['x', 'y'])
640 640
641 641 >>> args = ['x', '-Rbar', 'y']
642 642 >>> _earlygetopt(['-R'], args), args
643 643 (['bar'], ['x', 'y'])
644 644 """
645 645 try:
646 646 argcount = args.index("--")
647 647 except ValueError:
648 648 argcount = len(args)
649 649 shortopts = [opt for opt in aliases if len(opt) == 2]
650 650 values = []
651 651 pos = 0
652 652 while pos < argcount:
653 653 fullarg = arg = args[pos]
654 654 equals = arg.find('=')
655 655 if equals > -1:
656 656 arg = arg[:equals]
657 657 if arg in aliases:
658 658 del args[pos]
659 659 if equals > -1:
660 660 values.append(fullarg[equals + 1:])
661 661 argcount -= 1
662 662 else:
663 663 if pos + 1 >= argcount:
664 664 # ignore and let getopt report an error if there is no value
665 665 break
666 666 values.append(args.pop(pos))
667 667 argcount -= 2
668 668 elif arg[:2] in shortopts:
669 669 # short option can have no following space, e.g. hg log -Rfoo
670 670 values.append(args.pop(pos)[2:])
671 671 argcount -= 1
672 672 else:
673 673 pos += 1
674 674 return values
675 675
676 676 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
677 677 # run pre-hook, and abort if it fails
678 678 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
679 679 pats=cmdpats, opts=cmdoptions)
680 680 ret = _runcommand(ui, options, cmd, d)
681 681 # run post-hook, passing command result
682 682 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
683 683 result=ret, pats=cmdpats, opts=cmdoptions)
684 684 return ret
685 685
686 686 def _getlocal(ui, rpath):
687 687 """Return (path, local ui object) for the given target path.
688 688
689 689 Takes paths in [cwd]/.hg/hgrc into account."
690 690 """
691 691 try:
692 692 wd = os.getcwd()
693 693 except OSError as e:
694 694 raise util.Abort(_("error getting current working directory: %s") %
695 695 e.strerror)
696 696 path = cmdutil.findrepo(wd) or ""
697 697 if not path:
698 698 lui = ui
699 699 else:
700 700 lui = ui.copy()
701 701 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
702 702
703 703 if rpath and rpath[-1]:
704 704 path = lui.expandpath(rpath[-1])
705 705 lui = ui.copy()
706 706 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
707 707
708 708 return path, lui
709 709
710 710 def _checkshellalias(lui, ui, args, precheck=True):
711 711 """Return the function to run the shell alias, if it is required
712 712
713 713 'precheck' is whether this function is invoked before adding
714 714 aliases or not.
715 715 """
716 716 options = {}
717 717
718 718 try:
719 719 args = fancyopts.fancyopts(args, commands.globalopts, options)
720 720 except fancyopts.getopt.GetoptError:
721 721 return
722 722
723 723 if not args:
724 724 return
725 725
726 726 if precheck:
727 727 strict = True
728 728 norepo = commands.norepo
729 729 optionalrepo = commands.optionalrepo
730 730 def restorecommands():
731 731 commands.norepo = norepo
732 732 commands.optionalrepo = optionalrepo
733 733 cmdtable = commands.table.copy()
734 734 addaliases(lui, cmdtable)
735 735 else:
736 736 strict = False
737 737 def restorecommands():
738 738 pass
739 739 cmdtable = commands.table
740 740
741 741 cmd = args[0]
742 742 try:
743 743 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
744 744 except (error.AmbiguousCommand, error.UnknownCommand):
745 745 restorecommands()
746 746 return
747 747
748 748 cmd = aliases[0]
749 749 fn = entry[0]
750 750
751 751 if cmd and util.safehasattr(fn, 'shell'):
752 752 d = lambda: fn(ui, *args[1:])
753 753 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
754 754 [], {})
755 755
756 756 restorecommands()
757 757
758 758 _loaded = set()
759 759 def _dispatch(req):
760 760 args = req.args
761 761 ui = req.ui
762 762
763 763 # check for cwd
764 764 cwd = _earlygetopt(['--cwd'], args)
765 765 if cwd:
766 766 os.chdir(cwd[-1])
767 767
768 768 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
769 769 path, lui = _getlocal(ui, rpath)
770 770
771 771 # Now that we're operating in the right directory/repository with
772 772 # the right config settings, check for shell aliases
773 773 shellaliasfn = _checkshellalias(lui, ui, args)
774 774 if shellaliasfn:
775 775 return shellaliasfn()
776 776
777 777 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
778 778 # reposetup. Programs like TortoiseHg will call _dispatch several
779 779 # times so we keep track of configured extensions in _loaded.
780 780 extensions.loadall(lui)
781 781 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
782 782 # Propagate any changes to lui.__class__ by extensions
783 783 ui.__class__ = lui.__class__
784 784
785 785 # (uisetup and extsetup are handled in extensions.loadall)
786 786
787 787 for name, module in exts:
788 788 cmdtable = getattr(module, 'cmdtable', {})
789 789 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
790 790 if overrides:
791 791 ui.warn(_("extension '%s' overrides commands: %s\n")
792 792 % (name, " ".join(overrides)))
793 793 commands.table.update(cmdtable)
794 794 _loaded.add(name)
795 795
796 796 # (reposetup is handled in hg.repository)
797 797
798 798 addaliases(lui, commands.table)
799 799
800 800 if not lui.configbool("ui", "strict"):
801 801 # All aliases and commands are completely defined, now.
802 802 # Check abbreviation/ambiguity of shell alias again, because shell
803 803 # alias may cause failure of "_parse" (see issue4355)
804 804 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
805 805 if shellaliasfn:
806 806 return shellaliasfn()
807 807
808 808 # check for fallback encoding
809 809 fallback = lui.config('ui', 'fallbackencoding')
810 810 if fallback:
811 811 encoding.fallbackencoding = fallback
812 812
813 813 fullargs = args
814 814 cmd, func, args, options, cmdoptions = _parse(lui, args)
815 815
816 816 if options["config"]:
817 817 raise util.Abort(_("option --config may not be abbreviated!"))
818 818 if options["cwd"]:
819 819 raise util.Abort(_("option --cwd may not be abbreviated!"))
820 820 if options["repository"]:
821 821 raise util.Abort(_(
822 822 "option -R has to be separated from other options (e.g. not -qR) "
823 823 "and --repository may only be abbreviated as --repo!"))
824 824
825 825 if options["encoding"]:
826 826 encoding.encoding = options["encoding"]
827 827 if options["encodingmode"]:
828 828 encoding.encodingmode = options["encodingmode"]
829 829 if options["time"]:
830 830 def get_times():
831 831 t = os.times()
832 832 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
833 833 t = (t[0], t[1], t[2], t[3], time.clock())
834 834 return t
835 835 s = get_times()
836 836 def print_time():
837 837 t = get_times()
838 838 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
839 839 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
840 840 atexit.register(print_time)
841 841
842 842 uis = set([ui, lui])
843 843
844 844 if req.repo:
845 845 uis.add(req.repo.ui)
846 846
847 847 if options['verbose'] or options['debug'] or options['quiet']:
848 848 for opt in ('verbose', 'debug', 'quiet'):
849 849 val = str(bool(options[opt]))
850 850 for ui_ in uis:
851 851 ui_.setconfig('ui', opt, val, '--' + opt)
852 852
853 853 if options['traceback']:
854 854 for ui_ in uis:
855 855 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
856 856
857 857 if options['noninteractive']:
858 858 for ui_ in uis:
859 859 ui_.setconfig('ui', 'interactive', 'off', '-y')
860 860
861 861 if cmdoptions.get('insecure', False):
862 862 for ui_ in uis:
863 863 ui_.setconfig('web', 'cacerts', '!', '--insecure')
864 864
865 865 if options['version']:
866 866 return commands.version_(ui)
867 867 if options['help']:
868 868 return commands.help_(ui, cmd, command=True)
869 869 elif not cmd:
870 870 return commands.help_(ui, 'shortlist')
871 871
872 872 repo = None
873 873 cmdpats = args[:]
874 874 if cmd not in commands.norepo.split():
875 875 # use the repo from the request only if we don't have -R
876 876 if not rpath and not cwd:
877 877 repo = req.repo
878 878
879 879 if repo:
880 880 # set the descriptors of the repo ui to those of ui
881 881 repo.ui.fin = ui.fin
882 882 repo.ui.fout = ui.fout
883 883 repo.ui.ferr = ui.ferr
884 884 else:
885 885 try:
886 886 repo = hg.repository(ui, path=path)
887 887 if not repo.local():
888 888 raise util.Abort(_("repository '%s' is not local") % path)
889 889 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
890 890 except error.RequirementError:
891 891 raise
892 892 except error.RepoError:
893 893 if rpath and rpath[-1]: # invalid -R path
894 894 raise
895 895 if cmd not in commands.optionalrepo.split():
896 896 if (cmd in commands.inferrepo.split() and
897 897 args and not path): # try to infer -R from command args
898 898 repos = map(cmdutil.findrepo, args)
899 899 guess = repos[0]
900 900 if guess and repos.count(guess) == len(repos):
901 901 req.args = ['--repository', guess] + fullargs
902 902 return _dispatch(req)
903 903 if not path:
904 904 raise error.RepoError(_("no repository found in '%s'"
905 905 " (.hg not found)")
906 906 % os.getcwd())
907 907 raise
908 908 if repo:
909 909 ui = repo.ui
910 910 if options['hidden']:
911 911 repo = repo.unfiltered()
912 912 args.insert(0, repo)
913 913 elif rpath:
914 914 ui.warn(_("warning: --repository ignored\n"))
915 915
916 916 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
917 917 ui.log("command", '%s\n', msg)
918 918 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
919 919 try:
920 920 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
921 921 cmdpats, cmdoptions)
922 922 finally:
923 923 if repo and repo != req.repo:
924 924 repo.close()
925 925
926 926 def lsprofile(ui, func, fp):
927 927 format = ui.config('profiling', 'format', default='text')
928 928 field = ui.config('profiling', 'sort', default='inlinetime')
929 929 limit = ui.configint('profiling', 'limit', default=30)
930 930 climit = ui.configint('profiling', 'nested', default=0)
931 931
932 932 if format not in ['text', 'kcachegrind']:
933 933 ui.warn(_("unrecognized profiling format '%s'"
934 934 " - Ignored\n") % format)
935 935 format = 'text'
936 936
937 937 try:
938 938 from . import lsprof
939 939 except ImportError:
940 940 raise util.Abort(_(
941 941 'lsprof not available - install from '
942 942 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
943 943 p = lsprof.Profiler()
944 944 p.enable(subcalls=True)
945 945 try:
946 946 return func()
947 947 finally:
948 948 p.disable()
949 949
950 950 if format == 'kcachegrind':
951 951 from . import lsprofcalltree
952 952 calltree = lsprofcalltree.KCacheGrind(p)
953 953 calltree.output(fp)
954 954 else:
955 955 # format == 'text'
956 956 stats = lsprof.Stats(p.getstats())
957 957 stats.sort(field)
958 958 stats.pprint(limit=limit, file=fp, climit=climit)
959 959
960 960 def flameprofile(ui, func, fp):
961 961 try:
962 962 from flamegraph import flamegraph
963 963 except ImportError:
964 964 raise util.Abort(_(
965 965 'flamegraph not available - install from '
966 966 'https://github.com/evanhempel/python-flamegraph'))
967 967 # developer config: profiling.freq
968 968 freq = ui.configint('profiling', 'freq', default=1000)
969 969 filter_ = None
970 970 collapse_recursion = True
971 971 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
972 972 filter_, collapse_recursion)
973 973 start_time = time.clock()
974 974 try:
975 975 thread.start()
976 976 func()
977 977 finally:
978 978 thread.stop()
979 979 thread.join()
980 980 print 'Collected %d stack frames (%d unique) in %2.2f seconds.' % (
981 981 time.clock() - start_time, thread.num_frames(),
982 982 thread.num_frames(unique=True))
983 983
984 984
985 985 def statprofile(ui, func, fp):
986 986 try:
987 987 import statprof
988 988 except ImportError:
989 989 raise util.Abort(_(
990 990 'statprof not available - install using "easy_install statprof"'))
991 991
992 992 freq = ui.configint('profiling', 'freq', default=1000)
993 993 if freq > 0:
994 994 statprof.reset(freq)
995 995 else:
996 996 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
997 997
998 998 statprof.start()
999 999 try:
1000 1000 return func()
1001 1001 finally:
1002 1002 statprof.stop()
1003 1003 statprof.display(fp)
1004 1004
1005 1005 def _runcommand(ui, options, cmd, cmdfunc):
1006 1006 """Enables the profiler if applicable.
1007 1007
1008 1008 ``profiling.enabled`` - boolean config that enables or disables profiling
1009 1009 """
1010 1010 def checkargs():
1011 1011 try:
1012 1012 return cmdfunc()
1013 1013 except error.SignatureError:
1014 1014 raise error.CommandError(cmd, _("invalid arguments"))
1015 1015
1016 1016 if options['profile'] or ui.configbool('profiling', 'enabled'):
1017 1017 profiler = os.getenv('HGPROF')
1018 1018 if profiler is None:
1019 1019 profiler = ui.config('profiling', 'type', default='ls')
1020 1020 if profiler not in ('ls', 'stat', 'flame'):
1021 1021 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1022 1022 profiler = 'ls'
1023 1023
1024 1024 output = ui.config('profiling', 'output')
1025 1025
1026 1026 if output == 'blackbox':
1027 1027 import StringIO
1028 1028 fp = StringIO.StringIO()
1029 1029 elif output:
1030 1030 path = ui.expandpath(output)
1031 1031 fp = open(path, 'wb')
1032 1032 else:
1033 1033 fp = sys.stderr
1034 1034
1035 1035 try:
1036 1036 if profiler == 'ls':
1037 1037 return lsprofile(ui, checkargs, fp)
1038 1038 elif profiler == 'flame':
1039 1039 return flameprofile(ui, checkargs, fp)
1040 1040 else:
1041 1041 return statprofile(ui, checkargs, fp)
1042 1042 finally:
1043 1043 if output:
1044 1044 if output == 'blackbox':
1045 1045 val = "Profile:\n%s" % fp.getvalue()
1046 1046 # ui.log treats the input as a format string,
1047 1047 # so we need to escape any % signs.
1048 1048 val = val.replace('%', '%%')
1049 1049 ui.log('profile', val)
1050 1050 fp.close()
1051 1051 else:
1052 1052 return checkargs()
General Comments 0
You need to be logged in to leave comments. Login now