changeset 50923:c642c03969ff

dynamic-import: use sysstr for importing extension and others This logic is used by extensions, and python hooks and merge-tools. All this logic eventually deals with native string (unicode in Python 3). This patch makes it handle `str` directly instead of relying on some pycompat low lever layer to do the conversion at the last minutes. We adjust the Python version filtering of a test as the output seems to be present with Python 3.7 too.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 31 Aug 2023 02:41:33 +0200
parents 0e6cea0c3113
children 9bffc6c4e4c5
files mercurial/extensions.py mercurial/filemerge.py mercurial/hook.py tests/test-hook.t
diffstat 4 files changed, 35 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/extensions.py	Thu Aug 31 01:54:48 2023 +0200
+++ b/mercurial/extensions.py	Thu Aug 31 02:41:33 2023 +0200
@@ -84,9 +84,8 @@
 
 
 def loadpath(path, module_name):
-    module_name = module_name.replace(b'.', b'_')
+    module_name = module_name.replace('.', '_')
     path = util.normpath(util.expandpath(path))
-    module_name = pycompat.fsdecode(module_name)
     path = pycompat.fsdecode(path)
     if os.path.isdir(path):
         # module/__init__.py style
@@ -106,30 +105,31 @@
 
 def _importh(name):
     """import and return the <name> module"""
-    mod = __import__(pycompat.sysstr(name))
-    components = name.split(b'.')
+    mod = __import__(name)
+    components = name.split('.')
     for comp in components[1:]:
         mod = getattr(mod, comp)
     return mod
 
 
 def _importext(name, path=None, reportfunc=None):
+    name = pycompat.fsdecode(name)
     if path:
         # the module will be loaded in sys.modules
         # choose an unique name so that it doesn't
         # conflicts with other modules
-        mod = loadpath(path, b'hgext.%s' % name)
+        mod = loadpath(path, 'hgext.%s' % name)
     else:
         try:
-            mod = _importh(b"hgext.%s" % name)
+            mod = _importh("hgext.%s" % name)
         except ImportError as err:
             if reportfunc:
-                reportfunc(err, b"hgext.%s" % name, b"hgext3rd.%s" % name)
+                reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
             try:
-                mod = _importh(b"hgext3rd.%s" % name)
+                mod = _importh("hgext3rd.%s" % name)
             except ImportError as err:
                 if reportfunc:
-                    reportfunc(err, b"hgext3rd.%s" % name, name)
+                    reportfunc(err, "hgext3rd.%s" % name, name)
                 mod = _importh(name)
     return mod
 
@@ -140,9 +140,9 @@
     ui.log(
         b'extension',
         b'    - could not import %s (%s): trying %s\n',
-        failed,
+        stringutil.forcebytestr(failed),
         stringutil.forcebytestr(err),
-        next,
+        stringutil.forcebytestr(next),
     )
     if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'):
         ui.traceback()
--- a/mercurial/filemerge.py	Thu Aug 31 01:54:48 2023 +0200
+++ b/mercurial/filemerge.py	Thu Aug 31 02:41:33 2023 +0200
@@ -834,12 +834,13 @@
                 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
                 from . import extensions
 
-                mod = extensions.loadpath(toolpath, b'hgmerge.%s' % tool)
+                mod_name = 'hgmerge.%s' % pycompat.sysstr(tool)
+                mod = extensions.loadpath(toolpath, mod_name)
             except Exception:
                 raise error.Abort(
                     _(b"loading python merge script failed: %s") % toolpath
                 )
