view onsub.py @ 12:90b54c4fe4fe

onsub extension: pre and post order
author jakob krainz <jakob@hawo-net.de>
date Tue, 06 Mar 2012 12:55:40 +0100
parents ecc4fd16555d
children 34aa014b5989
line wrap: on
line source

# onsub.py - execute commands recursively on subrepositories
#
# Copyright 2010, 2011 aragost Trifork
#
# This software may be used and distributed according to the terms of
# the GNU General Public License version 2 or any later version.

import os
from mercurial.i18n import _
from mercurial import extensions, subrepo, util

"""execute a command in each subrepository"""

def onsub(ui, repo, *args, **opts):
    """execute a command in each subrepository

    Executes CMD with the current working directory set to the root of
    each subrepository. By default, execution stops if CMD returns a
    non-zero exit code. Use --ignore-errors to override this.

    Use --verbose/-v to print the command being run and the subrepo
    name for each run of CMD in a subrepo. Alternately, use
    --print0/-0 to print just the subrepo name followed by a NUL
    character instead of a newline. This can be useful in combination
    with :hg:`status --print0`.

    The command has access to the following environment variables:

    ``HG_REPO``:
        Absolute path to the top-level repository in which the onsub
        command was executed.

    ``HG_SUBPATH``:
        Relative path to the current subrepository from the top-level
        repository.

    ``HG_SUBURL``:
        URL for the current subrepository as specified in the
        containing repository's ``.hgsub`` file.

    ``HG_SUBSTATE``:
        State of the current subrepository as specified in the
        containing repository's ``.hgsubstate`` file.
    """
    if len(args) == 2:
        precmd = args[0]
        postcmd = args[1]
        if opts.get('breadth_first') or opts.get('post_order'):
            raise util.Abort(_("onsub: '-b' and '-p' imply the use of only one command"))
    elif len(args) == 1:
        if opts.get('post_order'):
            precmd = None
            postcmd = args[0]
        else:
            precmd = args[0]
            postcmd = None
    elif len(args) == 0:
        # cmd == '' means only do print0
        if opts.get('post_order'):
            precmd = None
            postcmd = ''
        else:
            precmd = ''
            postcmd = None
    else:
        raise util.Abort(_("onsub: at most 2 command arguments required"))
    if opts.get('post_order') and opts.get('breadth_first'):
        raise util.Abort(_("onsub: '-b' and '-p' are mutually exclusive"))
    foreach(ui, repo, precmd, postcmd,
            not opts.get('breadth_first'),
            opts.get('max_depth'),
            opts.get('print0'),
            opts.get('ignore_errors'),
            opts.get('root_repo'))

def execCmd(ui, rootrepo, sub, doaction, print0, cmd, inroot, ignoreerrors):
    """ if doaction, execute cmd; else, do nothing.  if inroot,
    then command is executed inside rootrepo; else, inside sub
    
    If cmd == None, do nothing. If cmd == '', do only the print0 (if needed). 
    Else, do either print0 or the debugging message, then execute the command.
    """
    if not doaction:
        return
    if inroot:
        envargdict = dict(HG_SUBPATH='.',
                          HG_SUBURL='.',
                          HG_SUBSTATE=rootrepo['.'].hex(),
                          HG_REPO=rootrepo.root)
        relpath = '.'
        cmdwd = rootrepo.root
    else:
        # subrepo.relpath was renamed to subrepo.subrelpath in
        # 18b5b6392fcf.
        if hasattr(subrepo, 'relpath'):
            relpath = subrepo.relpath(sub)
        else:
            relpath = subrepo.subrelpath(sub)
        envargdict = dict(HG_SUBPATH=relpath,
                          HG_SUBURL=sub._path,
                          HG_SUBSTATE=sub._state[1],
                          HG_REPO=rootrepo.root)
        cmdwd = os.path.join(rootrepo.root, relpath)
    if ignoreerrors:
        onerr = None
    else:
        onerr = util.Abort
    if cmd != None:
        if print0:
            ui.write(relpath, "\0")
        if cmd != '':
            if not print0: ui.note(_("executing '%s' in %s\n") % (cmd, relpath))
            util.system(cmd, environ=envargdict, cwd=cmdwd, onerr=onerr,
                        errprefix=_('terminated onsub in %s') % relpath)
    


def foreach(ui, repo, precmd, postcmd, depthfirst, maxdepth, print0, ignoreerrors, includeroot):
    """execute (pre/post)cmd in repo.root and in each subrepository"""
    seen = set()
    execCmd(ui=ui, rootrepo=repo, sub=None, doaction=includeroot, 
            print0=print0, cmd=precmd, inroot=True, ignoreerrors=ignoreerrors) 
    ctx = repo['.']
    work = [(1, ctx.sub(subpath)) for subpath in sorted(ctx.substate)]
    if depthfirst:
        work.reverse()
    while work:
        if depthfirst:
            (depth, sub) = work[-1]
            if sub in seen:
                dopreaction = False
                dopostaction = True
                doaddchildren = False
                work.pop()
            else:
                dopreaction = True
                dopostaction = False
                doaddchildren = True
                seen.add(sub)
        else:
            (depth, sub) = work.pop(0)
            dopreaction = True
            dopostaction = False
            doaddchildren = True
        if depth > maxdepth >= 0:
            continue
        execCmd(ui=ui, rootrepo=repo, sub=sub, doaction=dopreaction, 
                print0=print0, cmd=precmd, inroot=False, ignoreerrors=ignoreerrors) 
        if doaddchildren:
            if isinstance(sub, subrepo.hgsubrepo):
                rev = sub._state[1]
                ctx = sub._repo[rev]
                w = [(depth + 1, ctx.sub(subpath)) 
                     for subpath in sorted(ctx.substate)]
                if depthfirst:
                    w.reverse()
                work.extend(w)
        execCmd(ui=ui, rootrepo=repo, sub=sub, doaction=dopostaction, 
                print0=print0, cmd=postcmd, inroot=False, ignoreerrors=ignoreerrors) 
    execCmd(ui=ui, rootrepo=repo, sub=None, doaction=includeroot, 
            print0=print0, cmd=postcmd, inroot=True, ignoreerrors=ignoreerrors) 
            
cmdtable = {
    "onsub":
        (onsub,
         [('b', 'breadth-first', None,
           _('use breadth-first traversal')),
          ('p', 'post-order', None,
           _('use post-order depth-first traversal')),
          ('', 'root-repo', None,
           _('include root repository in traversal')),
          ('', 'max-depth', -1,
           _('limit recursion to N levels (negative for no limit)'), 'N'),
          ('', 'ignore-errors', None,
           _('continue execution despite errors')),
          ('0', 'print0', None,
           _('end subrepository names with NUL, for use with xargs'))],
         _('[-b] [-0] [--ignore-errors] CMD'))
}