141 # and its close() actually closes the underlying file descriptor. |
155 # and its close() actually closes the underlying file descriptor. |
142 try: |
156 try: |
143 fp.close() |
157 fp.close() |
144 except IOError: |
158 except IOError: |
145 pass |
159 pass |
|
160 |
|
161 |
146 else: |
162 else: |
|
163 |
147 def initstdio(): |
164 def initstdio(): |
148 for fp in (sys.stdin, sys.stdout, sys.stderr): |
165 for fp in (sys.stdin, sys.stdout, sys.stderr): |
149 procutil.setbinary(fp) |
166 procutil.setbinary(fp) |
150 |
167 |
151 def _silencestdio(): |
168 def _silencestdio(): |
152 pass |
169 pass |
|
170 |
153 |
171 |
154 def _getsimilar(symbols, value): |
172 def _getsimilar(symbols, value): |
155 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() |
173 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() |
156 # The cutoff for similarity here is pretty arbitrary. It should |
174 # The cutoff for similarity here is pretty arbitrary. It should |
157 # probably be investigated and tweaked. |
175 # probably be investigated and tweaked. |
158 return [s for s in symbols if sim(s) > 0.6] |
176 return [s for s in symbols if sim(s) > 0.6] |
|
177 |
159 |
178 |
160 def _reportsimilar(write, similar): |
179 def _reportsimilar(write, similar): |
161 if len(similar) == 1: |
180 if len(similar) == 1: |
162 write(_("(did you mean %s?)\n") % similar[0]) |
181 write(_("(did you mean %s?)\n") % similar[0]) |
163 elif similar: |
182 elif similar: |
164 ss = ", ".join(sorted(similar)) |
183 ss = ", ".join(sorted(similar)) |
165 write(_("(did you mean one of %s?)\n") % ss) |
184 write(_("(did you mean one of %s?)\n") % ss) |
166 |
185 |
|
186 |
167 def _formatparse(write, inst): |
187 def _formatparse(write, inst): |
168 similar = [] |
188 similar = [] |
169 if isinstance(inst, error.UnknownIdentifier): |
189 if isinstance(inst, error.UnknownIdentifier): |
170 # make sure to check fileset first, as revset can invoke fileset |
190 # make sure to check fileset first, as revset can invoke fileset |
171 similar = _getsimilar(inst.symbols, inst.function) |
191 similar = _getsimilar(inst.symbols, inst.function) |
172 if len(inst.args) > 1: |
192 if len(inst.args) > 1: |
173 write(_("hg: parse error at %s: %s\n") % |
193 write( |
174 (pycompat.bytestr(inst.args[1]), inst.args[0])) |
194 _("hg: parse error at %s: %s\n") |
|
195 % (pycompat.bytestr(inst.args[1]), inst.args[0]) |
|
196 ) |
175 if inst.args[0].startswith(' '): |
197 if inst.args[0].startswith(' '): |
176 write(_("unexpected leading whitespace\n")) |
198 write(_("unexpected leading whitespace\n")) |
177 else: |
199 else: |
178 write(_("hg: parse error: %s\n") % inst.args[0]) |
200 write(_("hg: parse error: %s\n") % inst.args[0]) |
179 _reportsimilar(write, similar) |
201 _reportsimilar(write, similar) |
180 if inst.hint: |
202 if inst.hint: |
181 write(_("(%s)\n") % inst.hint) |
203 write(_("(%s)\n") % inst.hint) |
182 |
204 |
|
205 |
183 def _formatargs(args): |
206 def _formatargs(args): |
184 return ' '.join(procutil.shellquote(a) for a in args) |
207 return ' '.join(procutil.shellquote(a) for a in args) |
|
208 |
185 |
209 |
186 def dispatch(req): |
210 def dispatch(req): |
187 """run the command specified in req.args; returns an integer status code""" |
211 """run the command specified in req.args; returns an integer status code""" |
188 with tracing.log('dispatch.dispatch'): |
212 with tracing.log('dispatch.dispatch'): |
189 if req.ferr: |
213 if req.ferr: |
261 duration=duration, |
288 duration=duration, |
262 canonical_command=req.canonical_command, |
289 canonical_command=req.canonical_command, |
263 ) |
290 ) |
264 try: |
291 try: |
265 req._runexithandlers() |
292 req._runexithandlers() |
266 except: # exiting, so no re-raises |
293 except: # exiting, so no re-raises |
267 ret = ret or -1 |
294 ret = ret or -1 |
268 return ret |
295 return ret |
269 |
296 |
|
297 |
270 def _runcatch(req): |
298 def _runcatch(req): |
271 with tracing.log('dispatch._runcatch'): |
299 with tracing.log('dispatch._runcatch'): |
|
300 |
272 def catchterm(*args): |
301 def catchterm(*args): |
273 raise error.SignalInterrupt |
302 raise error.SignalInterrupt |
274 |
303 |
275 ui = req.ui |
304 ui = req.ui |
276 try: |
305 try: |
277 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': |
306 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': |
278 num = getattr(signal, name, None) |
307 num = getattr(signal, name, None) |
279 if num: |
308 if num: |
280 signal.signal(num, catchterm) |
309 signal.signal(num, catchterm) |
281 except ValueError: |
310 except ValueError: |
282 pass # happens if called in a thread |
311 pass # happens if called in a thread |
283 |
312 |
284 def _runcatchfunc(): |
313 def _runcatchfunc(): |
285 realcmd = None |
314 realcmd = None |
286 try: |
315 try: |
287 cmdargs = fancyopts.fancyopts( |
316 cmdargs = fancyopts.fancyopts( |
288 req.args[:], commands.globalopts, {}) |
317 req.args[:], commands.globalopts, {} |
|
318 ) |
289 cmd = cmdargs[0] |
319 cmd = cmdargs[0] |
290 aliases, entry = cmdutil.findcmd(cmd, commands.table, False) |
320 aliases, entry = cmdutil.findcmd(cmd, commands.table, False) |
291 realcmd = aliases[0] |
321 realcmd = aliases[0] |
292 except (error.UnknownCommand, error.AmbiguousCommand, |
322 except ( |
293 IndexError, getopt.GetoptError): |
323 error.UnknownCommand, |
|
324 error.AmbiguousCommand, |
|
325 IndexError, |
|
326 getopt.GetoptError, |
|
327 ): |
294 # Don't handle this here. We know the command is |
328 # Don't handle this here. We know the command is |
295 # invalid, but all we're worried about for now is that |
329 # invalid, but all we're worried about for now is that |
296 # it's not a command that server operators expect to |
330 # it's not a command that server operators expect to |
297 # be safe to offer to users in a sandbox. |
331 # be safe to offer to users in a sandbox. |
298 pass |
332 pass |
303 # restrict to exactly that set of arguments, and prohibit |
337 # restrict to exactly that set of arguments, and prohibit |
304 # any repo name that starts with '--' to prevent |
338 # any repo name that starts with '--' to prevent |
305 # shenanigans wherein a user does something like pass |
339 # shenanigans wherein a user does something like pass |
306 # --debugger or --config=ui.debugger=1 as a repo |
340 # --debugger or --config=ui.debugger=1 as a repo |
307 # name. This used to actually run the debugger. |
341 # name. This used to actually run the debugger. |
308 if (len(req.args) != 4 or |
342 if ( |
309 req.args[0] != '-R' or |
343 len(req.args) != 4 |
310 req.args[1].startswith('--') or |
344 or req.args[0] != '-R' |
311 req.args[2] != 'serve' or |
345 or req.args[1].startswith('--') |
312 req.args[3] != '--stdio'): |
346 or req.args[2] != 'serve' |
|
347 or req.args[3] != '--stdio' |
|
348 ): |
313 raise error.Abort( |
349 raise error.Abort( |
314 _('potentially unsafe serve --stdio invocation: %s') % |
350 _('potentially unsafe serve --stdio invocation: %s') |
315 (stringutil.pprint(req.args),)) |
351 % (stringutil.pprint(req.args),) |
|
352 ) |
316 |
353 |
317 try: |
354 try: |
318 debugger = 'pdb' |
355 debugger = 'pdb' |
319 debugtrace = { |
356 debugtrace = {'pdb': pdb.set_trace} |
320 'pdb': pdb.set_trace |
357 debugmortem = {'pdb': pdb.post_mortem} |
321 } |
|
322 debugmortem = { |
|
323 'pdb': pdb.post_mortem |
|
324 } |
|
325 |
358 |
326 # read --config before doing anything else |
359 # read --config before doing anything else |
327 # (e.g. to change trust settings for reading .hg/hgrc) |
360 # (e.g. to change trust settings for reading .hg/hgrc) |
328 cfgs = _parseconfig(req.ui, req.earlyoptions['config']) |
361 cfgs = _parseconfig(req.ui, req.earlyoptions['config']) |
329 |
362 |
345 # debugging has been requested |
378 # debugging has been requested |
346 with demandimport.deactivated(): |
379 with demandimport.deactivated(): |
347 try: |
380 try: |
348 debugmod = __import__(debugger) |
381 debugmod = __import__(debugger) |
349 except ImportError: |
382 except ImportError: |
350 pass # Leave debugmod = pdb |
383 pass # Leave debugmod = pdb |
351 |
384 |
352 debugtrace[debugger] = debugmod.set_trace |
385 debugtrace[debugger] = debugmod.set_trace |
353 debugmortem[debugger] = debugmod.post_mortem |
386 debugmortem[debugger] = debugmod.post_mortem |
354 |
387 |
355 # enter the debugger before command execution |
388 # enter the debugger before command execution |
356 if req.earlyoptions['debugger']: |
389 if req.earlyoptions['debugger']: |
357 ui.warn(_("entering debugger - " |
390 ui.warn( |
358 "type c to continue starting hg or h for help\n")) |
391 _( |
359 |
392 "entering debugger - " |
360 if (debugger != 'pdb' and |
393 "type c to continue starting hg or h for help\n" |
361 debugtrace[debugger] == debugtrace['pdb']): |
394 ) |
362 ui.warn(_("%s debugger specified " |
395 ) |
363 "but its module was not found\n") % debugger) |
396 |
|
397 if ( |
|
398 debugger != 'pdb' |
|
399 and debugtrace[debugger] == debugtrace['pdb'] |
|
400 ): |
|
401 ui.warn( |
|
402 _( |
|
403 "%s debugger specified " |
|
404 "but its module was not found\n" |
|
405 ) |
|
406 % debugger |
|
407 ) |
364 with demandimport.deactivated(): |
408 with demandimport.deactivated(): |
365 debugtrace[debugger]() |
409 debugtrace[debugger]() |
366 try: |
410 try: |
367 return _dispatch(req) |
411 return _dispatch(req) |
368 finally: |
412 finally: |
369 ui.flush() |
413 ui.flush() |
370 except: # re-raises |
414 except: # re-raises |
371 # enter the debugger when we hit an exception |
415 # enter the debugger when we hit an exception |
372 if req.earlyoptions['debugger']: |
416 if req.earlyoptions['debugger']: |
373 traceback.print_exc() |
417 traceback.print_exc() |
374 debugmortem[debugger](sys.exc_info()[2]) |
418 debugmortem[debugger](sys.exc_info()[2]) |
375 raise |
419 raise |
|
420 |
376 return _callcatch(ui, _runcatchfunc) |
421 return _callcatch(ui, _runcatchfunc) |
|
422 |
377 |
423 |
378 def _callcatch(ui, func): |
424 def _callcatch(ui, func): |
379 """like scmutil.callcatch but handles more high-level exceptions about |
425 """like scmutil.callcatch but handles more high-level exceptions about |
380 config parsing and commands. besides, use handlecommandexception to handle |
426 config parsing and commands. besides, use handlecommandexception to handle |
381 uncaught exceptions. |
427 uncaught exceptions. |
382 """ |
428 """ |
383 try: |
429 try: |
384 return scmutil.callcatch(ui, func) |
430 return scmutil.callcatch(ui, func) |
385 except error.AmbiguousCommand as inst: |
431 except error.AmbiguousCommand as inst: |
386 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") % |
432 ui.warn( |
387 (inst.args[0], " ".join(inst.args[1]))) |
433 _("hg: command '%s' is ambiguous:\n %s\n") |
|
434 % (inst.args[0], " ".join(inst.args[1])) |
|
435 ) |
388 except error.CommandError as inst: |
436 except error.CommandError as inst: |
389 if inst.args[0]: |
437 if inst.args[0]: |
390 ui.pager('help') |
438 ui.pager('help') |
391 msgbytes = pycompat.bytestr(inst.args[1]) |
439 msgbytes = pycompat.bytestr(inst.args[1]) |
392 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes)) |
440 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes)) |
425 if not handlecommandexception(ui): |
474 if not handlecommandexception(ui): |
426 raise |
475 raise |
427 |
476 |
428 return -1 |
477 return -1 |
429 |
478 |
|
479 |
430 def aliasargs(fn, givenargs): |
480 def aliasargs(fn, givenargs): |
431 args = [] |
481 args = [] |
432 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction |
482 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction |
433 if not util.safehasattr(fn, '_origfunc'): |
483 if not util.safehasattr(fn, '_origfunc'): |
434 args = getattr(fn, 'args', args) |
484 args = getattr(fn, 'args', args) |
435 if args: |
485 if args: |
436 cmd = ' '.join(map(procutil.shellquote, args)) |
486 cmd = ' '.join(map(procutil.shellquote, args)) |
437 |
487 |
438 nums = [] |
488 nums = [] |
|
489 |
439 def replacer(m): |
490 def replacer(m): |
440 num = int(m.group(1)) - 1 |
491 num = int(m.group(1)) - 1 |
441 nums.append(num) |
492 nums.append(num) |
442 if num < len(givenargs): |
493 if num < len(givenargs): |
443 return givenargs[num] |
494 return givenargs[num] |
444 raise error.Abort(_('too few arguments for command alias')) |
495 raise error.Abort(_('too few arguments for command alias')) |
|
496 |
445 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd) |
497 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd) |
446 givenargs = [x for i, x in enumerate(givenargs) |
498 givenargs = [x for i, x in enumerate(givenargs) if i not in nums] |
447 if i not in nums] |
|
448 args = pycompat.shlexsplit(cmd) |
499 args = pycompat.shlexsplit(cmd) |
449 return args + givenargs |
500 return args + givenargs |
|
501 |
450 |
502 |
451 def aliasinterpolate(name, args, cmd): |
503 def aliasinterpolate(name, args, cmd): |
452 '''interpolate args into cmd for shell aliases |
504 '''interpolate args into cmd for shell aliases |
453 |
505 |
454 This also handles $0, $@ and "$@". |
506 This also handles $0, $@ and "$@". |
497 return |
550 return |
498 |
551 |
499 if self.definition.startswith('!'): |
552 if self.definition.startswith('!'): |
500 shdef = self.definition[1:] |
553 shdef = self.definition[1:] |
501 self.shell = True |
554 self.shell = True |
|
555 |
502 def fn(ui, *args): |
556 def fn(ui, *args): |
503 env = {'HG_ARGS': ' '.join((self.name,) + args)} |
557 env = {'HG_ARGS': ' '.join((self.name,) + args)} |
|
558 |
504 def _checkvar(m): |
559 def _checkvar(m): |
505 if m.groups()[0] == '$': |
560 if m.groups()[0] == '$': |
506 return m.group() |
561 return m.group() |
507 elif int(m.groups()[0]) <= len(args): |
562 elif int(m.groups()[0]) <= len(args): |
508 return m.group() |
563 return m.group() |
509 else: |
564 else: |
510 ui.debug("No argument found for substitution " |
565 ui.debug( |
511 "of %i variable in alias '%s' definition.\n" |
566 "No argument found for substitution " |
512 % (int(m.groups()[0]), self.name)) |
567 "of %i variable in alias '%s' definition.\n" |
|
568 % (int(m.groups()[0]), self.name) |
|
569 ) |
513 return '' |
570 return '' |
|
571 |
514 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef) |
572 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef) |
515 cmd = aliasinterpolate(self.name, args, cmd) |
573 cmd = aliasinterpolate(self.name, args, cmd) |
516 return ui.system(cmd, environ=env, |
574 return ui.system( |
517 blockedtag='alias_%s' % self.name) |
575 cmd, environ=env, blockedtag='alias_%s' % self.name |
|
576 ) |
|
577 |
518 self.fn = fn |
578 self.fn = fn |
519 self.alias = True |
579 self.alias = True |
520 self._populatehelp(ui, name, shdef, self.fn) |
580 self._populatehelp(ui, name, shdef, self.fn) |
521 return |
581 return |
522 |
582 |
523 try: |
583 try: |
524 args = pycompat.shlexsplit(self.definition) |
584 args = pycompat.shlexsplit(self.definition) |
525 except ValueError as inst: |
585 except ValueError as inst: |
526 self.badalias = (_("error in definition for alias '%s': %s") |
586 self.badalias = _("error in definition for alias '%s': %s") % ( |
527 % (self.name, stringutil.forcebytestr(inst))) |
587 self.name, |
|
588 stringutil.forcebytestr(inst), |
|
589 ) |
528 return |
590 return |
529 earlyopts, args = _earlysplitopts(args) |
591 earlyopts, args = _earlysplitopts(args) |
530 if earlyopts: |
592 if earlyopts: |
531 self.badalias = (_("error in definition for alias '%s': %s may " |
593 self.badalias = _( |
532 "only be given on the command line") |
594 "error in definition for alias '%s': %s may " |
533 % (self.name, '/'.join(pycompat.ziplist(*earlyopts) |
595 "only be given on the command line" |
534 [0]))) |
596 ) % (self.name, '/'.join(pycompat.ziplist(*earlyopts)[0])) |
535 return |
597 return |
536 self.cmdname = cmd = args.pop(0) |
598 self.cmdname = cmd = args.pop(0) |
537 self.givenargs = args |
599 self.givenargs = args |
538 |
600 |
539 try: |
601 try: |
546 |
608 |
547 self.alias = True |
609 self.alias = True |
548 self._populatehelp(ui, name, cmd, self.fn, cmdhelp) |
610 self._populatehelp(ui, name, cmd, self.fn, cmdhelp) |
549 |
611 |
550 except error.UnknownCommand: |
612 except error.UnknownCommand: |
551 self.badalias = (_("alias '%s' resolves to unknown command '%s'") |
613 self.badalias = _("alias '%s' resolves to unknown command '%s'") % ( |
552 % (self.name, cmd)) |
614 self.name, |
|
615 cmd, |
|
616 ) |
553 self.unknowncmd = True |
617 self.unknowncmd = True |
554 except error.AmbiguousCommand: |
618 except error.AmbiguousCommand: |
555 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'") |
619 self.badalias = _( |
556 % (self.name, cmd)) |
620 "alias '%s' resolves to ambiguous command '%s'" |
|
621 ) % (self.name, cmd) |
557 |
622 |
558 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None): |
623 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None): |
559 # confine strings to be passed to i18n.gettext() |
624 # confine strings to be passed to i18n.gettext() |
560 cfg = {} |
625 cfg = {} |
561 for k in ('doc', 'help', 'category'): |
626 for k in ('doc', 'help', 'category'): |
562 v = ui.config('alias', '%s:%s' % (name, k), None) |
627 v = ui.config('alias', '%s:%s' % (name, k), None) |
563 if v is None: |
628 if v is None: |
564 continue |
629 continue |
565 if not encoding.isasciistr(v): |
630 if not encoding.isasciistr(v): |
566 self.badalias = (_("non-ASCII character in alias definition " |
631 self.badalias = _( |
567 "'%s:%s'") % (name, k)) |
632 "non-ASCII character in alias definition " "'%s:%s'" |
|
633 ) % (name, k) |
568 return |
634 return |
569 cfg[k] = v |
635 cfg[k] = v |
570 |
636 |
571 self.help = cfg.get('help', defaulthelp or '') |
637 self.help = cfg.get('help', defaulthelp or '') |
572 if self.help and self.help.startswith("hg " + cmd): |
638 if self.help and self.help.startswith("hg " + cmd): |
573 # drop prefix in old-style help lines so hg shows the alias |
639 # drop prefix in old-style help lines so hg shows the alias |
574 self.help = self.help[4 + len(cmd):] |
640 self.help = self.help[4 + len(cmd) :] |
575 |
641 |
576 self.owndoc = 'doc' in cfg |
642 self.owndoc = 'doc' in cfg |
577 doc = cfg.get('doc', pycompat.getdoc(fn)) |
643 doc = cfg.get('doc', pycompat.getdoc(fn)) |
578 if doc is not None: |
644 if doc is not None: |
579 doc = pycompat.sysstr(doc) |
645 doc = pycompat.sysstr(doc) |
585 def args(self): |
651 def args(self): |
586 args = pycompat.maplist(util.expandpath, self.givenargs) |
652 args = pycompat.maplist(util.expandpath, self.givenargs) |
587 return aliasargs(self.fn, args) |
653 return aliasargs(self.fn, args) |
588 |
654 |
589 def __getattr__(self, name): |
655 def __getattr__(self, name): |
590 adefaults = {r'norepo': True, r'intents': set(), |
656 adefaults = { |
591 r'optionalrepo': False, r'inferrepo': False} |
657 r'norepo': True, |
|
658 r'intents': set(), |
|
659 r'optionalrepo': False, |
|
660 r'inferrepo': False, |
|
661 } |
592 if name not in adefaults: |
662 if name not in adefaults: |
593 raise AttributeError(name) |
663 raise AttributeError(name) |
594 if self.badalias or util.safehasattr(self, 'shell'): |
664 if self.badalias or util.safehasattr(self, 'shell'): |
595 return adefaults[name] |
665 return adefaults[name] |
596 return getattr(self.fn, name) |
666 return getattr(self.fn, name) |
605 hint = _("'%s' is provided by '%s' extension") % (cmd, ext) |
675 hint = _("'%s' is provided by '%s' extension") % (cmd, ext) |
606 except error.UnknownCommand: |
676 except error.UnknownCommand: |
607 pass |
677 pass |
608 raise error.Abort(self.badalias, hint=hint) |
678 raise error.Abort(self.badalias, hint=hint) |
609 if self.shadows: |
679 if self.shadows: |
610 ui.debug("alias '%s' shadows command '%s'\n" % |
680 ui.debug( |
611 (self.name, self.cmdname)) |
681 "alias '%s' shadows command '%s'\n" % (self.name, self.cmdname) |
612 |
682 ) |
613 ui.log('commandalias', "alias '%s' expands to '%s'\n", |
683 |
614 self.name, self.definition) |
684 ui.log( |
|
685 'commandalias', |
|
686 "alias '%s' expands to '%s'\n", |
|
687 self.name, |
|
688 self.definition, |
|
689 ) |
615 if util.safehasattr(self, 'shell'): |
690 if util.safehasattr(self, 'shell'): |
616 return self.fn(ui, *args, **opts) |
691 return self.fn(ui, *args, **opts) |
617 else: |
692 else: |
618 try: |
693 try: |
619 return util.checksignature(self.fn)(ui, *args, **opts) |
694 return util.checksignature(self.fn)(ui, *args, **opts) |
620 except error.SignatureError: |
695 except error.SignatureError: |
621 args = ' '.join([self.cmdname] + self.args) |
696 args = ' '.join([self.cmdname] + self.args) |
622 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args)) |
697 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args)) |
623 raise |
698 raise |
624 |
699 |
|
700 |
625 class lazyaliasentry(object): |
701 class lazyaliasentry(object): |
626 """like a typical command entry (func, opts, help), but is lazy""" |
702 """like a typical command entry (func, opts, help), but is lazy""" |
627 |
703 |
628 def __init__(self, ui, name, definition, cmdtable, source): |
704 def __init__(self, ui, name, definition, cmdtable, source): |
629 self.ui = ui |
705 self.ui = ui |
681 except getopt.GetoptError as inst: |
760 except getopt.GetoptError as inst: |
682 raise error.CommandError(None, stringutil.forcebytestr(inst)) |
761 raise error.CommandError(None, stringutil.forcebytestr(inst)) |
683 |
762 |
684 if args: |
763 if args: |
685 cmd, args = args[0], args[1:] |
764 cmd, args = args[0], args[1:] |
686 aliases, entry = cmdutil.findcmd(cmd, commands.table, |
765 aliases, entry = cmdutil.findcmd( |
687 ui.configbool("ui", "strict")) |
766 cmd, commands.table, ui.configbool("ui", "strict") |
|
767 ) |
688 cmd = aliases[0] |
768 cmd = aliases[0] |
689 args = aliasargs(entry[0], args) |
769 args = aliasargs(entry[0], args) |
690 defaults = ui.config("defaults", cmd) |
770 defaults = ui.config("defaults", cmd) |
691 if defaults: |
771 if defaults: |
692 args = pycompat.maplist( |
772 args = ( |
693 util.expandpath, pycompat.shlexsplit(defaults)) + args |
773 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults)) |
|
774 + args |
|
775 ) |
694 c = list(entry[1]) |
776 c = list(entry[1]) |
695 else: |
777 else: |
696 cmd = None |
778 cmd = None |
697 c = [] |
779 c = [] |
698 |
780 |
711 options[n] = cmdoptions[n] |
793 options[n] = cmdoptions[n] |
712 del cmdoptions[n] |
794 del cmdoptions[n] |
713 |
795 |
714 return (cmd, cmd and entry[0] or None, args, options, cmdoptions) |
796 return (cmd, cmd and entry[0] or None, args, options, cmdoptions) |
715 |
797 |
|
798 |
716 def _parseconfig(ui, config): |
799 def _parseconfig(ui, config): |
717 """parse the --config options from the command line""" |
800 """parse the --config options from the command line""" |
718 configs = [] |
801 configs = [] |
719 |
802 |
720 for cfg in config: |
803 for cfg in config: |
721 try: |
804 try: |
722 name, value = [cfgelem.strip() |
805 name, value = [cfgelem.strip() for cfgelem in cfg.split('=', 1)] |
723 for cfgelem in cfg.split('=', 1)] |
|
724 section, name = name.split('.', 1) |
806 section, name = name.split('.', 1) |
725 if not section or not name: |
807 if not section or not name: |
726 raise IndexError |
808 raise IndexError |
727 ui.setconfig(section, name, value, '--config') |
809 ui.setconfig(section, name, value, '--config') |
728 configs.append((section, name, value)) |
810 configs.append((section, name, value)) |
729 except (IndexError, ValueError): |
811 except (IndexError, ValueError): |
730 raise error.Abort(_('malformed --config option: %r ' |
812 raise error.Abort( |
731 '(use --config section.name=value)') |
813 _( |
732 % pycompat.bytestr(cfg)) |
814 'malformed --config option: %r ' |
|
815 '(use --config section.name=value)' |
|
816 ) |
|
817 % pycompat.bytestr(cfg) |
|
818 ) |
733 |
819 |
734 return configs |
820 return configs |
|
821 |
735 |
822 |
736 def _earlyparseopts(ui, args): |
823 def _earlyparseopts(ui, args): |
737 options = {} |
824 options = {} |
738 fancyopts.fancyopts(args, commands.globalopts, options, |
825 fancyopts.fancyopts( |
739 gnu=not ui.plain('strictflags'), early=True, |
826 args, |
740 optaliases={'repository': ['repo']}) |
827 commands.globalopts, |
|
828 options, |
|
829 gnu=not ui.plain('strictflags'), |
|
830 early=True, |
|
831 optaliases={'repository': ['repo']}, |
|
832 ) |
741 return options |
833 return options |
|
834 |
742 |
835 |
743 def _earlysplitopts(args): |
836 def _earlysplitopts(args): |
744 """Split args into a list of possible early options and remainder args""" |
837 """Split args into a list of possible early options and remainder args""" |
745 shortoptions = 'R:' |
838 shortoptions = 'R:' |
746 # TODO: perhaps 'debugger' should be included |
839 # TODO: perhaps 'debugger' should be included |
747 longoptions = ['cwd=', 'repository=', 'repo=', 'config='] |
840 longoptions = ['cwd=', 'repository=', 'repo=', 'config='] |
748 return fancyopts.earlygetopt(args, shortoptions, longoptions, |
841 return fancyopts.earlygetopt( |
749 gnu=True, keepsep=True) |
842 args, shortoptions, longoptions, gnu=True, keepsep=True |
|
843 ) |
|
844 |
750 |
845 |
751 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions): |
846 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions): |
752 # run pre-hook, and abort if it fails |
847 # run pre-hook, and abort if it fails |
753 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs), |
848 hook.hook( |
754 pats=cmdpats, opts=cmdoptions) |
849 lui, |
|
850 repo, |
|
851 "pre-%s" % cmd, |
|
852 True, |
|
853 args=" ".join(fullargs), |
|
854 pats=cmdpats, |
|
855 opts=cmdoptions, |
|
856 ) |
755 try: |
857 try: |
756 ret = _runcommand(ui, options, cmd, d) |
858 ret = _runcommand(ui, options, cmd, d) |
757 # run post-hook, passing command result |
859 # run post-hook, passing command result |
758 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs), |
860 hook.hook( |
759 result=ret, pats=cmdpats, opts=cmdoptions) |
861 lui, |
|
862 repo, |
|
863 "post-%s" % cmd, |
|
864 False, |
|
865 args=" ".join(fullargs), |
|
866 result=ret, |
|
867 pats=cmdpats, |
|
868 opts=cmdoptions, |
|
869 ) |
760 except Exception: |
870 except Exception: |
761 # run failure hook and re-raise |
871 # run failure hook and re-raise |
762 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs), |
872 hook.hook( |
763 pats=cmdpats, opts=cmdoptions) |
873 lui, |
|
874 repo, |
|
875 "fail-%s" % cmd, |
|
876 False, |
|
877 args=" ".join(fullargs), |
|
878 pats=cmdpats, |
|
879 opts=cmdoptions, |
|
880 ) |
764 raise |
881 raise |
765 return ret |
882 return ret |
|
883 |
766 |
884 |
767 def _getlocal(ui, rpath, wd=None): |
885 def _getlocal(ui, rpath, wd=None): |
768 """Return (path, local ui object) for the given target path. |
886 """Return (path, local ui object) for the given target path. |
769 |
887 |
770 Takes paths in [cwd]/.hg/hgrc into account." |
888 Takes paths in [cwd]/.hg/hgrc into account." |
771 """ |
889 """ |
772 if wd is None: |
890 if wd is None: |
773 try: |
891 try: |
774 wd = encoding.getcwd() |
892 wd = encoding.getcwd() |
775 except OSError as e: |
893 except OSError as e: |
776 raise error.Abort(_("error getting current working directory: %s") % |
894 raise error.Abort( |
777 encoding.strtolocal(e.strerror)) |
895 _("error getting current working directory: %s") |
|
896 % encoding.strtolocal(e.strerror) |
|
897 ) |
778 path = cmdutil.findrepo(wd) or "" |
898 path = cmdutil.findrepo(wd) or "" |
779 if not path: |
899 if not path: |
780 lui = ui |
900 lui = ui |
781 else: |
901 else: |
782 lui = ui.copy() |
902 lui = ui.copy() |
885 if options["config"] != req.earlyoptions["config"]: |
1011 if options["config"] != req.earlyoptions["config"]: |
886 raise error.Abort(_("option --config may not be abbreviated!")) |
1012 raise error.Abort(_("option --config may not be abbreviated!")) |
887 if options["cwd"] != req.earlyoptions["cwd"]: |
1013 if options["cwd"] != req.earlyoptions["cwd"]: |
888 raise error.Abort(_("option --cwd may not be abbreviated!")) |
1014 raise error.Abort(_("option --cwd may not be abbreviated!")) |
889 if options["repository"] != req.earlyoptions["repository"]: |
1015 if options["repository"] != req.earlyoptions["repository"]: |
890 raise error.Abort(_( |
1016 raise error.Abort( |
891 "option -R has to be separated from other options (e.g. not " |
1017 _( |
892 "-qR) and --repository may only be abbreviated as --repo!")) |
1018 "option -R has to be separated from other options (e.g. not " |
|
1019 "-qR) and --repository may only be abbreviated as --repo!" |
|
1020 ) |
|
1021 ) |
893 if options["debugger"] != req.earlyoptions["debugger"]: |
1022 if options["debugger"] != req.earlyoptions["debugger"]: |
894 raise error.Abort(_("option --debugger may not be abbreviated!")) |
1023 raise error.Abort(_("option --debugger may not be abbreviated!")) |
895 # don't validate --profile/--traceback, which can be enabled from now |
1024 # don't validate --profile/--traceback, which can be enabled from now |
896 |
1025 |
897 if options["encoding"]: |
1026 if options["encoding"]: |
898 encoding.encoding = options["encoding"] |
1027 encoding.encoding = options["encoding"] |
899 if options["encodingmode"]: |
1028 if options["encodingmode"]: |
900 encoding.encodingmode = options["encodingmode"] |
1029 encoding.encodingmode = options["encodingmode"] |
901 if options["time"]: |
1030 if options["time"]: |
|
1031 |
902 def get_times(): |
1032 def get_times(): |
903 t = os.times() |
1033 t = os.times() |
904 if t[4] == 0.0: |
1034 if t[4] == 0.0: |
905 # Windows leaves this as zero, so use time.clock() |
1035 # Windows leaves this as zero, so use time.clock() |
906 t = (t[0], t[1], t[2], t[3], time.clock()) |
1036 t = (t[0], t[1], t[2], t[3], time.clock()) |
907 return t |
1037 return t |
|
1038 |
908 s = get_times() |
1039 s = get_times() |
|
1040 |
909 def print_time(): |
1041 def print_time(): |
910 t = get_times() |
1042 t = get_times() |
911 ui.warn( |
1043 ui.warn( |
912 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") % |
1044 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") |
913 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3])) |
1045 % ( |
|
1046 t[4] - s[4], |
|
1047 t[0] - s[0], |
|
1048 t[2] - s[2], |
|
1049 t[1] - s[1], |
|
1050 t[3] - s[3], |
|
1051 ) |
|
1052 ) |
|
1053 |
914 ui.atexit(print_time) |
1054 ui.atexit(print_time) |
915 if options["profile"]: |
1055 if options["profile"]: |
916 profiler.start() |
1056 profiler.start() |
917 |
1057 |
918 # if abbreviated version of this were used, take them in account, now |
1058 # if abbreviated version of this were used, take them in account, now |
975 repo.ui.fout = ui.fout |
1115 repo.ui.fout = ui.fout |
976 repo.ui.ferr = ui.ferr |
1116 repo.ui.ferr = ui.ferr |
977 repo.ui.fmsg = ui.fmsg |
1117 repo.ui.fmsg = ui.fmsg |
978 else: |
1118 else: |
979 try: |
1119 try: |
980 repo = hg.repository(ui, path=path, |
1120 repo = hg.repository( |
981 presetupfuncs=req.prereposetups, |
1121 ui, |
982 intents=func.intents) |
1122 path=path, |
|
1123 presetupfuncs=req.prereposetups, |
|
1124 intents=func.intents, |
|
1125 ) |
983 if not repo.local(): |
1126 if not repo.local(): |
984 raise error.Abort(_("repository '%s' is not local") |
1127 raise error.Abort( |
985 % path) |
1128 _("repository '%s' is not local") % path |
986 repo.ui.setconfig("bundle", "mainreporoot", repo.root, |
1129 ) |
987 'repo') |
1130 repo.ui.setconfig( |
|
1131 "bundle", "mainreporoot", repo.root, 'repo' |
|
1132 ) |
988 except error.RequirementError: |
1133 except error.RequirementError: |
989 raise |
1134 raise |
990 except error.RepoError: |
1135 except error.RepoError: |
991 if rpath: # invalid -R path |
1136 if rpath: # invalid -R path |
992 raise |
1137 raise |
993 if not func.optionalrepo: |
1138 if not func.optionalrepo: |
994 if func.inferrepo and args and not path: |
1139 if func.inferrepo and args and not path: |
995 # try to infer -R from command args |
1140 # try to infer -R from command args |
996 repos = pycompat.maplist(cmdutil.findrepo, args) |
1141 repos = pycompat.maplist(cmdutil.findrepo, args) |
1015 msg = _formatargs(fullargs) |
1164 msg = _formatargs(fullargs) |
1016 ui.log("command", '%s\n', msg) |
1165 ui.log("command", '%s\n', msg) |
1017 strcmdopt = pycompat.strkwargs(cmdoptions) |
1166 strcmdopt = pycompat.strkwargs(cmdoptions) |
1018 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt) |
1167 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt) |
1019 try: |
1168 try: |
1020 return runcommand(lui, repo, cmd, fullargs, ui, options, d, |
1169 return runcommand( |
1021 cmdpats, cmdoptions) |
1170 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions |
|
1171 ) |
1022 finally: |
1172 finally: |
1023 if repo and repo != req.repo: |
1173 if repo and repo != req.repo: |
1024 repo.close() |
1174 repo.close() |
|
1175 |
1025 |
1176 |
1026 def _runcommand(ui, options, cmd, cmdfunc): |
1177 def _runcommand(ui, options, cmd, cmdfunc): |
1027 """Run a command function, possibly with profiling enabled.""" |
1178 """Run a command function, possibly with profiling enabled.""" |
1028 try: |
1179 try: |
1029 with tracing.log("Running %s command" % cmd): |
1180 with tracing.log("Running %s command" % cmd): |
1030 return cmdfunc() |
1181 return cmdfunc() |
1031 except error.SignatureError: |
1182 except error.SignatureError: |
1032 raise error.CommandError(cmd, _('invalid arguments')) |
1183 raise error.CommandError(cmd, _('invalid arguments')) |
|
1184 |
1033 |
1185 |
1034 def _exceptionwarning(ui): |
1186 def _exceptionwarning(ui): |
1035 """Produce a warning message for the current active exception""" |
1187 """Produce a warning message for the current active exception""" |
1036 |
1188 |
1037 # For compatibility checking, we discard the portion of the hg |
1189 # For compatibility checking, we discard the portion of the hg |
1067 if worst[0] is None or nearest < worst[1]: |
1219 if worst[0] is None or nearest < worst[1]: |
1068 worst = name, nearest, report |
1220 worst = name, nearest, report |
1069 if worst[0] is not None: |
1221 if worst[0] is not None: |
1070 name, testedwith, report = worst |
1222 name, testedwith, report = worst |
1071 if not isinstance(testedwith, (bytes, str)): |
1223 if not isinstance(testedwith, (bytes, str)): |
1072 testedwith = '.'.join([stringutil.forcebytestr(c) |
1224 testedwith = '.'.join( |
1073 for c in testedwith]) |
1225 [stringutil.forcebytestr(c) for c in testedwith] |
1074 warning = (_('** Unknown exception encountered with ' |
1226 ) |
1075 'possibly-broken third-party extension %s\n' |
1227 warning = _( |
1076 '** which supports versions %s of Mercurial.\n' |
1228 '** Unknown exception encountered with ' |
1077 '** Please disable %s and try your action again.\n' |
1229 'possibly-broken third-party extension %s\n' |
1078 '** If that fixes the bug please report it to %s\n') |
1230 '** which supports versions %s of Mercurial.\n' |
1079 % (name, testedwith, name, stringutil.forcebytestr(report))) |
1231 '** Please disable %s and try your action again.\n' |
|
1232 '** If that fixes the bug please report it to %s\n' |
|
1233 ) % (name, testedwith, name, stringutil.forcebytestr(report)) |
1080 else: |
1234 else: |
1081 bugtracker = ui.config('ui', 'supportcontact') |
1235 bugtracker = ui.config('ui', 'supportcontact') |
1082 if bugtracker is None: |
1236 if bugtracker is None: |
1083 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker") |
1237 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker") |
1084 warning = (_("** unknown exception encountered, " |
1238 warning = ( |
1085 "please report by visiting\n** ") + bugtracker + '\n') |
1239 _( |
|
1240 "** unknown exception encountered, " |
|
1241 "please report by visiting\n** " |
|
1242 ) |
|
1243 + bugtracker |
|
1244 + '\n' |
|
1245 ) |
1086 sysversion = pycompat.sysbytes(sys.version).replace('\n', '') |
1246 sysversion = pycompat.sysbytes(sys.version).replace('\n', '') |
1087 warning += ((_("** Python %s\n") % sysversion) + |
1247 warning += ( |
1088 (_("** Mercurial Distributed SCM (version %s)\n") % |
1248 (_("** Python %s\n") % sysversion) |
1089 util.version()) + |
1249 + (_("** Mercurial Distributed SCM (version %s)\n") % util.version()) |
1090 (_("** Extensions loaded: %s\n") % |
1250 + ( |
1091 ", ".join([x[0] for x in extensions.extensions()]))) |
1251 _("** Extensions loaded: %s\n") |
|
1252 % ", ".join([x[0] for x in extensions.extensions()]) |
|
1253 ) |
|
1254 ) |
1092 return warning |
1255 return warning |
|
1256 |
1093 |
1257 |
1094 def handlecommandexception(ui): |
1258 def handlecommandexception(ui): |
1095 """Produce a warning message for broken commands |
1259 """Produce a warning message for broken commands |
1096 |
1260 |
1097 Called when handling an exception; the exception is reraised if |
1261 Called when handling an exception; the exception is reraised if |
1098 this function returns False, ignored otherwise. |
1262 this function returns False, ignored otherwise. |
1099 """ |
1263 """ |
1100 warning = _exceptionwarning(ui) |
1264 warning = _exceptionwarning(ui) |
1101 ui.log("commandexception", "%s\n%s\n", warning, |
1265 ui.log( |
1102 pycompat.sysbytes(traceback.format_exc())) |
1266 "commandexception", |
|
1267 "%s\n%s\n", |
|
1268 warning, |
|
1269 pycompat.sysbytes(traceback.format_exc()), |
|
1270 ) |
1103 ui.warn(warning) |
1271 ui.warn(warning) |
1104 return False # re-raise the exception |
1272 return False # re-raise the exception |