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