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