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