##// END OF EJS Templates
dispatch: split out warning message generation to separate function...
Martijn Pieters -
r28821:d3369dc6 default
parent child Browse files
Show More
@@ -1,1077 +1,1083
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 336 except: # perhaps re-raises
337 337 if not handlecommandexception(ui):
338 338 raise
339 339
340 340 return -1
341 341
342 342 def aliasargs(fn, givenargs):
343 343 args = getattr(fn, 'args', [])
344 344 if args:
345 345 cmd = ' '.join(map(util.shellquote, args))
346 346
347 347 nums = []
348 348 def replacer(m):
349 349 num = int(m.group(1)) - 1
350 350 nums.append(num)
351 351 if num < len(givenargs):
352 352 return givenargs[num]
353 353 raise error.Abort(_('too few arguments for command alias'))
354 354 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
355 355 givenargs = [x for i, x in enumerate(givenargs)
356 356 if i not in nums]
357 357 args = shlex.split(cmd)
358 358 return args + givenargs
359 359
360 360 def aliasinterpolate(name, args, cmd):
361 361 '''interpolate args into cmd for shell aliases
362 362
363 363 This also handles $0, $@ and "$@".
364 364 '''
365 365 # util.interpolate can't deal with "$@" (with quotes) because it's only
366 366 # built to match prefix + patterns.
367 367 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
368 368 replacemap['$0'] = name
369 369 replacemap['$$'] = '$'
370 370 replacemap['$@'] = ' '.join(args)
371 371 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
372 372 # parameters, separated out into words. Emulate the same behavior here by
373 373 # quoting the arguments individually. POSIX shells will then typically
374 374 # tokenize each argument into exactly one word.
375 375 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
376 376 # escape '\$' for regex
377 377 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
378 378 r = re.compile(regex)
379 379 return r.sub(lambda x: replacemap[x.group()], cmd)
380 380
381 381 class cmdalias(object):
382 382 def __init__(self, name, definition, cmdtable):
383 383 self.name = self.cmd = name
384 384 self.cmdname = ''
385 385 self.definition = definition
386 386 self.fn = None
387 387 self.args = []
388 388 self.opts = []
389 389 self.help = ''
390 390 self.badalias = None
391 391 self.unknowncmd = False
392 392
393 393 try:
394 394 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
395 395 for alias, e in cmdtable.iteritems():
396 396 if e is entry:
397 397 self.cmd = alias
398 398 break
399 399 self.shadows = True
400 400 except error.UnknownCommand:
401 401 self.shadows = False
402 402
403 403 if not self.definition:
404 404 self.badalias = _("no definition for alias '%s'") % self.name
405 405 return
406 406
407 407 if self.definition.startswith('!'):
408 408 self.shell = True
409 409 def fn(ui, *args):
410 410 env = {'HG_ARGS': ' '.join((self.name,) + args)}
411 411 def _checkvar(m):
412 412 if m.groups()[0] == '$':
413 413 return m.group()
414 414 elif int(m.groups()[0]) <= len(args):
415 415 return m.group()
416 416 else:
417 417 ui.debug("No argument found for substitution "
418 418 "of %i variable in alias '%s' definition."
419 419 % (int(m.groups()[0]), self.name))
420 420 return ''
421 421 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
422 422 cmd = aliasinterpolate(self.name, args, cmd)
423 423 return ui.system(cmd, environ=env)
424 424 self.fn = fn
425 425 return
426 426
427 427 try:
428 428 args = shlex.split(self.definition)
429 429 except ValueError as inst:
430 430 self.badalias = (_("error in definition for alias '%s': %s")
431 431 % (self.name, inst))
432 432 return
433 433 self.cmdname = cmd = args.pop(0)
434 434 args = map(util.expandpath, args)
435 435
436 436 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
437 437 if _earlygetopt([invalidarg], args):
438 438 self.badalias = (_("error in definition for alias '%s': %s may "
439 439 "only be given on the command line")
440 440 % (self.name, invalidarg))
441 441 return
442 442
443 443 try:
444 444 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
445 445 if len(tableentry) > 2:
446 446 self.fn, self.opts, self.help = tableentry
447 447 else:
448 448 self.fn, self.opts = tableentry
449 449
450 450 self.args = aliasargs(self.fn, args)
451 451 if self.help.startswith("hg " + cmd):
452 452 # drop prefix in old-style help lines so hg shows the alias
453 453 self.help = self.help[4 + len(cmd):]
454 454 self.__doc__ = self.fn.__doc__
455 455
456 456 except error.UnknownCommand:
457 457 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
458 458 % (self.name, cmd))
459 459 self.unknowncmd = True
460 460 except error.AmbiguousCommand:
461 461 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
462 462 % (self.name, cmd))
463 463
464 464 def __getattr__(self, name):
465 465 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
466 466 if name not in adefaults:
467 467 raise AttributeError(name)
468 468 if self.badalias or util.safehasattr(self, 'shell'):
469 469 return adefaults[name]
470 470 return getattr(self.fn, name)
471 471
472 472 def __call__(self, ui, *args, **opts):
473 473 if self.badalias:
474 474 hint = None
475 475 if self.unknowncmd:
476 476 try:
477 477 # check if the command is in a disabled extension
478 478 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
479 479 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
480 480 except error.UnknownCommand:
481 481 pass
482 482 raise error.Abort(self.badalias, hint=hint)
483 483 if self.shadows:
484 484 ui.debug("alias '%s' shadows command '%s'\n" %
485 485 (self.name, self.cmdname))
486 486
487 487 if util.safehasattr(self, 'shell'):
488 488 return self.fn(ui, *args, **opts)
489 489 else:
490 490 try:
491 491 return util.checksignature(self.fn)(ui, *args, **opts)
492 492 except error.SignatureError:
493 493 args = ' '.join([self.cmdname] + self.args)
494 494 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
495 495 raise
496 496
497 497 def addaliases(ui, cmdtable):
498 498 # aliases are processed after extensions have been loaded, so they
499 499 # may use extension commands. Aliases can also use other alias definitions,
500 500 # but only if they have been defined prior to the current definition.
501 501 for alias, definition in ui.configitems('alias'):
502 502 aliasdef = cmdalias(alias, definition, cmdtable)
503 503
504 504 try:
505 505 olddef = cmdtable[aliasdef.cmd][0]
506 506 if olddef.definition == aliasdef.definition:
507 507 continue
508 508 except (KeyError, AttributeError):
509 509 # definition might not exist or it might not be a cmdalias
510 510 pass
511 511
512 512 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
513 513
514 514 def _parse(ui, args):
515 515 options = {}
516 516 cmdoptions = {}
517 517
518 518 try:
519 519 args = fancyopts.fancyopts(args, commands.globalopts, options)
520 520 except fancyopts.getopt.GetoptError as inst:
521 521 raise error.CommandError(None, inst)
522 522
523 523 if args:
524 524 cmd, args = args[0], args[1:]
525 525 aliases, entry = cmdutil.findcmd(cmd, commands.table,
526 526 ui.configbool("ui", "strict"))
527 527 cmd = aliases[0]
528 528 args = aliasargs(entry[0], args)
529 529 defaults = ui.config("defaults", cmd)
530 530 if defaults:
531 531 args = map(util.expandpath, shlex.split(defaults)) + args
532 532 c = list(entry[1])
533 533 else:
534 534 cmd = None
535 535 c = []
536 536
537 537 # combine global options into local
538 538 for o in commands.globalopts:
539 539 c.append((o[0], o[1], options[o[1]], o[3]))
540 540
541 541 try:
542 542 args = fancyopts.fancyopts(args, c, cmdoptions, True)
543 543 except fancyopts.getopt.GetoptError as inst:
544 544 raise error.CommandError(cmd, inst)
545 545
546 546 # separate global options back out
547 547 for o in commands.globalopts:
548 548 n = o[1]
549 549 options[n] = cmdoptions[n]
550 550 del cmdoptions[n]
551 551
552 552 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
553 553
554 554 def _parseconfig(ui, config):
555 555 """parse the --config options from the command line"""
556 556 configs = []
557 557
558 558 for cfg in config:
559 559 try:
560 560 name, value = [cfgelem.strip()
561 561 for cfgelem in cfg.split('=', 1)]
562 562 section, name = name.split('.', 1)
563 563 if not section or not name:
564 564 raise IndexError
565 565 ui.setconfig(section, name, value, '--config')
566 566 configs.append((section, name, value))
567 567 except (IndexError, ValueError):
568 568 raise error.Abort(_('malformed --config option: %r '
569 569 '(use --config section.name=value)') % cfg)
570 570
571 571 return configs
572 572
573 573 def _earlygetopt(aliases, args):
574 574 """Return list of values for an option (or aliases).
575 575
576 576 The values are listed in the order they appear in args.
577 577 The options and values are removed from args.
578 578
579 579 >>> args = ['x', '--cwd', 'foo', 'y']
580 580 >>> _earlygetopt(['--cwd'], args), args
581 581 (['foo'], ['x', 'y'])
582 582
583 583 >>> args = ['x', '--cwd=bar', 'y']
584 584 >>> _earlygetopt(['--cwd'], args), args
585 585 (['bar'], ['x', 'y'])
586 586
587 587 >>> args = ['x', '-R', 'foo', 'y']
588 588 >>> _earlygetopt(['-R'], args), args
589 589 (['foo'], ['x', 'y'])
590 590
591 591 >>> args = ['x', '-Rbar', 'y']
592 592 >>> _earlygetopt(['-R'], args), args
593 593 (['bar'], ['x', 'y'])
594 594 """
595 595 try:
596 596 argcount = args.index("--")
597 597 except ValueError:
598 598 argcount = len(args)
599 599 shortopts = [opt for opt in aliases if len(opt) == 2]
600 600 values = []
601 601 pos = 0
602 602 while pos < argcount:
603 603 fullarg = arg = args[pos]
604 604 equals = arg.find('=')
605 605 if equals > -1:
606 606 arg = arg[:equals]
607 607 if arg in aliases:
608 608 del args[pos]
609 609 if equals > -1:
610 610 values.append(fullarg[equals + 1:])
611 611 argcount -= 1
612 612 else:
613 613 if pos + 1 >= argcount:
614 614 # ignore and let getopt report an error if there is no value
615 615 break
616 616 values.append(args.pop(pos))
617 617 argcount -= 2
618 618 elif arg[:2] in shortopts:
619 619 # short option can have no following space, e.g. hg log -Rfoo
620 620 values.append(args.pop(pos)[2:])
621 621 argcount -= 1
622 622 else:
623 623 pos += 1
624 624 return values
625 625
626 626 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
627 627 # run pre-hook, and abort if it fails
628 628 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
629 629 pats=cmdpats, opts=cmdoptions)
630 630 ret = _runcommand(ui, options, cmd, d)
631 631 # run post-hook, passing command result
632 632 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
633 633 result=ret, pats=cmdpats, opts=cmdoptions)
634 634 return ret
635 635
636 636 def _getlocal(ui, rpath, wd=None):
637 637 """Return (path, local ui object) for the given target path.
638 638
639 639 Takes paths in [cwd]/.hg/hgrc into account."
640 640 """
641 641 if wd is None:
642 642 try:
643 643 wd = os.getcwd()
644 644 except OSError as e:
645 645 raise error.Abort(_("error getting current working directory: %s") %
646 646 e.strerror)
647 647 path = cmdutil.findrepo(wd) or ""
648 648 if not path:
649 649 lui = ui
650 650 else:
651 651 lui = ui.copy()
652 652 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
653 653
654 654 if rpath and rpath[-1]:
655 655 path = lui.expandpath(rpath[-1])
656 656 lui = ui.copy()
657 657 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
658 658
659 659 return path, lui
660 660
661 661 def _checkshellalias(lui, ui, args, precheck=True):
662 662 """Return the function to run the shell alias, if it is required
663 663
664 664 'precheck' is whether this function is invoked before adding
665 665 aliases or not.
666 666 """
667 667 options = {}
668 668
669 669 try:
670 670 args = fancyopts.fancyopts(args, commands.globalopts, options)
671 671 except fancyopts.getopt.GetoptError:
672 672 return
673 673
674 674 if not args:
675 675 return
676 676
677 677 if precheck:
678 678 strict = True
679 679 cmdtable = commands.table.copy()
680 680 addaliases(lui, cmdtable)
681 681 else:
682 682 strict = False
683 683 cmdtable = commands.table
684 684
685 685 cmd = args[0]
686 686 try:
687 687 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
688 688 except (error.AmbiguousCommand, error.UnknownCommand):
689 689 return
690 690
691 691 cmd = aliases[0]
692 692 fn = entry[0]
693 693
694 694 if cmd and util.safehasattr(fn, 'shell'):
695 695 d = lambda: fn(ui, *args[1:])
696 696 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
697 697 [], {})
698 698
699 699 def _cmdattr(ui, cmd, func, attr):
700 700 try:
701 701 return getattr(func, attr)
702 702 except AttributeError:
703 703 ui.deprecwarn("missing attribute '%s', use @command decorator "
704 704 "to register '%s'" % (attr, cmd), '3.8')
705 705 return False
706 706
707 707 _loaded = set()
708 708
709 709 # list of (objname, loadermod, loadername) tuple:
710 710 # - objname is the name of an object in extension module, from which
711 711 # extra information is loaded
712 712 # - loadermod is the module where loader is placed
713 713 # - loadername is the name of the function, which takes (ui, extensionname,
714 714 # extraobj) arguments
715 715 extraloaders = [
716 716 ('cmdtable', commands, 'loadcmdtable'),
717 717 ('filesetpredicate', fileset, 'loadpredicate'),
718 718 ('revsetpredicate', revset, 'loadpredicate'),
719 719 ('templatefilter', templatefilters, 'loadfilter'),
720 720 ('templatefunc', templater, 'loadfunction'),
721 721 ('templatekeyword', templatekw, 'loadkeyword'),
722 722 ]
723 723
724 724 def _dispatch(req):
725 725 args = req.args
726 726 ui = req.ui
727 727
728 728 # check for cwd
729 729 cwd = _earlygetopt(['--cwd'], args)
730 730 if cwd:
731 731 os.chdir(cwd[-1])
732 732
733 733 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
734 734 path, lui = _getlocal(ui, rpath)
735 735
736 736 # Now that we're operating in the right directory/repository with
737 737 # the right config settings, check for shell aliases
738 738 shellaliasfn = _checkshellalias(lui, ui, args)
739 739 if shellaliasfn:
740 740 return shellaliasfn()
741 741
742 742 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
743 743 # reposetup. Programs like TortoiseHg will call _dispatch several
744 744 # times so we keep track of configured extensions in _loaded.
745 745 extensions.loadall(lui)
746 746 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
747 747 # Propagate any changes to lui.__class__ by extensions
748 748 ui.__class__ = lui.__class__
749 749
750 750 # (uisetup and extsetup are handled in extensions.loadall)
751 751
752 752 for name, module in exts:
753 753 for objname, loadermod, loadername in extraloaders:
754 754 extraobj = getattr(module, objname, None)
755 755 if extraobj is not None:
756 756 getattr(loadermod, loadername)(ui, name, extraobj)
757 757 _loaded.add(name)
758 758
759 759 # (reposetup is handled in hg.repository)
760 760
761 761 addaliases(lui, commands.table)
762 762
763 763 if not lui.configbool("ui", "strict"):
764 764 # All aliases and commands are completely defined, now.
765 765 # Check abbreviation/ambiguity of shell alias again, because shell
766 766 # alias may cause failure of "_parse" (see issue4355)
767 767 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
768 768 if shellaliasfn:
769 769 return shellaliasfn()
770 770
771 771 # check for fallback encoding
772 772 fallback = lui.config('ui', 'fallbackencoding')
773 773 if fallback:
774 774 encoding.fallbackencoding = fallback
775 775
776 776 fullargs = args
777 777 cmd, func, args, options, cmdoptions = _parse(lui, args)
778 778
779 779 if options["config"]:
780 780 raise error.Abort(_("option --config may not be abbreviated!"))
781 781 if options["cwd"]:
782 782 raise error.Abort(_("option --cwd may not be abbreviated!"))
783 783 if options["repository"]:
784 784 raise error.Abort(_(
785 785 "option -R has to be separated from other options (e.g. not -qR) "
786 786 "and --repository may only be abbreviated as --repo!"))
787 787
788 788 if options["encoding"]:
789 789 encoding.encoding = options["encoding"]
790 790 if options["encodingmode"]:
791 791 encoding.encodingmode = options["encodingmode"]
792 792 if options["time"]:
793 793 def get_times():
794 794 t = os.times()
795 795 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
796 796 t = (t[0], t[1], t[2], t[3], time.clock())
797 797 return t
798 798 s = get_times()
799 799 def print_time():
800 800 t = get_times()
801 801 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
802 802 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
803 803 atexit.register(print_time)
804 804
805 805 uis = set([ui, lui])
806 806
807 807 if req.repo:
808 808 uis.add(req.repo.ui)
809 809
810 810 if options['verbose'] or options['debug'] or options['quiet']:
811 811 for opt in ('verbose', 'debug', 'quiet'):
812 812 val = str(bool(options[opt]))
813 813 for ui_ in uis:
814 814 ui_.setconfig('ui', opt, val, '--' + opt)
815 815
816 816 if options['traceback']:
817 817 for ui_ in uis:
818 818 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
819 819
820 820 if options['noninteractive']:
821 821 for ui_ in uis:
822 822 ui_.setconfig('ui', 'interactive', 'off', '-y')
823 823
824 824 if cmdoptions.get('insecure', False):
825 825 for ui_ in uis:
826 826 ui_.setconfig('web', 'cacerts', '!', '--insecure')
827 827
828 828 if options['version']:
829 829 return commands.version_(ui)
830 830 if options['help']:
831 831 return commands.help_(ui, cmd, command=cmd is not None)
832 832 elif not cmd:
833 833 return commands.help_(ui, 'shortlist')
834 834
835 835 repo = None
836 836 cmdpats = args[:]
837 837 if not _cmdattr(ui, cmd, func, 'norepo'):
838 838 # use the repo from the request only if we don't have -R
839 839 if not rpath and not cwd:
840 840 repo = req.repo
841 841
842 842 if repo:
843 843 # set the descriptors of the repo ui to those of ui
844 844 repo.ui.fin = ui.fin
845 845 repo.ui.fout = ui.fout
846 846 repo.ui.ferr = ui.ferr
847 847 else:
848 848 try:
849 849 repo = hg.repository(ui, path=path)
850 850 if not repo.local():
851 851 raise error.Abort(_("repository '%s' is not local") % path)
852 852 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
853 853 except error.RequirementError:
854 854 raise
855 855 except error.RepoError:
856 856 if rpath and rpath[-1]: # invalid -R path
857 857 raise
858 858 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
859 859 if (_cmdattr(ui, cmd, func, 'inferrepo') and
860 860 args and not path):
861 861 # try to infer -R from command args
862 862 repos = map(cmdutil.findrepo, args)
863 863 guess = repos[0]
864 864 if guess and repos.count(guess) == len(repos):
865 865 req.args = ['--repository', guess] + fullargs
866 866 return _dispatch(req)
867 867 if not path:
868 868 raise error.RepoError(_("no repository found in '%s'"
869 869 " (.hg not found)")
870 870 % os.getcwd())
871 871 raise
872 872 if repo:
873 873 ui = repo.ui
874 874 if options['hidden']:
875 875 repo = repo.unfiltered()
876 876 args.insert(0, repo)
877 877 elif rpath:
878 878 ui.warn(_("warning: --repository ignored\n"))
879 879
880 880 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
881 881 ui.log("command", '%s\n', msg)
882 882 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
883 883 try:
884 884 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
885 885 cmdpats, cmdoptions)
886 886 finally:
887 887 if repo and repo != req.repo:
888 888 repo.close()
889 889
890 890 def lsprofile(ui, func, fp):
891 891 format = ui.config('profiling', 'format', default='text')
892 892 field = ui.config('profiling', 'sort', default='inlinetime')
893 893 limit = ui.configint('profiling', 'limit', default=30)
894 894 climit = ui.configint('profiling', 'nested', default=0)
895 895
896 896 if format not in ['text', 'kcachegrind']:
897 897 ui.warn(_("unrecognized profiling format '%s'"
898 898 " - Ignored\n") % format)
899 899 format = 'text'
900 900
901 901 try:
902 902 from . import lsprof
903 903 except ImportError:
904 904 raise error.Abort(_(
905 905 'lsprof not available - install from '
906 906 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
907 907 p = lsprof.Profiler()
908 908 p.enable(subcalls=True)
909 909 try:
910 910 return func()
911 911 finally:
912 912 p.disable()
913 913
914 914 if format == 'kcachegrind':
915 915 from . import lsprofcalltree
916 916 calltree = lsprofcalltree.KCacheGrind(p)
917 917 calltree.output(fp)
918 918 else:
919 919 # format == 'text'
920 920 stats = lsprof.Stats(p.getstats())
921 921 stats.sort(field)
922 922 stats.pprint(limit=limit, file=fp, climit=climit)
923 923
924 924 def flameprofile(ui, func, fp):
925 925 try:
926 926 from flamegraph import flamegraph
927 927 except ImportError:
928 928 raise error.Abort(_(
929 929 'flamegraph not available - install from '
930 930 'https://github.com/evanhempel/python-flamegraph'))
931 931 # developer config: profiling.freq
932 932 freq = ui.configint('profiling', 'freq', default=1000)
933 933 filter_ = None
934 934 collapse_recursion = True
935 935 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
936 936 filter_, collapse_recursion)
937 937 start_time = time.clock()
938 938 try:
939 939 thread.start()
940 940 func()
941 941 finally:
942 942 thread.stop()
943 943 thread.join()
944 944 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
945 945 time.clock() - start_time, thread.num_frames(),
946 946 thread.num_frames(unique=True)))
947 947
948 948
949 949 def statprofile(ui, func, fp):
950 950 try:
951 951 import statprof
952 952 except ImportError:
953 953 raise error.Abort(_(
954 954 'statprof not available - install using "easy_install statprof"'))
955 955
956 956 freq = ui.configint('profiling', 'freq', default=1000)
957 957 if freq > 0:
958 958 statprof.reset(freq)
959 959 else:
960 960 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
961 961
962 962 statprof.start()
963 963 try:
964 964 return func()
965 965 finally:
966 966 statprof.stop()
967 967 statprof.display(fp)
968 968
969 969 def _runcommand(ui, options, cmd, cmdfunc):
970 970 """Enables the profiler if applicable.
971 971
972 972 ``profiling.enabled`` - boolean config that enables or disables profiling
973 973 """
974 974 def checkargs():
975 975 try:
976 976 return cmdfunc()
977 977 except error.SignatureError:
978 978 raise error.CommandError(cmd, _("invalid arguments"))
979 979
980 980 if options['profile'] or ui.configbool('profiling', 'enabled'):
981 981 profiler = os.getenv('HGPROF')
982 982 if profiler is None:
983 983 profiler = ui.config('profiling', 'type', default='ls')
984 984 if profiler not in ('ls', 'stat', 'flame'):
985 985 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
986 986 profiler = 'ls'
987 987
988 988 output = ui.config('profiling', 'output')
989 989
990 990 if output == 'blackbox':
991 991 import StringIO
992 992 fp = StringIO.StringIO()
993 993 elif output:
994 994 path = ui.expandpath(output)
995 995 fp = open(path, 'wb')
996 996 else:
997 997 fp = sys.stderr
998 998
999 999 try:
1000 1000 if profiler == 'ls':
1001 1001 return lsprofile(ui, checkargs, fp)
1002 1002 elif profiler == 'flame':
1003 1003 return flameprofile(ui, checkargs, fp)
1004 1004 else:
1005 1005 return statprofile(ui, checkargs, fp)
1006 1006 finally:
1007 1007 if output:
1008 1008 if output == 'blackbox':
1009 1009 val = "Profile:\n%s" % fp.getvalue()
1010 1010 # ui.log treats the input as a format string,
1011 1011 # so we need to escape any % signs.
1012 1012 val = val.replace('%', '%%')
1013 1013 ui.log('profile', val)
1014 1014 fp.close()
1015 1015 else:
1016 1016 return checkargs()
1017 1017
1018 def handlecommandexception(ui):
1019 """Produce a warning message for broken commands
1018 def _exceptionwarning(ui):
1019 """Produce a warning message for the current active exception"""
1020 1020
1021 Called when handling an exception; the exception is reraised if
1022 this function returns False, ignored otherwise.
1023 """
1024 1021 # For compatibility checking, we discard the portion of the hg
1025 1022 # version after the + on the assumption that if a "normal
1026 1023 # user" is running a build with a + in it the packager
1027 1024 # probably built from fairly close to a tag and anyone with a
1028 1025 # 'make local' copy of hg (where the version number can be out
1029 1026 # of date) will be clueful enough to notice the implausible
1030 1027 # version number and try updating.
1031 1028 ct = util.versiontuple(n=2)
1032 1029 worst = None, ct, ''
1033 1030 if ui.config('ui', 'supportcontact', None) is None:
1034 1031 for name, mod in extensions.extensions():
1035 1032 testedwith = getattr(mod, 'testedwith', '')
1036 1033 report = getattr(mod, 'buglink', _('the extension author.'))
1037 1034 if not testedwith.strip():
1038 1035 # We found an untested extension. It's likely the culprit.
1039 1036 worst = name, 'unknown', report
1040 1037 break
1041 1038
1042 1039 # Never blame on extensions bundled with Mercurial.
1043 1040 if testedwith == 'internal':
1044 1041 continue
1045 1042
1046 1043 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1047 1044 if ct in tested:
1048 1045 continue
1049 1046
1050 1047 lower = [t for t in tested if t < ct]
1051 1048 nearest = max(lower or tested)
1052 1049 if worst[0] is None or nearest < worst[1]:
1053 1050 worst = name, nearest, report
1054 1051 if worst[0] is not None:
1055 1052 name, testedwith, report = worst
1056 1053 if not isinstance(testedwith, str):
1057 1054 testedwith = '.'.join([str(c) for c in testedwith])
1058 1055 warning = (_('** Unknown exception encountered with '
1059 1056 'possibly-broken third-party extension %s\n'
1060 1057 '** which supports versions %s of Mercurial.\n'
1061 1058 '** Please disable %s and try your action again.\n'
1062 1059 '** If that fixes the bug please report it to %s\n')
1063 1060 % (name, testedwith, name, report))
1064 1061 else:
1065 1062 bugtracker = ui.config('ui', 'supportcontact', None)
1066 1063 if bugtracker is None:
1067 1064 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1068 1065 warning = (_("** unknown exception encountered, "
1069 1066 "please report by visiting\n** ") + bugtracker + '\n')
1070 1067 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
1071 1068 (_("** Mercurial Distributed SCM (version %s)\n") %
1072 1069 util.version()) +
1073 1070 (_("** Extensions loaded: %s\n") %
1074 1071 ", ".join([x[0] for x in extensions.extensions()])))
1072 return warning
1073
1074 def handlecommandexception(ui):
1075 """Produce a warning message for broken commands
1076
1077 Called when handling an exception; the exception is reraised if
1078 this function returns False, ignored otherwise.
1079 """
1080 warning = _exceptionwarning(ui)
1075 1081 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1076 1082 ui.warn(warning)
1077 1083 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now