46
|
1 # onsub.py - execute commands recursively on subrepositories
|
|
2 #
|
|
3 # Copyright 2010, 2011 aragost Trifork
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of
|
|
6 # the GNU General Public License version 2 or any later version.
|
|
7
|
|
8 import os
|
|
9 from mercurial.i18n import _
|
|
10 from mercurial import extensions, subrepo, util, registrar
|
|
11
|
|
12 """execute a command in each subrepository"""
|
|
13
|
|
14 cmdtable = {}
|
|
15 command = registrar.command(cmdtable)
|
|
16
|
|
17 @command(b'onsub',
|
|
18 [(b'b', b'breadth-first', None,
|
|
19 _(b'use breadth-first traversal')),
|
|
20 (b'p', b'post-order', None,
|
|
21 _(b'use post-order depth-first traversal')),
|
|
22 (b'', b'root-repo', None,
|
|
23 _(b'include root repository in traversal')),
|
|
24 (b'', b'max-depth', -1,
|
|
25 _(b'limit recursion to N levels (negative for no limit)'), b'N'),
|
|
26 (b'', b'ignore-errors', None,
|
|
27 _(b'continue execution despite errors')),
|
|
28 (b't', b'type', b'',
|
|
29 _(b'the type of repo to filter'), b'TYPE'),
|
|
30 (b'0', b'print0', None,
|
|
31 _(b'end subrepository names with NUL, for use with xargs'))],
|
|
32 _(b'[-b] [-0] [-t TYPE] [--ignore-errors] CMD [POST-CMD]'),
|
|
33 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
|
|
34 helpbasic=True
|
|
35 )
|
|
36 def onsub(ui, repo, *args, **opts):
|
|
37 """execute a command in each subrepository
|
|
38
|
|
39 Executes CMD with the current working directory set to the root of
|
|
40 each subrepository. By default, execution stops if CMD returns a
|
|
41 non-zero exit code. Use --ignore-errors to override this.
|
|
42
|
|
43 If a POST-CMD is specified, this will be executed after all
|
|
44 subrepositories below the current subrepository has been visited.
|
|
45 This corresponds to a post-order traversal of the tree.
|
|
46
|
|
47 It is an error to specify a POST-CMD together with the
|
|
48 --breadth-first flag.
|
|
49
|
|
50 Use --verbose/-v to print the command being run and the subrepo
|
|
51 name for each run of CMD in a subrepo. Alternately, use
|
|
52 --print0/-0 to print just the subrepo name followed by a NUL
|
|
53 character instead of a newline. This can be useful in combination
|
|
54 with :hg:`status --print0`.
|
|
55
|
|
56 The command has access to the following environment variables:
|
|
57
|
|
58 ``HG_REPO``:
|
|
59 Absolute path to the top-level repository in which the onsub
|
|
60 command was executed.
|
|
61
|
|
62 ``HG_SUBPATH``:
|
|
63 Relative path to the current subrepository from the top-level
|
|
64 repository.
|
|
65
|
|
66 ``HG_SUBURL``:
|
|
67 URL for the current subrepository as specified in the
|
|
68 containing repository's ``.hgsub`` file.
|
|
69
|
|
70 ``HG_SUBSTATE``:
|
|
71 State of the current subrepository as specified in the
|
|
72 containing repository's ``.hgsubstate`` file.
|
|
73
|
|
74 ``HG_SUBTYPE``:
|
|
75 The type of the current subrepository (hg, git or svn).
|
|
76 """
|
|
77
|
|
78 # function level "constants" - these won't be modified by the nested functions
|
|
79 print0 = opts.get('print0')
|
|
80 if opts.get('ignore_errors'):
|
|
81 onerr = None
|
|
82 else:
|
|
83 onerr = util.Abort
|
|
84 maxdepth = opts.get('max_depth')
|
|
85 precmd = None
|
|
86 postcmd = None
|
|
87 includeroot = opts.get('root_repo')
|
|
88 repotypefilter = opts.get('type')
|
|
89
|
|
90 def execCmd(sub, cmd, kind):
|
|
91 """if sub == None, cmd is executed inside repo; else, inside sub.
|
|
92 If cmd == None, do nothing. If cmd == '', do only the print0 (if needed).
|
|
93 Else, do either print0 or the debugging message, then execute the command.
|
|
94 kind is the type of the (sub)repo.
|
|
95 """
|
|
96 if sub == None:
|
|
97 envargdict = dict(HG_SUBPATH='.',
|
|
98 HG_SUBURL='.',
|
|
99 HG_SUBSTATE=repo['.'].hex(),
|
|
100 HG_REPO=repo.root,
|
|
101 HG_SUBTYPE=kind)
|
|
102 relpath = '.'
|
|
103 cmdwd = repo.root
|
|
104 else:
|
|
105 # subrepo.relpath was renamed to subrepo.subrelpath in
|
|
106 # 18b5b6392fcf.
|
|
107 if hasattr(subrepo, 'relpath'):
|
|
108 relpath = subrepo.relpath(sub)
|
|
109 else:
|
|
110 relpath = subrepo.subrelpath(sub)
|
|
111 envargdict = dict(HG_SUBPATH=relpath,
|
|
112 HG_SUBURL=sub._path,
|
|
113 HG_SUBSTATE=sub._state[1],
|
|
114 HG_REPO=repo.root,
|
|
115 HG_SUBTYPE=kind)
|
|
116 cmdwd = os.path.join(repo.root, relpath)
|
|
117 if cmd != None and (repotypefilter == '' or repotypefilter == kind):
|
|
118 if print0:
|
|
119 ui.write(relpath, "\0")
|
|
120 if cmd != '':
|
|
121 if not print0: ui.write(_("executing '%s' in %s\n") % (cmd, relpath))
|
|
122 util.system(cmd, environ=envargdict, cwd=cmdwd, onerr=onerr,
|
|
123 errprefix=_('terminated onsub in %s') % relpath)
|
|
124
|
|
125 def bfs():
|
|
126 """execute precmd in repo.root and in each subrepository, breadth-first"""
|
|
127 if includeroot:
|
|
128 execCmd(None, precmd, 'hg')
|
|
129 ctx = repo['.']
|
|
130 work = [(1, ctx.sub(subpath), ctx.substate[subpath][2]) for subpath in sorted(ctx.substate)]
|
|
131 while work:
|
|
132 (depth, sub, kind) = work.pop(0)
|
|
133 if depth > maxdepth >= 0:
|
|
134 continue
|
|
135 execCmd(sub, precmd, kind)
|
|
136 if kind == 'hg':
|
|
137 rev = sub._state[1]
|
|
138 ctx = sub._repo[rev]
|
|
139 w = [(depth + 1, ctx.sub(subpath), ctx.substate[subpath][2])
|
|
140 for subpath in sorted(ctx.substate)]
|
|
141 work.extend(w)
|
|
142
|
|
143 def dfs():
|
|
144 """execute pre-/postcmd in repo.root and in each subrepository, depth-first"""
|
|
145
|
|
146 def dfs_rek(depth, sub, kind):
|
|
147 if depth > maxdepth >= 0:
|
|
148 return
|
|
149 execCmd(sub, precmd, kind)
|
|
150 if kind == 'hg':
|
|
151 rev = sub._state[1]
|
|
152 ctx = sub._repo[rev]
|
|
153 for subpath in sorted(ctx.substate):
|
|
154 dfs_rek(depth+1, ctx.sub(subpath), ctx.substate[subpath][2])
|
|
155 execCmd(sub, postcmd, kind)
|
|
156
|
|
157 ctx = repo['.']
|
|
158 work = [(ctx.sub(subpath), ctx.substate[subpath][2]) for subpath in sorted(ctx.substate)]
|
|
159 if includeroot:
|
|
160 execCmd(None, precmd, 'hg')
|
|
161 for (sub, kind) in work:
|
|
162 dfs_rek(1, sub, kind)
|
|
163 if includeroot:
|
|
164 execCmd(None, postcmd, 'hg')
|
|
165
|
|
166 ### start of main function part ###
|
|
167 if len(args) == 2:
|
|
168 precmd = args[0]
|
|
169 postcmd = args[1]
|
|
170 if opts.get('breadth_first') or opts.get('post_order'):
|
|
171 raise util.Abort(_("onsub: '-b' and '-p' imply the use of only one command"))
|
|
172 elif len(args) == 1:
|
|
173 if opts.get('post_order'):
|
|
174 precmd = None
|
|
175 postcmd = args[0]
|
|
176 else:
|
|
177 precmd = args[0]
|
|
178 postcmd = None
|
|
179 elif len(args) == 0:
|
|
180 # cmd == '' means only do print0
|
|
181 if opts.get('post_order'):
|
|
182 precmd = None
|
|
183 postcmd = ''
|
|
184 else:
|
|
185 precmd = ''
|
|
186 postcmd = None
|
|
187 else:
|
|
188 raise util.Abort(_("onsub: at most 2 command arguments required"))
|
|
189 if opts.get('post_order') and opts.get('breadth_first'):
|
|
190 raise util.Abort(_("onsub: '-b' and '-p' are mutually exclusive"))
|
|
191
|
|
192 if opts.get('breadth_first'):
|
|
193 bfs()
|
|
194 else:
|
|
195 dfs()
|