[PATCH] foreach: new command for traversing subrepositories
Martin Geisler
mg at lazybytes.net
Tue Aug 31 09:52:59 CDT 2010
# 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.
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
More information about the Mercurial-devel
mailing list