[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