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