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