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