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