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