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