sapling/eden/scm/edenscm/hgext/debugshell.py
Jun Wu b7a9d6344c debugshell: update locals() with globals()
Summary:
Some features in debushell, like `%time` uses `locals()`. They currently cannot
use common variables like `cl` like:

  In [1]: %time len(cl)
  ---------------------------------------------------------------------------
  NameError                                 Traceback (most recent call last)
  <timed eval> in <module>

  NameError: name 'cl' is not defined

Fix it by updating `locals()`.

  In [1]: %time len(cl)
  CPU times: user 700 µs, sys: 130 µs, total: 830 µs
  Wall time: 1.28 ms

Reviewed By: ikostia

Differential Revision: D26590943

fbshipit-source-id: 2b7fd3274f1ea41f7996edbbdae0af8e103a9c0a
2021-02-22 16:53:54 -08:00

219 lines
6.1 KiB
Python

# Portions Copyright (c) Facebook, Inc. and its affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
# Copyright 2010 Mercurial Contributors
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
# debugshell extension
"""a python shell with repo, changelog & manifest objects"""
from __future__ import absolute_import
import os
import shlex
import sys
import bindings
import edenscm
import edenscmnative
from edenscm import hgdemandimport
from edenscm import hgext, mercurial, traceimport
from edenscm.hgext import commitcloud as cc
from edenscm.mercurial import pycompat, registrar, util
from edenscm.mercurial.i18n import _
from edenscm.mercurial.pycompat import decodeutf8
cmdtable = {}
command = registrar.command(cmdtable)
def _assignobjects(objects, repo):
objects.update(
{
# Shortcuts
"b": bindings,
"m": mercurial,
"x": hgext,
"td": bindings.tracing.tracingdata,
# Modules
"bindings": bindings,
"edenscm": edenscm,
"edenscmnative": edenscmnative,
"mercurial": mercurial,
# Utilities
"util": mercurial.util,
"hex": mercurial.node.hex,
"bin": mercurial.node.bin,
}
)
if repo:
objects.update(
{
"repo": repo,
"cl": repo.changelog,
"mf": repo.manifestlog,
# metalog is not available on hg server-side repos. Consider making it
# available unconditionally once we get rid of hg servers.
"ml": getattr(repo.svfs, "metalog", None),
"ms": getattr(repo, "_mutationstore", None),
}
)
# Commit cloud service.
ui = repo.ui
try:
token = cc.token.TokenLocator(ui).token
if token is not None:
objects["serv"] = cc.service.get(ui, token)
except Exception:
pass
# EdenAPI client
try:
objects["api"] = repo.edenapi
except Exception:
pass
# Import other handy modules
for name in ["os", "sys", "subprocess", "re"]:
objects[name] = __import__(name)
@command(
"debugshell|dbsh|debugsh",
[("c", "command", "", _("program passed in as string"), _("CMD"))],
optionalrepo=True,
)
def debugshell(ui, repo, *args, **opts):
command = opts.get("command")
_assignobjects(locals(), repo)
globals().update(locals())
sys.argv = pycompat.sysargv = args
if command:
exec(command)
return 0
if args:
path = args[0]
with open(path) as f:
command = f.read()
globalvars = dict(globals())
localvars = dict(locals())
globalvars["__file__"] = path
exec(command, globalvars, localvars)
return 0
elif not ui.interactive():
command = ui.fin.read()
exec(command)
return 0
# IPython is incompatible with demandimport.
with hgdemandimport.deactivated():
_startipython(ui, repo)
def _startipython(ui, repo):
from IPython.terminal.embed import InteractiveShellEmbed
from IPython.terminal.ipapp import load_default_config
bannermsg = "loaded repo: %s\n" "using source: %s" % (
repo and repo.root or "(none)",
mercurial.__path__[0],
) + (
"\n\nAvailable variables:\n"
" m: edenscm.mercurial\n"
" x: edenscm.hgext\n"
" b: bindings\n"
" ui: the ui object\n"
" c: run command and take output\n"
)
if repo:
bannermsg += (
" repo: the repo object\n"
" serv: commitcloud service\n"
" api: edenapi client\n"
" cl: repo.changelog\n"
" mf: repo.manifestlog\n"
" ml: repo.svfs.metalog\n"
" ms: repo._mutationstore\n"
)
bannermsg += """
Available IPython magics (auto magic is on, `%` is optional):
time: measure time
timeit: benchmark
trace: run and print ASCII trace (better with --trace command flag)
hg: run commands inline
"""
config = load_default_config()
config.InteractiveShellEmbed = config.TerminalInteractiveShell
config.InteractiveShell.automagic = True
config.InteractiveShell.banner2 = bannermsg
config.InteractiveShell.confirm_exit = False
shell = InteractiveShellEmbed.instance(config=config)
_configipython(ui, shell)
locals().update(globals())
shell()
def c(args):
"""Run command with args and take its output.
Example::
c(['log', '-r.'])
c('log -r.')
%trace c('log -r.')
"""
if isinstance(args, str):
args = shlex.split(args)
ui = globals()["ui"]
fin = util.stringio()
fout = util.stringio()
bindings.commands.run(["hg"] + args, fin, fout, ui.ferr)
return fout.getvalue()
def _configipython(ui, ipython):
"""Set up IPython features like magics"""
from IPython.core.magic import register_line_magic
# get_ipython is used by register_line_magic
get_ipython = ipython.get_ipython # noqa: F841
@register_line_magic
def hg(line):
args = ["hg"] + shlex.split(line)
return bindings.commands.run(args, ui.fin, ui.fout, ui.ferr)
@register_line_magic
def trace(line, ui=ui, shell=ipython):
"""run and print ASCII trace"""
code = compile(line, "<magic-trace>", "exec")
td = bindings.tracing.tracingdata()
ns = shell.user_ns
ns.update(globals())
start = util.timer()
_execwith(td, code, ns)
durationmicros = (util.timer() - start) * 1e6
# hide spans less than 50 microseconds, or 1% of the total time
asciitrace = td.ascii(int(durationmicros / 100) + 50)
ui.write_err("%s" % asciitrace)
if not traceimport.enabled:
ui.write_err("(use 'debugshell --trace' to enable more detailed trace)\n")
return td
def _execwith(td, code, ns):
with td:
exec(code, ns)