[PATCH] foreach: new command for traversing subrepositories

Augie Fackler durin42 at gmail.com
Tue Aug 31 20:24:19 CDT 2010


On Aug 31, 2010, at 9:52 AM, Martin Geisler wrote:

> # HG changeset patch
> # User Martin Geisler <mg at lazybytes.net>
> # Date 1283266348 -7200
> # Node ID 3875e9ddeb830ab19b85efb6b823cb85c0e80e7a
> # Parent  36a65283c3afd6f957da54817b3b8c577aa78ec4
> foreach: new command for traversing subrepositories
>
> This should be a useful building block for people who make heavy use
> of subrepositories. Inspired by a Git command of the same name.

I agree with Mads, it's not obvious in the slightest this relates to  
subrepos. Maybe first we should have a command that lists subrepos so  
you can do simple shell scripts along these lines before we commit to  
a dedicated command?

(non-user of subrepos, just trying to avoid subcommand pollution)

>
> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
> --- a/mercurial/cmdutil.py
> +++ b/mercurial/cmdutil.py
> @@ -10,7 +10,7 @@
> import os, sys, errno, re, glob, tempfile
> import util, templater, patch, error, encoding, templatekw
> import match as matchmod
> -import similar, revset
> +import similar, revset, subrepo
>
> revrangesep = ':'
>
> @@ -1224,6 +1224,34 @@
>                 yield change(rev)
>     return iterate()
>
> +def foreach(ui, repo, cmd, depthfirst):
> +    """execute cmd in repo.root and in each subrepository"""
> +    ctx = repo['.']
> +    work = [ctx.sub(subpath) for subpath in sorted(ctx.substate)]
> +    if depthfirst:
> +        work.reverse()
> +
> +    while work:
> +        if depthfirst:
> +            sub = work.pop()
> +        else:
> +            sub = work.pop(0)
> +
> +        relpath = subrepo.relpath(sub)
> +
> +        ui.note(_("executing '%s' in %s\n") % (cmd, relpath))
> +        util.system(cmd, environ=dict(HG_SUBPATH=relpath,
> +                                      HG_SUBURL=sub._path,
> +                                      HG_SUBSTATE=sub._state[1],
> +                                      HG_REPO=repo.root),
> +                    cwd=os.path.join(repo.root, relpath),  
> onerr=util.Abort,
> +                    errprefix=_('terminated foreach in %s') %  
> relpath)
> +
> +        w = sub.subrepos()
> +        if depthfirst:
> +            w.reverse()
> +        work.extend(w)
> +
> def commit(ui, repo, commitfunc, pats, opts):
>     '''commit the specified files or all outstanding changes'''
>     date = opts.get('date')
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -1504,6 +1504,32 @@
>                  switch_parent=opts.get('switch_parent'),
>                  opts=patch.diffopts(ui, opts))
>
> +def foreach(ui, repo, *args, **opts):
> +    """execute a command in each subrepository
> +
> +    The command is executed with the current working directory set to
> +    the root of each subrepository. It has access to the following
> +    environment variables:
> +
> +    ``HG_REPO``:
> +        Absolute path to the top-level repository in which the  
> foreach
> +        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.
> +    """
> +    cmd = ' '.join(args)
> +    cmdutil.foreach(ui, repo, cmd, not opts.get('breadth_first'))
> +
> def forget(ui, repo, *pats, **opts):
>     """forget the specified files on the next commit
>
> @@ -4171,6 +4197,10 @@
>         (forget,
>          [] + walkopts,
>          _('[OPTION]... FILE...')),
> +    "foreach":
> +        (foreach,
> +         [('', 'breadth-first', None, _('use breadth-first  
> traversal'))],
> +         _('[--breadth-first] CMD')),
>     "grep":
>         (grep,
>          [('0', 'print0', None, _('end fields with NUL')),
> diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
> --- a/mercurial/subrepo.py
> +++ b/mercurial/subrepo.py
> @@ -241,6 +241,8 @@
>         """
>         raise NotImplementedError
>
> +    def subrepos(self):
> +        return []
>
> class hgsubrepo(abstractsubrepo):
>     def __init__(self, ctx, path, state):
> @@ -271,6 +273,11 @@
>                 addpathconfig('default-push', defpushpath)
>             fp.close()
>
> +    def subrepos(self):
> +        rev = self._state[1]
> +        ctx = self._repo[rev]
> +        return [ctx.sub(subpath) for subpath in sorted(ctx.substate)]
> +
>     def dirty(self):
>         r = self._state[1]
>         if r == '':
> diff --git a/tests/test-foreach.t b/tests/test-foreach.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-foreach.t
> @@ -0,0 +1,69 @@
> +
> +Create some nicely nested subrepositories:
> +
> +  $ hg init
> +  $ for d in a b; do hg init $d; echo "$d = $d" >> .hgsub; done
> +  $ hg add .hgsub
> +
> +  $ cd a
> +
> +  $ for d in x y; do hg init $d; echo "$d = $d" >> .hgsub; done
> +  $ hg add .hgsub
> +
> +  $ cd y
> +  $ for d in r s t; do hg init $d; echo "$d = $d" >> .hgsub; done
> +  $ hg add .hgsub
> +  $ cd ..
> +
> +  $ cd ..
> +
> +  $ cd b
> +  $ for d in u v; do hg init $d; echo "$d = $d" >> .hgsub; done
> +  $ hg add .hgsub
> +  $ cd ..
> +
> +  $ hg commit -m init
> +  committing subrepository a
> +  committing subrepository a/x
> +  committing subrepository a/y
> +  committing subrepository a/y/r
> +  committing subrepository a/y/s
> +  committing subrepository a/y/t
> +  committing subrepository b
> +  committing subrepository b/u
> +  committing subrepository b/v
> +
> +The default depth-first traversal:
> +
> +  $ hg foreach 'echo $HG_SUBPATH'
> +  a
> +  a/x
> +  a/y
> +  a/y/r
> +  a/y/s
> +  a/y/t
> +  b
> +  b/u
> +  b/v
> +
> +Breadth-first traversal:
> +
> +  $ hg foreach 'echo $HG_SUBPATH' --breadth-first
> +  a
> +  b
> +  a/x
> +  a/y
> +  b/u
> +  b/v
> +  a/y/r
> +  a/y/s
> +  a/y/t
> +
> +Test aborting:
> +
> +  $ hg foreach -v 'test $HG_SUBPATH != "a/y/r"'
> +  executing 'test $HG_SUBPATH != "a/y/r"' in a
> +  executing 'test $HG_SUBPATH != "a/y/r"' in a/x
> +  executing 'test $HG_SUBPATH != "a/y/r"' in a/y
> +  executing 'test $HG_SUBPATH != "a/y/r"' in a/y/r
> +  abort: terminated foreach in a/y/r: test exited with status 1
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel



More information about the Mercurial-devel mailing list