##// END OF EJS Templates
dispatch: mark callcatch() as a private function
Yuya Nishihara -
r32040:0fb78cb9 default
parent child Browse files
Show More
@@ -1,961 +1,961 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 difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 debugcommands,
29 29 demandimport,
30 30 encoding,
31 31 error,
32 32 extensions,
33 33 fancyopts,
34 34 fileset,
35 35 help,
36 36 hg,
37 37 hook,
38 38 profiling,
39 39 pycompat,
40 40 revset,
41 41 scmutil,
42 42 templatefilters,
43 43 templatekw,
44 44 templater,
45 45 ui as uimod,
46 46 util,
47 47 )
48 48
49 49 class request(object):
50 50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
51 51 ferr=None):
52 52 self.args = args
53 53 self.ui = ui
54 54 self.repo = repo
55 55
56 56 # input/output/error streams
57 57 self.fin = fin
58 58 self.fout = fout
59 59 self.ferr = ferr
60 60
61 61 def _runexithandlers(self):
62 62 exc = None
63 63 handlers = self.ui._exithandlers
64 64 try:
65 65 while handlers:
66 66 func, args, kwargs = handlers.pop()
67 67 try:
68 68 func(*args, **kwargs)
69 69 except: # re-raises below
70 70 if exc is None:
71 71 exc = sys.exc_info()[1]
72 72 self.ui.warn(('error in exit handlers:\n'))
73 73 self.ui.traceback(force=True)
74 74 finally:
75 75 if exc is not None:
76 76 raise exc
77 77
78 78 def run():
79 79 "run the command in sys.argv"
80 80 req = request(pycompat.sysargv[1:])
81 81 err = None
82 82 try:
83 83 status = (dispatch(req) or 0) & 255
84 84 except error.StdioError as err:
85 85 status = -1
86 86 if util.safehasattr(req.ui, 'fout'):
87 87 try:
88 88 req.ui.fout.close()
89 89 except IOError as err:
90 90 status = -1
91 91 if util.safehasattr(req.ui, 'ferr'):
92 92 if err is not None and err.errno != errno.EPIPE:
93 93 req.ui.ferr.write('abort: %s\n' % err.strerror)
94 94 req.ui.ferr.close()
95 95 sys.exit(status & 255)
96 96
97 97 def _getsimilar(symbols, value):
98 98 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
99 99 # The cutoff for similarity here is pretty arbitrary. It should
100 100 # probably be investigated and tweaked.
101 101 return [s for s in symbols if sim(s) > 0.6]
102 102
103 103 def _reportsimilar(write, similar):
104 104 if len(similar) == 1:
105 105 write(_("(did you mean %s?)\n") % similar[0])
106 106 elif similar:
107 107 ss = ", ".join(sorted(similar))
108 108 write(_("(did you mean one of %s?)\n") % ss)
109 109
110 110 def _formatparse(write, inst):
111 111 similar = []
112 112 if isinstance(inst, error.UnknownIdentifier):
113 113 # make sure to check fileset first, as revset can invoke fileset
114 114 similar = _getsimilar(inst.symbols, inst.function)
115 115 if len(inst.args) > 1:
116 116 write(_("hg: parse error at %s: %s\n") %
117 117 (inst.args[1], inst.args[0]))
118 118 if (inst.args[0][0] == ' '):
119 119 write(_("unexpected leading whitespace\n"))
120 120 else:
121 121 write(_("hg: parse error: %s\n") % inst.args[0])
122 122 _reportsimilar(write, similar)
123 123 if inst.hint:
124 124 write(_("(%s)\n") % inst.hint)
125 125
126 126 def _formatargs(args):
127 127 return ' '.join(util.shellquote(a) for a in args)
128 128
129 129 def dispatch(req):
130 130 "run the command specified in req.args"
131 131 if req.ferr:
132 132 ferr = req.ferr
133 133 elif req.ui:
134 134 ferr = req.ui.ferr
135 135 else:
136 136 ferr = util.stderr
137 137
138 138 try:
139 139 if not req.ui:
140 140 req.ui = uimod.ui.load()
141 141 if '--traceback' in req.args:
142 142 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
143 143
144 144 # set ui streams from the request
145 145 if req.fin:
146 146 req.ui.fin = req.fin
147 147 if req.fout:
148 148 req.ui.fout = req.fout
149 149 if req.ferr:
150 150 req.ui.ferr = req.ferr
151 151 except error.Abort as inst:
152 152 ferr.write(_("abort: %s\n") % inst)
153 153 if inst.hint:
154 154 ferr.write(_("(%s)\n") % inst.hint)
155 155 return -1
156 156 except error.ParseError as inst:
157 157 _formatparse(ferr.write, inst)
158 158 return -1
159 159
160 160 msg = _formatargs(req.args)
161 161 starttime = util.timer()
162 162 ret = None
163 163 try:
164 164 ret = _runcatch(req)
165 165 except KeyboardInterrupt:
166 166 try:
167 167 req.ui.warn(_("interrupted!\n"))
168 168 except IOError as inst:
169 169 if inst.errno != errno.EPIPE:
170 170 raise
171 171 ret = -1
172 172 finally:
173 173 duration = util.timer() - starttime
174 174 req.ui.flush()
175 175 if req.ui.logblockedtimes:
176 176 req.ui._blockedtimes['command_duration'] = duration * 1000
177 177 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
178 178 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
179 179 msg, ret or 0, duration)
180 180 try:
181 181 req._runexithandlers()
182 182 except: # exiting, so no re-raises
183 183 ret = ret or -1
184 184 return ret
185 185
186 186 def _runcatch(req):
187 187 def catchterm(*args):
188 188 raise error.SignalInterrupt
189 189
190 190 ui = req.ui
191 191 try:
192 192 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
193 193 num = getattr(signal, name, None)
194 194 if num:
195 195 signal.signal(num, catchterm)
196 196 except ValueError:
197 197 pass # happens if called in a thread
198 198
199 199 def _runcatchfunc():
200 200 try:
201 201 debugger = 'pdb'
202 202 debugtrace = {
203 203 'pdb' : pdb.set_trace
204 204 }
205 205 debugmortem = {
206 206 'pdb' : pdb.post_mortem
207 207 }
208 208
209 209 # read --config before doing anything else
210 210 # (e.g. to change trust settings for reading .hg/hgrc)
211 211 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
212 212
213 213 if req.repo:
214 214 # copy configs that were passed on the cmdline (--config) to
215 215 # the repo ui
216 216 for sec, name, val in cfgs:
217 217 req.repo.ui.setconfig(sec, name, val, source='--config')
218 218
219 219 # developer config: ui.debugger
220 220 debugger = ui.config("ui", "debugger")
221 221 debugmod = pdb
222 222 if not debugger or ui.plain():
223 223 # if we are in HGPLAIN mode, then disable custom debugging
224 224 debugger = 'pdb'
225 225 elif '--debugger' in req.args:
226 226 # This import can be slow for fancy debuggers, so only
227 227 # do it when absolutely necessary, i.e. when actual
228 228 # debugging has been requested
229 229 with demandimport.deactivated():
230 230 try:
231 231 debugmod = __import__(debugger)
232 232 except ImportError:
233 233 pass # Leave debugmod = pdb
234 234
235 235 debugtrace[debugger] = debugmod.set_trace
236 236 debugmortem[debugger] = debugmod.post_mortem
237 237
238 238 # enter the debugger before command execution
239 239 if '--debugger' in req.args:
240 240 ui.warn(_("entering debugger - "
241 241 "type c to continue starting hg or h for help\n"))
242 242
243 243 if (debugger != 'pdb' and
244 244 debugtrace[debugger] == debugtrace['pdb']):
245 245 ui.warn(_("%s debugger specified "
246 246 "but its module was not found\n") % debugger)
247 247 with demandimport.deactivated():
248 248 debugtrace[debugger]()
249 249 try:
250 250 return _dispatch(req)
251 251 finally:
252 252 ui.flush()
253 253 except: # re-raises
254 254 # enter the debugger when we hit an exception
255 255 if '--debugger' in req.args:
256 256 traceback.print_exc()
257 257 debugmortem[debugger](sys.exc_info()[2])
258 258 ui.traceback()
259 259 raise
260 260
261 return callcatch(ui, _runcatchfunc)
261 return _callcatch(ui, _runcatchfunc)
262 262
263 def callcatch(ui, func):
263 def _callcatch(ui, func):
264 264 """like scmutil.callcatch but handles more high-level exceptions about
265 265 config parsing and commands. besides, use handlecommandexception to handle
266 266 uncaught exceptions.
267 267 """
268 268 try:
269 269 return scmutil.callcatch(ui, func)
270 270 except error.AmbiguousCommand as inst:
271 271 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
272 272 (inst.args[0], " ".join(inst.args[1])))
273 273 except error.CommandError as inst:
274 274 if inst.args[0]:
275 275 ui.pager('help')
276 276 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
277 277 commands.help_(ui, inst.args[0], full=False, command=True)
278 278 else:
279 279 ui.pager('help')
280 280 ui.warn(_("hg: %s\n") % inst.args[1])
281 281 commands.help_(ui, 'shortlist')
282 282 except error.ParseError as inst:
283 283 _formatparse(ui.warn, inst)
284 284 return -1
285 285 except error.UnknownCommand as inst:
286 286 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
287 287 try:
288 288 # check if the command is in a disabled extension
289 289 # (but don't check for extensions themselves)
290 290 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
291 291 ui.warn(nocmdmsg)
292 292 ui.write(formatted)
293 293 except (error.UnknownCommand, error.Abort):
294 294 suggested = False
295 295 if len(inst.args) == 2:
296 296 sim = _getsimilar(inst.args[1], inst.args[0])
297 297 if sim:
298 298 ui.warn(nocmdmsg)
299 299 _reportsimilar(ui.warn, sim)
300 300 suggested = True
301 301 if not suggested:
302 302 ui.pager('help')
303 303 ui.warn(nocmdmsg)
304 304 commands.help_(ui, 'shortlist')
305 305 except IOError:
306 306 raise
307 307 except KeyboardInterrupt:
308 308 raise
309 309 except: # probably re-raises
310 310 if not handlecommandexception(ui):
311 311 raise
312 312
313 313 return -1
314 314
315 315 def aliasargs(fn, givenargs):
316 316 args = getattr(fn, 'args', [])
317 317 if args:
318 318 cmd = ' '.join(map(util.shellquote, args))
319 319
320 320 nums = []
321 321 def replacer(m):
322 322 num = int(m.group(1)) - 1
323 323 nums.append(num)
324 324 if num < len(givenargs):
325 325 return givenargs[num]
326 326 raise error.Abort(_('too few arguments for command alias'))
327 327 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
328 328 givenargs = [x for i, x in enumerate(givenargs)
329 329 if i not in nums]
330 330 args = pycompat.shlexsplit(cmd)
331 331 return args + givenargs
332 332
333 333 def aliasinterpolate(name, args, cmd):
334 334 '''interpolate args into cmd for shell aliases
335 335
336 336 This also handles $0, $@ and "$@".
337 337 '''
338 338 # util.interpolate can't deal with "$@" (with quotes) because it's only
339 339 # built to match prefix + patterns.
340 340 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
341 341 replacemap['$0'] = name
342 342 replacemap['$$'] = '$'
343 343 replacemap['$@'] = ' '.join(args)
344 344 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
345 345 # parameters, separated out into words. Emulate the same behavior here by
346 346 # quoting the arguments individually. POSIX shells will then typically
347 347 # tokenize each argument into exactly one word.
348 348 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
349 349 # escape '\$' for regex
350 350 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
351 351 r = re.compile(regex)
352 352 return r.sub(lambda x: replacemap[x.group()], cmd)
353 353
354 354 class cmdalias(object):
355 355 def __init__(self, name, definition, cmdtable, source):
356 356 self.name = self.cmd = name
357 357 self.cmdname = ''
358 358 self.definition = definition
359 359 self.fn = None
360 360 self.givenargs = []
361 361 self.opts = []
362 362 self.help = ''
363 363 self.badalias = None
364 364 self.unknowncmd = False
365 365 self.source = source
366 366
367 367 try:
368 368 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
369 369 for alias, e in cmdtable.iteritems():
370 370 if e is entry:
371 371 self.cmd = alias
372 372 break
373 373 self.shadows = True
374 374 except error.UnknownCommand:
375 375 self.shadows = False
376 376
377 377 if not self.definition:
378 378 self.badalias = _("no definition for alias '%s'") % self.name
379 379 return
380 380
381 381 if self.definition.startswith('!'):
382 382 self.shell = True
383 383 def fn(ui, *args):
384 384 env = {'HG_ARGS': ' '.join((self.name,) + args)}
385 385 def _checkvar(m):
386 386 if m.groups()[0] == '$':
387 387 return m.group()
388 388 elif int(m.groups()[0]) <= len(args):
389 389 return m.group()
390 390 else:
391 391 ui.debug("No argument found for substitution "
392 392 "of %i variable in alias '%s' definition."
393 393 % (int(m.groups()[0]), self.name))
394 394 return ''
395 395 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
396 396 cmd = aliasinterpolate(self.name, args, cmd)
397 397 return ui.system(cmd, environ=env,
398 398 blockedtag='alias_%s' % self.name)
399 399 self.fn = fn
400 400 return
401 401
402 402 try:
403 403 args = pycompat.shlexsplit(self.definition)
404 404 except ValueError as inst:
405 405 self.badalias = (_("error in definition for alias '%s': %s")
406 406 % (self.name, inst))
407 407 return
408 408 self.cmdname = cmd = args.pop(0)
409 409 self.givenargs = args
410 410
411 411 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
412 412 if _earlygetopt([invalidarg], args):
413 413 self.badalias = (_("error in definition for alias '%s': %s may "
414 414 "only be given on the command line")
415 415 % (self.name, invalidarg))
416 416 return
417 417
418 418 try:
419 419 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
420 420 if len(tableentry) > 2:
421 421 self.fn, self.opts, self.help = tableentry
422 422 else:
423 423 self.fn, self.opts = tableentry
424 424
425 425 if self.help.startswith("hg " + cmd):
426 426 # drop prefix in old-style help lines so hg shows the alias
427 427 self.help = self.help[4 + len(cmd):]
428 428 self.__doc__ = self.fn.__doc__
429 429
430 430 except error.UnknownCommand:
431 431 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
432 432 % (self.name, cmd))
433 433 self.unknowncmd = True
434 434 except error.AmbiguousCommand:
435 435 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
436 436 % (self.name, cmd))
437 437
438 438 @property
439 439 def args(self):
440 440 args = pycompat.maplist(util.expandpath, self.givenargs)
441 441 return aliasargs(self.fn, args)
442 442
443 443 def __getattr__(self, name):
444 444 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
445 445 if name not in adefaults:
446 446 raise AttributeError(name)
447 447 if self.badalias or util.safehasattr(self, 'shell'):
448 448 return adefaults[name]
449 449 return getattr(self.fn, name)
450 450
451 451 def __call__(self, ui, *args, **opts):
452 452 if self.badalias:
453 453 hint = None
454 454 if self.unknowncmd:
455 455 try:
456 456 # check if the command is in a disabled extension
457 457 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
458 458 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
459 459 except error.UnknownCommand:
460 460 pass
461 461 raise error.Abort(self.badalias, hint=hint)
462 462 if self.shadows:
463 463 ui.debug("alias '%s' shadows command '%s'\n" %
464 464 (self.name, self.cmdname))
465 465
466 466 ui.log('commandalias', "alias '%s' expands to '%s'\n",
467 467 self.name, self.definition)
468 468 if util.safehasattr(self, 'shell'):
469 469 return self.fn(ui, *args, **opts)
470 470 else:
471 471 try:
472 472 return util.checksignature(self.fn)(ui, *args, **opts)
473 473 except error.SignatureError:
474 474 args = ' '.join([self.cmdname] + self.args)
475 475 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
476 476 raise
477 477
478 478 def addaliases(ui, cmdtable):
479 479 # aliases are processed after extensions have been loaded, so they
480 480 # may use extension commands. Aliases can also use other alias definitions,
481 481 # but only if they have been defined prior to the current definition.
482 482 for alias, definition in ui.configitems('alias'):
483 483 source = ui.configsource('alias', alias)
484 484 aliasdef = cmdalias(alias, definition, cmdtable, source)
485 485
486 486 try:
487 487 olddef = cmdtable[aliasdef.cmd][0]
488 488 if olddef.definition == aliasdef.definition:
489 489 continue
490 490 except (KeyError, AttributeError):
491 491 # definition might not exist or it might not be a cmdalias
492 492 pass
493 493
494 494 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
495 495
496 496 def _parse(ui, args):
497 497 options = {}
498 498 cmdoptions = {}
499 499
500 500 try:
501 501 args = fancyopts.fancyopts(args, commands.globalopts, options)
502 502 except getopt.GetoptError as inst:
503 503 raise error.CommandError(None, inst)
504 504
505 505 if args:
506 506 cmd, args = args[0], args[1:]
507 507 aliases, entry = cmdutil.findcmd(cmd, commands.table,
508 508 ui.configbool("ui", "strict"))
509 509 cmd = aliases[0]
510 510 args = aliasargs(entry[0], args)
511 511 defaults = ui.config("defaults", cmd)
512 512 if defaults:
513 513 args = pycompat.maplist(
514 514 util.expandpath, pycompat.shlexsplit(defaults)) + args
515 515 c = list(entry[1])
516 516 else:
517 517 cmd = None
518 518 c = []
519 519
520 520 # combine global options into local
521 521 for o in commands.globalopts:
522 522 c.append((o[0], o[1], options[o[1]], o[3]))
523 523
524 524 try:
525 525 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
526 526 except getopt.GetoptError as inst:
527 527 raise error.CommandError(cmd, inst)
528 528
529 529 # separate global options back out
530 530 for o in commands.globalopts:
531 531 n = o[1]
532 532 options[n] = cmdoptions[n]
533 533 del cmdoptions[n]
534 534
535 535 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
536 536
537 537 def _parseconfig(ui, config):
538 538 """parse the --config options from the command line"""
539 539 configs = []
540 540
541 541 for cfg in config:
542 542 try:
543 543 name, value = [cfgelem.strip()
544 544 for cfgelem in cfg.split('=', 1)]
545 545 section, name = name.split('.', 1)
546 546 if not section or not name:
547 547 raise IndexError
548 548 ui.setconfig(section, name, value, '--config')
549 549 configs.append((section, name, value))
550 550 except (IndexError, ValueError):
551 551 raise error.Abort(_('malformed --config option: %r '
552 552 '(use --config section.name=value)') % cfg)
553 553
554 554 return configs
555 555
556 556 def _earlygetopt(aliases, args):
557 557 """Return list of values for an option (or aliases).
558 558
559 559 The values are listed in the order they appear in args.
560 560 The options and values are removed from args.
561 561
562 562 >>> args = ['x', '--cwd', 'foo', 'y']
563 563 >>> _earlygetopt(['--cwd'], args), args
564 564 (['foo'], ['x', 'y'])
565 565
566 566 >>> args = ['x', '--cwd=bar', 'y']
567 567 >>> _earlygetopt(['--cwd'], args), args
568 568 (['bar'], ['x', 'y'])
569 569
570 570 >>> args = ['x', '-R', 'foo', 'y']
571 571 >>> _earlygetopt(['-R'], args), args
572 572 (['foo'], ['x', 'y'])
573 573
574 574 >>> args = ['x', '-Rbar', 'y']
575 575 >>> _earlygetopt(['-R'], args), args
576 576 (['bar'], ['x', 'y'])
577 577 """
578 578 try:
579 579 argcount = args.index("--")
580 580 except ValueError:
581 581 argcount = len(args)
582 582 shortopts = [opt for opt in aliases if len(opt) == 2]
583 583 values = []
584 584 pos = 0
585 585 while pos < argcount:
586 586 fullarg = arg = args[pos]
587 587 equals = arg.find('=')
588 588 if equals > -1:
589 589 arg = arg[:equals]
590 590 if arg in aliases:
591 591 del args[pos]
592 592 if equals > -1:
593 593 values.append(fullarg[equals + 1:])
594 594 argcount -= 1
595 595 else:
596 596 if pos + 1 >= argcount:
597 597 # ignore and let getopt report an error if there is no value
598 598 break
599 599 values.append(args.pop(pos))
600 600 argcount -= 2
601 601 elif arg[:2] in shortopts:
602 602 # short option can have no following space, e.g. hg log -Rfoo
603 603 values.append(args.pop(pos)[2:])
604 604 argcount -= 1
605 605 else:
606 606 pos += 1
607 607 return values
608 608
609 609 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
610 610 # run pre-hook, and abort if it fails
611 611 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
612 612 pats=cmdpats, opts=cmdoptions)
613 613 try:
614 614 ret = _runcommand(ui, options, cmd, d)
615 615 # run post-hook, passing command result
616 616 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
617 617 result=ret, pats=cmdpats, opts=cmdoptions)
618 618 except Exception:
619 619 # run failure hook and re-raise
620 620 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
621 621 pats=cmdpats, opts=cmdoptions)
622 622 raise
623 623 return ret
624 624
625 625 def _getlocal(ui, rpath, wd=None):
626 626 """Return (path, local ui object) for the given target path.
627 627
628 628 Takes paths in [cwd]/.hg/hgrc into account."
629 629 """
630 630 if wd is None:
631 631 try:
632 632 wd = pycompat.getcwd()
633 633 except OSError as e:
634 634 raise error.Abort(_("error getting current working directory: %s") %
635 635 e.strerror)
636 636 path = cmdutil.findrepo(wd) or ""
637 637 if not path:
638 638 lui = ui
639 639 else:
640 640 lui = ui.copy()
641 641 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
642 642
643 643 if rpath and rpath[-1]:
644 644 path = lui.expandpath(rpath[-1])
645 645 lui = ui.copy()
646 646 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
647 647
648 648 return path, lui
649 649
650 650 def _checkshellalias(lui, ui, args):
651 651 """Return the function to run the shell alias, if it is required"""
652 652 options = {}
653 653
654 654 try:
655 655 args = fancyopts.fancyopts(args, commands.globalopts, options)
656 656 except getopt.GetoptError:
657 657 return
658 658
659 659 if not args:
660 660 return
661 661
662 662 cmdtable = commands.table
663 663
664 664 cmd = args[0]
665 665 try:
666 666 strict = ui.configbool("ui", "strict")
667 667 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
668 668 except (error.AmbiguousCommand, error.UnknownCommand):
669 669 return
670 670
671 671 cmd = aliases[0]
672 672 fn = entry[0]
673 673
674 674 if cmd and util.safehasattr(fn, 'shell'):
675 675 d = lambda: fn(ui, *args[1:])
676 676 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
677 677 [], {})
678 678
679 679 _loaded = set()
680 680
681 681 # list of (objname, loadermod, loadername) tuple:
682 682 # - objname is the name of an object in extension module, from which
683 683 # extra information is loaded
684 684 # - loadermod is the module where loader is placed
685 685 # - loadername is the name of the function, which takes (ui, extensionname,
686 686 # extraobj) arguments
687 687 extraloaders = [
688 688 ('cmdtable', commands, 'loadcmdtable'),
689 689 ('colortable', color, 'loadcolortable'),
690 690 ('filesetpredicate', fileset, 'loadpredicate'),
691 691 ('revsetpredicate', revset, 'loadpredicate'),
692 692 ('templatefilter', templatefilters, 'loadfilter'),
693 693 ('templatefunc', templater, 'loadfunction'),
694 694 ('templatekeyword', templatekw, 'loadkeyword'),
695 695 ]
696 696
697 697 def _dispatch(req):
698 698 args = req.args
699 699 ui = req.ui
700 700
701 701 # check for cwd
702 702 cwd = _earlygetopt(['--cwd'], args)
703 703 if cwd:
704 704 os.chdir(cwd[-1])
705 705
706 706 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
707 707 path, lui = _getlocal(ui, rpath)
708 708
709 709 # Side-effect of accessing is debugcommands module is guaranteed to be
710 710 # imported and commands.table is populated.
711 711 debugcommands.command
712 712
713 713 uis = set([ui, lui])
714 714
715 715 if req.repo:
716 716 uis.add(req.repo.ui)
717 717
718 718 if '--profile' in args:
719 719 for ui_ in uis:
720 720 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
721 721
722 722 with profiling.maybeprofile(lui):
723 723 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
724 724 # reposetup. Programs like TortoiseHg will call _dispatch several
725 725 # times so we keep track of configured extensions in _loaded.
726 726 extensions.loadall(lui)
727 727 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
728 728 # Propagate any changes to lui.__class__ by extensions
729 729 ui.__class__ = lui.__class__
730 730
731 731 # (uisetup and extsetup are handled in extensions.loadall)
732 732
733 733 for name, module in exts:
734 734 for objname, loadermod, loadername in extraloaders:
735 735 extraobj = getattr(module, objname, None)
736 736 if extraobj is not None:
737 737 getattr(loadermod, loadername)(ui, name, extraobj)
738 738 _loaded.add(name)
739 739
740 740 # (reposetup is handled in hg.repository)
741 741
742 742 addaliases(lui, commands.table)
743 743
744 744 # All aliases and commands are completely defined, now.
745 745 # Check abbreviation/ambiguity of shell alias.
746 746 shellaliasfn = _checkshellalias(lui, ui, args)
747 747 if shellaliasfn:
748 748 return shellaliasfn()
749 749
750 750 # check for fallback encoding
751 751 fallback = lui.config('ui', 'fallbackencoding')
752 752 if fallback:
753 753 encoding.fallbackencoding = fallback
754 754
755 755 fullargs = args
756 756 cmd, func, args, options, cmdoptions = _parse(lui, args)
757 757
758 758 if options["config"]:
759 759 raise error.Abort(_("option --config may not be abbreviated!"))
760 760 if options["cwd"]:
761 761 raise error.Abort(_("option --cwd may not be abbreviated!"))
762 762 if options["repository"]:
763 763 raise error.Abort(_(
764 764 "option -R has to be separated from other options (e.g. not "
765 765 "-qR) and --repository may only be abbreviated as --repo!"))
766 766
767 767 if options["encoding"]:
768 768 encoding.encoding = options["encoding"]
769 769 if options["encodingmode"]:
770 770 encoding.encodingmode = options["encodingmode"]
771 771 if options["time"]:
772 772 def get_times():
773 773 t = os.times()
774 774 if t[4] == 0.0:
775 775 # Windows leaves this as zero, so use time.clock()
776 776 t = (t[0], t[1], t[2], t[3], time.clock())
777 777 return t
778 778 s = get_times()
779 779 def print_time():
780 780 t = get_times()
781 781 ui.warn(
782 782 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
783 783 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
784 784 ui.atexit(print_time)
785 785
786 786 if options['verbose'] or options['debug'] or options['quiet']:
787 787 for opt in ('verbose', 'debug', 'quiet'):
788 788 val = str(bool(options[opt]))
789 789 if pycompat.ispy3:
790 790 val = val.encode('ascii')
791 791 for ui_ in uis:
792 792 ui_.setconfig('ui', opt, val, '--' + opt)
793 793
794 794 if options['traceback']:
795 795 for ui_ in uis:
796 796 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
797 797
798 798 if options['noninteractive']:
799 799 for ui_ in uis:
800 800 ui_.setconfig('ui', 'interactive', 'off', '-y')
801 801
802 802 if util.parsebool(options['pager']):
803 803 ui.pager('internal-always-' + cmd)
804 804 elif options['pager'] != 'auto':
805 805 ui.disablepager()
806 806
807 807 if cmdoptions.get('insecure', False):
808 808 for ui_ in uis:
809 809 ui_.insecureconnections = True
810 810
811 811 # setup color handling
812 812 coloropt = options['color']
813 813 for ui_ in uis:
814 814 if coloropt:
815 815 ui_.setconfig('ui', 'color', coloropt, '--color')
816 816 color.setup(ui_)
817 817
818 818 if options['version']:
819 819 return commands.version_(ui)
820 820 if options['help']:
821 821 return commands.help_(ui, cmd, command=cmd is not None)
822 822 elif not cmd:
823 823 return commands.help_(ui, 'shortlist')
824 824
825 825 repo = None
826 826 cmdpats = args[:]
827 827 if not func.norepo:
828 828 # use the repo from the request only if we don't have -R
829 829 if not rpath and not cwd:
830 830 repo = req.repo
831 831
832 832 if repo:
833 833 # set the descriptors of the repo ui to those of ui
834 834 repo.ui.fin = ui.fin
835 835 repo.ui.fout = ui.fout
836 836 repo.ui.ferr = ui.ferr
837 837 else:
838 838 try:
839 839 repo = hg.repository(ui, path=path)
840 840 if not repo.local():
841 841 raise error.Abort(_("repository '%s' is not local")
842 842 % path)
843 843 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
844 844 'repo')
845 845 except error.RequirementError:
846 846 raise
847 847 except error.RepoError:
848 848 if rpath and rpath[-1]: # invalid -R path
849 849 raise
850 850 if not func.optionalrepo:
851 851 if func.inferrepo and args and not path:
852 852 # try to infer -R from command args
853 853 repos = map(cmdutil.findrepo, args)
854 854 guess = repos[0]
855 855 if guess and repos.count(guess) == len(repos):
856 856 req.args = ['--repository', guess] + fullargs
857 857 return _dispatch(req)
858 858 if not path:
859 859 raise error.RepoError(_("no repository found in"
860 860 " '%s' (.hg not found)")
861 861 % pycompat.getcwd())
862 862 raise
863 863 if repo:
864 864 ui = repo.ui
865 865 if options['hidden']:
866 866 repo = repo.unfiltered()
867 867 args.insert(0, repo)
868 868 elif rpath:
869 869 ui.warn(_("warning: --repository ignored\n"))
870 870
871 871 msg = _formatargs(fullargs)
872 872 ui.log("command", '%s\n', msg)
873 873 strcmdopt = pycompat.strkwargs(cmdoptions)
874 874 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
875 875 try:
876 876 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
877 877 cmdpats, cmdoptions)
878 878 finally:
879 879 if repo and repo != req.repo:
880 880 repo.close()
881 881
882 882 def _runcommand(ui, options, cmd, cmdfunc):
883 883 """Run a command function, possibly with profiling enabled."""
884 884 try:
885 885 return cmdfunc()
886 886 except error.SignatureError:
887 887 raise error.CommandError(cmd, _('invalid arguments'))
888 888
889 889 def _exceptionwarning(ui):
890 890 """Produce a warning message for the current active exception"""
891 891
892 892 # For compatibility checking, we discard the portion of the hg
893 893 # version after the + on the assumption that if a "normal
894 894 # user" is running a build with a + in it the packager
895 895 # probably built from fairly close to a tag and anyone with a
896 896 # 'make local' copy of hg (where the version number can be out
897 897 # of date) will be clueful enough to notice the implausible
898 898 # version number and try updating.
899 899 ct = util.versiontuple(n=2)
900 900 worst = None, ct, ''
901 901 if ui.config('ui', 'supportcontact', None) is None:
902 902 for name, mod in extensions.extensions():
903 903 testedwith = getattr(mod, 'testedwith', '')
904 904 if pycompat.ispy3 and isinstance(testedwith, str):
905 905 testedwith = testedwith.encode(u'utf-8')
906 906 report = getattr(mod, 'buglink', _('the extension author.'))
907 907 if not testedwith.strip():
908 908 # We found an untested extension. It's likely the culprit.
909 909 worst = name, 'unknown', report
910 910 break
911 911
912 912 # Never blame on extensions bundled with Mercurial.
913 913 if extensions.ismoduleinternal(mod):
914 914 continue
915 915
916 916 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
917 917 if ct in tested:
918 918 continue
919 919
920 920 lower = [t for t in tested if t < ct]
921 921 nearest = max(lower or tested)
922 922 if worst[0] is None or nearest < worst[1]:
923 923 worst = name, nearest, report
924 924 if worst[0] is not None:
925 925 name, testedwith, report = worst
926 926 if not isinstance(testedwith, (bytes, str)):
927 927 testedwith = '.'.join([str(c) for c in testedwith])
928 928 warning = (_('** Unknown exception encountered with '
929 929 'possibly-broken third-party extension %s\n'
930 930 '** which supports versions %s of Mercurial.\n'
931 931 '** Please disable %s and try your action again.\n'
932 932 '** If that fixes the bug please report it to %s\n')
933 933 % (name, testedwith, name, report))
934 934 else:
935 935 bugtracker = ui.config('ui', 'supportcontact', None)
936 936 if bugtracker is None:
937 937 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
938 938 warning = (_("** unknown exception encountered, "
939 939 "please report by visiting\n** ") + bugtracker + '\n')
940 940 if pycompat.ispy3:
941 941 sysversion = sys.version.encode(u'utf-8')
942 942 else:
943 943 sysversion = sys.version
944 944 sysversion = sysversion.replace('\n', '')
945 945 warning += ((_("** Python %s\n") % sysversion) +
946 946 (_("** Mercurial Distributed SCM (version %s)\n") %
947 947 util.version()) +
948 948 (_("** Extensions loaded: %s\n") %
949 949 ", ".join([x[0] for x in extensions.extensions()])))
950 950 return warning
951 951
952 952 def handlecommandexception(ui):
953 953 """Produce a warning message for broken commands
954 954
955 955 Called when handling an exception; the exception is reraised if
956 956 this function returns False, ignored otherwise.
957 957 """
958 958 warning = _exceptionwarning(ui)
959 959 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
960 960 ui.warn(warning)
961 961 return False # re-raise the exception
@@ -1,180 +1,180 b''
1 1
2 2 $ cat << EOF > buggylocking.py
3 3 > """A small extension that tests our developer warnings
4 4 > """
5 5 >
6 6 > from mercurial import cmdutil, repair, util
7 7 >
8 8 > cmdtable = {}
9 9 > command = cmdutil.command(cmdtable)
10 10 >
11 11 > @command('buggylocking', [], '')
12 12 > def buggylocking(ui, repo):
13 13 > lo = repo.lock()
14 14 > wl = repo.wlock()
15 15 > wl.release()
16 16 > lo.release()
17 17 >
18 18 > @command('buggytransaction', [], '')
19 19 > def buggylocking(ui, repo):
20 20 > tr = repo.transaction('buggy')
21 21 > # make sure we rollback the transaction as we don't want to rely on the__del__
22 22 > tr.release()
23 23 >
24 24 > @command('properlocking', [], '')
25 25 > def properlocking(ui, repo):
26 26 > """check that reentrance is fine"""
27 27 > wl = repo.wlock()
28 28 > lo = repo.lock()
29 29 > tr = repo.transaction('proper')
30 30 > tr2 = repo.transaction('proper')
31 31 > lo2 = repo.lock()
32 32 > wl2 = repo.wlock()
33 33 > wl2.release()
34 34 > lo2.release()
35 35 > tr2.close()
36 36 > tr.close()
37 37 > lo.release()
38 38 > wl.release()
39 39 >
40 40 > @command('nowaitlocking', [], '')
41 41 > def nowaitlocking(ui, repo):
42 42 > lo = repo.lock()
43 43 > wl = repo.wlock(wait=False)
44 44 > wl.release()
45 45 > lo.release()
46 46 >
47 47 > @command('stripintr', [], '')
48 48 > def stripintr(ui, repo):
49 49 > lo = repo.lock()
50 50 > tr = repo.transaction('foobar')
51 51 > try:
52 52 > repair.strip(repo.ui, repo, [repo['.'].node()])
53 53 > finally:
54 54 > lo.release()
55 55 > @command('oldanddeprecated', [], '')
56 56 > def oldanddeprecated(ui, repo):
57 57 > """test deprecation warning API"""
58 58 > def foobar(ui):
59 59 > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337')
60 60 > foobar(ui)
61 61 > @command('nouiwarning', [], '')
62 62 > def nouiwarning(ui, repo):
63 63 > util.nouideprecwarn('this is a test', '13.37')
64 64 > EOF
65 65
66 66 $ cat << EOF >> $HGRCPATH
67 67 > [extensions]
68 68 > buggylocking=$TESTTMP/buggylocking.py
69 69 > mock=$TESTDIR/mockblackbox.py
70 70 > blackbox=
71 71 > [devel]
72 72 > all-warnings=1
73 73 > EOF
74 74
75 75 $ hg init lock-checker
76 76 $ cd lock-checker
77 77 $ hg buggylocking
78 78 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
79 79 $ cat << EOF >> $HGRCPATH
80 80 > [devel]
81 81 > all=0
82 82 > check-locks=1
83 83 > EOF
84 84 $ hg buggylocking
85 85 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
86 86 $ hg buggylocking --traceback
87 87 devel-warn: "wlock" acquired after "lock" at:
88 88 */hg:* in * (glob)
89 89 */mercurial/dispatch.py:* in run (glob)
90 90 */mercurial/dispatch.py:* in dispatch (glob)
91 91 */mercurial/dispatch.py:* in _runcatch (glob)
92 */mercurial/dispatch.py:* in callcatch (glob)
92 */mercurial/dispatch.py:* in _callcatch (glob)
93 93 */mercurial/scmutil.py* in callcatch (glob)
94 94 */mercurial/dispatch.py:* in _runcatchfunc (glob)
95 95 */mercurial/dispatch.py:* in _dispatch (glob)
96 96 */mercurial/dispatch.py:* in runcommand (glob)
97 97 */mercurial/dispatch.py:* in _runcommand (glob)
98 98 */mercurial/dispatch.py:* in <lambda> (glob)
99 99 */mercurial/util.py:* in check (glob)
100 100 $TESTTMP/buggylocking.py:* in buggylocking (glob)
101 101 $ hg properlocking
102 102 $ hg nowaitlocking
103 103
104 104 $ echo a > a
105 105 $ hg add a
106 106 $ hg commit -m a
107 107 $ hg stripintr 2>&1 | egrep -v '^(\*\*| )'
108 108 saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/*-backup.hg (glob)
109 109 Traceback (most recent call last):
110 110 mercurial.error.ProgrammingError: cannot strip from inside a transaction
111 111
112 112 $ hg oldanddeprecated
113 113 devel-warn: foorbar is deprecated, go shopping
114 114 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
115 115
116 116 $ hg oldanddeprecated --traceback
117 117 devel-warn: foorbar is deprecated, go shopping
118 118 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
119 119 */hg:* in <module> (glob)
120 120 */mercurial/dispatch.py:* in run (glob)
121 121 */mercurial/dispatch.py:* in dispatch (glob)
122 122 */mercurial/dispatch.py:* in _runcatch (glob)
123 */mercurial/dispatch.py:* in callcatch (glob)
123 */mercurial/dispatch.py:* in _callcatch (glob)
124 124 */mercurial/scmutil.py* in callcatch (glob)
125 125 */mercurial/dispatch.py:* in _runcatchfunc (glob)
126 126 */mercurial/dispatch.py:* in _dispatch (glob)
127 127 */mercurial/dispatch.py:* in runcommand (glob)
128 128 */mercurial/dispatch.py:* in _runcommand (glob)
129 129 */mercurial/dispatch.py:* in <lambda> (glob)
130 130 */mercurial/util.py:* in check (glob)
131 131 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
132 132 $ hg blackbox -l 7
133 133 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
134 134 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
135 135 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
136 136 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
137 137 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
138 138 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
139 139 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
140 140 */hg:* in <module> (glob)
141 141 */mercurial/dispatch.py:* in run (glob)
142 142 */mercurial/dispatch.py:* in dispatch (glob)
143 143 */mercurial/dispatch.py:* in _runcatch (glob)
144 */mercurial/dispatch.py:* in callcatch (glob)
144 */mercurial/dispatch.py:* in _callcatch (glob)
145 145 */mercurial/scmutil.py* in callcatch (glob)
146 146 */mercurial/dispatch.py:* in _runcatchfunc (glob)
147 147 */mercurial/dispatch.py:* in _dispatch (glob)
148 148 */mercurial/dispatch.py:* in runcommand (glob)
149 149 */mercurial/dispatch.py:* in _runcommand (glob)
150 150 */mercurial/dispatch.py:* in <lambda> (glob)
151 151 */mercurial/util.py:* in check (glob)
152 152 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
153 153 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
154 154 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
155 155
156 156 Test programming error failure:
157 157
158 158 $ hg buggytransaction 2>&1 | egrep -v '^ '
159 159 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
160 160 ** which supports versions unknown of Mercurial.
161 161 ** Please disable buggylocking and try your action again.
162 162 ** If that fixes the bug please report it to the extension author.
163 163 ** Python * (glob)
164 164 ** Mercurial Distributed SCM (*) (glob)
165 165 ** Extensions loaded: * (glob)
166 166 Traceback (most recent call last):
167 167 mercurial.error.ProgrammingError: transaction requires locking
168 168
169 169 Old style deprecation warning
170 170
171 171 $ hg nouiwarning
172 172 $TESTTMP/buggylocking.py:61: DeprecationWarning: this is a test
173 173 (compatibility will be dropped after Mercurial-13.37, update your code.)
174 174 util.nouideprecwarn('this is a test', '13.37')
175 175
176 176 (disabled outside of test run)
177 177
178 178 $ HGEMITWARNINGS= hg nouiwarning
179 179
180 180 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now