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