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