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