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