mercurial/hook.py
author Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
Wed, 12 Nov 2008 14:07:47 +0100
changeset 7353 982b55ec80be
parent 7280 810ca383da9c
child 7416 196b05a548d0
permissions -rw-r--r--
patchbomb: make `hg email` reusable for other patch sources Adds two internal options, patches and patchnames, which allow other extensions to email a given set of patches. The pbranch extension needs this to send its patches which are diffs between topic branches.

# hook.py - hook support for mercurial
#
# Copyright 2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

from i18n import _
import util, os, sys

def _pythonhook(ui, repo, name, hname, funcname, args, throw):
    '''call python hook. hook is callable object, looked up as
    name in python module. if callable returns "true", hook
    fails, else passes. if hook raises exception, treated as
    hook failure. exception propagates if throw is "true".

    reason for "true" meaning "hook failed" is so that
    unmodified commands (e.g. mercurial.commands.update) can
    be run as hooks without wrappers to convert return values.'''

    ui.note(_("calling hook %s: %s\n") % (hname, funcname))
    obj = funcname
    if not callable(obj):
        d = funcname.rfind('.')
        if d == -1:
            raise util.Abort(_('%s hook is invalid ("%s" not in '
                               'a module)') % (hname, funcname))
        modname = funcname[:d]
        try:
            obj = __import__(modname)
        except ImportError:
            try:
                # extensions are loaded with hgext_ prefix
                obj = __import__("hgext_%s" % modname)
            except ImportError:
                raise util.Abort(_('%s hook is invalid '
                                   '(import of "%s" failed)') %
                                 (hname, modname))
        try:
            for p in funcname.split('.')[1:]:
                obj = getattr(obj, p)
        except AttributeError:
            raise util.Abort(_('%s hook is invalid '
                               '("%s" is not defined)') %
                             (hname, funcname))
        if not callable(obj):
            raise util.Abort(_('%s hook is invalid '
                               '("%s" is not callable)') %
                             (hname, funcname))
    try:
        r = obj(ui=ui, repo=repo, hooktype=name, **args)
    except (KeyboardInterrupt, util.SignalInterrupt):
        raise
    except Exception, exc:
        if isinstance(exc, util.Abort):
            ui.warn(_('error: %s hook failed: %s\n') %
                         (hname, exc.args[0]))
        else:
            ui.warn(_('error: %s hook raised an exception: '
                           '%s\n') % (hname, exc))
        if throw:
            raise
        ui.print_exc()
        return True
    if r:
        if throw:
            raise util.Abort(_('%s hook failed') % hname)
        ui.warn(_('warning: %s hook failed\n') % hname)
    return r

def _exthook(ui, repo, name, cmd, args, throw):
    ui.note(_("running hook %s: %s\n") % (name, cmd))
    env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
    if repo:
        cwd = repo.root
    else:
        cwd = os.getcwd()
    r = util.system(cmd, environ=env, cwd=cwd)
    if r:
        desc, r = util.explain_exit(r)
        if throw:
            raise util.Abort(_('%s hook %s') % (name, desc))
        ui.warn(_('warning: %s hook %s\n') % (name, desc))
    return r

_redirect = False
def redirect(state):
    global _redirect
    _redirect = state

def hook(ui, repo, name, throw=False, **args):
    r = False

    if _redirect:
        # temporarily redirect stdout to stderr
        oldstdout = os.dup(sys.__stdout__.fileno())
        os.dup2(sys.__stderr__.fileno(), sys.__stdout__.fileno())

    for hname, cmd in util.sort(ui.configitems('hooks')):
        if hname.split('.')[0] != name or not cmd:
            continue
        if callable(cmd):
            r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
        elif cmd.startswith('python:'):
            r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(),
                            args, throw) or r
        else:
            r = _exthook(ui, repo, hname, cmd, args, throw) or r

    if _redirect:
        os.dup2(oldstdout, sys.__stdout__.fileno())
        os.close(oldstdout)

    return r