-            mergefn = getattr(mod, scriptfn, None)
+            mergefn = getattr(mod, pycompat.sysstr(scriptfn), None)
             if mergefn is None:
                 raise error.Abort(
                     _(b"%s does not have function: %s") % (toolpath, scriptfn)
--- a/mercurial/hook.py	Thu Aug 31 01:54:48 2023 +0200
+++ b/mercurial/hook.py	Thu Aug 31 02:41:33 2023 +0200
@@ -40,13 +40,14 @@
 
     if callable(funcname):
         obj = funcname
-        funcname = pycompat.sysbytes(obj.__module__ + "." + obj.__name__)
+        funcname = obj.__module__ + "." + obj.__name__
     else:
-        d = funcname.rfind(b'.')
+        funcname = pycompat.sysstr(funcname)
+        d = funcname.rfind('.')
         if d == -1:
             raise error.HookLoadError(
                 _(b'%s hook is invalid: "%s" not in a module')
-                % (hname, funcname)
+                % (hname, stringutil.forcebytestr(funcname))
             )
         modname = funcname[:d]
         oldpaths = sys.path
@@ -89,27 +90,30 @@
                         )
                     else:
                         tracebackhint = None
-                    raise error.HookLoadError(
-                        _(b'%s hook is invalid: import of "%s" failed')
-                        % (hname, modname),
-                        hint=tracebackhint,
+                    msg = _(b'%s hook is invalid: import of "%s" failed')
+                    msg %= (
+                        stringutil.forcebytestr(hname),
+                        stringutil.forcebytestr(modname),
                     )
+                    raise error.HookLoadError(msg, hint=tracebackhint)
         sys.path = oldpaths
         try:
-            for p in funcname.split(b'.')[1:]:
+            for p in funcname.split('.')[1:]:
                 obj = getattr(obj, p)
         except AttributeError:
             raise error.HookLoadError(
                 _(b'%s hook is invalid: "%s" is not defined')
-                % (hname, funcname)
+                % (hname, stringutil.forcebytestr(funcname))
             )
         if not callable(obj):
             raise error.HookLoadError(
                 _(b'%s hook is invalid: "%s" is not callable')
-                % (hname, funcname)
+                % (hname, stringutil.forcebytestr(funcname))
             )
 
-    ui.note(_(b"calling hook %s: %s\n") % (hname, funcname))
+    ui.note(
+        _(b"calling hook %s: %s\n") % (hname, stringutil.forcebytestr(funcname))
+    )
     starttime = util.timer()
 
     try:
@@ -134,7 +138,7 @@
             b'pythonhook',
             b'pythonhook-%s: %s finished in %0.2f seconds\n',
             htype,
-            funcname,
+            stringutil.forcebytestr(funcname),
             duration,
         )
     if r:
@@ -347,11 +351,12 @@
                     if repo:
                         path = os.path.join(repo.root, path)
                     try:
-                        mod = extensions.loadpath(path, b'hghook.%s' % hname)
+                        mod_name = 'hghook.%s' % pycompat.sysstr(hname)
+                        mod = extensions.loadpath(path, mod_name)
                     except Exception:
                         ui.write(_(b"loading %s hook failed:\n") % hname)
                         raise
-                    hookfn = getattr(mod, cmd)
+                    hookfn = getattr(mod, pycompat.sysstr(cmd))
                 else:
                     hookfn = cmd[7:].strip()
                 r, raised = pythonhook(
--- a/tests/test-hook.t	Thu Aug 31 01:54:48 2023 +0200
+++ b/tests/test-hook.t	Thu Aug 31 02:41:33 2023 +0200
@@ -991,7 +991,7 @@
   Traceback (most recent call last):
   ModuleNotFoundError: No module named 'hgext_syntaxerror'
   Traceback (most recent call last):
-      raise error.HookLoadError( (py38 !)
+      raise error.HookLoadError(msg, hint=tracebackhint) (py37 !)
   mercurial.error.HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
   abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
 
@@ -1156,7 +1156,7 @@
   Traceback (most recent call last):
   ModuleNotFoundError: No module named 'hgext_importfail'
   Traceback (most recent call last):
-      raise error.HookLoadError( (py38 !)
+      raise error.HookLoadError(msg, hint=tracebackhint) (py37 !)
   mercurial.error.HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed
   abort: precommit.importfail hook is invalid: import of "importfail" failed