[PATCH] foreach: new command for traversing subrepositories

Martin Geisler mg at lazybytes.net
Wed Sep 1 02:15:01 CDT 2010


Mads Kiilerich <mads at kiilerich.com> writes:

>  Martin Geisler roter, On 08/31/2010 04:52 PM:
>> # HG changeset patch
>> # User Martin Geisler<mg at lazybytes.net>
>> # Date 1283266348 -7200
>> # Node ID 3875e9ddeb830ab19b85efb6b823cb85c0e80e7a
>> # Parent  36a65283c3afd6f957da54817b3b8c577aa78ec4
>> foreach: new command for traversing subrepositories
>
> The name is nice, but very generic and gives no hint that it deals
> with subrepos. Isn't that a problem?

Hmm, I had not considered that. The original suggestion by my client was
to call it 'onsub' but I looked at the original in Git. There it is a
subcommand and you write things like:

  $ git submodule foreach 'echo $path `git rev-parse HEAD`'

The documentation for the submodule stuff is here:

  http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html

>> This should be a useful building block for people who make heavy use
>> of subrepositories. Inspired by a Git command of the same name.
>
> Can you give some relevant use cases?
>
> hg foreach 'hg pull -u'
> ?

Yes, that is a good example, as is

  $ hg foreach hg incoming
  $ hg foreach hg outgoing

though I want to add -S/--subrepos flags to all those commands anyway
since that is the nice way to support things like

  $ hg incoming -l 3 --subrepos

since the unquoted -l flag would make foreach complain.

>> +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)
>
> Termination on the first non-successful command isn't documented?

No, but we should probably do that -- I just figured it was pretty
obvious from the error message what was going on.

> Is that something we always want? I can imagine that it will confuse
> users more often than it will do what they want.

Adding '|| true' works fine, but I'm sure you will say that those
confused users will be even more confused by that. We can certainly add
an option like --continue that will make it keep going despite errors.

>> +        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.
>
> I think this is our first command that takes a shell command as
> unnamed parameter(s). Perhaps it should be made extra clear what the
> quoting rules are and how several command parameters are handled.
>
> It seems like this works like the way ssh executes a command, where
> parameters to the command can be specified as separate parameters -
> but where parameters that requires quoting must be double quoted, and
> where it in non-trivial cases often is simplest to put everything in
> one big quotation. (sudo behaves differently and exec's the parameters
> directly.)

Good points... I had initially thought you could just turn off option
processing with '--'. So that

  $ hg foreach -- hg grep "foo bar"

would be the same as

  $ hg foreach 'hg grep "foo bar"'

but this is actually not true since the first is translated into

  $ hg grep foo bar

in each subrepo when we execute ' '.join(args).

> Perhaps it also would be helpful to clarify that it is a "shell
> command" (and thus not just an "executable") that can be specified.
> (That clarification might be useful in other help texts too.)

Right.

>>   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.
>
> Both variables points to a repo. But one is a REPO the other is a PATH?
>
> HG_REPO is also a bit ambiguous as the subrepo also is a repo.
>
> How about HG_TOPREPO/HG_SUBREPO? Do we have an "official" name for the
> "top, outermost repo"?

I don't think we have such a name.


-- 
Martin Geisler

Mercurial links: http://mercurial.ch/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: not available
URL: <http://selenic.com/pipermail/mercurial-devel/attachments/20100901/0ecd28d0/attachment.pgp>


More information about the Mercurial-devel mailing list