##// END OF EJS Templates
dispatch: explicitly pass fancyopts optional arg as a keyword...
Augie Fackler -
r29822:61a4cdc9 default
parent child Browse files
Show More
@@ -1,973 +1,973 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 if util.safehasattr(self, 'shell'):
504 504 return self.fn(ui, *args, **opts)
505 505 else:
506 506 try:
507 507 return util.checksignature(self.fn)(ui, *args, **opts)
508 508 except error.SignatureError:
509 509 args = ' '.join([self.cmdname] + self.args)
510 510 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
511 511 raise
512 512
513 513 def addaliases(ui, cmdtable):
514 514 # aliases are processed after extensions have been loaded, so they
515 515 # may use extension commands. Aliases can also use other alias definitions,
516 516 # but only if they have been defined prior to the current definition.
517 517 for alias, definition in ui.configitems('alias'):
518 518 source = ui.configsource('alias', alias)
519 519 aliasdef = cmdalias(alias, definition, cmdtable, source)
520 520
521 521 try:
522 522 olddef = cmdtable[aliasdef.cmd][0]
523 523 if olddef.definition == aliasdef.definition:
524 524 continue
525 525 except (KeyError, AttributeError):
526 526 # definition might not exist or it might not be a cmdalias
527 527 pass
528 528
529 529 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
530 530
531 531 def _parse(ui, args):
532 532 options = {}
533 533 cmdoptions = {}
534 534
535 535 try:
536 536 args = fancyopts.fancyopts(args, commands.globalopts, options)
537 537 except fancyopts.getopt.GetoptError as inst:
538 538 raise error.CommandError(None, inst)
539 539
540 540 if args:
541 541 cmd, args = args[0], args[1:]
542 542 aliases, entry = cmdutil.findcmd(cmd, commands.table,
543 543 ui.configbool("ui", "strict"))
544 544 cmd = aliases[0]
545 545 args = aliasargs(entry[0], args)
546 546 defaults = ui.config("defaults", cmd)
547 547 if defaults:
548 548 args = map(util.expandpath, shlex.split(defaults)) + args
549 549 c = list(entry[1])
550 550 else:
551 551 cmd = None
552 552 c = []
553 553
554 554 # combine global options into local
555 555 for o in commands.globalopts:
556 556 c.append((o[0], o[1], options[o[1]], o[3]))
557 557
558 558 try:
559 args = fancyopts.fancyopts(args, c, cmdoptions, True)
559 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
560 560 except fancyopts.getopt.GetoptError as inst:
561 561 raise error.CommandError(cmd, inst)
562 562
563 563 # separate global options back out
564 564 for o in commands.globalopts:
565 565 n = o[1]
566 566 options[n] = cmdoptions[n]
567 567 del cmdoptions[n]
568 568
569 569 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
570 570
571 571 def _parseconfig(ui, config):
572 572 """parse the --config options from the command line"""
573 573 configs = []
574 574
575 575 for cfg in config:
576 576 try:
577 577 name, value = [cfgelem.strip()
578 578 for cfgelem in cfg.split('=', 1)]
579 579 section, name = name.split('.', 1)
580 580 if not section or not name:
581 581 raise IndexError
582 582 ui.setconfig(section, name, value, '--config')
583 583 configs.append((section, name, value))
584 584 except (IndexError, ValueError):
585 585 raise error.Abort(_('malformed --config option: %r '
586 586 '(use --config section.name=value)') % cfg)
587 587
588 588 return configs
589 589
590 590 def _earlygetopt(aliases, args):
591 591 """Return list of values for an option (or aliases).
592 592
593 593 The values are listed in the order they appear in args.
594 594 The options and values are removed from args.
595 595
596 596 >>> args = ['x', '--cwd', 'foo', 'y']
597 597 >>> _earlygetopt(['--cwd'], args), args
598 598 (['foo'], ['x', 'y'])
599 599
600 600 >>> args = ['x', '--cwd=bar', 'y']
601 601 >>> _earlygetopt(['--cwd'], args), args
602 602 (['bar'], ['x', 'y'])
603 603
604 604 >>> args = ['x', '-R', 'foo', 'y']
605 605 >>> _earlygetopt(['-R'], args), args
606 606 (['foo'], ['x', 'y'])
607 607
608 608 >>> args = ['x', '-Rbar', 'y']
609 609 >>> _earlygetopt(['-R'], args), args
610 610 (['bar'], ['x', 'y'])
611 611 """
612 612 try:
613 613 argcount = args.index("--")
614 614 except ValueError:
615 615 argcount = len(args)
616 616 shortopts = [opt for opt in aliases if len(opt) == 2]
617 617 values = []
618 618 pos = 0
619 619 while pos < argcount:
620 620 fullarg = arg = args[pos]
621 621 equals = arg.find('=')
622 622 if equals > -1:
623 623 arg = arg[:equals]
624 624 if arg in aliases:
625 625 del args[pos]
626 626 if equals > -1:
627 627 values.append(fullarg[equals + 1:])
628 628 argcount -= 1
629 629 else:
630 630 if pos + 1 >= argcount:
631 631 # ignore and let getopt report an error if there is no value
632 632 break
633 633 values.append(args.pop(pos))
634 634 argcount -= 2
635 635 elif arg[:2] in shortopts:
636 636 # short option can have no following space, e.g. hg log -Rfoo
637 637 values.append(args.pop(pos)[2:])
638 638 argcount -= 1
639 639 else:
640 640 pos += 1
641 641 return values
642 642
643 643 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
644 644 # run pre-hook, and abort if it fails
645 645 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
646 646 pats=cmdpats, opts=cmdoptions)
647 647 try:
648 648 ret = _runcommand(ui, options, cmd, d)
649 649 # run post-hook, passing command result
650 650 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
651 651 result=ret, pats=cmdpats, opts=cmdoptions)
652 652 except Exception:
653 653 # run failure hook and re-raise
654 654 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
655 655 pats=cmdpats, opts=cmdoptions)
656 656 raise
657 657 return ret
658 658
659 659 def _getlocal(ui, rpath, wd=None):
660 660 """Return (path, local ui object) for the given target path.
661 661
662 662 Takes paths in [cwd]/.hg/hgrc into account."
663 663 """
664 664 if wd is None:
665 665 try:
666 666 wd = os.getcwd()
667 667 except OSError as e:
668 668 raise error.Abort(_("error getting current working directory: %s") %
669 669 e.strerror)
670 670 path = cmdutil.findrepo(wd) or ""
671 671 if not path:
672 672 lui = ui
673 673 else:
674 674 lui = ui.copy()
675 675 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
676 676
677 677 if rpath and rpath[-1]:
678 678 path = lui.expandpath(rpath[-1])
679 679 lui = ui.copy()
680 680 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
681 681
682 682 return path, lui
683 683
684 684 def _checkshellalias(lui, ui, args):
685 685 """Return the function to run the shell alias, if it is required"""
686 686 options = {}
687 687
688 688 try:
689 689 args = fancyopts.fancyopts(args, commands.globalopts, options)
690 690 except fancyopts.getopt.GetoptError:
691 691 return
692 692
693 693 if not args:
694 694 return
695 695
696 696 cmdtable = commands.table
697 697
698 698 cmd = args[0]
699 699 try:
700 700 strict = ui.configbool("ui", "strict")
701 701 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
702 702 except (error.AmbiguousCommand, error.UnknownCommand):
703 703 return
704 704
705 705 cmd = aliases[0]
706 706 fn = entry[0]
707 707
708 708 if cmd and util.safehasattr(fn, 'shell'):
709 709 d = lambda: fn(ui, *args[1:])
710 710 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
711 711 [], {})
712 712
713 713 def _cmdattr(ui, cmd, func, attr):
714 714 try:
715 715 return getattr(func, attr)
716 716 except AttributeError:
717 717 ui.deprecwarn("missing attribute '%s', use @command decorator "
718 718 "to register '%s'" % (attr, cmd), '3.8')
719 719 return False
720 720
721 721 _loaded = set()
722 722
723 723 # list of (objname, loadermod, loadername) tuple:
724 724 # - objname is the name of an object in extension module, from which
725 725 # extra information is loaded
726 726 # - loadermod is the module where loader is placed
727 727 # - loadername is the name of the function, which takes (ui, extensionname,
728 728 # extraobj) arguments
729 729 extraloaders = [
730 730 ('cmdtable', commands, 'loadcmdtable'),
731 731 ('filesetpredicate', fileset, 'loadpredicate'),
732 732 ('revsetpredicate', revset, 'loadpredicate'),
733 733 ('templatefilter', templatefilters, 'loadfilter'),
734 734 ('templatefunc', templater, 'loadfunction'),
735 735 ('templatekeyword', templatekw, 'loadkeyword'),
736 736 ]
737 737
738 738 def _dispatch(req):
739 739 args = req.args
740 740 ui = req.ui
741 741
742 742 # check for cwd
743 743 cwd = _earlygetopt(['--cwd'], args)
744 744 if cwd:
745 745 os.chdir(cwd[-1])
746 746
747 747 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
748 748 path, lui = _getlocal(ui, rpath)
749 749
750 750 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
751 751 # reposetup. Programs like TortoiseHg will call _dispatch several
752 752 # times so we keep track of configured extensions in _loaded.
753 753 extensions.loadall(lui)
754 754 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
755 755 # Propagate any changes to lui.__class__ by extensions
756 756 ui.__class__ = lui.__class__
757 757
758 758 # (uisetup and extsetup are handled in extensions.loadall)
759 759
760 760 for name, module in exts:
761 761 for objname, loadermod, loadername in extraloaders:
762 762 extraobj = getattr(module, objname, None)
763 763 if extraobj is not None:
764 764 getattr(loadermod, loadername)(ui, name, extraobj)
765 765 _loaded.add(name)
766 766
767 767 # (reposetup is handled in hg.repository)
768 768
769 769 addaliases(lui, commands.table)
770 770
771 771 # All aliases and commands are completely defined, now.
772 772 # Check abbreviation/ambiguity of shell alias.
773 773 shellaliasfn = _checkshellalias(lui, ui, args)
774 774 if shellaliasfn:
775 775 return shellaliasfn()
776 776
777 777 # check for fallback encoding
778 778 fallback = lui.config('ui', 'fallbackencoding')
779 779 if fallback:
780 780 encoding.fallbackencoding = fallback
781 781
782 782 fullargs = args
783 783 cmd, func, args, options, cmdoptions = _parse(lui, args)
784 784
785 785 if options["config"]:
786 786 raise error.Abort(_("option --config may not be abbreviated!"))
787 787 if options["cwd"]:
788 788 raise error.Abort(_("option --cwd may not be abbreviated!"))
789 789 if options["repository"]:
790 790 raise error.Abort(_(
791 791 "option -R has to be separated from other options (e.g. not -qR) "
792 792 "and --repository may only be abbreviated as --repo!"))
793 793
794 794 if options["encoding"]:
795 795 encoding.encoding = options["encoding"]
796 796 if options["encodingmode"]:
797 797 encoding.encodingmode = options["encodingmode"]
798 798 if options["time"]:
799 799 def get_times():
800 800 t = os.times()
801 801 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
802 802 t = (t[0], t[1], t[2], t[3], time.clock())
803 803 return t
804 804 s = get_times()
805 805 def print_time():
806 806 t = get_times()
807 807 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
808 808 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
809 809 atexit.register(print_time)
810 810
811 811 uis = set([ui, lui])
812 812
813 813 if req.repo:
814 814 uis.add(req.repo.ui)
815 815
816 816 if options['verbose'] or options['debug'] or options['quiet']:
817 817 for opt in ('verbose', 'debug', 'quiet'):
818 818 val = str(bool(options[opt]))
819 819 for ui_ in uis:
820 820 ui_.setconfig('ui', opt, val, '--' + opt)
821 821
822 822 if options['profile']:
823 823 for ui_ in uis:
824 824 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
825 825
826 826 if options['traceback']:
827 827 for ui_ in uis:
828 828 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
829 829
830 830 if options['noninteractive']:
831 831 for ui_ in uis:
832 832 ui_.setconfig('ui', 'interactive', 'off', '-y')
833 833
834 834 if cmdoptions.get('insecure', False):
835 835 for ui_ in uis:
836 836 ui_.insecureconnections = True
837 837
838 838 if options['version']:
839 839 return commands.version_(ui)
840 840 if options['help']:
841 841 return commands.help_(ui, cmd, command=cmd is not None)
842 842 elif not cmd:
843 843 return commands.help_(ui, 'shortlist')
844 844
845 845 repo = None
846 846 cmdpats = args[:]
847 847 if not _cmdattr(ui, cmd, func, 'norepo'):
848 848 # use the repo from the request only if we don't have -R
849 849 if not rpath and not cwd:
850 850 repo = req.repo
851 851
852 852 if repo:
853 853 # set the descriptors of the repo ui to those of ui
854 854 repo.ui.fin = ui.fin
855 855 repo.ui.fout = ui.fout
856 856 repo.ui.ferr = ui.ferr
857 857 else:
858 858 try:
859 859 repo = hg.repository(ui, path=path)
860 860 if not repo.local():
861 861 raise error.Abort(_("repository '%s' is not local") % path)
862 862 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
863 863 except error.RequirementError:
864 864 raise
865 865 except error.RepoError:
866 866 if rpath and rpath[-1]: # invalid -R path
867 867 raise
868 868 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
869 869 if (_cmdattr(ui, cmd, func, 'inferrepo') and
870 870 args and not path):
871 871 # try to infer -R from command args
872 872 repos = map(cmdutil.findrepo, args)
873 873 guess = repos[0]
874 874 if guess and repos.count(guess) == len(repos):
875 875 req.args = ['--repository', guess] + fullargs
876 876 return _dispatch(req)
877 877 if not path:
878 878 raise error.RepoError(_("no repository found in '%s'"
879 879 " (.hg not found)")
880 880 % os.getcwd())
881 881 raise
882 882 if repo:
883 883 ui = repo.ui
884 884 if options['hidden']:
885 885 repo = repo.unfiltered()
886 886 args.insert(0, repo)
887 887 elif rpath:
888 888 ui.warn(_("warning: --repository ignored\n"))
889 889
890 890 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
891 891 ui.log("command", '%s\n', msg)
892 892 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
893 893 try:
894 894 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
895 895 cmdpats, cmdoptions)
896 896 finally:
897 897 if repo and repo != req.repo:
898 898 repo.close()
899 899
900 900 def _runcommand(ui, options, cmd, cmdfunc):
901 901 """Run a command function, possibly with profiling enabled."""
902 902 with profiling.maybeprofile(ui):
903 903 try:
904 904 return cmdfunc()
905 905 except error.SignatureError:
906 906 raise error.CommandError(cmd, _('invalid arguments'))
907 907
908 908 def _exceptionwarning(ui):
909 909 """Produce a warning message for the current active exception"""
910 910
911 911 # For compatibility checking, we discard the portion of the hg
912 912 # version after the + on the assumption that if a "normal
913 913 # user" is running a build with a + in it the packager
914 914 # probably built from fairly close to a tag and anyone with a
915 915 # 'make local' copy of hg (where the version number can be out
916 916 # of date) will be clueful enough to notice the implausible
917 917 # version number and try updating.
918 918 ct = util.versiontuple(n=2)
919 919 worst = None, ct, ''
920 920 if ui.config('ui', 'supportcontact', None) is None:
921 921 for name, mod in extensions.extensions():
922 922 testedwith = getattr(mod, 'testedwith', '')
923 923 report = getattr(mod, 'buglink', _('the extension author.'))
924 924 if not testedwith.strip():
925 925 # We found an untested extension. It's likely the culprit.
926 926 worst = name, 'unknown', report
927 927 break
928 928
929 929 # Never blame on extensions bundled with Mercurial.
930 930 if testedwith == 'internal':
931 931 continue
932 932
933 933 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
934 934 if ct in tested:
935 935 continue
936 936
937 937 lower = [t for t in tested if t < ct]
938 938 nearest = max(lower or tested)
939 939 if worst[0] is None or nearest < worst[1]:
940 940 worst = name, nearest, report
941 941 if worst[0] is not None:
942 942 name, testedwith, report = worst
943 943 if not isinstance(testedwith, str):
944 944 testedwith = '.'.join([str(c) for c in testedwith])
945 945 warning = (_('** Unknown exception encountered with '
946 946 'possibly-broken third-party extension %s\n'
947 947 '** which supports versions %s of Mercurial.\n'
948 948 '** Please disable %s and try your action again.\n'
949 949 '** If that fixes the bug please report it to %s\n')
950 950 % (name, testedwith, name, report))
951 951 else:
952 952 bugtracker = ui.config('ui', 'supportcontact', None)
953 953 if bugtracker is None:
954 954 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
955 955 warning = (_("** unknown exception encountered, "
956 956 "please report by visiting\n** ") + bugtracker + '\n')
957 957 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
958 958 (_("** Mercurial Distributed SCM (version %s)\n") %
959 959 util.version()) +
960 960 (_("** Extensions loaded: %s\n") %
961 961 ", ".join([x[0] for x in extensions.extensions()])))
962 962 return warning
963 963
964 964 def handlecommandexception(ui):
965 965 """Produce a warning message for broken commands
966 966
967 967 Called when handling an exception; the exception is reraised if
968 968 this function returns False, ignored otherwise.
969 969 """
970 970 warning = _exceptionwarning(ui)
971 971 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
972 972 ui.warn(warning)
973 973 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now