mercurial/hook.py
changeset 43076 2372284d9457
parent 40971 8c8fcb385c46
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    22 from .utils import (
    22 from .utils import (
    23     procutil,
    23     procutil,
    24     stringutil,
    24     stringutil,
    25 )
    25 )
    26 
    26 
       
    27 
    27 def pythonhook(ui, repo, htype, hname, funcname, args, throw):
    28 def pythonhook(ui, repo, htype, hname, funcname, args, throw):
    28     '''call python hook. hook is callable object, looked up as
    29     '''call python hook. hook is callable object, looked up as
    29     name in python module. if callable returns "true", hook
    30     name in python module. if callable returns "true", hook
    30     fails, else passes. if hook raises exception, treated as
    31     fails, else passes. if hook raises exception, treated as
    31     hook failure. exception propagates if throw is "true".
    32     hook failure. exception propagates if throw is "true".
    40     else:
    41     else:
    41         d = funcname.rfind('.')
    42         d = funcname.rfind('.')
    42         if d == -1:
    43         if d == -1:
    43             raise error.HookLoadError(
    44             raise error.HookLoadError(
    44                 _('%s hook is invalid: "%s" not in a module')
    45                 _('%s hook is invalid: "%s" not in a module')
    45                 % (hname, funcname))
    46                 % (hname, funcname)
       
    47             )
    46         modname = funcname[:d]
    48         modname = funcname[:d]
    47         oldpaths = sys.path
    49         oldpaths = sys.path
    48         if procutil.mainfrozen():
    50         if procutil.mainfrozen():
    49             # binary installs require sys.path manipulation
    51             # binary installs require sys.path manipulation
    50             modpath, modfile = os.path.split(modname)
    52             modpath, modfile = os.path.split(modname)
    60                     # extensions are loaded with hgext_ prefix
    62                     # extensions are loaded with hgext_ prefix
    61                     obj = __import__(r"hgext_%s" % pycompat.sysstr(modname))
    63                     obj = __import__(r"hgext_%s" % pycompat.sysstr(modname))
    62                 except (ImportError, SyntaxError):
    64                 except (ImportError, SyntaxError):
    63                     e2 = sys.exc_info()
    65                     e2 = sys.exc_info()
    64                     if ui.tracebackflag:
    66                     if ui.tracebackflag:
    65                         ui.warn(_('exception from first failed import '
    67                         ui.warn(
    66                                   'attempt:\n'))
    68                             _(
       
    69                                 'exception from first failed import '
       
    70                                 'attempt:\n'
       
    71                             )
       
    72                         )
    67                     ui.traceback(e1)
    73                     ui.traceback(e1)
    68                     if ui.tracebackflag:
    74                     if ui.tracebackflag:
    69                         ui.warn(_('exception from second failed import '
    75                         ui.warn(
    70                                   'attempt:\n'))
    76                             _(
       
    77                                 'exception from second failed import '
       
    78                                 'attempt:\n'
       
    79                             )
       
    80                         )
    71                     ui.traceback(e2)
    81                     ui.traceback(e2)
    72 
    82 
    73                     if not ui.tracebackflag:
    83                     if not ui.tracebackflag:
    74                         tracebackhint = _(
    84                         tracebackhint = _(
    75                             'run with --traceback for stack trace')
    85                             'run with --traceback for stack trace'
       
    86                         )
    76                     else:
    87                     else:
    77                         tracebackhint = None
    88                         tracebackhint = None
    78                     raise error.HookLoadError(
    89                     raise error.HookLoadError(
    79                         _('%s hook is invalid: import of "%s" failed') %
    90                         _('%s hook is invalid: import of "%s" failed')
    80                         (hname, modname), hint=tracebackhint)
    91                         % (hname, modname),
       
    92                         hint=tracebackhint,
       
    93                     )
    81         sys.path = oldpaths
    94         sys.path = oldpaths
    82         try:
    95         try:
    83             for p in funcname.split('.')[1:]:
    96             for p in funcname.split('.')[1:]:
    84                 obj = getattr(obj, p)
    97                 obj = getattr(obj, p)
    85         except AttributeError:
    98         except AttributeError:
    86             raise error.HookLoadError(
    99             raise error.HookLoadError(
    87                 _('%s hook is invalid: "%s" is not defined')
   100                 _('%s hook is invalid: "%s" is not defined') % (hname, funcname)
    88                 % (hname, funcname))
   101             )
    89         if not callable(obj):
   102         if not callable(obj):
    90             raise error.HookLoadError(
   103             raise error.HookLoadError(
    91                 _('%s hook is invalid: "%s" is not callable')
   104                 _('%s hook is invalid: "%s" is not callable')
    92                 % (hname, funcname))
   105                 % (hname, funcname)
       
   106             )
    93 
   107 
    94     ui.note(_("calling hook %s: %s\n") % (hname, funcname))
   108     ui.note(_("calling hook %s: %s\n") % (hname, funcname))
    95     starttime = util.timer()
   109     starttime = util.timer()
    96 
   110 
    97     try:
   111     try:
    98         r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
   112         r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
    99     except Exception as exc:
   113     except Exception as exc:
   100         if isinstance(exc, error.Abort):
   114         if isinstance(exc, error.Abort):
   101             ui.warn(_('error: %s hook failed: %s\n') %
   115             ui.warn(_('error: %s hook failed: %s\n') % (hname, exc.args[0]))
   102                          (hname, exc.args[0]))
       
   103         else:
   116         else:
   104             ui.warn(_('error: %s hook raised an exception: '
   117             ui.warn(
   105                       '%s\n') % (hname, stringutil.forcebytestr(exc)))
   118                 _('error: %s hook raised an exception: ' '%s\n')
       
   119                 % (hname, stringutil.forcebytestr(exc))
       
   120             )
   106         if throw:
   121         if throw:
   107             raise
   122             raise
   108         if not ui.tracebackflag:
   123         if not ui.tracebackflag:
   109             ui.warn(_('(run with --traceback for stack trace)\n'))
   124             ui.warn(_('(run with --traceback for stack trace)\n'))
   110         ui.traceback()
   125         ui.traceback()
   111         return True, True
   126         return True, True
   112     finally:
   127     finally:
   113         duration = util.timer() - starttime
   128         duration = util.timer() - starttime
   114         ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
   129         ui.log(
   115                htype, funcname, duration)
   130             'pythonhook',
       
   131             'pythonhook-%s: %s finished in %0.2f seconds\n',
       
   132             htype,
       
   133             funcname,
       
   134             duration,
       
   135         )
   116     if r:
   136     if r:
   117         if throw:
   137         if throw:
   118             raise error.HookAbort(_('%s hook failed') % hname)
   138             raise error.HookAbort(_('%s hook failed') % hname)
   119         ui.warn(_('warning: %s hook failed\n') % hname)
   139         ui.warn(_('warning: %s hook failed\n') % hname)
   120     return r, False
   140     return r, False
       
   141 
   121 
   142 
   122 def _exthook(ui, repo, htype, name, cmd, args, throw):
   143 def _exthook(ui, repo, htype, name, cmd, args, throw):
   123     starttime = util.timer()
   144     starttime = util.timer()
   124     env = {}
   145     env = {}
   125 
   146 
   152     else:
   173     else:
   153         cwd = encoding.getcwd()
   174         cwd = encoding.getcwd()
   154     r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
   175     r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
   155 
   176 
   156     duration = util.timer() - starttime
   177     duration = util.timer() - starttime
   157     ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
   178     ui.log(
   158            name, cmd, duration)
   179         'exthook',
       
   180         'exthook-%s: %s finished in %0.2f seconds\n',
       
   181         name,
       
   182         cmd,
       
   183         duration,
       
   184     )
   159     if r:
   185     if r:
   160         desc = procutil.explainexit(r)
   186         desc = procutil.explainexit(r)
   161         if throw:
   187         if throw:
   162             raise error.HookAbort(_('%s hook %s') % (name, desc))
   188             raise error.HookAbort(_('%s hook %s') % (name, desc))
   163         ui.warn(_('warning: %s hook %s\n') % (name, desc))
   189         ui.warn(_('warning: %s hook %s\n') % (name, desc))
   164     return r
   190     return r
   165 
   191 
       
   192 
   166 # represent an untrusted hook command
   193 # represent an untrusted hook command
   167 _fromuntrusted = object()
   194 _fromuntrusted = object()
       
   195 
   168 
   196 
   169 def _allhooks(ui):
   197 def _allhooks(ui):
   170     """return a list of (hook-id, cmd) pairs sorted by priority"""
   198     """return a list of (hook-id, cmd) pairs sorted by priority"""
   171     hooks = _hookitems(ui)
   199     hooks = _hookitems(ui)
   172     # Be careful in this section, propagating the real commands from untrusted
   200     # Be careful in this section, propagating the real commands from untrusted
   179             (lp, lo, lk, lv) = trustedvalue
   207             (lp, lo, lk, lv) = trustedvalue
   180             hooks[name] = (lp, lo, lk, _fromuntrusted)
   208             hooks[name] = (lp, lo, lk, _fromuntrusted)
   181     # (end of the security sensitive section)
   209     # (end of the security sensitive section)
   182     return [(k, v) for p, o, k, v in sorted(hooks.values())]
   210     return [(k, v) for p, o, k, v in sorted(hooks.values())]
   183 
   211 
       
   212 
   184 def _hookitems(ui, _untrusted=False):
   213 def _hookitems(ui, _untrusted=False):
   185     """return all hooks items ready to be sorted"""
   214     """return all hooks items ready to be sorted"""
   186     hooks = {}
   215     hooks = {}
   187     for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
   216     for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
   188         if name.startswith('priority.') or name.startswith('tonative.'):
   217         if name.startswith('priority.') or name.startswith('tonative.'):
   190 
   219 
   191         priority = ui.configint('hooks', 'priority.%s' % name, 0)
   220         priority = ui.configint('hooks', 'priority.%s' % name, 0)
   192         hooks[name] = (-priority, len(hooks), name, cmd)
   221         hooks[name] = (-priority, len(hooks), name, cmd)
   193     return hooks
   222     return hooks
   194 
   223 
       
   224 
   195 _redirect = False
   225 _redirect = False
       
   226 
       
   227 
   196 def redirect(state):
   228 def redirect(state):
   197     global _redirect
   229     global _redirect
   198     _redirect = state
   230     _redirect = state
       
   231 
   199 
   232 
   200 def hashook(ui, htype):
   233 def hashook(ui, htype):
   201     """return True if a hook is configured for 'htype'"""
   234     """return True if a hook is configured for 'htype'"""
   202     if not ui.callhooks:
   235     if not ui.callhooks:
   203         return False
   236         return False
   204     for hname, cmd in _allhooks(ui):
   237     for hname, cmd in _allhooks(ui):
   205         if hname.split('.')[0] == htype and cmd:
   238         if hname.split('.')[0] == htype and cmd:
   206             return True
   239             return True
   207     return False
   240     return False
   208 
   241 
       
   242 
   209 def hook(ui, repo, htype, throw=False, **args):
   243 def hook(ui, repo, htype, throw=False, **args):
   210     if not ui.callhooks:
   244     if not ui.callhooks:
   211         return False
   245         return False
   212 
   246 
   213     hooks = []
   247     hooks = []
   218     res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
   252     res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
   219     r = False
   253     r = False
   220     for hname, cmd in hooks:
   254     for hname, cmd in hooks:
   221         r = res[hname][0] or r
   255         r = res[hname][0] or r
   222     return r
   256     return r
       
   257 
   223 
   258 
   224 def runhooks(ui, repo, htype, hooks, throw=False, **args):
   259 def runhooks(ui, repo, htype, hooks, throw=False, **args):
   225     args = pycompat.byteskwargs(args)
   260     args = pycompat.byteskwargs(args)
   226     res = {}
   261     res = {}
   227     oldstdout = -1
   262     oldstdout = -1
   243 
   278 
   244             if cmd is _fromuntrusted:
   279             if cmd is _fromuntrusted:
   245                 if throw:
   280                 if throw:
   246                     raise error.HookAbort(
   281                     raise error.HookAbort(
   247                         _('untrusted hook %s not executed') % hname,
   282                         _('untrusted hook %s not executed') % hname,
   248                         hint = _("see 'hg help config.trusted'"))
   283                         hint=_("see 'hg help config.trusted'"),
       
   284                     )
   249                 ui.warn(_('warning: untrusted hook %s not executed\n') % hname)
   285                 ui.warn(_('warning: untrusted hook %s not executed\n') % hname)
   250                 r = 1
   286                 r = 1
   251                 raised = False
   287                 raised = False
   252             elif callable(cmd):
   288             elif callable(cmd):
   253                 r, raised = pythonhook(ui, repo, htype, hname, cmd, args,
   289                 r, raised = pythonhook(ui, repo, htype, hname, cmd, args, throw)
   254                                         throw)
       
   255             elif cmd.startswith('python:'):
   290             elif cmd.startswith('python:'):
   256                 if cmd.count(':') >= 2:
   291                 if cmd.count(':') >= 2:
   257                     path, cmd = cmd[7:].rsplit(':', 1)
   292                     path, cmd = cmd[7:].rsplit(':', 1)
   258                     path = util.expandpath(path)
   293                     path = util.expandpath(path)
   259                     if repo:
   294                     if repo:
   264                         ui.write(_("loading %s hook failed:\n") % hname)
   299                         ui.write(_("loading %s hook failed:\n") % hname)
   265                         raise
   300                         raise
   266                     hookfn = getattr(mod, cmd)
   301                     hookfn = getattr(mod, cmd)
   267                 else:
   302                 else:
   268                     hookfn = cmd[7:].strip()
   303                     hookfn = cmd[7:].strip()
   269                 r, raised = pythonhook(ui, repo, htype, hname, hookfn, args,
   304                 r, raised = pythonhook(
   270                                         throw)
   305                     ui, repo, htype, hname, hookfn, args, throw
       
   306                 )
   271             else:
   307             else:
   272                 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
   308                 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
   273                 raised = False
   309                 raised = False
   274 
   310 
   275             res[hname] = r, raised
   311             res[hname] = r, raised