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