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